From c67db37637f456fffd6badc7667f49a73126ef56 Mon Sep 17 00:00:00 2001 From: Derek <256792747+decofe@users.noreply.github.com> Date: Sun, 31 May 2026 12:40:39 +0000 Subject: [PATCH 1/2] Document async job polling auth --- src/pages/advanced/async-jobs.mdx | 100 ++++++++++++++++++++++++++++++ src/pages/advanced/identity.mdx | 2 + vocs.config.ts | 1 + 3 files changed, 103 insertions(+) create mode 100644 src/pages/advanced/async-jobs.mdx diff --git a/src/pages/advanced/async-jobs.mdx b/src/pages/advanced/async-jobs.mdx new file mode 100644 index 00000000..1de57b12 --- /dev/null +++ b/src/pages/advanced/async-jobs.mdx @@ -0,0 +1,100 @@ +--- +description: "Use MPP with asynchronous APIs that submit paid jobs and poll for results." +imageDescription: "Authorize async job polling after an MPP payment" +--- + +# Async jobs [Submit paid work and poll for results] + +Asynchronous APIs split work across two request families: + +1. `POST /api/generate/{model}/{op}` creates the job. +2. `GET /api/jobs/{jobId}` polls until the job completes. + +Use MPP for the paid submit request. For the polling request, prefer the same MPP identity surface over a separate sign-in flow: either accept the original payment Credential for the created job, or require a zero-dollar MPP Challenge that proves the caller controls the same wallet. + +## Recommended contract + +The submit endpoint returns a normal `402` Challenge. The client pays, retries with a Credential, and receives a job ID: + +```http +POST /api/generate/video/text-to-video HTTP/1.1 +Authorization: Payment ... +``` + +```json +{ + "jobId": "job_123", + "status": "pending" +} +``` + +The server stores the verified Credential `source` with the job. Polling then verifies the caller against that stored owner: + +```ts [server.ts] +import { Credential } from 'mppx' + +export async function submit(request: Request) { + const result = await mppx.charge({ amount: '0.25' })(request) + if (result.status === 402) return result.challenge + + const credential = Credential.fromRequest(request) + const jobId = await createJob({ owner: credential.source }) + + return result.withReceipt(Response.json({ jobId, status: 'pending' })) +} +``` + +```ts [server.ts] +import { Credential } from 'mppx' + +export async function poll(request: Request) { + const result = await mppx.charge({ amount: '0' })(request) + if (result.status === 402) return result.challenge + + const credential = Credential.fromRequest(request) + const job = await getJob(jobIdFromUrl(request)) + + if (job.owner !== credential.source) { + return Response.json({ error: 'Not your job' }, { status: 403 }) + } + + return result.withReceipt(Response.json(job.status)) +} +``` + +This keeps both requests on the standard MPP `402` → Credential → retry path. Clients that use `mppx` don't need a second authentication implementation to retrieve work they already paid for. + +## StableStudio video + +StableStudio video generation follows the same async shape: + +| Step | Endpoint | Auth | +|---|---|---| +| Submit | `POST /api/generate/{model}/{op}` | MPP charge Credential | +| Poll | `GET /api/jobs/{jobId}` | Prefer zero-dollar MPP Credential tied to the submit wallet | + +Avoid gating the poll endpoint only with Sign-In-With-X when the submit endpoint used MPP. A charge-based client already has a verified wallet identity in the MPP Credential, and polling only needs to prove continuity with that identity. + +If the poll endpoint must support Sign-In-With-X, also support the MPP polling contract above for clients that paid through MPP. + +## Chain support + +For jobs paid on Tempo, any identity challenge used for polling must accept Tempo: + +```txt +eip155:4217 +``` + +Do not advertise only Base (`eip155:8453`) or Solana for a Tempo-paid job. The polling challenge must accept the chain the client used to pay, otherwise an MPP client can submit the job and then fail to retrieve it. + +## If you use Sign-In-With-X + +A Sign-In-With-X fallback should be symmetric with the account used for payment: + +1. Return a `402` response that includes the `sign-in-with-x` extension. +2. Include `eip155:4217` in `supportedChains` when the submit payment settled on Tempo. +3. Build the CAIP-122 message from the challenge. +4. Sign with the same account using EIP-191 `personal_sign`. +5. Send the base64-encoded result in the `Sign-In-With-X` header. + +First-class `mppx` client support should parse the extension, select a supported chain for the configured account, sign the CAIP-122 message, and attach `Sign-In-With-X` automatically in the same wrapper that handles MPP Challenges and Credentials. diff --git a/src/pages/advanced/identity.mdx b/src/pages/advanced/identity.mdx index 54cf6269..ec9a8522 100644 --- a/src/pages/advanced/identity.mdx +++ b/src/pages/advanced/identity.mdx @@ -65,6 +65,8 @@ For Tempo, the server rejects `transaction` and `hash` payloads for zero-amount ### Case study: long-running jobs +For a complete async API contract, including StableStudio-style submit and poll endpoints, see [Async jobs](/advanced/async-jobs). + A service accepts a paid request to start work, then lets the client poll for results using zero-dollar auth. The server keys workloads on the client's public key. ::::steps diff --git a/vocs.config.ts b/vocs.config.ts index e95c10bb..a45a155e 100644 --- a/vocs.config.ts +++ b/vocs.config.ts @@ -361,6 +361,7 @@ export default defineConfig({ items: [ { text: "Discovery", link: "/advanced/discovery" }, { text: "Identity", link: "/advanced/identity" }, + { text: "Async jobs", link: "/advanced/async-jobs" }, { text: "Refunds", link: "/advanced/refunds" }, ], }, From e1c20d0ebde113c451f24ce03844f86d85321ace Mon Sep 17 00:00:00 2001 From: Brendan Ryan <1572504+brendanjryan@users.noreply.github.com> Date: Sun, 31 May 2026 09:32:39 -0700 Subject: [PATCH 2/2] docs: address async jobs review comments --- src/pages/advanced/async-jobs.mdx | 35 ------------------------------- src/pages/advanced/identity.mdx | 2 +- 2 files changed, 1 insertion(+), 36 deletions(-) diff --git a/src/pages/advanced/async-jobs.mdx b/src/pages/advanced/async-jobs.mdx index 1de57b12..9baeeed0 100644 --- a/src/pages/advanced/async-jobs.mdx +++ b/src/pages/advanced/async-jobs.mdx @@ -63,38 +63,3 @@ export async function poll(request: Request) { ``` This keeps both requests on the standard MPP `402` → Credential → retry path. Clients that use `mppx` don't need a second authentication implementation to retrieve work they already paid for. - -## StableStudio video - -StableStudio video generation follows the same async shape: - -| Step | Endpoint | Auth | -|---|---|---| -| Submit | `POST /api/generate/{model}/{op}` | MPP charge Credential | -| Poll | `GET /api/jobs/{jobId}` | Prefer zero-dollar MPP Credential tied to the submit wallet | - -Avoid gating the poll endpoint only with Sign-In-With-X when the submit endpoint used MPP. A charge-based client already has a verified wallet identity in the MPP Credential, and polling only needs to prove continuity with that identity. - -If the poll endpoint must support Sign-In-With-X, also support the MPP polling contract above for clients that paid through MPP. - -## Chain support - -For jobs paid on Tempo, any identity challenge used for polling must accept Tempo: - -```txt -eip155:4217 -``` - -Do not advertise only Base (`eip155:8453`) or Solana for a Tempo-paid job. The polling challenge must accept the chain the client used to pay, otherwise an MPP client can submit the job and then fail to retrieve it. - -## If you use Sign-In-With-X - -A Sign-In-With-X fallback should be symmetric with the account used for payment: - -1. Return a `402` response that includes the `sign-in-with-x` extension. -2. Include `eip155:4217` in `supportedChains` when the submit payment settled on Tempo. -3. Build the CAIP-122 message from the challenge. -4. Sign with the same account using EIP-191 `personal_sign`. -5. Send the base64-encoded result in the `Sign-In-With-X` header. - -First-class `mppx` client support should parse the extension, select a supported chain for the configured account, sign the CAIP-122 message, and attach `Sign-In-With-X` automatically in the same wrapper that handles MPP Challenges and Credentials. diff --git a/src/pages/advanced/identity.mdx b/src/pages/advanced/identity.mdx index ec9a8522..633c4d0b 100644 --- a/src/pages/advanced/identity.mdx +++ b/src/pages/advanced/identity.mdx @@ -65,7 +65,7 @@ For Tempo, the server rejects `transaction` and `hash` payloads for zero-amount ### Case study: long-running jobs -For a complete async API contract, including StableStudio-style submit and poll endpoints, see [Async jobs](/advanced/async-jobs). +For a complete async API contract, including submit and poll endpoints, see [Async jobs](/advanced/async-jobs). A service accepts a paid request to start work, then lets the client poll for results using zero-dollar auth. The server keys workloads on the client's public key.