POST /explain/async
Non-blocking variant of
POST /explain. Returns ajobIdimmediately. PollGET /result/:jobIdfor the result.
URL: https://mpp.oculr.xyz/explain/async
Auth: MPP/x402, metered to the same total as sync /explain and collected across the job lifecycle (pricing): $0.01 is charged at submit, each GET /result/:jobId poll collects what the analysis has accrued since the previous poll, and the first poll after the job finishes charges the true-up. Handled transparently by the standard mppx polyfill; plain fetch() works for the submit, but the polls need a paying client.
Request
Body
Same as POST /explain:
| Field | Type | Required | Description |
|---|---|---|---|
txHash | string | yes | EVM tx hash matching ^0x[0-9a-fA-F]{64}$. |
chainId | number | no | EIP-155 chain ID. When provided, skips multi-chain auto-detection. |
context | string | no | Caller intent passed to the analysis agent. |
model | string | no | claude-opus-5 | claude-opus-4-8 | claude-opus-4-7 | claude-sonnet-4-6 | claude-haiku-4-5-20251001. Defaults to claude-opus-5. |
report | boolean | no | If true, the completed job also carries a self-contained HTML report. |
Headers
| Header | Value |
|---|---|
Content-Type | application/json |
Response
202 Accepted
{
"jobId": "550e8400-e29b-41d4-a716-446655440000",
"status": "pending"
}| Field | Type | Notes |
|---|---|---|
jobId | UUID | Pass this to GET /result/:jobId. |
status | "pending" | Always pending on creation. Becomes running then complete (or error) over time. |
Jobs expire 1 hour after they're created. After expiry, polling /result/:jobId returns 404.
Examples
TypeScript
const { jobId } = await fetch('https://mpp.oculr.xyz/explain/async', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ txHash: '0x…' }),
}).then(r => r.json())Shell (mppx CLI)
JOB=$(mppx https://mpp.oculr.xyz/explain/async \
-J '{"txHash":"0x4e4b8ed4…"}' | jq -r '.jobId')
echo "Job: $JOB"Errors
| Code | When it happens | Body |
|---|---|---|
400 | txHash is missing or malformed. | { "error": "txHash must be a valid 32-byte hex hash (0x...)" } |
402 | No active MPP payment session. mppx handles this transparently. | Standard MPP/x402 challenge. |
500 | An internal error before the job was created. Generic body, no code field - the requestId is the handle to quote to support. | { "error": "Internal server error", "requestId": "err-..." } |
502 | Never returned by this route, on any deployment. Submit answers 202 before the analysis starts, and the application maps every uncaught error to 500. An upstream-payment failure lands on the job instead: GET /result/:jobId then answers 200 with errorCode: "upstream_payment_unavailable". A literal 502 exists only on the unmetered blocking-JSON POST /explain, i.e. a self-hosted PRECOG_DEV_MODE=true deployment. | (no such response) |
Because submit returns before the analysis runs, nothing that goes wrong during the analysis can change this endpoint's status code. Poll GET /result/:jobId and branch on job.status / job.errorCode. See POST /explain → upstream_payment_unavailable handling for the full pattern.
Related
- GET /result/ - poll for the result
- Use as an agent → async polling pattern