Pay per job with x402

x402 lets an agent pay for one Emboss job over the A2A endpoint with USDC on the Base network, with no Emboss account. Emboss's agent card at https://api.getemboss.ai/.well-known/agent-card.json advertises the https://github.com/google-a2a/a2a-x402/v0.1 extension. This page is the x402 sibling of Pay per call with MPP; see the pay-per-call hub for the other payment rail.

Ephemeral processing: source and output documents are deleted 60 to 70 minutes after the last activity on a form. The pay-per-call API always runs under ephemeral processing.

The paid skills

  • make_fillable: turn a flat or scanned PDF form into a fillable PDF with detected fields (from $0.05).
  • fill_from_context: fill a PDF form using facts found in supporting documents (from $0.08).
  • fill_form: fill a PDF form from structured data such as JSON or key-value answers (from $0.07).
  • send_fax: send a PDF to a US, Canadian or Mexican fax number, taking a to DataPart and a PDF FilePart. Priced per transmitted page, with no floor price. The task stays working until the fax is delivered or fails; poll it with tasks/get.

suggest_mapping and fill_batch work on a form already in an Emboss account (they take its form_id), so they need an account and cannot be paid per job. Reading a filled form back is paid per call over HTTP instead, with /pay/read (see Over HTTP below). The first three prices are floor prices for a short form; longer forms cost more. See Pricing for the full schedule.

Check the price first

The quote_job skill is free over A2A and returns the exact price for the PDF you send, with no payment and no job created. The pay-per-call API's POST https://api.getemboss.ai/pay/quote returns the same prices without A2A.

{
  "currency": "USD",
  "expires_at": "2026-09-05T18:31:49.316308+00:00",
  "pages": 1.0,
  "price_cents": "5",
  "quote_id": "f28119f5-0f99-4226-b596-7df0cd7cb197"
}

The quote_id in the response is payable later with the execute_quote skill, or you can send the paid skill directly and pay the challenge it returns.

Paying

Send the SendMessage request without payment. Emboss creates a task in state TASK_STATE_INPUT_REQUIRED with an x402.payment.status of payment-required and an x402.payment.required block naming the price, the network, and the address to pay. Sign an EIP-3009 TransferWithAuthorization for the exact amount, resubmit a new SendMessage on the same task with the signed payload, and Emboss settles it through the Coinbase facilitator on Base. The completed task carries x402.payment.receipts and, on a make_fillable or fill_form job, an artifact with the result and a download link.

A paid job that takes longer than about 4 seconds answers working instead; check the task with tasks/get (GetTask) until it ends (completed, failed or rejected), and once it is completed the result and the receipt are there. A task ends rejected, with the reason, when the request could not be done, such as a fill whose values match no field. If the payment did not go through, the task can still complete; see the rules below. On make_fillable, fill_form and fill_from_context the payment is taken only after the job has run; a paid send_fax is charged just before the fax is sent.

Here is the real x402.payment.required block from a live make_fillable task:

{
  "accepts": [
    {
      "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bda02913",
      "description": "Emboss make_fillable (quote 3e714663-ff2c-45ac-ae11-3c7015facfef)",
      "extra": {
        "name": "USD Coin",
        "version": "2"
      },
      "maxAmountRequired": "50000",
      "maxTimeoutSeconds": 900.0,
      "mimeType": "application/json",
      "network": "base",
      "payTo": "0x57a4ba72d5cf3f4fe01c65a23aa449522d063869",
      "resource": "urn:emboss:quote:3e714663-ff2c-45ac-ae11-3c7015facfef",
      "scheme": "exact"
    }
  ],
  "x402Version": 1.0
}

And the receipt Emboss returned once that job settled:

{
  "network": "base",
  "payer": "0xBd11871b4BEE0269fC24cA6864da57fA754aBfac",
  "success": true,
  "transaction": "0x51511ce3cae9407222b1fe3867ff163ffe48d040f82742b70e1ed59574209001"
}

The completed task's artifact carries the result data alongside a FilePart whose url is the download link, such as https://getemboss.ai/d/<token>:

