MishiSpark

How an agent pays: HTTP 402

An agent with an API key pays per call with credits. When the credits cannot cover a call, the API does not just refuse it. It answers 402 Payment Required with a payment challenge. A person pays the challenge, and the agent sends the same request again.

Not public yet. Credits, the 402 challenge and buying credits are switched off while we finish them. This page describes how they work when they open.

The flow

  1. 1. Request an audit with an API key

    The key needs the scope audit:write. API keys do not need the human check (Turnstile). Send an Idempotency-Key: the retry in step 4 must use the same value.

    curl -X POST https://api.spark.mishipay.com/api/v1/checkout-audit/requests \
      -H "X-API-Key: $SPARK_API_KEY" \
      -H "Idempotency-Key: audit-example-com-1" \
      -H "Content-Type: application/json" \
      -d '{"url": "https://example.com", "effort": "standard",
           "consent": {"accept_terms": true}}'
  2. 2. Read the 402 payment challenge

    The credits cannot cover the price, so the API answers 402. The WWW-Authenticate: Payment header and the payment object carry the same challenge. The amount to pay is the price minus the balance, but at least the minimum top-up (500 credits, $5). The challenge is open for 15 minutes. A retry with the same Idempotency-Key gets the same challenge.

    Response to POST /api/v1/checkout-audit/requests
    HTTP/1.1 402 Payment Required
    Content-Type: application/json
    WWW-Authenticate: Payment realm="MishiSpark", challenge="7c1e4a52-3f0b-4d8e-9a61-2b5d0c9e8f14", amount="2380", currency="USD", rails="adyen"
    X-Credits-Required: 2500
    X-Credits-Available: 120
    
    {
      "error": {
        "code": "INSUFFICIENT_CREDITS",
        "message": "A standard audit costs 2500 credits; 120 available",
        "request_id": "0b6f3d2e-8a41-4c7e-b5d9-1e2f3a4b5c6d"
      },
      "payment": {
        "challenge_id": "7c1e4a52-3f0b-4d8e-9a61-2b5d0c9e8f14",
        "price_credits": 2500,
        "balance_credits": 120,
        "amount_credits": 2380,
        "amount_cents": 2380,
        "currency": "USD",
        "rails": [
          {
            "rail": "adyen",
            "kind": "topup_page"
          }
        ],
        "topup_url": "https://spark.mishipay.com/billing/credits?challenge=7c1e4a52-3f0b-4d8e-9a61-2b5d0c9e8f14",
        "pay_link_url": null,
        "expires_at": "2026-10-22T10:15:00+00:00",
        "retry": "Pay, then send the same request again with the same Idempotency-Key."
      }
    }

    amount in the header is in cents. rails lists the ways to pay that are switched on; today that is the Adyen top-up page only. While buying credits is switched off, rails is empty and the header says rails="none".

  3. 3. A person pays

    An agent cannot complete a card payment. Give the topup_url to a person. They sign in and pay on the credits page. Or, with the scope billing:write, the agent starts the payment itself and gives the person the payment session to complete:

    curl -X POST https://api.spark.mishipay.com/api/v1/credits/purchase \
      -H "X-API-Key: $SPARK_API_KEY" \
      -H "Idempotency-Key: pay-7c1e4a52" \
      -H "Content-Type: application/json" \
      -d '{"challenge_id": "7c1e4a52-3f0b-4d8e-9a61-2b5d0c9e8f14"}'

    409 PURCHASE_DISABLED means buying credits is not open yet; 409 CHALLENGE_NOT_OPEN means the challenge is already paid, expired or cancelled. Then poll the challenge (scope billing:read) until it is paid:

    curl https://api.spark.mishipay.com/api/v1/credits/challenges/7c1e4a52-3f0b-4d8e-9a61-2b5d0c9e8f14 \
      -H "X-API-Key: $SPARK_API_KEY"
    # "status": "open" -> "paid" (or "expired", "cancelled")
  4. 4. Retry with the same Idempotency-Key

    Send the original request again with the same Idempotency-Key. The credits now cover it, so it runs and you pay once.

    On metered calls (an analytics tool call, a chat query, an export) the response carries Payment-Receipt: challenge="…", rail="adyen", ref="…", which names the paid challenge. The audit request answers 201 with the audit id, a one-time access_key and the report_url. It does not send Payment-Receipt today.

  5. 5. Poll the status, then read the report

    Poll GET /api/v1/checkout-audit/{id} (scope audit:read) about every 30 seconds until the status is complete, budget_exceeded, failed or cancelled. Then read GET /api/v1/checkout-audit/{id}/report.

    The credits are held when the API accepts the request. A complete audit, or one that reached its budget, is charged in full. A cancelled audit gives the credits back. A failed audit is refunded.

KEY_BUDGET_EXCEEDED

A 402 with the code KEY_BUDGET_EXCEEDED has no challenge, no WWW-Authenticate header and no payment object. The API key reached its own monthly budget or its spend ceiling, which its owner set on the key. Buying credits does not help. Use another key with budget left, or wait for the next month when the monthly budget is the limit.

Audit prices

EffortCreditsUSD
quick
10 products, one model, no extended thinking
300$3
standard
50 products, up to three models, conformance checks
2,500$25
deep
Up to 500 products, extended thinking, live runs on verified domains
15,000$150

Ready to double your store sales?

Connect your store in 60 seconds. Get your first AI diagnostic free.