{
 "$schema": "https://json-schema.org/draft/2020-12/schema",
 "$id": "https://travel.agenthaven.dev/contracts/schemas/mandate-request.schema.json",
 "title": "Mandate request and its document (authority-api.md)",
 "description": "What a buyer sends to POST <issuer>/mandate-requests (request), and what the authority answers there and at GET <issuer>/mandate-requests/<request_id> (document). The document carries no person, no number and no quote content: the buyer has the quote, and the person stays in the authority's registry. Status: IMPLEMENTED by agent-buyer (HttpAuthorityTransport, authority_http.py) and agent-authority (https://sealedby.dev), both since 2026-09-11. Since 2026-09-13 the request may name a `format` (poc-v1, the default, or ap2-v0.2) and carry `cnf_jwk`, the buyer's public key; an issued document then also carries `ap2`, the two open roots of the AP2 v0.2 form (contracts/mandate-ap2-profile.md). Every document says its `format`. Since 2026-09-13 the request may also carry `intent_ceiling`, the ceiling the buyer extracted from the person's words, for the approval page. Since 2026-09-20 (authority-api.md v0.8) the request may carry `assistant_token`, the access token of the assistant it came through.",
 "$defs": {
  "request": {
   "type": "object",
   "required": [
    "quote_jws",
    "sub",
    "act_sub",
    "cnf_jkt",
    "correlation_id"
   ],
   "additionalProperties": false,
   "properties": {
    "quote_jws": {
     "type": "string",
     "description": "The seller's quote, the compact quote+jwt exactly as received. The authority verifies it by the seller's domain and recomputes quote_hash from its payload octets."
    },
    "sub": {
     "type": "string",
     "minLength": 1,
     "description": "The opaque id the authority holds for the person. Never a phone number: a phone-shaped value is refused as request_invalid."
    },
    "act_sub": {
     "type": "string",
     "pattern": "^spiffe://",
     "description": "The buyer instance that will present the mandate (mandate act.sub)."
    },
    "cnf_jkt": {
     "type": "string",
     "pattern": "^[A-Za-z0-9_-]{43}$",
     "description": "RFC 7638 thumbprint of the buyer's stable signing key (mandate cnf.jkt)."
    },
    "correlation_id": {
     "type": "string",
     "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
     "description": "The buyer's transaction id; must equal the quote's."
    },
    "format": {
     "type": "string",
     "enum": [
      "poc-v1",
      "ap2-v0.2"
     ],
     "default": "poc-v1",
     "description": "The mandate format the buyer will present. poc-v1: the two mandate+jwt (intent, cart). ap2-v0.2: those two, plus an open Checkout Mandate and an open Payment Mandate as Delegate SD-JWT roots, built by the reference SDK, which the buyer closes with one key-binding hop each at checkout. Absent means poc-v1."
    },
    "cnf_jwk": {
     "type": "object",
     "required": [
      "kty",
      "crv",
      "x",
      "y"
     ],
     "additionalProperties": false,
     "properties": {
      "kty": {
       "const": "EC"
      },
      "crv": {
       "const": "P-256"
      },
      "x": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{43}$"
      },
      "y": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{43}$"
      },
      "kid": {
       "type": "string",
       "description": "Tolerated and dropped: the thumbprint is the name."
      }
     },
     "description": "The buyer's stable signing key, public members only. Its RFC 7638 thumbprint must equal cnf_jkt, else request_invalid. Required for ap2-v0.2 (the open roots carry it whole in cnf.jwk, where the mandate+jwt carries only the thumbprint); allowed with poc-v1."
    },
    "intent_ceiling": {
     "type": "object",
     "required": ["value", "currency"],
     "additionalProperties": false,
     "properties": {
      "value": { "type": "integer", "minimum": 0, "description": "Minor units." },
      "currency": { "type": "string", "pattern": "^[A-Z]{3}$" }
     },
     "description": "The ceiling the buyer took from the person's own words (the intent it captured), for the approval page to show beside the ceiling the registry holds, so an extraction error is visible to the person before they approve (authority-api.md, the approval page). Never what a mandate is held to: the intent mandate carries the registry's ceiling, whatever this says. Optional since 2026-09-13."
    },
    "assistant_token": {
     "type": "string",
     "maxLength": 4096,
     "pattern": "^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+$",
     "description": "Optional, since authority-api.md v0.8 (Which assistant asked): the access token (at+jwt) the person's assistant presented at the buyer's entrance for the call that led to this request, exactly as received. The authority issued it and checks its own signature, the person, the resource, the scope, its age and its grant; a token that does not hold is assistant_unknown (HTTP 403)."
    }
   }
  },
  "document": {
   "type": "object",
   "required": [
    "request_id",
    "status",
    "correlation_id",
    "created_at"
   ],
   "properties": {
    "request_id": {
     "type": "string",
     "minLength": 16,
     "description": "Opaque, unguessable."
    },
    "status": {
     "type": "string",
     "enum": [
      "pending",
      "issued",
      "denied",
      "expired"
     ]
    },
    "correlation_id": {
     "type": "string"
    },
    "quote_hash": {
     "type": "string",
     "pattern": "^[0-9a-f]{64}$",
     "description": "The authority's own recomputation; absent when the quote could not be read."
    },
    "created_at": {
     "type": "string",
     "format": "date-time"
    },
    "expires_at": {
     "type": "string",
     "format": "date-time",
     "description": "When the approval link stops working; pending only."
    },
    "decided_at": {
     "type": "string",
     "format": "date-time"
    },
    "poll": {
     "type": "string",
     "description": "Path of this document, for the buyer to read again; pending only."
    },
    "approval_url": {
     "type": "string",
     "format": "uri",
     "description": "Pending only, and only when the authority runs the relay notifier (authority-api.md, step 7, since 2026-09-16): the approval link, for the buyer to show the person through its own channel. With any other notifier the link never reaches the buyer, and this member is absent."
    },
    "mandates": {
     "type": "array",
     "items": {
      "type": "string"
     },
     "minItems": 2,
     "maxItems": 2,
     "description": "issued only: the compact mandate+jwt of the intent and the cart stage, in no promised order; the buyer tells them apart by stage."
    },
    "jti": {
     "type": "object",
     "required": [
      "intent",
      "cart"
     ],
     "properties": {
      "intent": {
       "type": "string"
      },
      "cart": {
       "type": "string"
      }
     },
     "description": "issued only."
    },
    "reason": {
     "type": "string",
     "description": "denied and expired: a code from reason-codes.json authority_side."
    },
    "detail": {
     "type": "string",
     "description": "The authority's sentence for a human; never a number, never the person."
    },
    "format": {
     "type": "string",
     "enum": [
      "poc-v1",
      "ap2-v0.2"
     ],
     "description": "The format the request asked for, on every document from pending on."
    },
    "ap2": {
     "type": "object",
     "required": [
      "jti",
      "checkout",
      "payment"
     ],
     "additionalProperties": false,
     "properties": {
      "jti": {
       "type": "string",
       "description": "One jti shared by the two roots: the handle of GET /mandates/{jti} and of a revocation for the pair."
      },
      "checkout": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+(~[A-Za-z0-9_-]*)+$",
       "description": "The open Checkout Mandate: a compact SD-JWT, issuer JWT then its disclosures, ending in `~`. Every disclosure travels with it. Content in mandate-ap2.schema.json."
      },
      "payment": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+(~[A-Za-z0-9_-]*)+$",
       "description": "The open Payment Mandate, same form, bound to the checkout root by payment.reference."
      }
     },
     "description": "issued and format ap2-v0.2 only: the AP2 v0.2 form of the same approval, beside the two mandate+jwt."
    }
   },
   "allOf": [
    {
     "if": {
      "properties": {
       "status": {
        "const": "pending"
       }
      }
     },
     "then": {
      "required": [
       "expires_at",
       "poll"
      ]
     }
    },
    {
     "if": {
      "properties": {
       "status": {
        "const": "issued"
       }
      }
     },
     "then": {
      "required": [
       "decided_at",
       "mandates",
       "jti"
      ]
     }
    },
    {
     "if": {
      "properties": {
       "status": {
        "enum": [
         "denied",
         "expired"
        ]
       }
      }
     },
     "then": {
      "required": [
       "decided_at",
       "reason"
      ]
     }
    },
    {
     "if": {
      "properties": {
       "status": {
        "const": "issued"
       },
       "format": {
        "const": "ap2-v0.2"
       }
      },
      "required": [
       "format"
      ]
     },
     "then": {
      "required": [
       "ap2"
      ]
     }
    }
   ]
  }
 },
 "oneOf": [
  {
   "$ref": "#/$defs/request"
  },
  {
   "$ref": "#/$defs/document"
  }
 ]
}