{
  "artifact_id": "9d8c2b1a-4e3f-4a5b-8c6d-7e9f0a1b2c3d",
  "artifact_token": "<artifact token>",
  "artifacts": [
    {
      "artifact_id": "9d8c2b1a-4e3f-4a5b-8c6d-7e9f0a1b2c3d",
      "mime_type": "application/pdf",
      "role": "fillable"
    }
  ],
  "fields": [
    {
      "description": "The full name of the applicant.",
      "group": "applicant_info",
      "id": 0,
      "kind": "text",
      "label": "Name:",
      "options": null,
      "required": true
    },
    {
      "description": "The date the form is being completed.",
      "group": "applicant_info",
      "id": 1,
      "kind": "date",
      "label": "Date:",
      "options": null,
      "required": true
    },
    {
      "description": "The email address of the applicant.",
      "group": "applicant_info",
      "id": 2,
      "kind": "text",
      "label": "Email:",
      "options": null,
      "required": true
    }
  ],
  "form_id": "fa147595-8c16-46eb-ad35-40c8e3fc7e09",
  "pages": 1,
  "quote_id": "f8b49202-b7a9-4854-ba44-5e54dd0bec7c",
  "status": "ready",
  "title": "Regression Intake Form",
  "warnings": []
}

warnings lists anything Emboss noticed about the form; it is empty for an ordinary form like this one. On a LiveCycle dynamic form it carries xfa_dynamic_form, and the result adds a message saying this is a LiveCycle dynamic form, to open it in Acrobat or Reader, print it to PDF, and upload that copy; see XFA forms. artifact_token is the proof of ownership that lets a payer with no Emboss account reuse this file in a later operation, such as sending it as a fax; send it with the artifact_id. See Artifacts and lineage.

Python, with the official x402 SDK (pip install "x402==2.21.0" eth-account):

# copied from scripts/x402_live_e2e.py, which made the first two paid x402 jobs
from x402.client import x402ClientSync
from x402.schemas.v1 import PaymentRequiredV1
from x402.mechanisms.evm.exact.v1.client import ExactEvmSchemeV1

def sign_payment(required):
    acct = agent_account()
    client = x402ClientSync().register_v1("base", ExactEvmSchemeV1(acct))
    pr = PaymentRequiredV1.model_validate(required)
    payload = client.create_payment_payload(pr)
    return payload.model_dump(by_alias=True)


def a2a_send(client, skill, parts, *, task_id=None, metadata=None, rpc_id=1):
    """One anonymous SendMessage. First call: params.metadata.skillId + parts.
    Resubmission: message.task_id + message.metadata (x402 status + payload),
    no skillId (a2a-x402 v0.1 section 4.5), exactly as the gated tests do."""
    msg = {"message_id": f"m-{rpc_id}-{int(time.time())}", "role": "ROLE_USER", "parts": parts}
    params = {"message": msg}
    if task_id:
        msg["task_id"] = task_id
        msg["metadata"] = metadata or {}
    else:
        params["metadata"] = {"skillId": skill}
    r = client.post(A2A_URL, json={"jsonrpc": "2.0", "id": rpc_id, "method": "SendMessage", "params": params},
                    headers={"Content-Type": "application/json", "A2A-Version": "1.0"}, timeout=600)
    body = r.json()
    if "error" in body:
        raise RuntimeError(f"A2A error {body['error']}")
    return body["result"]["task"]

Resubmit on the same task_id with the signed payload:

payload = sign_payment(required)
task2 = a2a_send(client, args.skill, [{"data": {}}], task_id=task["id"],
                 metadata={"x402.payment.status": "payment-submitted", "x402.payment.payload": payload},
                 rpc_id=2)

JavaScript, with the official x402 SDK (npm i @x402/core@2.25.0 @x402/evm@2.25.0 viem@2.56.3):

// copied from scripts/x402_live_e2e.mjs, which made a paid x402 job
import { privateKeyToAccount } from "viem/accounts";
import { x402Client } from "@x402/core/client";
import { ExactEvmSchemeV1 } from "@x402/evm/v1";

