{
  "openapi": "3.1.0",
  "info": {
    "title": "Treasury Copilot Agent API",
    "version": "1.6.0",
    "description": "HTTP API for policy-gated agent spending. Current automatic execution uses Base Sepolia test USDC. POST /spend is asynchronous and returns 202 after GenLayer submission.\n\nSettlement depends on the chain a treasury is bound to. Base Sepolia settles through an ERC-7715 permission relayed by 1Shot. Arc Testnet settles through an agent treasury vault the owner deploys and owns, because Arc has neither the MetaMask Delegation Framework nor 1Shot support. A request may name any chain Circle CCTP supports as its destination; the policy reviews and stores that destination on chain, so a relayer cannot redirect an approved payment."
  },
  "servers": [
    {
      "url": "https://treasurycopilot.app/api/v1",
      "description": "Live testnet deployment. Serves both settlement backends: Base Sepolia (84532, ERC-7715 delegation) and Arc Testnet (5042002, agent treasury vault). The chain a key is bound to is carried in the key itself."
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/spend": {
      "post": {
        "summary": "Submit a spend request",
        "description": "Verifies the API key, policy binding, exact token units, and optional evidence; submits queue_request with the platform signer; then returns 202. Policy V5 starts GenLayer prompt-comparative review inside that same transaction. Legacy V4 requests use bounded automatic review with cron recovery.",
        "operationId": "createSpendRequest",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SpendRequest"
              },
              "examples": {
                "signedInvoice": {
                  "value": {
                    "agent_address": "0x1111111111111111111111111111111111111111",
                    "recipient": "0x2222222222222222222222222222222222222222",
                    "amount": "25.00",
                    "category": "software_subscription",
                    "justification": "Vercel invoice INV-4471",
                    "idempotency_key": "vercel-inv-4471-2026-07",
                    "evidence": [
                      {
                        "type": "signed_invoice",
                        "invoice_id": "INV-4471",
                        "merchant_id": "vercel",
                        "expected_recipient": "0x2222222222222222222222222222222222222222",
                        "expected_amount": "25000000",
                        "issued_at": 1784800000,
                        "expires_at": 1785400000,
                        "content_hash": "0x7777777777777777777777777777777777777777777777777777777777777777",
                        "signer": "0x3333333333333333333333333333333333333333",
                        "signature": "0x..."
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "An identical idempotent request already exists and is finalized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SpendAccepted"
                }
              }
            }
          },
          "202": {
            "description": "Submitted to GenLayer; poll the Location URL",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "schema": {
                  "type": "integer",
                  "const": 10
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SpendAccepted"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamFailure"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/requests": {
      "get": {
        "summary": "Recover a request by idempotency key",
        "operationId": "findRequestByIdempotencyKey",
        "parameters": [
          {
            "name": "idempotency_key",
            "in": "query",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/IdempotencyKey"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Request is visible on GenLayer",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RequestEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "GenLayer holds no request for this idempotency key: the submission was never accepted, or it was rejected before reaching the policy. retryable is false because polling this endpoint cannot make a record appear. Re-send the original POST once with the same key; that is idempotent and will not double-pay. The returned derived_request_id is computed from the key, not read from chain, and must not be treated as a payment id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        }
      }
    },
    "/requests/{request_id}": {
      "get": {
        "summary": "Get one on-chain request",
        "operationId": "getRequest",
        "parameters": [
          {
            "name": "request_id",
            "in": "path",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/RequestId"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Current request lifecycle state",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RequestEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Request is not visible for this API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/policy": {
      "get": {
        "summary": "Get safe policy configuration and recipient discovery data",
        "operationId": "getPolicy",
        "responses": {
          "200": {
            "description": "Policy state with delegation secrets removed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PolicyResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/balance": {
      "get": {
        "summary": "Get token balance and policy budget",
        "operationId": "getBalance",
        "responses": {
          "200": {
            "description": "Live EVM token balance and GenLayer policy limits",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "type": "object",
                      "additionalProperties": true
                    },
                    {
                      "type": "object",
                      "properties": {
                        "ready_to_spend": {
                          "type": "boolean",
                          "description": "False when anything prevents a payment. Check this first."
                        },
                        "blockers": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/SpendBlocker"
                          },
                          "description": "Empty when ready_to_spend is true."
                        },
                        "cap_boundary": {
                          "enum": [
                            "inclusive"
                          ],
                          "description": "An amount exactly equal to per_tx_cap is allowed."
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamFailure"
          }
        }
      }
    },
    "/history": {
      "get": {
        "summary": "List on-chain request history",
        "operationId": "getHistory",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Newest requests first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "policy",
                    "agent",
                    "requests"
                  ],
                  "properties": {
                    "policy": {
                      "$ref": "#/components/schemas/Address"
                    },
                    "agent": {
                      "$ref": "#/components/schemas/Address"
                    },
                    "requests": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Request"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "tcp_ agent API key"
      }
    },
    "schemas": {
      "Address": {
        "type": "string",
        "pattern": "^0x[0-9a-fA-F]{40}$",
        "description": "Compared case-insensitively, so EIP-55 checksummed and all-lowercase forms are equivalent."
      },
      "RequestId": {
        "type": "string",
        "pattern": "^0x[0-9a-fA-F]{64}$"
      },
      "IdempotencyKey": {
        "type": "string",
        "minLength": 8,
        "maxLength": 128,
        "pattern": "^[A-Za-z0-9._:-]+$"
      },
      "Amount": {
        "type": "string",
        "pattern": "^(0|[1-9][0-9]*)(\\.[0-9]+)?$",
        "description": "Positive display-unit decimal string. Scientific notation, JSON numbers, negatives, zero, and excess token precision are rejected."
      },
      "SpendRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "agent_address",
          "recipient",
          "amount",
          "category",
          "justification",
          "idempotency_key"
        ],
        "properties": {
          "agent_address": {
            "$ref": "#/components/schemas/Address"
          },
          "recipient": {
            "$ref": "#/components/schemas/Address"
          },
          "amount": {
            "$ref": "#/components/schemas/Amount"
          },
          "category": {
            "type": "string",
            "minLength": 2,
            "maxLength": 64,
            "description": "Owner-defined label. The API does not maintain a global category enum."
          },
          "justification": {
            "type": "string",
            "minLength": 4,
            "maxLength": 1200
          },
          "idempotency_key": {
            "$ref": "#/components/schemas/IdempotencyKey"
          },
          "evidence": {
            "type": "array",
            "maxItems": 3,
            "items": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/InvoiceUrlEvidence"
                },
                {
                  "$ref": "#/components/schemas/SignedInvoiceEvidence"
                }
              ]
            },
            "minItems": 1,
            "description": "Required, 1-3 items. An empty array is rejected with 422 invalid_evidence. An `invoice_url` item must be HTTPS, must return HTTP 200, and must serve an allow-listed content type (application/json and application/pdf are accepted; image/* is not). The `sha256` must be the digest of the bytes actually served."
          },
          "destination_chain": {
            "oneOf": [
              {
                "type": "integer"
              },
              {
                "type": "string",
                "pattern": "^\\d+$"
              }
            ],
            "description": "Optional. Chain the payee is paid on. Omit it to settle on the treasury's own chain. Any chain Circle CCTP supports in the same Circle environment is accepted; an unsupported chain id is refused with 400 rather than denied on chain. The destination is part of a request's identity, so one idempotency_key cannot be replayed with a different destination. Accepted aliases: destination_chain_id, destinationChainId.",
            "examples": [
              84532,
              "5042002"
            ]
          }
        }
      },
      "InvoiceCommon": {
        "type": "object",
        "required": [
          "invoice_id",
          "merchant_id",
          "expected_recipient",
          "expected_amount",
          "issued_at"
        ],
        "properties": {
          "invoice_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128
          },
          "merchant_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128
          },
          "expected_recipient": {
            "$ref": "#/components/schemas/Address"
          },
          "expected_amount": {
            "type": "string",
            "pattern": "^[0-9]+$",
            "description": "Exact token base units, for example 25000000 for 25 USDC with 6 decimals."
          },
          "issued_at": {
            "type": "integer",
            "minimum": 1
          },
          "expires_at": {
            "type": "integer",
            "minimum": 1
          }
        }
      },
      "InvoiceUrlEvidence": {
        "allOf": [
          {
            "$ref": "#/components/schemas/InvoiceCommon"
          },
          {
            "type": "object",
            "required": [
              "type",
              "uri",
              "merchant_domain",
              "sha256"
            ],
            "properties": {
              "type": {
                "const": "invoice_url"
              },
              "uri": {
                "type": "string",
                "format": "uri",
                "maxLength": 2048,
                "description": "HTTPS only. Private, localhost, link-local, reserved destinations and nonstandard ports are rejected."
              },
              "merchant_domain": {
                "type": "string",
                "maxLength": 253
              },
              "sha256": {
                "type": "string",
                "pattern": "^0x[0-9a-fA-F]{64}$"
              }
            }
          }
        ]
      },
      "SignedInvoiceEvidence": {
        "allOf": [
          {
            "$ref": "#/components/schemas/InvoiceCommon"
          },
          {
            "type": "object",
            "required": [
              "type",
              "signer",
              "signature",
              "content_hash"
            ],
            "properties": {
              "type": {
                "const": "signed_invoice"
              },
              "signer": {
                "$ref": "#/components/schemas/Address"
              },
              "signature": {
                "type": "string",
                "pattern": "^0x[0-9a-fA-F]{130}$"
              },
              "content_hash": {
                "type": "string",
                "pattern": "^0x[0-9a-fA-F]{64}$"
              }
            }
          }
        ]
      },
      "Request": {
        "type": "object",
        "required": [
          "request_id",
          "recipient",
          "amount",
          "amount_units",
          "category",
          "justification",
          "verdict",
          "status",
          "execution_status"
        ],
        "properties": {
          "request_id": {
            "$ref": "#/components/schemas/RequestId"
          },
          "recipient": {
            "$ref": "#/components/schemas/Address"
          },
          "amount": {
            "type": "string"
          },
          "amount_units": {
            "type": "string",
            "pattern": "^[0-9]+$"
          },
          "category": {
            "type": "string"
          },
          "justification": {
            "type": "string"
          },
          "evidence": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "evidence_digest": {
            "type": "string"
          },
          "invoice_key": {
            "type": "string"
          },
          "verdict": {
            "enum": [
              "pending",
              "approved",
              "denied"
            ]
          },
          "decision_mode": {
            "enum": [
              "deterministic",
              "prompt_comparative"
            ]
          },
          "status": {
            "enum": [
              "submitted",
              "reviewing",
              "review_pending",
              "pending",
              "approved",
              "denied",
              "ready",
              "executing",
              "bridging",
              "failed",
              "executed",
              "not_applicable"
            ],
            "description": "Raw lifecycle value. Reported to humans as: submitted/reviewing/review_pending -> Under review; ready/approved -> Approved; executing -> Paying; bridging -> Bridging to destination; executed -> Accepted / Executed; denied and not_applicable -> Denied (the policy stores not_applicable when a denial means execution never applied, so it means denied, not unknown); failed -> Failed, will retry."
          },
          "reasoning": {
            "type": "string"
          },
          "execution_status": {
            "enum": [
              "submitted",
              "reviewing",
              "review_pending",
              "ready",
              "executing",
              "bridging",
              "failed",
              "executed",
              "not_applicable"
            ],
            "description": "Raw lifecycle value. Reported to humans as: submitted/reviewing/review_pending -> Under review; ready/approved -> Approved; executing -> Paying; bridging -> Bridging to destination; executed -> Accepted / Executed; denied and not_applicable -> Denied (the policy stores not_applicable when a denial means execution never applied, so it means denied, not unknown); failed -> Failed, will retry."
          },
          "execution_error": {
            "type": "string"
          },
          "tx_hash": {
            "type": "string"
          },
          "explorer_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          },
          "route": {
            "enum": [
              "direct",
              "cctp"
            ],
            "description": "direct settles on the treasury's own chain. cctp burns on the settlement chain for delivery on another."
          },
          "destination_chain": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ChainRef"
              },
              {
                "type": "null"
              }
            ],
            "description": "Chain the payee is paid on. Equals `chain` for a direct payment."
          },
          "bridge": {
            "$ref": "#/components/schemas/BridgeState"
          },
          "denial_code": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DenialReason"
              },
              {
                "type": "null"
              }
            ],
            "description": "Set only when verdict is denied. A denied request is terminal: it will never change, so stop polling it."
          }
        }
      },
      "SpendAccepted": {
        "type": "object",
        "required": [
          "request_id",
          "verdict",
          "status",
          "request",
          "poll_url",
          "idempotent_replay",
          "genlayer"
        ],
        "properties": {
          "request_id": {
            "$ref": "#/components/schemas/RequestId"
          },
          "verdict": {
            "enum": [
              "pending",
              "approved",
              "denied"
            ]
          },
          "reasoning": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "request": {
            "$ref": "#/components/schemas/Request"
          },
          "poll_url": {
            "type": "string"
          },
          "idempotent_replay": {
            "type": "boolean"
          },
          "genlayer": {
            "type": "object",
            "properties": {
              "request_tx_hash": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        }
      },
      "RequestEnvelope": {
        "type": "object",
        "required": [
          "policy",
          "request"
        ],
        "properties": {
          "policy": {
            "$ref": "#/components/schemas/Address"
          },
          "request": {
            "$ref": "#/components/schemas/Request"
          }
        }
      },
      "PolicyResponse": {
        "type": "object",
        "required": [
          "policy",
          "state"
        ],
        "properties": {
          "policy": {
            "$ref": "#/components/schemas/Address"
          },
          "state": {
            "$ref": "#/components/schemas/PolicyState"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error",
          "message",
          "fields",
          "retryable"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Machine-readable code. Operational states worth handling explicitly: `vault_unfunded` and `insufficient_vault_balance` (402, the call was valid but the treasury has no money and only the owner can fix it), `insufficient_fee_reserve` (402, a cross-chain payment has no fee reserve), `vault_not_registered` (409), `policy_not_allowed` (403, the deployment's policy allowlist refused this policy), `unsupported_destination_chain` (422), `cross_chain_unavailable` (422), `settlement_disabled` (503), `no_record` (404 from the recovery endpoint when GenLayer holds no request for an idempotency key), plus the auth and validation codes."
          },
          "message": {
            "type": "string"
          },
          "fields": {
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          "request_id": {
            "type": "string",
            "description": "Always present, and mirrored in the x-correlation-id header."
          },
          "retryable": {
            "type": "boolean",
            "description": "Only 5xx responses are retryable. A 402 is terminal until the owner funds the treasury, so retrying it unchanged cannot succeed."
          },
          "owner_action_required": {
            "type": "boolean",
            "description": "Present on 402. The agent cannot clear this itself."
          }
        }
      },
      "ChainId": {
        "type": "integer",
        "description": "EVM chain id naming where an approved payment is delivered. Payable testnet chains are 1301 (Unichain Sepolia), 1328 (Sei Testnet), 4801 (World Chain Sepolia), 10143 (Monad Testnet), 43113 (Avalanche Fuji), 59141 (Linea Sepolia), 80002 (Polygon Amoy), 84532 (Base Sepolia), 421614 (Arbitrum Sepolia), 763373 (Ink Sepolia), 812242 (Codex Testnet), 5042002 (Arc Testnet), 11155111 (Ethereum Sepolia), 11155420 (OP Sepolia). Payable mainnet chains are 1 (Ethereum), 10 (OP Mainnet), 50 (XDC), 130 (Unichain), 137 (Polygon PoS), 143 (Monad), 146 (Sonic), 480 (World Chain), 999 (HyperEVM), 1329 (Sei), 5042 (Arc), 8453 (Base Mainnet), 42161 (Arbitrum One), 43114 (Avalanche), 57073 (Ink), 59144 (Linea), 81224 (Codex), 98866 (Plume). A route must stay within one Circle environment: a testnet policy cannot pay to a mainnet chain or the reverse, because the sandbox and production attestation services never see each other's messages. A chain id is not a CCTP domain — a testnet shares its mainnet domain, so the two must not be confused. X Layer (196) is not payable: Circle supports CCTP there but not the Forwarding Service this platform relies on for the destination mint.",
        "examples": [
          84532,
          5042002,
          8453,
          5042
        ]
      },
      "ChainRef": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "chain_id",
          "name"
        ],
        "properties": {
          "chain_id": {
            "$ref": "#/components/schemas/ChainId"
          },
          "name": {
            "type": "string",
            "examples": [
              "Base Sepolia",
              "Arc Testnet"
            ]
          },
          "explorer_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          }
        }
      },
      "BridgeState": {
        "type": "object",
        "additionalProperties": false,
        "description": "Cross-chain lifecycle, tracked independently of execution. Empty strings on a direct payment. The burn alone is not the payee's receipt: treat destination_tx_hash as the confirmation.",
        "required": [
          "tx_hash",
          "attestation_status",
          "destination_tx_hash"
        ],
        "properties": {
          "tx_hash": {
            "type": "string",
            "description": "CCTP burn transaction on the settlement chain."
          },
          "attestation_status": {
            "type": "string",
            "description": "Circle's view of the burn. Empty until the burn is indexed."
          },
          "destination_tx_hash": {
            "type": "string",
            "description": "Mint transaction on the destination chain. Present only once Circle has minted."
          },
          "destination_explorer_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          }
        }
      },
      "PolicyState": {
        "type": "object",
        "additionalProperties": false,
        "description": "What an agent key may learn about its own policy. Policy text and the recipient whitelist are withheld, because an agent that reads them can mirror the owner's wording back as justification and enumerate approved payees. Budget figures are NOT withheld: /balance is the source of truth for weekly_cap, weekly_spent, weekly_available, per_tx_cap and cap_boundary. An amount exactly equal to per_tx_cap is allowed.",
        "required": [
          "contract_version",
          "settlement_backend",
          "settlement_binding_registered"
        ],
        "properties": {
          "contract_version": {
            "type": "string",
            "examples": [
              "6"
            ]
          },
          "authorized_agent": {
            "$ref": "#/components/schemas/Address"
          },
          "settlement_backend": {
            "enum": [
              "delegated-7710",
              "arc-vault"
            ],
            "description": "delegated-7710 moves USDC out of the owner's own account through 1Shot, so there is nothing to fund. arc-vault pays out of a contract the owner deployed, which starts empty and must be funded before any payment can settle."
          },
          "settlement_chain": {
            "$ref": "#/components/schemas/ChainRef"
          },
          "token_address": {
            "$ref": "#/components/schemas/Address"
          },
          "settlement_binding_registered": {
            "type": "boolean",
            "description": "Whether the binding this backend requires exists: a registered delegation for delegated-7710, a registered vault for arc-vault. Check this rather than delegation_registered, which is always false on a vault-settled policy and is not a fault there."
          },
          "review": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "semantic_review_required_for_all_requests": {
                "type": "boolean"
              },
              "immediate_review_submission": {
                "type": "boolean"
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "disclosure": {
            "type": "string"
          }
        }
      },
      "SpendBlocker": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "code",
          "message",
          "owner_action_required"
        ],
        "properties": {
          "code": {
            "enum": [
              "vault_not_registered",
              "delegation_not_registered",
              "vault_unfunded",
              "weekly_cap_exhausted"
            ]
          },
          "message": {
            "type": "string"
          },
          "owner_action_required": {
            "type": "boolean",
            "description": "True when only the owner can clear this, such as funding a vault. False when it clears on its own, such as a weekly cap that resets."
          }
        }
      },
      "DenialReason": {
        "enum": [
          "recipient_not_approved",
          "evidence_required",
          "amount_above_cap",
          "weekly_cap_exhausted",
          "destination_not_permitted",
          "policy_text_rejected"
        ],
        "description": "Why a request was denied. Present on a denied request so an agent can tell a missing whitelist entry from missing evidence without guessing."
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Malformed JSON or unsupported request shape",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing, malformed, expired, tampered, rotated, or revoked API key",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Agent, policy, owner, chain, token, or funding binding mismatch",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Conflict": {
        "description": "Idempotency conflict or policy migration required",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unprocessable": {
        "description": "Invalid amount, address, evidence, category, justification, or unsupported chain",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Infrastructure rate limit exceeded when distributed edge limiting is configured; honor Retry-After",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "UpstreamFailure": {
        "description": "GenLayer, EVM RPC, or 1Shot upstream failure; retry with the same idempotency key",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unavailable": {
        "description": "Platform signer, GenLayer consensus, execution-slot capacity, or service configuration unavailable. Honor Retry-After when present.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}
