Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.getovra.com/llms.txt

Use this file to discover all available pages before exploring further.

MPP is the Machine Payments Protocol: an IETF draft (draft-httpauth-payment-00, co-authored Tempo Labs + Stripe, see paymentauth.org) that turns HTTP 402 Payment Required into a real, machine-readable challenge. Ovra implements the card method as a credential issuer. If a merchant returns WWW-Authenticate: Payment ..., your agent can pay it without ever rendering a form, opening a browser, or seeing a card number.
Code-complete (Phase 7). Sandbox-only end-to-end. The credential envelope is a real JWE; merchants ship a private key in their MPP onboarding to decrypt it.

Wire flow

1

Merchant returns 402

HTTP/1.1 402 Payment Required
WWW-Authenticate: Payment challenge="<base64-json>"
The challenge body declares amount, currency, payee, expiry, and resource URL.
2

Agent mints a credential

Agent calls POST /v1/mpp/credentials/mint with the raw challenge plus an approved intent and a card. Ovra mints a JWE-wrapped network token bound to the intent.
3

Agent presents the credential

Agent retries the request with Authorization: Payment <credential>.
4

Merchant decrypts and settles

Merchant uses its own private key (RSA-OAEP-256 + A256GCM) to unwrap the DPAN + cryptogram, then settles via its own acquirer.
5

Merchant returns Payment-Receipt

The receipt header is the canonical record of settlement.
6

Agent verifies

Agent calls POST /v1/mpp/credentials/:id/verify (or /by-challenge/:challengeId/verify) — Ovra runs the CAS consume, drives the intent FSM to completed, writes the transactions row with rail = 'mpp', fires mpp.transaction.completed.

High-level convenience: POST /v1/mpp/pay

For most agent code paths, you don’t want to orchestrate the round-trip yourself. The high-level endpoint does it all:
curl -X POST https://api.getovra.com/v1/mpp/pay \
  -H "Authorization: Bearer $OVRA_AGENT_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "url": "https://shop.example.com/api/orders",
    "intentId": "int_...",
    "cardId": "ca_..."
  }'
What it does:
  1. GET the URL, expects 402 with an MPP challenge.
  2. Mints a credential bound to your intentId + cardId.
  3. Retries the request with Authorization: Payment <cred>.
  4. Receives Payment-Receipt, calls verify, writes the transaction.
Response (200):
{
  "credential_id": "mppc_a1b2c3...",
  "merchant_status": 200,
  "receipt": "eyJ2ZXJzaW9uIjoiMSIs...",
  "merchant_body": { "order_id": "ord_42" }
}

Low-level: mint + verify

curl -X POST https://api.getovra.com/v1/mpp/credentials/mint \
  -H "Authorization: Bearer $OVRA_AGENT_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "challenge": "Payment challenge=\"eyJ2ZXJzaW9uIjoiMSIs...\"",
    "intentId": "int_...",
    "cardId": "ca_..."
  }'

Error matrix

StatusCodeMeaning
400E_MPP_CRED_MALFORMED_CHALLENGEChallenge unparseable
400E_MPP_CRED_AMOUNT_MISMATCHIntent amount ≠ challenge amount
400E_MPP_CRED_MERCHANT_MISMATCHIntent merchant ≠ challenge payTo
400E_MPP_CRED_PAR_REQUIREDPAR is mandatory but missing
403E_MPP_CRED_INTENT_NOT_APPROVEDIntent not in approved state
403E_MPP_CRED_INTENT_MISMATCHIntent owner ≠ caller
404E_MPP_CRED_MERCHANT_NOT_FOUNDNo merchant registered for the challenge realm
410E_MPP_CRED_INTENT_EXPIREDIntent past expiresAt
410E_MPP_CRED_CARD_CONSUMEDSingle-use card already burned
502E_UPSTREAM_ERRORCard-issuer or PCI-proxy upstream failure
Verify failures use RFC 9457 Problem Details (application/problem+json) with type URIs invalid-challenge or verification-failed — never an oracle. Replays return 402 invalid-challenge regardless of the underlying cause.

Merchant onboarding

If you operate a merchant that wants to accept MPP, register via POST /v1/merchants with an invite code:
curl -X POST https://api.getovra.com/v1/merchants \
  -H "Authorization: Bearer $OVRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "inviteCode": "INVITE-2026-...",
    "name": "Acme Shop",
    "encryptionJwk": { "kty": "RSA", "alg": "RSA-OAEP-256", "use": "enc", ... },
    "webhookUrl": "https://shop.acme.com/ovra/webhooks"
  }'
Response (one-shot reveal, save it):
{
  "id": "mer_...",
  "slug": "acme-shop",
  "merchant_secret_key": "mer_sk_a1b2c3...",
  "warning": "Save merchant_secret_key now — it will not be shown again."
}
Use mer_sk_* to call /verify. The JWK validates as RSA-OAEP-256 + A256GCM only; downgrades are rejected at write time.

Webhooks

EventWhen
mpp.credential.mintedCredential issued via /mint
mpp.credential.consumedCredential successfully verified
mpp.credential.expiredReserved (sweep is future work; expiry surfaces today via 410 from mint and 402 from verify)
mpp.transaction.completedVerified MPP transaction settled

Sacred guarantees

  • Zero card data in agent context. The credential envelope is a base64url-nopad JWE; only the merchant’s private key can decrypt it. Even if the agent logs the full mint response, no PAN/DPAN/cryptogram bleeds.
  • Single-use. CAS consume on verify — replay returns 402.
  • Bound to one intent. Reused intent IDs across challenges are rejected with E_MPP_CRED_INTENT_MISMATCH.

Next

CUA

The other Pay mode — for merchants without MPP.

Pay overview

How MPP and CUA share one product narrative.

Intents

The approval primitive every credential binds to.

Webhooks

Subscribe to mpp.* events for real-time updates.