const account = privateKeyToAccount(process.env.TEMPO_PRIVATE_KEY);

async function send(params, id) {
  const r = await fetch(A2A_URL, {
    method: "POST",
    headers: { "Content-Type": "application/json", "A2A-Version": "1.0" },
    body: JSON.stringify({ jsonrpc: "2.0", id, method: "SendMessage", params }),
  });
  const body = await r.json();
  if (body.error) throw new Error(`A2A error ${JSON.stringify(body.error)}`);
  return body.result.task;
}

async function signPayment(required) {
  const client = new x402Client();
  client.registerV1("base", new ExactEvmSchemeV1(account));
  const payload = await client.createPaymentPayload(required);
  return JSON.parse(JSON.stringify(payload));   // plain JSON, no bigint/class instances
}

The first call and the paid resubmission:

const task = await send({ message: { message_id: `m-1-${Date.now()}`, role: "ROLE_USER", parts }, metadata: { skillId: "make_fillable" } }, 1);
const required = task.status.message.metadata["x402.payment.required"];

const payload = await signPayment(required);
const task2 = await send({ message: { message_id: `m-2-${Date.now()}`, role: "ROLE_USER", task_id: task.id, parts: [{ data: {} }],
  metadata: { "x402.payment.status": "payment-submitted", "x402.payment.payload": payload } } }, 2);

Both clients sign the same thing under the hood: an EIP-3009 TransferWithAuthorization typed-data message authorizing the exact maxAmountRequired in USDC to payTo, which the SDK builds from the x402.payment.required block.

Over HTTP

x402 also works directly against the pay-per-call API, with no A2A envelope. Every response from POST https://api.getemboss.ai/pay/<op> carries an MPP challenge (WWW-Authenticate: Payment) and an x402 v2 requirement in a PAYMENT-REQUIRED header, base64-encoded JSON. Pay either one. The x402 retry is the identical multipart request plus a PAYMENT-SIGNATURE header carrying the signed payload. The pay-per-call API also takes read-back this way, which is not a paid skill over A2A: POST https://api.getemboss.ai/pay/read reads a filled PDF's form boxes back as labelled values (from $0.01). The uploaded file is held until the read is paid for (deleted with the price if no payment arrives within the hour) and deleted as soon as the read returns; the values read stay on the quote for 24 hours so a lost answer can be fetched again. POST https://api.getemboss.ai/pay/fax sends a PDF to a US, Canadian or Mexican fax number (to in E.164), priced per transmitted page. See Read a filled form back and Send a fax.

A bodyless POST gets a floor-price 402, useful for discovery tools that probe a route before sending a real file. Here is the decoded PAYMENT-REQUIRED from a bodyless probe against /pay/make-fillable; the bazaar extension is the Bazaar discovery declaration for the route, elided here to its shape:

A JSON body with no pdf_url or pdf_base64 gets this same probe. A PAYMENT-SIGNATURE the pay-per-call API cannot read, or one whose payload has no nonce, is answered with a fresh 402 whose detail names the problem, never a 400, so a client can sign again and retry without a separate error path.

The pay-per-call API takes the PDF as JSON or as multipart. The JSON body is what Circle's CLI and SDK send; the multipart body is what the captured samples below were made with, and both price identically.

{"pdf_url": "https://example.com/form.pdf", "values": {"Full name": "Ada Lovelace"}}
{
  "accepts": [
    {
      "amount": "50000",
      "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bda02913",
      "extra": {
        "name": "USD Coin",
        "quoteId": "probe",
        "version": "2"
      },
      "maxTimeoutSeconds": 899,
      "network": "eip155:8453",
      "payTo": "0x57a4ba72d5cf3f4fe01c65a23aa449522d063869",
      "scheme": "exact"
    }
  ],
  "extensions": { "bazaar": { ... } },
  "resource": {
    "description": "Turn a flat or scanned PDF form into a fillable PDF with detected fields. Also: detect form fields in a PDF; add fillable fields to a document; extract a PDF form schema; make a government or business form fillable. Price scales with page count; this 402 carries the exact amount for the PDF sent.",
    "mimeType": "application/json",
    "serviceName": "Emboss",
    "tags": ["pdf", "forms", "x402", "mpp", "document-processing", "pdf-form-fields", "form-schema", "make-fillable"],
    "url": "https://api.getemboss.ai/pay/make-fillable"
  },
  "x402Version": 2
}

