Pay per call with MPP

The Machine Payments Protocol (MPP) lets an agent pay for a single Emboss operation over plain HTTP, with no account. Emboss issues a standard MPP payment challenge, you pay it with a stablecoin or a card, and the operation runs. This page covers the pay-per-call API in detail.

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 operations

Each operation takes a PDF as JSON (pdf_url or pdf_base64) or as a multipart upload. Prices below are base prices for a short form; longer forms cost more, and the exact price for your request is always in the payment challenge. See Pricing.

  • POST https://api.getemboss.ai/pay/make-fillable: turn a flat or scanned PDF form into a fillable PDF with detected fields (from $0.05).
  • POST https://api.getemboss.ai/pay/fill-with-context: fill a PDF form using facts found in supporting documents (from $0.08).
  • POST https://api.getemboss.ai/pay/fill: fill a PDF form from structured data such as JSON or key-value answers (from $0.07).
  • POST https://api.getemboss.ai/pay/read: read a filled PDF's form boxes back as labelled values (from $0.01). Anonymous: 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: send a PDF to a fax number (to in E.164), priced per transmitted page. In place of a PDF, send the artifact_id of a file from an earlier paid operation together with its artifact_token, or a sources list to fax several artifacts as one packet; see Artifacts and lineage.

For what each operation does and returns, see the MCP tools reference; the pay-per-call API runs the same engine.

Check the price first

Every operation is priced by page count, so you can ask for the exact price of your PDF for free before paying. The check is stateless: it reserves nothing and creates no job.

For a one-page form and a short note:

curl https://api.getemboss.ai/pay/quote \
  -F "file=@form.pdf" \
  -F "context_text=Grace Hopper, born 9 December 1906, 1 Navy Way, Arlington, VA 22202."
{
  "already_fillable": false,
  "card_minimum_usd": "0.50",
  "context_pages": 1,
  "expires": null,
  "methods": [
    "tempo",
    "stripe",
    "x402"
  ],
  "next": "POST /pay/<op> with the same file. The 402 challenge carries this exact price.",
  "pages": 1,
  "prices": {
    "fax": {
      "atomic": "30000",
      "cents": 3,
      "usd": "0.03"
    },
    "fill": {
      "atomic": "70000",
      "cents": 7,
      "usd": "0.07"
    },
    "fill-with-context": {
      "atomic": "80000",
      "cents": 8,
      "usd": "0.08"
    },
    "make-fillable": {
      "atomic": "50000",
      "cents": 5,
      "usd": "0.05"
    },
    "read": {
      "atomic": "10000",
      "cents": 1,
      "usd": "0.01"
    }
  }
}

The same check with a JSON body, for clients that cannot send multipart:

curl https://api.getemboss.ai/pay/quote \
  -H "content-type: application/json" \
  -d '{"pdf_url": "https://example.com/form.pdf"}'

pdf_base64 (the PDF as standard base64) works in place of pdf_url. For /pay/fill, values is a JSON object in the body; for /pay/fill-with-context, supporting documents go in context_base64 (a list of {"name", "base64"}), context_text, or context_urls.

Send the same context files with the check to price fill-with-context accurately. If the PDF already has form fields, the response says so and make-fillable is not needed.

Paying

Send the request without payment first. Emboss answers 402 Payment Required with a WWW-Authenticate: Payment header that carries one challenge per payment method. Pay one of them, then resend the identical request with the Authorization: Payment credential. Emboss verifies the payment and responds 202 Accepted with a job_id, a status_url to poll, and a Payment-Receipt header.

An Authorization header that does not use the Payment scheme is answered with a fresh 402 rather than a 400: the challenge tells the caller how to pay, and nothing is parsed or claimed.

Two payment methods are offered on every challenge:

  • Tempo stablecoin (USDC.e). You pay exactly the quoted price on the Tempo network.
  • Stripe Shared Payment Token (card via Link). Card payments have a minimum of 50 cents, so a card pays 50 cents for an operation quoted below that; the challenge states the amount you will be charged.

Standard MPP clients handle the whole exchange for you. Because every operation uploads a PDF, send the PDF as JSON (pdf_url or pdf_base64), or use a client that sends the file as multipart bytes.

Python, with the mpp package (pip install "pympp[tempo]"):

import asyncio
from mpp.client import post
from mpp.methods.tempo import tempo, TempoAccount, ChargeIntent

method = tempo(
    account=TempoAccount.from_env("TEMPO_PRIVATE_KEY"),
    intents={"charge": ChargeIntent()},
)
r = asyncio.run(post(
    "https://api.getemboss.ai/pay/make-fillable",
    methods=[method],
    files={"file": ("form.pdf", open("form.pdf", "rb"), "application/pdf")},
))
job = r.json()  # 202: job_id, status_url, quote_id

JavaScript, with mppx (npm i mppx viem):

import { readFileSync } from "node:fs";
import { privateKeyToAccount } from "viem/accounts";
import { Mppx, tempo } from "mppx/client";

const mppx = Mppx.create({
  methods: [tempo({ account: privateKeyToAccount(process.env.TEMPO_PRIVATE_KEY) })],
});

const form = new FormData();
form.append("file", new Blob([readFileSync("form.pdf")], { type: "application/pdf" }), "form.pdf");

const r = await mppx.fetch("https://api.getemboss.ai/pay/make-fillable", { method: "POST", body: form });
const job = await r.json(); // 202: job_id, status_url, quote_id

Both pay the challenge and return the 202 body. 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. Check status_url every few seconds until status is ready or failed, then download the finished PDF from result.download_url. The ready result also carries artifact_id and artifact_token: send both to a later paid operation, such as /pay/fax, to reuse the file without paying to upload it again. See Artifacts and lineage.

The tempo CLI's --data @file flag is text-only: it strips line endings from the body and corrupts a PDF's compressed streams. Use one of the clients above for file uploads.

Checking on a job

The 402 already carries the job's job_id and status_url, so you hold the link to your result before you pay. GET status_url is free and answers one word in status:

  • awaiting_payment: priced, not yet paid.
  • working: still running. Check again after poll_after_seconds (also sent as a Retry-After header).
  • ready: finished. result carries the output and, for a job that produces a file, result.download_url: the same link on every check.
  • failed: the job did not finish. error says why, and retry, when present, says how to run it again at no charge.
  • expired: the result's files are deleted, about an hour after the job, under ephemeral processing; or a price that was never paid has expired, and sending the request again gets a new one.

quote_status keeps the older word (quoted, claimed, executed or expired) for clients that read it.

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.

If the answer to the paid call is lost, check the status_url from the 402, or resend the identical request with the same payment credential: nothing is charged again, and you get the job back. A job that failed runs again at no charge when you resend it the same way, up to three runs in all for one payment; after the third failed run it stays failed, and contact@getemboss.ai can help with the job id. A resend after the result expired is answered 410 Gone: a new run needs a new payment. A resend must carry the same document and settings as the first call; a different one is refused with 409.

A resend, a free retry included, works only while its payment challenge is still valid (the challenge's expires says until when). After that, a resend gets a new price, which a client that pays automatically would pay a second time. Check the status_url instead: a job that failed then no longer offers retry, and contact@getemboss.ai can help with the job id.

Two optional fields on fill-with-context

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

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.

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.