GET /result/
Poll for the result of an async job started by
POST /explain/async. Polls collect what you already owe; the first poll after the job finishes charges the true-up.
URL: https://mpp.oculr.xyz/result/:jobId
Auth: Every poll collects what the analysis has accrued since your previous poll - it answers 402 for that amount, and a poll with nothing yet to collect is free. The first poll after the job finishes charges the fee-bearing true-up, so the cumulative total (submit charge + polls + true-up) equals the sync SSE price exactly. An mppx client pays every one of them transparently; subsequent fetches of an already-paid finished result are free. Failed jobs true up the same way.
Request
Parameters
| Param | Type | Required | Description |
|---|---|---|---|
jobId | UUID (path) | yes | The jobId returned by POST /explain/async. |
Response
200 OK
{
"jobId": "550e8400-e29b-41d4-a716-446655440000",
"status": "complete",
"result": { /* ExplanationResult - see POST /explain */ }
}| Field | Type | Notes |
|---|---|---|
jobId | UUID | Echoes the request. |
status | "pending" | "running" | "complete" | "error" | Transitions in order. |
result | ExplanationResult | Present only when status === "complete". Schema documented in POST /explain → Response. |
error | string | Present only when status === "error". |
errorCode | string | Present when the error has a stable code, e.g. upstream_payment_unavailable. |
html | string | Present when the job was started with report: true - a self-contained HTML report. |
Examples
TypeScript
// `Mppx.create()` must be active - fetch() auto-pays whatever this poll collects.
const job = await fetch(`https://mpp.oculr.xyz/result/${jobId}`).then(r => r.json())
if (job.status === 'complete') return job.result
if (job.status === 'error') throw new Error(job.error)
// else still pending or running - poll againShell (mppx CLI)
mppx https://mpp.oculr.xyz/result/$JOB | jq '{status, summary: .result.summary}'Polling loop
while (true) {
await new Promise(r => setTimeout(r, 5000))
const job = await fetch(`https://mpp.oculr.xyz/result/${jobId}`).then(r => r.json())
if (job.status === 'complete') return job.result
if (job.status === 'error') throw new Error(job.error)
}Errors
| Code | When it happens | Body |
|---|---|---|
402 | Accrued metered cost is owed before this poll is answered - mid-run or on the finished result. mppx clients handle it automatically. | Standard MPP/x402 challenge. |
404 | Job expired (TTL 1 hour) or jobId is unknown. | { "error": "Job not found - expired or invalid ID" } |
Errors raised during analysis are surfaced via the status: "error" response, not via an HTTP error code. Check job.errorCode === "upstream_payment_unavailable" for the same outbound-payment failure documented on POST /explain.
Related
- POST /explain/async - starts the job
- POST /explain - the synchronous alternative
- Use as an agent → async polling pattern