Pay only a priced 402. Sending an actual PDF to the same route returns the exact amount for that file, with a real quoteId that correlates the retry to the quote. The amount is USDC atomic units (6 decimals; 50000 here is about $0.05) and equals the atomic field the free POST /pay/quote endpoint returns for the same file:

{
  "accepts": [
    {
      "amount": "50000",
      "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bda02913",
      "extra": {
        "name": "USD Coin",
        "quoteId": "af8779a3-cf34-4ddf-adda-3a1ae02d88c0",
        "version": "2"
      },
      "maxTimeoutSeconds": 900,
      "network": "eip155:8453",
      "payTo": "0x57a4ba72d5cf3f4fe01c65a23aa449522d063869",
      "scheme": "exact"
    }
  ],
  "extensions": { "bazaar": { ... } },
  "resource": {
    "description": "Turn a flat or scanned PDF form into a fillable PDF with detected fields. Also: detect form fields in a PDF; add fillable fields to a document; extract a PDF form schema; make a government or business form fillable. Price scales with page count; this 402 carries the exact amount for the PDF sent.",
    "mimeType": "application/json",
    "serviceName": "Emboss",
    "tags": ["pdf", "forms", "x402", "mpp", "document-processing", "pdf-form-fields", "form-schema", "make-fillable"],
    "url": "https://api.getemboss.ai/pay/make-fillable"
  },
  "x402Version": 2
}

Circle's CLI pays the pay-per-call API directly from an agent wallet with a Gateway balance. Copied from the run that made the first Gateway-settled job:

circle services pay https://api.getemboss.ai/pay/make-fillable -X POST -d '{"pdf_url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf"}' --address 0xccd12f8a05714196a8ab1f9b4631dc96b7757584 --chain BASE --max-amount 0.10
{"job_id":"d0926b48-be08-40bc-93fc-28a433e67c34","status_url":"https://api.getemboss.ai/pay/jobs/d0926b48-be08-40bc-93fc-28a433e67c34?token=<token>","quote_id":"d0926b48-be08-40bc-93fc-28a433e67c34"}

The pay-per-call API answered 202 (the payment is authorized). Polling the status_url shows the filled form, and the Gateway balance went from 0.50 USDC to 0.44 USDC (the 0.06 USDC quote for a 6-page make-fillable), settled through Circle Gateway on Base.

Echo accepted unchanged when you sign: the pay-per-call API rebuilds the requirement from the quote and rejects a signature whose accepted requirement does not match field by field. Pay the priced 402 and resend the same multipart request with PAYMENT-SIGNATURE set. A successful response is a 202 with {job_id, status_url, quote_id, status} plus a PAYMENT-RESPONSE header. The paid call answers within about 10 seconds: status is ready when the job finished in that time, working when it is still running, or failed. Here is the decoded response from a paid run:

{
  "success": true,
  "transaction": "0xe1d920d01cce47b6744bd5d5d36cf59fd97fcf875d5e85e9297eb9b673a9644e",
  "network": "eip155:8453",
  "payer": "0xBd11871b4BEE0269fC24cA6864da57fA754aBfac"
}

The 402 already carries job_id and status_url. Check status_url every few seconds until status is ready or failed, then download result.download_url. The status words are explained under Checking on a job.

For a fax, ready means the carrier accepted it; keep checking until result.status is delivered, failed or expired. A failed or expired fax is refunded: by card automatically, up to three refunds a day, and by stablecoin through contact@getemboss.ai.

Two optional fields on fill-with-context

POST https://api.getemboss.ai/pay/fill-with-context takes two optional form fields alongside the file, on both rails.

