> ## 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 zahlen Merchants über HTTP mit JWE-wrapped Network-Token.

MPP ist das **Machine Payments Protocol**: ein IETF-Draft (`draft-httpauth-payment-00`, co-authored Tempo Labs + Stripe, siehe [paymentauth.org](https://paymentauth.org)) der HTTP `402 Payment Required` in eine echte, maschinenlesbare Challenge verwandelt. Ovra implementiert die `card`-Methode als Credential-Issuer.

Wenn ein Merchant `WWW-Authenticate: Payment ...` returnt, kann dein Agent zahlen — ohne ein Formular zu rendern, einen Browser zu öffnen oder eine Kartennummer zu sehen.

<Note>
  Code-complete (Phase 7). Sandbox-only End-to-End. Das Credential-Envelope ist ein echtes JWE; Merchants liefern in ihrem MPP-Onboarding einen Private Key zum Entschlüsseln.
</Note>

## Wire-Flow

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

    Der Challenge-Body deklariert Amount, Currency, Payee, Expiry und Resource-URL.
  </Step>

  <Step title="Agent mintet Credential">
    Agent ruft `POST /v1/mpp/credentials/mint` mit der rohen Challenge plus genehmigtem Intent und einer Karte. Ovra mintet ein JWE-wrapped Network-Token gebunden an den Intent.
  </Step>

  <Step title="Agent präsentiert Credential">
    Agent wiederholt den Request mit `Authorization: Payment <credential>`.
  </Step>

  <Step title="Merchant entschlüsselt und settlet">
    Merchant verwendet eigenen Private Key (RSA-OAEP-256 + A256GCM) zum Unwrap von DPAN + Cryptogram, settlet via eigenen Acquirer.
  </Step>

  <Step title="Merchant returnt Payment-Receipt">
    Der Receipt-Header ist der kanonische Settlement-Record.
  </Step>

  <Step title="Agent verifiziert">
    Agent ruft `POST /v1/mpp/credentials/:id/verify` (oder `/by-challenge/:challengeId/verify`) — Ovra führt CAS-Consume aus, treibt Intent-FSM auf `completed`, schreibt `transactions`-Row mit `rail = 'mpp'`, feuert `mpp.transaction.completed`.
  </Step>
</Steps>

## High-Level-Convenience: `POST /v1/mpp/pay`

Für die meisten Agent-Code-Pfade willst du den Roundtrip nicht selbst orchestrieren. Der High-Level-Endpoint macht alles:

```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_..."
  }'
```

Was er tut:

1. `GET` die URL, erwartet `402` mit MPP-Challenge.
2. Mintet eine Credential gebunden an `intentId` + `cardId`.
3. Wiederholt den Request mit `Authorization: Payment <cred>`.
4. Empfängt `Payment-Receipt`, ruft Verify, schreibt die Transaktion.

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. Präsentieren theme={}
  curl https://shop.example.com/api/orders \
    -H "Authorization: Payment $CREDENTIAL"
  ```

  ```bash 3. Verifizieren (Merchant ruft das — oder du, mit 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                             | Bedeutung                                         |
| ------ | -------------------------------- | ------------------------------------------------- |
| 400    | `E_MPP_CRED_MALFORMED_CHALLENGE` | Challenge nicht parsbar                           |
| 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 ist mandatory aber fehlt                      |
| 403    | `E_MPP_CRED_INTENT_NOT_APPROVED` | Intent nicht in `approved`-State                  |
| 403    | `E_MPP_CRED_INTENT_MISMATCH`     | Intent-Owner ≠ Caller                             |
| 404    | `E_MPP_CRED_MERCHANT_NOT_FOUND`  | Kein Merchant für die Challenge-Realm registriert |
| 410    | `E_MPP_CRED_INTENT_EXPIRED`      | Intent jenseits `expiresAt`                       |
| 410    | `E_MPP_CRED_CARD_CONSUMED`       | Single-use-Card bereits verbrannt                 |
| 502    | `E_UPSTREAM_ERROR`               | Card-Issuer- oder PCI-Proxy-Upstream-Failure      |

Verify-Failures nutzen RFC-9457 Problem Details (`application/problem+json`) mit Type-URIs `invalid-challenge` oder `verification-failed` — niemals ein Oracle. Replays returnen `402 invalid-challenge` egal welche Underlying-Cause.

## Merchant-Onboarding

Wenn du einen Merchant betreibst der MPP akzeptieren möchte, registriere via `POST /v1/merchants` mit 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, speichern):

```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."
}
```

`mer_sk_*` zum Aufruf von `/verify` verwenden. Der JWK validiert ausschließlich als RSA-OAEP-256 + A256GCM; Downgrades werden zur Write-Time abgelehnt.

## Webhooks

| Event                       | Wann                                                                                          |
| --------------------------- | --------------------------------------------------------------------------------------------- |
| `mpp.credential.minted`     | Credential via `/mint` ausgestellt                                                            |
| `mpp.credential.consumed`   | Credential erfolgreich verifiziert                                                            |
| `mpp.credential.expired`    | Reserviert (Sweep ist Future Work; Expiry surfaced heute via 410 von Mint und 402 von Verify) |
| `mpp.transaction.completed` | Verifizierte MPP-Transaktion settled                                                          |

## Sacred Guarantees

* **Null Card-Data im Agent-Kontext.** Das Credential-Envelope ist eine base64url-nopad JWE; nur der Private Key des Merchants kann sie entschlüsseln. Selbst wenn der Agent die volle Mint-Response loggt blutet kein PAN/DPAN/Cryptogram.
* **Single-use.** CAS-Consume bei Verify — Replay returnt 402.
* **Gebunden an einen Intent.** Wiederverwendete Intent-IDs über Challenges hinweg werden mit `E_MPP_CRED_INTENT_MISMATCH` abgelehnt.

## Weiter

<CardGroup cols={2}>
  <Card title="CUA" icon="browser" href="/de/concepts/cua">
    Der andere Pay-Modus — für Merchants ohne MPP.
  </Card>

  <Card title="Pay-Übersicht" icon="credit-card" href="/de/concepts/pay">
    Wie MPP und CUA ein Produktnarrativ teilen.
  </Card>

  <Card title="Intents" icon="file-signature" href="/de/concepts/intents">
    Die Approval-Primitive an die jede Credential bindet.
  </Card>

  <Card title="Webhooks" icon="bell" href="/de/concepts/webhooks">
    `mpp.*` Events für Echtzeit-Updates abonnieren.
  </Card>
</CardGroup>
