The Buildml executor is a standalone FastAPI service responsible for running user-submitted Python code inside an isolated environment. It exposes a single HTTP endpoint that accepts a code payload, executes the corresponding test suite, and returns a structured result. The Next.js application never calls the executor directly from a tRPC mutation — instead, jobs are dispatched through Upstash QStash and processed asynchronously via a webhook handler.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/Praashh/buildml/llms.txt
Use this file to discover all available pages before exploring further.
The FastAPI executor is a separate service with its own repository and Docker image. It is not part of the Next.js application. You must run it alongside the Next.js app and point
EXECUTOR_URL at it.Execution Endpoint
The executor exposes a single route:Request
Headers:| Header | Value |
|---|---|
Content-Type | application/json |
x-secret | Value of EXECUTOR_SECRET env var |
| Field | Type | Description |
|---|---|---|
code | string | The Python source code submitted by the user |
task_id | string | The problem’s unique slug field; used to locate the test file at /app/tests/{task_id}.py inside the executor container |
problem_set_slug | string | The parent ProblemSet slug; used for organising test suites within the container |
Response
Number of test cases that passed.
Total number of test cases executed.
Combined standard output captured during execution.
Standard error output, if any.
Top-level error string set when the executor itself encounters a fatal failure (e.g. a syntax error that prevents the code from running at all). When present, the submission status is set to
ERROR.Full Execution Flow
The diagram below shows how a code submission travels from the user’s browser through QStash to the executor and back.Step 1 — tRPC mutation dispatches to QStash
When a user clicks Run or Submit, the tRPC procedure insrc/server/api/routers/submission.ts publishes a message to QStash. Both RUN and SUBMIT types go through QStash — the difference is that RUN creates no database record (results are stored temporarily in Redis), while SUBMIT first creates a Submission row with status: "PENDING" before queuing:
src/server/api/routers/submission.ts
Step 2 — QStash delivers the message to the webhook
QStash signs the request withQSTASH_CURRENT_SIGNING_KEY and delivers it to {DEPLOYMENT_URL}/api/webhooks/process-submission. The webhook handler verifies the upstash-signature header using the Receiver from src/lib/qstash.ts:
src/app/api/webhooks/process-submission/route.ts
Step 3 — Webhook calls the executor
After verifying the signature and loading the problem data from PostgreSQL, the webhook calls the executor synchronously:src/app/api/webhooks/process-submission/route.ts
Step 4 — Results are stored
Once the executor responds, the webhook handler determines the final status and persists the result:| Type | Storage | Key / Location |
|---|---|---|
RUN | Upstash Redis | run_result:{runId} with a 600-second TTL |
SUBMIT | PostgreSQL Submission row | status and output fields updated in-place |
submission.getStatus (a tRPC query) using the runId or submissionId returned from the initial mutation to retrieve the result once it is available.
Status Values
Thestatus field on a Submission row (or in a run_result:* Redis entry) takes one of four values:
| Status | Meaning |
|---|---|
PENDING | Job has been queued in QStash; execution has not completed yet |
PASS | All test cases passed (passed === total && total > 0) |
FAIL | One or more test cases failed |
ERROR | A fatal error occurred (syntax error, import failure, executor unreachable, etc.) |
Rate Limiting
Both therun and submit mutations are rate-limited per user via @upstash/ratelimit using a sliding window algorithm (configured in src/lib/rate-limiter.ts):
| Operation | Limit |
|---|---|
run | 5 requests per 10 seconds |
submit | 2 requests per 30 seconds |
TOO_MANY_REQUESTS TRPCError.
Environment Variables
Base URL of the FastAPI executor service. The
/execute path is appended automatically.Default: http://localhost:8000Docker Compose example: http://executor:8000Shared secret sent in the
x-secret header on every request from the webhook handler to the executor. The executor must validate this header and reject requests with an incorrect or missing value.Default: dev-secret