policy is safe or strict. safe is the default and writes any value the engine reported as high or medium confidence; strict writes only high confidence values and leaves the rest open.

mode is one_call or two_call. one_call is the default and returns the finished PDF. two_call stops at the proposal, so a payer can read every proposed answer before anything is written into the document. When the job finishes within the paid call's wait, the 202 carries three additive keys alongside the usual job_id, status_url, quote_id and status:

{
  "job_id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
  "status_url": "https://api.getemboss.ai/pay/jobs/a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d?token=<token>",
  "quote_id": "f28119f5-0f99-4226-b596-7df0cd7cb197",
  "status": "ready",
  "proposal_id": "3e714663-ff2c-45ac-ae11-3c7015facfef",
  "commit_token": "<opaque token>",
  "proposal_url": "https://api.getemboss.ai/proposals/3e714663-ff2c-45ac-ae11-3c7015facfef?token=<opaque token>"
}

Otherwise the paid call answers working, and these same three keys come in the status check's answer once status is ready.

Read the proposal at proposal_url, then commit it with POST https://api.getemboss.ai/proposals/{proposal_id}/commit, sending the token in an X-Proposal-Token header. The token proves you own the proposal you paid for and lives 45 minutes. Committing costs nothing more: the work was billed when you paid for the prepare.

curl -X POST https://api.getemboss.ai/proposals/3e714663-ff2c-45ac-ae11-3c7015facfef/commit \
  -H "X-Proposal-Token: <commit_token>" \
  -H "Content-Type: application/json" \
  -d '{"values": [{"field_id": 14, "value": "12-3456789"}]}'

See Prepare, commit and verify for the proposal shape, the three results and the receipt. If two-call mode is not enabled on the service, mode=two_call returns a 503; every other path is unaffected.

Python, with the official x402 SDK (pip install "x402==2.21.0" eth-account httpx):

# copied from scripts/x402_live_e2e.py --http, which paid the first x402 job on the pay-per-call API
from x402.client import x402Client
from x402.http.clients.httpx import x402_httpx_transport
from x402.mechanisms.evm.exact.client import ExactEvmScheme

def http_client(account):
    client = x402Client().register("eip155:8453", ExactEvmScheme(account))
    return httpx.AsyncClient(transport=x402_httpx_transport(client))


async def run_http(account, form_dir, op):
    files, data = multipart_body(form_dir, args.skill)
    async with http_client(account) as client:
        resp = await client.post(f"https://api.getemboss.ai/pay/{op}", files=files, data=data, timeout=600)
    payment_response_hdr = resp.headers.get("PAYMENT-RESPONSE")
    if payment_response_hdr:
        from x402.http.utils import decode_payment_response_header
        sr = decode_payment_response_header(payment_response_hdr)
        tx = sr.transaction or None
    # resp.status_code == 202; resp.json() has job_id, status_url, quote_id

x402_httpx_transport pays the 402 automatically: it sees the first 402, signs a v2 payload for the requirement, and resends the same request with PAYMENT-SIGNATURE set, so a single client.post(...) call is the whole retry.

JavaScript, with the official x402 SDK (npm i @x402/fetch@2.25.0 @x402/core@2.25.0 @x402/evm@2.25.0 viem@2.56.3):

// copied from scripts/x402_live_e2e.mjs --http, which paid an x402 job on the pay-per-call API
import { x402Client } from "@x402/core/client";
import { decodePaymentResponseHeader } from "@x402/core/http";
import { ExactEvmScheme } from "@x402/evm";
import { wrapFetchWithPayment } from "@x402/fetch";

const client = new x402Client().register("eip155:8453", new ExactEvmScheme(account));
const fetchWithPay = wrapFetchWithPayment(fetch, client);

const paidResp = await fetchWithPay(url, { method: "POST", body: multipartBodyFor(form, skill) });
const paymentResponseHeader = paidResp.headers.get("PAYMENT-RESPONSE");
if (paymentResponseHeader) {
  const settle = decodePaymentResponseHeader(paymentResponseHeader);
  // settle.success, settle.transaction, settle.network, settle.payer
}
// paidResp.status === 202; await paidResp.json() has job_id, status_url, quote_id

