> ## 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

> Machine Payments Protocol — agents pay merchants over HTTP with a JWE-wrapped network token.

MPP is the **Machine Payments Protocol**: an IETF draft (`draft-httpauth-payment-00`, co-authored Tempo Labs + Stripe, see [paymentauth.org](https://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.

<Note>
  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.
</Note>

## Wire flow

<Steps>
  <Step title="Merchant returns 402">
    ```http theme={}
    HTTP/1.1 402 Payment Required
    WWW-Authenticate: Payment challenge="<base64-json>"
    ```

    The challenge body declares amount, currency, payee, expiry, and resource URL.
  </Step>

  <Step title="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.
  </Step>

  <Step title="Agent presents the credential">
    Agent retries the request with `Authorization: Payment <credential>`.
  </Step>

  <Step title="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.
  </Step>

  <Step title="Merchant returns Payment-Receipt">
    The receipt header is the canonical record of settlement.
  </Step>

  <Step title="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`.
  </Step>
</Steps>

## 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:

```bash theme={}
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):

```json theme={}
{
  "credential_id": "mppc_a1b2c3...",
  "merchant_status": 200,
  "receipt": "eyJ2ZXJzaW9uIjoiMSIs...",
  "merchant_body": { "order_id": "ord_42" }
}
```

## Low-level: mint + verify

<CodeGroup>
  ```bash 1. Mint theme={}
  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_..."
    }'
  ```

  ```bash 2. Present theme={}
  curl https://shop.example.com/api/orders \
    -H "Authorization: Payment $CREDENTIAL"
  ```

  ```bash 3. Verify (merchant calls this — or you can, with merchant key) theme={}
  curl -X POST https://api.getovra.com/v1/mpp/credentials/$CRED_ID/verify \
    -H "X-Merchant-Key: mer_sk_..."
  ```
</CodeGroup>

## Error matrix

| Status | Code                             | Meaning                                        |
| ------ | -------------------------------- | ---------------------------------------------- |
| 400    | `E_MPP_CRED_MALFORMED_CHALLENGE` | Challenge unparseable                          |
| 400    | `E_MPP_CRED_AMOUNT_MISMATCH`     | Intent amount ≠ challenge amount               |
| 400    | `E_MPP_CRED_MERCHANT_MISMATCH`   | Intent merchant ≠ challenge `payTo`            |
| 400    | `E_MPP_CRED_PAR_REQUIRED`        | PAR is mandatory but missing                   |
| 403    | `E_MPP_CRED_INTENT_NOT_APPROVED` | Intent not in `approved` state                 |
| 403    | `E_MPP_CRED_INTENT_MISMATCH`     | Intent owner ≠ caller                          |
| 404    | `E_MPP_CRED_MERCHANT_NOT_FOUND`  | No merchant registered for the challenge realm |
| 410    | `E_MPP_CRED_INTENT_EXPIRED`      | Intent past `expiresAt`                        |
| 410    | `E_MPP_CRED_CARD_CONSUMED`       | Single-use card already burned                 |
| 502    | `E_UPSTREAM_ERROR`               | Card-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:

```bash theme={}
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):

```json theme={}
{
  "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

| Event                       | When                                                                                         |
| --------------------------- | -------------------------------------------------------------------------------------------- |
| `mpp.credential.minted`     | Credential issued via `/mint`                                                                |
| `mpp.credential.consumed`   | Credential successfully verified                                                             |
| `mpp.credential.expired`    | Reserved (sweep is future work; expiry surfaces today via 410 from mint and 402 from verify) |
| `mpp.transaction.completed` | Verified 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

<CardGroup cols={2}>
  <Card title="CUA" icon="browser" href="/concepts/cua">
    The other Pay mode — for merchants without MPP.
  </Card>

  <Card title="Pay overview" icon="credit-card" href="/concepts/pay">
    How MPP and CUA share one product narrative.
  </Card>

  <Card title="Intents" icon="file-signature" href="/concepts/intents">
    The approval primitive every credential binds to.
  </Card>

  <Card title="Webhooks" icon="bell" href="/concepts/webhooks">
    Subscribe to `mpp.*` events for real-time updates.
  </Card>
</CardGroup>
