API Gateway
API Gateway is both an integration pattern and a commercial Ratify surface in the Identities AI app.
The open-source SDKs let you build gateway enforcement yourself. The commercial Ratify API Gateway gives you managed policy, audit, revocation, tenant configuration, and enforcement around the same proof bundles.
This is the surface you use when an agent platform needs to authorize:
- MCP tool calls
- A2A requests
- REST API actions
- financial actions such as
payments:send - data reads and writes
- regulated internal tools
The gateway pattern is:
- Map each route, tool, or operation to a required Ratify scope.
- Extract the proof bundle from the request.
- Reconstruct verifier context from observable request data.
- Call the SDK verifier.
- Reject failed proofs before the request reaches the protected handler.
Header convention
Section titled “Header convention”Use a single proof header unless the transport has a native auth envelope:
X-Ratify-Proof: <base64url-encoded-proof-bundle-json>Scope mapping
Section titled “Scope mapping”routes: "GET /v1/accounts/:id": required_scope: "data:read" "POST /v1/payments": required_scope: "payments:send" "POST /mcp/tools/send_email": required_scope: "execute:tool" "POST /v1/ledger/post": required_scope: "payments:send" "PATCH /v1/customers/:id": required_scope: "data:write"Enforcement rule
Section titled “Enforcement rule”If verification fails, return 401 for missing proof and 403 for invalid, expired, revoked, under-scoped, or constraint-denied proof.
Never let callers provide their own constraint context for high-stakes checks. The gateway should derive amount, route, IP, tenant, and request body hash from the request it actually received.
Gateway code example
Section titled “Gateway code example”result := ratify.Verify(bundle, ratify.VerifyOptions{ RequiredScope: ratify.ScopePaymentsSend, Context: ratify.VerifierContext{ // populate amount, route, tenant, etc from the request },})if !result.Valid { http.Error(w, result.ErrorReason, http.StatusForbidden) return}const result = await verifyBundle(bundle, { required_scope: SCOPE_PAYMENTS_SEND, context: { // populate amount, route, tenant, etc from the request },});if (!result.valid) { return new Response(result.error_reason, { status: 403 });}result = verify_bundle(bundle, VerifyOptions(required_scope=SCOPE_PAYMENTS_SEND))if not result.valid: raise PermissionError(result.error_reason)let result = verify_bundle( &bundle, &VerifyOptions { required_scope: SCOPE_PAYMENTS_SEND.into(), ..Default::default() },);assert!(result.valid, "{}", result.error_reason);