wrapFetchWithPayment does the same automatic retry as the Python transport: the wrapped fetch sees the 402, signs, and resends with PAYMENT-SIGNATURE set.

Rules a payer should know

  • The price in the challenge is the price charged: it comes from the same engine as the account rail and the pay-per-call API's free quote.
  • One payment authorization per quote on the A2A rail: a rejected or failed attempt uses up that quote's nonce, so ask for a new quote instead of signing again on the same task. The pay-per-call API works differently: a rejected or failed x402 attempt there gets a fresh 402, and a new signature (a new nonce) on the same quote is accepted.
  • On the A2A rail, a message sent again on a task you paid for is answered "This task is paid; check it for the result." Check the task with tasks/get (GetTask) instead of starting a new one, which would mean a new price.
  • On a paid send_fax, or a paid job cut off by an update to Emboss, if Emboss cannot confirm whether the payment went through, the task ends failed with "We could not confirm your payment. Contact contact@getemboss.ai with your task id." Contact us with the task id rather than paying again on a new task: the payment may already have been taken. On the other paid skills the job has already run, so the task completes with the result and the same sentence; see below.
  • A quote that is not paid expires an hour after it is made; once paid, its job and result stay available for 24 hours from the quote.
  • On the pay-per-call API, a paid job answers one of five words at its status_url: awaiting_payment, working, ready, failed or expired. For a fax, ready means the carrier accepted it; keep checking until result.status is delivered, failed or expired.
  • Resending the identical paid request with the same PAYMENT-SIGNATURE never charges again: it returns the job, runs a failed job again at no charge (up to three runs in all for one payment), and answers 410 Gone once the result has expired. The same signature with a different document or setting is refused with 409.
  • On the A2A rail the payer needs USDC on the Base network only; no ETH, the facilitator pays the gas. On the pay-per-call API the 402's accepts list carries the Base entry first, then one entry for every chain Circle Gateway supports (extra.name is GatewayWalletBatched); a payer with a Circle Gateway balance can pay any of those gaslessly, and a plain x402 wallet keeps paying the Base entry as before.
  • Anonymous quotes are limited per IP address and hour; a limited caller receives a rate-limit error and can retry in the next hour.

A resubmitted task can come back with an error instead of a receipt:

  • INSUFFICIENT_FUNDS: the payer's USDC balance is below the quoted amount.
  • INVALID_SIGNATURE: the signature doesn't verify against the authorization, including a malformed payload or a facilitator rejection with no closer code.
  • EXPIRED_PAYMENT: the authorization's validBefore falls inside the job's runtime window.
  • DUPLICATE_NONCE: that authorization's nonce was already used.

On make_fillable, fill_form and fill_from_context, Emboss runs the job before it settles the payment on-chain. Nothing is charged when the payment service clearly refused the payment: the task still completes with the result, its final answer carries a receipt whose success is false, with an errorReason, and a later check of the task carries the result with no receipt. If Emboss cannot confirm whether the payment went through (a server error, an answer that could not be read, none, or the payment step breaking off with an error), the payment may have been taken: the task completes with the result and "We could not confirm your payment. Contact contact@getemboss.ai with your task id.", in its answer and when you check the task later. Its receipt is marked not successful, unless Emboss recorded the payment, when the receipt shows it. Contact contact@getemboss.ai with the task id rather than paying again.

A paid send_fax settles first, just before the fax is sent: if the payment service clearly refuses the payment, the task ends failed with "Payment did not settle; nothing was sent." and nothing is charged; an unclear answer ends it as the rule above says.

Billing and privacy

Pay-per-call operations are paid per request and do not use an Emboss account. If you use the regular API with an API key instead, the account billing applies: The first 5 form creations, 5 context fills, 5 standard fills, 5 verifications, 5 reads, and 5 packages each month are free when the job is 5 pages or fewer (a context fill counts the form and its documents together); a longer job is charged and does not use a free one. Only the uploaded PDF and any context you attach are processed for the request; see the privacy policy.