{
  "openapi": "3.0.3",
  "info": {
    "title": "Penumbra Payment Orchestration API",
    "version": "2026-09-21",
    "description": "Multi-PSP payment orchestration with intelligent routing, crypto settlement, and real-time analytics. All monetary amounts are expressed in integer cents (e.g., 5000 = $50.00). All dates use ISO 8601 format. All resource IDs use typed prefixes (txn_, tok_, mer_, spl_, etc.).\n\n## Versioning\n\nThe API is versioned by date. Send `Penumbra-Version: 2026-04-09` to pin a version; without the header you get the latest. Every response names the version that served it in a `Penumbra-Version` header. A version is supported for two years from its date; from its first birthday responses carry a `Penumbra-Version-Warning` header. A malformed or unsupported version is refused with `400`. New endpoints, optional fields and webhook event types can arrive within a version, so ignore what you do not recognize; anything that would break an integration ships only as a new dated version.\n\n## Rate limits\n\nPer API key, per rolling 60 seconds, in two buckets: money movement (`/payments`, `/transfers/bank`, `/crypto/transfer`, `/forex/convert` and recurring charges, reads included) at 30, and everything else at 100 for a full-access key or 300 for a read-only key. Responses carry `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`; over the limit you get `429` with a `Retry-After` header in seconds.\n\n## Webhooks\n\nEach merchant has one webhook endpoint, and every event about that merchant arrives there as a `WebhookEvent` (Pen.v1) envelope. Set the endpoint, turn delivery on and rotate the signing secret with `PUT /merchant-webhook/config`; the full secret is returned only when it is created or rotated. `X-Penumbra-Signature` carries `v1=` and the hex HMAC-SHA256 of `{X-Penumbra-Signature-Time}.{raw body}` keyed with the merchant's signing secret; compare in constant time and reject timestamps older than five minutes. A delivery without a 2xx within 10 seconds is retried after about 30 seconds, 2 minutes, 10 minutes and 30 minutes, then dead-lettered. Delivery is at least once; `X-Penumbra-Event-Id` is identical on every retry. `GET /merchant-webhook/deliveries` shows every delivery and its last result, and any delivery can be replayed. All 49 event types: https://penumbrahq.com/resources/webhook-events.html",
    "contact": {
      "name": "Arrowhead Advisory Group",
      "email": "arrowheadadvisorygroupops@gmail.com",
      "url": "https://penumbrahq.com"
    }
  },
  "servers": [
    {
      "url": "https://api.penumbrahq.com/api/v1",
      "description": "Production (use sk_live_ keys)"
    },
    {
      "url": "https://api.penumbrahq.com/api/v1",
      "description": "Sandbox (use sk_test_ keys)"
    }
  ],
  "security": [
    {
      "BearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Health",
      "description": "Service health check (no authentication required)"
    },
    {
      "name": "Authentication",
      "description": "Dashboard login and session verification, used by the Penumbra dashboard. API integrations authenticate with API keys and never need these."
    },
    {
      "name": "Payments",
      "description": "Create, capture, void, and refund payments with automatic PSP routing and failover"
    },
    {
      "name": "Tokens",
      "description": "Register and manage PSP-issued payment tokens. Card data never reaches Penumbra."
    },
    {
      "name": "Terminals",
      "description": "In-person card payments on Stripe Terminal readers, on your own Stripe account. The reader takes the card and its approval is the payment: Penumbra captures it and records it with your other payments. No saved card is charged."
    },
    {
      "name": "Merchants",
      "description": "Onboard and manage sub-merchants"
    },
    {
      "name": "Customers",
      "description": "View and manage customer records with lifetime value tracking"
    },
    {
      "name": "Crypto",
      "description": "USDC stablecoin transfers and payment requests via Circle"
    },
    {
      "name": "Bank Transfers",
      "description": "ACH, Same-Day ACH, FedNow, and RTP bank transfers"
    },
    {
      "name": "Forex",
      "description": "Currency exchange rates and conversions"
    },
    {
      "name": "Splits",
      "description": "Configure revenue split rules for merchants"
    },
    {
      "name": "Analytics",
      "description": "Transaction analytics, PSP comparison, and cost analysis"
    },
    {
      "name": "Routing",
      "description": "Manage intelligent routing rules and view routing decisions"
    },
    {
      "name": "Webhooks",
      "description": "Signed event delivery to each merchant's endpoint, and the operations that set the endpoint and show what was delivered. See the Webhooks section of the description and the WebhookEvent schema."
    },
    {
      "name": "API Keys",
      "description": "Generate and manage API keys"
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "tags": [
          "Health"
        ],
        "summary": "Health check",
        "description": "Returns the current service status and database connectivity. No authentication required.",
        "operationId": "getHealth",
        "security": [],
        "responses": {
          "200": {
            "description": "Service is healthy",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "ok",
                        "degraded"
                      ]
                    },
                    "timestamp": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "version": {
                      "type": "string"
                    },
                    "services": {
                      "type": "object",
                      "properties": {
                        "database": {
                          "type": "string",
                          "enum": [
                            "connected",
                            "unreachable"
                          ]
                        }
                      }
                    }
                  }
                },
                "example": {
                  "status": "ok",
                  "timestamp": "2026-04-09T12:00:00+00:00",
                  "version": "2026-04-09",
                  "services": {
                    "database": "connected"
                  }
                }
              }
            }
          },
          "503": {
            "description": "Service is degraded (database unreachable)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "degraded"
                    },
                    "timestamp": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "version": {
                      "type": "string"
                    },
                    "services": {
                      "type": "object",
                      "properties": {
                        "database": {
                          "type": "string",
                          "example": "unreachable"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/auth/login": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Dashboard login",
        "description": "Authenticate a dashboard admin user with email and password. Returns a signed session token valid for 24 hours. Progressive account lockout applies after 5 failed attempts.",
        "operationId": "authLogin",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "password"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "password": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "email": "admin@example.com",
                "password": "your-password"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Login successful",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "token": {
                      "type": "string",
                      "description": "Signed session token"
                    },
                    "user": {
                      "type": "object",
                      "properties": {
                        "email": {
                          "type": "string",
                          "format": "email"
                        },
                        "name": {
                          "type": "string"
                        },
                        "role": {
                          "type": "string"
                        }
                      }
                    },
                    "expires_in": {
                      "type": "integer",
                      "description": "Token lifetime in seconds",
                      "example": 86400
                    }
                  }
                },
                "example": {
                  "token": "eyJlbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIn0=.abc123signature",
                  "user": {
                    "email": "admin@example.com",
                    "name": "Admin User",
                    "role": "admin"
                  },
                  "expires_in": 86400
                }
              }
            }
          },
          "401": {
            "description": "Invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "type": "authentication_error",
                    "message": "Invalid email or password."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Account locked due to too many failed attempts",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "type": "rate_limit_error",
                    "message": "Too many attempts. Try again in 5 minutes."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/auth/verify": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Verify session token",
        "description": "Validate an existing session token. Returns user info if the token is valid and not expired. Supports single-session enforcement.",
        "operationId": "authVerify",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "token": {
                    "type": "string",
                    "description": "Session token to verify"
                  }
                }
              },
              "example": {
                "token": "eyJlbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIn0=.abc123signature"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Token is valid",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "valid": {
                      "type": "boolean"
                    },
                    "user": {
                      "type": "object",
                      "properties": {
                        "email": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "role": {
                          "type": "string"
                        }
                      }
                    }
                  }
                },
                "example": {
                  "valid": true,
                  "user": {
                    "email": "admin@example.com",
                    "name": "Admin User",
                    "role": "admin"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Token is invalid or expired",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "valid": {
                      "type": "boolean",
                      "example": false
                    },
                    "reason": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "valid": false,
                  "reason": "Session invalidated by a newer login."
                }
              }
            }
          }
        }
      }
    },
    "/payments": {
      "post": {
        "tags": [
          "Payments"
        ],
        "summary": "Create a payment",
        "description": "Create a new payment (authorize or charge) with automatic PSP routing and failover. Supports idempotency via the Idempotency-Key header. Includes OFAC sanctions screening when a merchant_id is provided.",
        "operationId": "createPayment",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Required. The same key with the same request returns the first result instead of repeating it; a request without one is refused with `400`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "amount",
                  "token_id"
                ],
                "properties": {
                  "amount": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Amount in cents"
                  },
                  "currency": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 3,
                    "default": "USD"
                  },
                  "token_id": {
                    "type": "string",
                    "description": "Token ID (tok_xxx)"
                  },
                  "merchant_id": {
                    "type": "string",
                    "nullable": true,
                    "description": "One of your merchants, by the id the Merchants API returns. Another organization's merchant, or an id that does not exist, is refused with `404` `merchant_not_found` before anything is charged."
                  },
                  "capture": {
                    "type": "boolean",
                    "default": false,
                    "description": "If true, auto-capture after authorization"
                  },
                  "metadata": {
                    "type": "object",
                    "nullable": true,
                    "additionalProperties": true
                  },
                  "psp": {
                    "type": "string",
                    "nullable": true,
                    "description": "PSP override (e.g., stripe, nmi, square)"
                  }
                }
              },
              "example": {
                "amount": 5000,
                "currency": "USD",
                "token_id": "tok_abc123def456ghi789jkl",
                "merchant_id": "mer_abc123def456ghi789jkl",
                "capture": true,
                "metadata": {
                  "order_id": "ORD-1234"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Payment authorized or captured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payment"
                },
                "example": {
                  "id": "txn_abc123def456ghi789jklmno",
                  "object": "payment",
                  "amount": 5000,
                  "currency": "USD",
                  "status": "captured",
                  "type": "charge",
                  "merchant_id": "mer_abc123def456ghi789jkl",
                  "token_id": "tok_abc123def456ghi789jkl",
                  "psp_used": "stripe",
                  "authorization_code": "AUTH_12345",
                  "decline_reason": null,
                  "captured_amount": 5000,
                  "refunded_amount": 0,
                  "routing_decision": "Routed to Stripe (default PSP)",
                  "metadata": {
                    "order_id": "ORD-1234"
                  },
                  "is_sandbox": false,
                  "created_at": "2026-04-09T12:00:00+00:00",
                  "updated_at": "2026-04-09T12:00:00+00:00"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (e.g., token not found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "type": "invalid_request_error",
                    "message": "Token not found or inactive."
                  }
                }
              }
            }
          },
          "402": {
            "description": "Payment declined",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payment"
                },
                "example": {
                  "id": "txn_abc123def456ghi789jklmno",
                  "object": "payment",
                  "amount": 5000,
                  "currency": "USD",
                  "status": "declined",
                  "type": "charge",
                  "decline_reason": "insufficient_funds",
                  "is_sandbox": false,
                  "created_at": "2026-04-09T12:00:00+00:00",
                  "updated_at": "2026-04-09T12:00:00+00:00"
                }
              }
            }
          },
          "403": {
            "description": "Blocked by OFAC sanctions screening",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "type": "compliance_error",
                    "message": "Transaction blocked: sanctions screening flag. Contact compliance."
                  }
                }
              }
            }
          },
          "404": {
            "description": "The `merchant_id` is not one of your merchants",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "type": "invalid_request_error",
                    "code": "merchant_not_found",
                    "message": "No merchant with that id in your account.",
                    "param": "merchant_id"
                  }
                }
              }
            }
          },
          "503": {
            "description": "No payment processor available",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "type": "api_error",
                    "message": "No payment processor available. Please try again later."
                  }
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Payments"
        ],
        "summary": "List payments",
        "description": "List all payments for the authenticated organization with optional filters.",
        "operationId": "listPayments",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "authorized",
                "captured",
                "declined",
                "voided",
                "refunded",
                "partially_refunded",
                "failed"
              ]
            }
          },
          {
            "name": "merchant_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "type",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "psp",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "created_after",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "created_before",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of payments",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/List"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Payment"
                          }
                        },
                        "total": {
                          "type": "integer"
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "id": "txn_abc123def456ghi789jklmno",
                      "object": "payment",
                      "amount": 5000,
                      "currency": "USD",
                      "status": "captured",
                      "type": "charge",
                      "psp_used": "stripe",
                      "is_sandbox": false,
                      "created_at": "2026-04-09T12:00:00+00:00"
                    }
                  ],
                  "has_more": false,
                  "total": 1
                }
              }
            }
          }
        }
      }
    },
    "/payments/{id}": {
      "get": {
        "tags": [
          "Payments"
        ],
        "summary": "Retrieve a payment",
        "description": "Retrieve the details of a specific payment by its ID.",
        "operationId": "getPayment",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Payment ID (txn_xxx)"
          }
        ],
        "responses": {
          "200": {
            "description": "Payment details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payment"
                }
              }
            }
          },
          "404": {
            "description": "Payment not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "type": "invalid_request_error",
                    "message": "Transaction not found."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/payments/{id}/capture": {
      "post": {
        "tags": [
          "Payments"
        ],
        "summary": "Capture a payment",
        "description": "Capture a previously authorized payment. Supports partial capture by specifying an amount less than the authorized amount.",
        "operationId": "capturePayment",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Payment ID (txn_xxx)"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Required. The same key with the same request returns the first result instead of repeating it; a request without one is refused with `400`."
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "amount": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Amount to capture in cents. Defaults to the full authorized amount."
                  }
                }
              },
              "example": {
                "amount": 3000
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Payment captured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payment"
                },
                "example": {
                  "id": "txn_abc123def456ghi789jklmno",
                  "object": "payment",
                  "amount": 5000,
                  "currency": "USD",
                  "status": "captured",
                  "captured_amount": 3000,
                  "refunded_amount": 0,
                  "created_at": "2026-04-09T12:00:00+00:00"
                }
              }
            }
          },
          "400": {
            "description": "Payment cannot be captured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "type": "invalid_request_error",
                    "message": "Transaction cannot be captured. Current status: captured."
                  }
                }
              }
            }
          },
          "404": {
            "description": "Payment not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/payments/{id}/void": {
      "post": {
        "tags": [
          "Payments"
        ],
        "summary": "Void a payment",
        "description": "Void a previously authorized payment before it has been captured.",
        "operationId": "voidPayment",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Payment ID (txn_xxx)"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Required. The same key with the same request returns the first result instead of repeating it; a request without one is refused with `400`."
          }
        ],
        "responses": {
          "200": {
            "description": "Payment voided",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payment"
                },
                "example": {
                  "id": "txn_abc123def456ghi789jklmno",
                  "object": "payment",
                  "amount": 5000,
                  "currency": "USD",
                  "status": "voided",
                  "created_at": "2026-04-09T12:00:00+00:00"
                }
              }
            }
          },
          "400": {
            "description": "Payment cannot be voided",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "type": "invalid_request_error",
                    "message": "Transaction cannot be voided. Current status: captured."
                  }
                }
              }
            }
          },
          "404": {
            "description": "Payment not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/payments/{id}/refund": {
      "post": {
        "tags": [
          "Payments"
        ],
        "summary": "Refund a payment",
        "description": "Refund a captured payment. Supports partial refunds by specifying an amount less than the captured amount. Multiple partial refunds are allowed up to the captured total.",
        "operationId": "refundPayment",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Payment ID (txn_xxx)"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Required. The same key with the same request returns the first result instead of repeating it; a request without one is refused with `400`."
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "amount": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Amount to refund in cents. Defaults to the remaining refundable amount."
                  }
                }
              },
              "example": {
                "amount": 2500
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Payment refunded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payment"
                },
                "example": {
                  "id": "txn_abc123def456ghi789jklmno",
                  "object": "payment",
                  "amount": 5000,
                  "currency": "USD",
                  "status": "partially_refunded",
                  "captured_amount": 5000,
                  "refunded_amount": 2500,
                  "created_at": "2026-04-09T12:00:00+00:00"
                }
              }
            }
          },
          "400": {
            "description": "Payment cannot be refunded or refund amount exceeds refundable amount",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "type": "invalid_request_error",
                    "message": "Refund amount (6000) exceeds refundable amount (5000)."
                  }
                }
              }
            }
          },
          "404": {
            "description": "Payment not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/tokens": {
      "post": {
        "tags": [
          "Tokens"
        ],
        "summary": "Create a token",
        "description": "Register a PSP-issued token with Penumbra. All card tokenization must happen client-side via PSP JavaScript libraries (Stripe.js, NMI Collect.js, Square Web Payments SDK). This endpoint accepts only pre-tokenized references. Raw card numbers and CVVs are rejected: cards are tokenized in the browser by the PSP's own library, so card data never reaches Penumbra. Your own PCI scope depends on how your checkout loads those fields.",
        "operationId": "createToken",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Required. The same key with the same request returns the first result instead of repeating it; a request without one is refused with `400`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "type"
                ],
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "card",
                      "bank_account",
                      "plaid"
                    ]
                  },
                  "psp_token": {
                    "type": "string",
                    "description": "PSP-issued token (required for card type)"
                  },
                  "psp": {
                    "type": "string",
                    "enum": [
                      "stripe",
                      "nmi",
                      "square",
                      "paypal"
                    ],
                    "description": "PSP that issued the token (required for card type)"
                  },
                  "card": {
                    "type": "object",
                    "description": "Card metadata (required for card type)",
                    "properties": {
                      "last4": {
                        "type": "string",
                        "minLength": 4,
                        "maxLength": 4
                      },
                      "brand": {
                        "type": "string",
                        "enum": [
                          "visa",
                          "mastercard",
                          "amex",
                          "discover",
                          "unknown"
                        ]
                      },
                      "exp_month": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 12
                      },
                      "exp_year": {
                        "type": "integer",
                        "minimum": 2026
                      },
                      "name": {
                        "type": "string",
                        "maxLength": 255,
                        "nullable": true
                      }
                    }
                  },
                  "bank_account": {
                    "type": "object",
                    "description": "Bank account details (required for bank_account type)",
                    "properties": {
                      "routing_number": {
                        "type": "string"
                      },
                      "account_number": {
                        "type": "string"
                      },
                      "account_type": {
                        "type": "string",
                        "enum": [
                          "checking",
                          "savings"
                        ]
                      }
                    }
                  },
                  "plaid": {
                    "type": "object",
                    "description": "Plaid link details (required for plaid type)",
                    "properties": {
                      "public_token": {
                        "type": "string"
                      },
                      "account_id": {
                        "type": "string"
                      }
                    }
                  }
                }
              },
              "example": {
                "type": "card",
                "psp_token": "pm_1abc2def3ghi",
                "psp": "stripe",
                "card": {
                  "last4": "4242",
                  "brand": "visa",
                  "exp_month": 12,
                  "exp_year": 2028
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Token created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Token"
                },
                "example": {
                  "id": "tok_abc123def456ghi789jkl012",
                  "object": "token",
                  "type": "card",
                  "last4": "4242",
                  "brand": "visa",
                  "exp_month": 12,
                  "exp_year": 2028,
                  "is_active": true,
                  "is_sandbox": false,
                  "created_at": "2026-04-09T12:00:00+00:00"
                }
              }
            }
          },
          "200": {
            "description": "Duplicate token found (returned existing token)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Token"
                }
              }
            }
          }
        }
      }
    },
    "/tokens/{id}": {
      "get": {
        "tags": [
          "Tokens"
        ],
        "summary": "Retrieve a token",
        "description": "Retrieve the details of a specific token by its ID.",
        "operationId": "getToken",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Token ID (tok_xxx)"
          }
        ],
        "responses": {
          "200": {
            "description": "Token details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Token"
                }
              }
            }
          },
          "404": {
            "description": "Token not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "type": "invalid_request_error",
                    "message": "Token not found."
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Tokens"
        ],
        "summary": "Deactivate a token",
        "description": "Deactivate a token (soft delete). The token is marked inactive and can no longer be used for payments.",
        "operationId": "deleteToken",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Token ID (tok_xxx)"
          }
        ],
        "responses": {
          "200": {
            "description": "Token deactivated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "object": {
                      "type": "string",
                      "example": "token"
                    },
                    "deleted": {
                      "type": "boolean",
                      "example": true
                    }
                  }
                },
                "example": {
                  "id": "tok_abc123def456ghi789jkl012",
                  "object": "token",
                  "deleted": true
                }
              }
            }
          },
          "404": {
            "description": "Token not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/terminals": {
      "post": {
        "tags": [
          "Terminals"
        ],
        "summary": "Register a reader",
        "description": "Registers a Stripe Terminal reader to your account: a new one by the registration code it shows, or one already on your Stripe account by its id. A new reader needs an address: pass `location`, or a `merchant_id` whose merchant has one. The reader belongs to the Stripe account your payments go through when you register it; if payments later move to another account, its sales are refused until you register it again. In the sandbox, the code `simulated-wpe` creates a simulated reader. Needs a full-access key.",
        "operationId": "registerTerminal",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "manufacturer"
                ],
                "properties": {
                  "manufacturer": {
                    "type": "string",
                    "enum": [
                      "stripe"
                    ]
                  },
                  "registration_code": {
                    "type": "string",
                    "maxLength": 64,
                    "description": "The registration code the reader shows. Required unless you pass `reader_id`. In the sandbox, `simulated-wpe`."
                  },
                  "reader_id": {
                    "type": "string",
                    "maxLength": 64,
                    "description": "A reader already registered on your Stripe account (`tmr_...`), in place of a code."
                  },
                  "label": {
                    "type": "string",
                    "maxLength": 120,
                    "description": "The reader's name in Stripe. Defaults to `Penumbra reader`."
                  },
                  "merchant_id": {
                    "type": "string",
                    "description": "The merchant whose webhook endpoint hears about this reader's sales, as the Merchants API returns it. Must be one of yours. A reader registered without one sends no webhook events."
                  },
                  "location_name": {
                    "type": "string",
                    "maxLength": 255
                  },
                  "location": {
                    "type": "object",
                    "description": "The reader's address; `line1`, `city`, `state` and `postal_code` are required when you give one. Required for a new reader unless `merchant_id` names a merchant with an address.",
                    "required": [
                      "line1",
                      "city",
                      "state",
                      "postal_code"
                    ],
                    "properties": {
                      "line1": {
                        "type": "string",
                        "maxLength": 255
                      },
                      "city": {
                        "type": "string",
                        "maxLength": 120
                      },
                      "state": {
                        "type": "string",
                        "maxLength": 64
                      },
                      "postal_code": {
                        "type": "string",
                        "maxLength": 20
                      },
                      "country": {
                        "type": "string",
                        "minLength": 2,
                        "maxLength": 2,
                        "default": "US"
                      }
                    }
                  }
                }
              },
              "example": {
                "manufacturer": "stripe",
                "registration_code": "simulated-wpe",
                "label": "Front counter",
                "location_name": "Front counter",
                "location": {
                  "line1": "1 Main St",
                  "city": "Pensacola",
                  "state": "FL",
                  "postal_code": "32503"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Registered",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Terminal"
                },
                "example": {
                  "id": "term_4kQ9vT2mXb7LpR1sWz8c",
                  "object": "terminal",
                  "manufacturer": "stripe",
                  "model": "bbpos_wisepos_e",
                  "serial_number": "WSC513105011295",
                  "status": "active",
                  "location_name": "Front counter",
                  "capabilities": [
                    "emv",
                    "nfc",
                    "magstripe"
                  ],
                  "created_at": "2026-09-21T15:00:00+00:00"
                }
              }
            }
          },
          "404": {
            "description": "The `merchant_id` or `reader_id` is not one of yours",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "type": "invalid_request_error",
                    "code": "merchant_not_found",
                    "message": "No merchant with that id."
                  }
                }
              }
            }
          },
          "409": {
            "description": "No Stripe account can take in-person payments for this key: in production, connect your own Stripe account first (`stripe_account_required`). A key and account in different modes get `environment_mismatch`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "type": "invalid_request_error",
                    "code": "stripe_account_required",
                    "message": "Connect your own Stripe account to take in-person payments."
                  }
                }
              }
            }
          },
          "422": {
            "description": "Stripe did not accept the reader or its location, or neither a registration code nor a reader id was given",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "type": "invalid_request_error",
                    "code": "registration_code_required",
                    "message": "Give the registration code the reader shows, or an existing reader_id."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/terminals/{id}": {
      "get": {
        "tags": [
          "Terminals"
        ],
        "summary": "Retrieve a reader",
        "description": "The reader, and whether it is online as Stripe reports it.",
        "operationId": "getTerminal",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The terminal's `id` (`term_...`)."
          }
        ],
        "responses": {
          "200": {
            "description": "The reader",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Terminal"
                },
                "example": {
                  "id": "term_4kQ9vT2mXb7LpR1sWz8c",
                  "object": "terminal",
                  "manufacturer": "stripe",
                  "model": "bbpos_wisepos_e",
                  "serial_number": "WSC513105011295",
                  "status": "active",
                  "location_name": "Front counter",
                  "capabilities": [
                    "emv",
                    "nfc",
                    "magstripe"
                  ],
                  "created_at": "2026-09-21T15:00:00+00:00",
                  "online": true,
                  "device": "stripe_terminal"
                }
              }
            }
          },
          "404": {
            "description": "No terminal with that id in your account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/terminals/{id}/payments": {
      "post": {
        "tags": [
          "Terminals"
        ],
        "summary": "Start an in-person payment",
        "description": "Puts a sale on the reader, which asks the customer to tap, insert or swipe. The answer is `202` with the sale `pending`: check it with `GET /terminals/{id}/payments/{transaction_id}` or wait for its webhook event. Nothing is charged until the reader approves the card. If the reader does not take the sale (busy or offline), you get `409` `reader_unavailable` and nothing is charged; try again with a new `Idempotency-Key`, because the same key returns the same answer. Counts toward the money-movement rate limit.",
        "operationId": "startTerminalPayment",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The terminal's `id` (`term_...`)."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Required. A retry with the same key returns the first answer and never starts a second sale."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "amount"
                ],
                "properties": {
                  "amount": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Amount in cents."
                  },
                  "currency": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 3,
                    "default": "USD"
                  }
                }
              },
              "example": {
                "amount": 2500,
                "currency": "USD"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The reader is waiting for the card",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TerminalPayment"
                },
                "example": {
                  "object": "terminal_payment",
                  "terminal_id": "term_4kQ9vT2mXb7LpR1sWz8c",
                  "transaction_id": "txn_Hk2mQ8vR4tLx9WbN3cZp7JsD",
                  "status": "pending",
                  "approved": false,
                  "amount": 2500,
                  "currency": "USD",
                  "auth_code": null,
                  "decline_reason": null,
                  "psp_used": "stripe",
                  "entry_mode": null,
                  "is_debit": null,
                  "card_brand": null,
                  "card_last4": null,
                  "created_at": "2026-09-21T15:04:05+00:00"
                }
              }
            }
          },
          "400": {
            "description": "No `Idempotency-Key` header",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "type": "invalid_request_error",
                    "message": "Idempotency-Key header is required for this endpoint. Provide a unique key (UUID recommended) to prevent duplicate operations."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No terminal with that id in your account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "The reader did not take the sale, and nothing was charged; the body also carries the attempt's `transaction_id`. Also returned when the reader belongs to a Stripe account payments no longer go through (`reader_account_changed`), or no Stripe account can take the sale in this mode",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "type": "invalid_request_error",
                    "code": "reader_unavailable",
                    "message": "The reader did not take the sale, and nothing was charged: Reader is currently busy."
                  },
                  "transaction_id": "txn_Hk2mQ8vR4tLx9WbN3cZp7JsD"
                }
              }
            }
          },
          "429": {
            "description": "Over the rate limit, or in the sandbox over the daily payment limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/terminals/{id}/payments/{transaction_id}": {
      "get": {
        "tags": [
          "Terminals"
        ],
        "summary": "Check an in-person payment",
        "description": "Where the sale stands, read from Stripe. Once the reader has approved the card, this captures the payment and answers `captured`, with the card's brand, last four digits and how it was read. A declined card answers `declined` with the reason. A finished sale never changes and is never captured twice. If nobody checks, Penumbra finishes the sale itself within about a minute. Counts toward the money-movement rate limit, so check every couple of seconds at most.",
        "operationId": "getTerminalPayment",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The terminal's `id` (`term_...`)."
          },
          {
            "name": "transaction_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The `transaction_id` the sale started with."
          }
        ],
        "responses": {
          "200": {
            "description": "The sale",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TerminalPayment"
                },
                "example": {
                  "object": "terminal_payment",
                  "terminal_id": "term_4kQ9vT2mXb7LpR1sWz8c",
                  "transaction_id": "txn_Hk2mQ8vR4tLx9WbN3cZp7JsD",
                  "status": "captured",
                  "approved": true,
                  "amount": 2500,
                  "currency": "USD",
                  "auth_code": "832540",
                  "decline_reason": null,
                  "psp_used": "stripe",
                  "entry_mode": "contactless",
                  "is_debit": false,
                  "card_brand": "visa",
                  "card_last4": "4242",
                  "created_at": "2026-09-21T15:04:05+00:00"
                }
              }
            }
          },
          "404": {
            "description": "No sale on this terminal with that id in your account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/terminals/{id}/payments/{transaction_id}/cancel": {
      "post": {
        "tags": [
          "Terminals"
        ],
        "summary": "Cancel an in-person payment",
        "description": "Calls off a sale the reader has not finished; it answers `voided`. A completed sale cannot be cancelled: refund it with `POST /payments/{id}/refund`, using its `transaction_id`. If the card is read in the moment before the cancel lands, the sale completes and you get `409` `sale_completed`.",
        "operationId": "cancelTerminalPayment",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The terminal's `id` (`term_...`)."
          },
          {
            "name": "transaction_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The `transaction_id` the sale started with."
          }
        ],
        "responses": {
          "200": {
            "description": "Cancelled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TerminalPayment"
                },
                "example": {
                  "object": "terminal_payment",
                  "terminal_id": "term_4kQ9vT2mXb7LpR1sWz8c",
                  "transaction_id": "txn_Hk2mQ8vR4tLx9WbN3cZp7JsD",
                  "status": "voided",
                  "approved": false,
                  "amount": 2500,
                  "currency": "USD",
                  "auth_code": null,
                  "decline_reason": "canceled",
                  "psp_used": "stripe",
                  "entry_mode": null,
                  "is_debit": null,
                  "card_brand": null,
                  "card_last4": null,
                  "created_at": "2026-09-21T15:04:05+00:00"
                }
              }
            }
          },
          "404": {
            "description": "No sale on this terminal with that id in your account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "The sale already finished (`sale_not_cancelable`), or finished as it was being cancelled (`sale_completed`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "type": "invalid_request_error",
                    "code": "sale_not_cancelable",
                    "message": "This sale is already captured; refund a completed sale instead."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/terminals/{id}/simulate": {
      "post": {
        "tags": [
          "Terminals"
        ],
        "summary": "Simulate a card (sandbox)",
        "description": "Sandbox only. Has a simulated reader take a card, as a customer's tap would, with the outcome you choose. Then check the sale for the result. You name an outcome, never a card number: a request body containing a card number is refused.",
        "operationId": "simulateTerminalCard",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The terminal's `id` (`term_...`)."
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "outcome": {
                    "type": "string",
                    "enum": [
                      "approve",
                      "decline",
                      "insufficient_funds"
                    ],
                    "default": "approve"
                  }
                }
              },
              "example": {
                "outcome": "decline"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The card was presented",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "enum": [
                        "terminal_simulation"
                      ]
                    },
                    "terminal_id": {
                      "type": "string"
                    },
                    "presented": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "object": "terminal_simulation",
                  "terminal_id": "term_4kQ9vT2mXb7LpR1sWz8c",
                  "presented": true
                }
              }
            }
          },
          "403": {
            "description": "Called with a production key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "type": "invalid_request_error",
                    "code": "sandbox_only",
                    "message": "Card presentment can only be simulated in sandbox."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No terminal with that id in your account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Stripe did not simulate the card, for example because no sale is waiting on the reader",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "type": "invalid_request_error",
                    "code": "simulation_failed",
                    "message": "Stripe did not simulate the card."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/merchants": {
      "post": {
        "tags": [
          "Merchants"
        ],
        "summary": "Create a merchant",
        "description": "Onboard a new sub-merchant. The merchant is created in pending_review status.",
        "operationId": "createMerchant",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "business_name",
                  "business_type",
                  "address_line1",
                  "city",
                  "state",
                  "zip",
                  "contact_name",
                  "contact_email",
                  "contact_phone"
                ],
                "properties": {
                  "business_name": {
                    "type": "string",
                    "maxLength": 255
                  },
                  "dba": {
                    "type": "string",
                    "maxLength": 255,
                    "nullable": true
                  },
                  "ein": {
                    "type": "string",
                    "maxLength": 20,
                    "nullable": true
                  },
                  "business_type": {
                    "type": "string",
                    "enum": [
                      "sole_prop",
                      "llc",
                      "corp",
                      "partnership",
                      "nonprofit"
                    ]
                  },
                  "address_line1": {
                    "type": "string",
                    "maxLength": 255
                  },
                  "address_line2": {
                    "type": "string",
                    "maxLength": 255,
                    "nullable": true
                  },
                  "city": {
                    "type": "string",
                    "maxLength": 100
                  },
                  "state": {
                    "type": "string",
                    "maxLength": 2
                  },
                  "zip": {
                    "type": "string",
                    "maxLength": 10
                  },
                  "country": {
                    "type": "string",
                    "maxLength": 2,
                    "default": "US"
                  },
                  "contact_name": {
                    "type": "string",
                    "maxLength": 255
                  },
                  "contact_email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 255
                  },
                  "contact_phone": {
                    "type": "string",
                    "maxLength": 20
                  },
                  "website": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 255,
                    "nullable": true
                  },
                  "mcc": {
                    "type": "string",
                    "maxLength": 4,
                    "nullable": true
                  },
                  "expected_monthly_volume": {
                    "type": "integer",
                    "minimum": 0,
                    "nullable": true,
                    "description": "Expected monthly volume in cents"
                  },
                  "bank_routing": {
                    "type": "string",
                    "maxLength": 9,
                    "nullable": true
                  },
                  "bank_account": {
                    "type": "string",
                    "maxLength": 17,
                    "nullable": true
                  },
                  "bank_account_type": {
                    "type": "string",
                    "enum": [
                      "checking",
                      "savings"
                    ],
                    "nullable": true
                  },
                  "metadata": {
                    "type": "object",
                    "nullable": true,
                    "additionalProperties": true
                  }
                }
              },
              "example": {
                "business_name": "Acme Corp",
                "business_type": "llc",
                "address_line1": "123 Main St",
                "city": "Los Angeles",
                "state": "CA",
                "zip": "90001",
                "contact_name": "John Smith",
                "contact_email": "john@acmecorp.com",
                "contact_phone": "555-123-4567",
                "mcc": "5999",
                "expected_monthly_volume": 5000000
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Merchant created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Merchant"
                },
                "example": {
                  "id": "mer_abc123def456ghi789jkl012",
                  "object": "merchant",
                  "business_name": "Acme Corp",
                  "dba": null,
                  "business_type": "llc",
                  "address": {
                    "line1": "123 Main St",
                    "line2": null,
                    "city": "Los Angeles",
                    "state": "CA",
                    "zip": "90001",
                    "country": "US"
                  },
                  "contact": {
                    "name": "John Smith",
                    "email": "john@acmecorp.com",
                    "phone": "555-123-4567"
                  },
                  "website": null,
                  "mcc": "5999",
                  "status": "pending_review",
                  "risk_tier": "low",
                  "kyc_completed": false,
                  "ofac_screened": false,
                  "is_sandbox": false,
                  "metadata": null,
                  "created_at": "2026-04-09T12:00:00+00:00",
                  "updated_at": "2026-04-09T12:00:00+00:00"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Merchants"
        ],
        "summary": "List merchants",
        "description": "List all merchants for the authenticated organization with optional search and filters.",
        "operationId": "listMerchants",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "pending_review",
                "approved",
                "rejected",
                "suspended",
                "deactivated"
              ]
            }
          },
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Search by business name, DBA, contact name, email, or ID"
          },
          {
            "name": "risk_tier",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "low",
                "medium",
                "high",
                "prohibited"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 250,
              "default": 50
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of merchants",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/List"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Merchant"
                          }
                        },
                        "total": {
                          "type": "integer"
                        },
                        "page": {
                          "type": "integer"
                        },
                        "per_page": {
                          "type": "integer"
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/merchants/{id}": {
      "get": {
        "tags": [
          "Merchants"
        ],
        "summary": "Retrieve a merchant",
        "description": "Retrieve the details of a specific merchant by its ID.",
        "operationId": "getMerchant",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Merchant ID (mer_xxx)"
          }
        ],
        "responses": {
          "200": {
            "description": "Merchant details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Merchant"
                }
              }
            }
          },
          "404": {
            "description": "Merchant not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "type": "invalid_request_error",
                    "message": "Merchant not found."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/customers": {
      "get": {
        "tags": [
          "Customers"
        ],
        "summary": "List customers",
        "description": "List customers with optional search and sorting. Supports sorting by lifetime value, last seen date, total spent, total transactions, or creation date.",
        "operationId": "listCustomers",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Search by name or email"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "inactive",
                "flagged"
              ]
            }
          },
          {
            "name": "sort",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "lifetime_value_cents",
                "last_seen_at",
                "total_spent_cents",
                "total_transactions",
                "created_at"
              ],
              "default": "lifetime_value_cents"
            }
          },
          {
            "name": "direction",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "desc"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 250,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of customers",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/List"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Customer"
                          }
                        },
                        "total": {
                          "type": "integer"
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "id": 1,
                      "object": "customer",
                      "email": "customer@example.com",
                      "name": "Jane Doe",
                      "status": "active",
                      "total_transactions": 12,
                      "total_spent_cents": 1250000,
                      "lifetime_value_cents": 1150000,
                      "is_high_value": true,
                      "created_at": "2026-01-15T08:30:00+00:00"
                    }
                  ],
                  "total": 1
                }
              }
            }
          }
        }
      }
    },
    "/customers/{id}": {
      "get": {
        "tags": [
          "Customers"
        ],
        "summary": "Retrieve a customer",
        "description": "Retrieve customer details including AI-generated insights on spending patterns and behavior.",
        "operationId": "getCustomer",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Customer ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Customer details with insights",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Customer"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "insights": {
                          "type": "object",
                          "additionalProperties": true
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Customer not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/crypto/transfer": {
      "post": {
        "tags": [
          "Crypto"
        ],
        "summary": "Create a crypto transfer",
        "description": "Initiate a USDC stablecoin transfer to a blockchain wallet via Circle. Supported chains: polygon, ethereum, solana, base, avalanche.",
        "operationId": "createCryptoTransfer",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Required. The same key with the same request returns the first result instead of repeating it; a request without one is refused with `400`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "to_address",
                  "amount_cents",
                  "chain"
                ],
                "properties": {
                  "to_address": {
                    "type": "string",
                    "description": "Destination wallet address"
                  },
                  "amount_cents": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Amount in cents (converted to USDC)"
                  },
                  "chain": {
                    "type": "string",
                    "enum": [
                      "polygon",
                      "ethereum",
                      "solana",
                      "base",
                      "avalanche"
                    ]
                  },
                  "from_address": {
                    "type": "string",
                    "nullable": true
                  },
                  "merchant_id": {
                    "type": "string",
                    "nullable": true
                  },
                  "memo": {
                    "type": "string",
                    "maxLength": 500,
                    "nullable": true
                  },
                  "metadata": {
                    "type": "object",
                    "nullable": true,
                    "additionalProperties": true
                  }
                }
              },
              "example": {
                "to_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
                "amount_cents": 100000,
                "chain": "polygon",
                "memo": "Merchant settlement Q1"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Transfer initiated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CryptoTransfer"
                },
                "example": {
                  "object": "crypto_transfer",
                  "transfer_id": 42,
                  "circle_payout_id": "payout_abc123",
                  "status": "pending",
                  "chain": "polygon",
                  "amount_cents": 100000,
                  "currency": "USDC",
                  "to_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
                  "blockchain_tx_hash": "0xdef456...",
                  "block_explorer_url": "https://polygonscan.com/tx/0xdef456...",
                  "created_at": "2026-04-09T12:00:00+00:00"
                }
              }
            }
          },
          "422": {
            "description": "Invalid wallet address",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "type": "validation_error",
                    "message": "The wallet address is not valid for the polygon chain.",
                    "param": "to_address"
                  }
                }
              }
            }
          },
          "502": {
            "description": "Circle payout failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/crypto/transfers": {
      "get": {
        "tags": [
          "Crypto"
        ],
        "summary": "List crypto transfers",
        "description": "List recent USDC crypto transfers with optional chain and status filters.",
        "operationId": "listCryptoTransfers",
        "parameters": [
          {
            "name": "chain",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "polygon",
                "ethereum",
                "solana",
                "base",
                "avalanche"
              ]
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "confirmed"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of crypto transfers",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/List"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/CryptoTransfer"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/crypto/wallets": {
      "get": {
        "tags": [
          "Crypto"
        ],
        "summary": "Get wallet info",
        "description": "Returns the master Circle wallet info including balance, supported chains, settlement times, and gas cost estimates.",
        "operationId": "getCryptoWallets",
        "responses": {
          "200": {
            "description": "Wallet information",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "example": "wallet"
                    },
                    "wallet_id": {
                      "type": "string"
                    },
                    "balance": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "supported_chains": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "settlement_times": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "string"
                      }
                    },
                    "gas_costs": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "integer"
                      }
                    },
                    "stub_mode": {
                      "type": "boolean",
                      "description": "True when Circle API key is not configured"
                    },
                    "health": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                },
                "example": {
                  "object": "wallet",
                  "wallet_id": "wallet_abc123",
                  "balance": {
                    "USDC": "50000.00"
                  },
                  "supported_chains": [
                    "polygon",
                    "ethereum",
                    "solana",
                    "base",
                    "avalanche"
                  ],
                  "settlement_times": {
                    "polygon": "~2 minutes",
                    "ethereum": "~15 minutes"
                  },
                  "gas_costs": {
                    "polygon": 5,
                    "ethereum": 500,
                    "solana": 2
                  },
                  "stub_mode": false,
                  "health": {
                    "status": "healthy"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/crypto/request": {
      "post": {
        "tags": [
          "Crypto"
        ],
        "summary": "Create a payment request",
        "description": "Create a crypto payment request. Sends a notification to the recipient asking them to pay a specified USDC amount on a given chain. Requests expire after 7 days.",
        "operationId": "createCryptoRequest",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "recipient",
                  "amount_cents",
                  "chain"
                ],
                "properties": {
                  "recipient": {
                    "type": "string",
                    "maxLength": 255,
                    "description": "Recipient identifier (email or wallet address)"
                  },
                  "amount_cents": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "chain": {
                    "type": "string",
                    "enum": [
                      "polygon",
                      "ethereum",
                      "solana",
                      "base",
                      "avalanche"
                    ]
                  },
                  "memo": {
                    "type": "string",
                    "maxLength": 500,
                    "nullable": true
                  }
                }
              },
              "example": {
                "recipient": "vendor@example.com",
                "amount_cents": 50000,
                "chain": "polygon",
                "memo": "Invoice #1234"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Payment request created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "example": "crypto_request"
                    },
                    "id": {
                      "type": "integer"
                    },
                    "recipient": {
                      "type": "string"
                    },
                    "amount_cents": {
                      "type": "integer"
                    },
                    "chain": {
                      "type": "string"
                    },
                    "memo": {
                      "type": "string",
                      "nullable": true
                    },
                    "status": {
                      "type": "string",
                      "example": "pending"
                    },
                    "direction": {
                      "type": "string",
                      "example": "sent"
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                },
                "example": {
                  "object": "crypto_request",
                  "id": 1,
                  "recipient": "vendor@example.com",
                  "amount_cents": 50000,
                  "chain": "polygon",
                  "memo": "Invoice #1234",
                  "status": "pending",
                  "direction": "sent",
                  "expires_at": "2026-04-16T12:00:00+00:00",
                  "created_at": "2026-04-09T12:00:00+00:00"
                }
              }
            }
          }
        }
      }
    },
    "/crypto/requests": {
      "get": {
        "tags": [
          "Crypto"
        ],
        "summary": "List payment requests",
        "description": "List crypto payment requests (both sent and received) for the authenticated organization.",
        "operationId": "listCryptoRequests",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of payment requests",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/List"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "recipient": {
                                "type": "string"
                              },
                              "amount_cents": {
                                "type": "integer"
                              },
                              "chain": {
                                "type": "string"
                              },
                              "memo": {
                                "type": "string",
                                "nullable": true
                              },
                              "status": {
                                "type": "string",
                                "enum": [
                                  "pending",
                                  "completed",
                                  "declined"
                                ]
                              },
                              "direction": {
                                "type": "string",
                                "enum": [
                                  "sent",
                                  "received"
                                ]
                              },
                              "expires_at": {
                                "type": "string",
                                "format": "date-time",
                                "nullable": true
                              },
                              "created_at": {
                                "type": "string",
                                "format": "date-time"
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/crypto/requests/{id}/respond": {
      "post": {
        "tags": [
          "Crypto"
        ],
        "summary": "Respond to a payment request",
        "description": "Accept or decline a crypto payment request. When accepted, a transfer is triggered on the specified chain. When declined, the request is closed.",
        "operationId": "respondToCryptoRequest",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Request ID (UUID)"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "action"
                ],
                "properties": {
                  "action": {
                    "type": "string",
                    "enum": [
                      "accept",
                      "decline"
                    ]
                  }
                }
              },
              "example": {
                "action": "accept"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Response recorded",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "example": "crypto_request_response"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "action": {
                      "type": "string",
                      "enum": [
                        "accept",
                        "decline"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "completed",
                        "declined"
                      ]
                    },
                    "responded_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                },
                "example": {
                  "object": "crypto_request_response",
                  "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                  "action": "accept",
                  "status": "completed",
                  "responded_at": "2026-04-09T14:00:00+00:00"
                }
              }
            }
          },
          "404": {
            "description": "Request not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Request is no longer pending",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "type": "invalid_request_error",
                    "message": "Request is no longer pending."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/transfers/bank": {
      "post": {
        "tags": [
          "Bank Transfers"
        ],
        "summary": "Create a bank transfer",
        "description": "Create a USD bank transfer instruction for standard ACH, same-day ACH, FedNow or RTP. Every transfer is created with status `pending` and moves to `completed` only when the rail carrying it confirms settlement. FedNow and RTP need a participating bank partner enabled on your account.",
        "operationId": "createBankTransfer",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Required. The same key with the same request returns the first result instead of repeating it; a request without one is refused with `400`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "from_account_id",
                  "to_identifier",
                  "amount_cents",
                  "speed"
                ],
                "properties": {
                  "from_account_id": {
                    "type": "string",
                    "description": "Source account identifier"
                  },
                  "to_identifier": {
                    "type": "string",
                    "maxLength": 255,
                    "description": "Destination account identifier"
                  },
                  "amount_cents": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Amount in cents"
                  },
                  "speed": {
                    "type": "string",
                    "enum": [
                      "ach_standard",
                      "ach_same_day",
                      "fednow",
                      "rtp"
                    ],
                    "description": "Transfer speed. Fees: ach_standard=$0, ach_same_day=$1.50, fednow=$0.50, rtp=$0.25"
                  },
                  "memo": {
                    "type": "string",
                    "maxLength": 500,
                    "nullable": true
                  }
                }
              },
              "example": {
                "from_account_id": "acct_abc123",
                "to_identifier": "acct_def456",
                "amount_cents": 250000,
                "speed": "ach_same_day",
                "memo": "Vendor payment"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Bank transfer created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BankTransfer"
                },
                "example": {
                  "object": "bank_transfer",
                  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                  "from_account_id": "acct_abc123",
                  "to_identifier": "acct_def456",
                  "amount_cents": 250000,
                  "speed": "ach_same_day",
                  "fee_cents": 150,
                  "memo": "Vendor payment",
                  "status": "pending",
                  "created_at": "2026-04-09T12:00:00+00:00"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Bank Transfers"
        ],
        "summary": "List bank transfers",
        "description": "List recent bank transfers for the authenticated organization.",
        "operationId": "listBankTransfers",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of bank transfers",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/List"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/BankTransfer"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/forex/rate": {
      "get": {
        "tags": [
          "Forex"
        ],
        "summary": "Get exchange rate",
        "description": "Returns the exchange rate between two currencies. Supported currencies: USD, EUR, GBP, CAD, JPY, AUD, CHF, MXN, BRL, INR. Includes a 0.5% fee on conversions.",
        "operationId": "getForexRate",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 3,
              "maxLength": 3
            },
            "description": "Source currency code (e.g., USD)"
          },
          {
            "name": "to",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 3,
              "maxLength": 3
            },
            "description": "Target currency code (e.g., EUR)"
          }
        ],
        "responses": {
          "200": {
            "description": "Exchange rate",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForexRate"
                },
                "example": {
                  "object": "forex_rate",
                  "from": "USD",
                  "to": "EUR",
                  "rate": 0.9215,
                  "fee_pct": 0.5,
                  "updated_at": "2026-04-09T12:00:00+00:00"
                }
              }
            }
          },
          "422": {
            "description": "Unsupported currency",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "type": "validation_error",
                    "message": "One or both currencies are not supported.",
                    "param": "from"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/forex/convert": {
      "post": {
        "tags": [
          "Forex"
        ],
        "summary": "Convert currency",
        "description": "Initiate a forex conversion. Converts one currency to another and sends the result to a recipient. A 0.5% fee is deducted from the send amount before conversion.",
        "operationId": "convertForex",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Required. The same key with the same request returns the first result instead of repeating it; a request without one is refused with `400`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "from_currency",
                  "to_currency",
                  "amount_cents",
                  "recipient"
                ],
                "properties": {
                  "from_currency": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 3
                  },
                  "to_currency": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 3
                  },
                  "amount_cents": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Amount to send in the from_currency, in cents"
                  },
                  "recipient": {
                    "type": "string",
                    "maxLength": 255,
                    "description": "Recipient identifier"
                  }
                }
              },
              "example": {
                "from_currency": "USD",
                "to_currency": "EUR",
                "amount_cents": 100000,
                "recipient": "vendor@example.com"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Conversion completed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForexConversion"
                },
                "example": {
                  "object": "forex_conversion",
                  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                  "from_currency": "USD",
                  "to_currency": "EUR",
                  "send_amount_cents": 100000,
                  "receive_amount_cents": 91690,
                  "rate": 0.9215,
                  "fee_pct": 0.5,
                  "fee_amount_cents": 500,
                  "recipient": "vendor@example.com",
                  "status": "completed",
                  "created_at": "2026-04-09T12:00:00+00:00"
                }
              }
            }
          },
          "422": {
            "description": "Unsupported currency",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/splits": {
      "post": {
        "tags": [
          "Splits"
        ],
        "summary": "Create a split configuration",
        "description": "Create a revenue split configuration for a merchant. Any existing active configuration for the same merchant is automatically deactivated. Version is auto-incremented.",
        "operationId": "createSplit",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Required. The same key with the same request returns the first result instead of repeating it; a request without one is refused with `400`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "merchant_id",
                  "recipients"
                ],
                "properties": {
                  "merchant_id": {
                    "type": "string"
                  },
                  "recipients": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "object",
                      "required": [
                        "recipient_type",
                        "recipient_name",
                        "distribution_type",
                        "distribution_value"
                      ],
                      "properties": {
                        "recipient_type": {
                          "type": "string",
                          "enum": [
                            "platform",
                            "merchant",
                            "agent",
                            "mca_lender",
                            "reserve",
                            "tax",
                            "custom"
                          ]
                        },
                        "recipient_name": {
                          "type": "string",
                          "maxLength": 255
                        },
                        "recipient_id": {
                          "type": "string",
                          "nullable": true
                        },
                        "distribution_type": {
                          "type": "string",
                          "enum": [
                            "percentage",
                            "fixed",
                            "tiered",
                            "conditional"
                          ]
                        },
                        "distribution_value": {
                          "type": "object",
                          "additionalProperties": true,
                          "description": "Distribution configuration. For percentage: {\"percent\": 10}. For fixed: {\"amount_cents\": 500}."
                        },
                        "priority": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 999,
                          "default": 50
                        },
                        "is_perpetual": {
                          "type": "boolean",
                          "default": false
                        },
                        "change_of_control_survives": {
                          "type": "boolean",
                          "default": false
                        },
                        "tax_id": {
                          "type": "string",
                          "nullable": true
                        },
                        "tax_classification": {
                          "type": "string",
                          "nullable": true
                        }
                      }
                    }
                  },
                  "effective_date": {
                    "type": "string",
                    "format": "date",
                    "nullable": true
                  }
                }
              },
              "example": {
                "merchant_id": "mer_abc123def456ghi789jkl012",
                "recipients": [
                  {
                    "recipient_type": "platform",
                    "recipient_name": "Penumbra",
                    "distribution_type": "percentage",
                    "distribution_value": {
                      "percent": 2.5
                    },
                    "priority": 1
                  },
                  {
                    "recipient_type": "merchant",
                    "recipient_name": "Acme Corp",
                    "distribution_type": "percentage",
                    "distribution_value": {
                      "percent": 97.5
                    },
                    "priority": 2
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Split configuration created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SplitConfig"
                },
                "example": {
                  "id": "spl_abc123def456ghi789jkl012",
                  "object": "split_config",
                  "merchant_id": "mer_abc123def456ghi789jkl012",
                  "version": 1,
                  "is_active": true,
                  "effective_date": "2026-04-09",
                  "recipients": [
                    {
                      "recipient_type": "platform",
                      "recipient_name": "Penumbra",
                      "distribution_type": "percentage",
                      "distribution_value": {
                        "percent": 2.5
                      },
                      "priority": 1,
                      "is_perpetual": false,
                      "change_of_control_survives": false
                    }
                  ],
                  "created_at": "2026-04-09T12:00:00+00:00"
                }
              }
            }
          }
        }
      }
    },
    "/splits/{merchant_id}": {
      "get": {
        "tags": [
          "Splits"
        ],
        "summary": "Get active split configuration",
        "description": "Get the active split configuration for a merchant.",
        "operationId": "getSplit",
        "parameters": [
          {
            "name": "merchant_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Merchant ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Split configuration",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SplitConfig"
                }
              }
            }
          },
          "404": {
            "description": "No active split configuration found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "type": "invalid_request_error",
                    "message": "No active split configuration found for this merchant."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/splits/{merchant_id}/ledger": {
      "get": {
        "tags": [
          "Splits"
        ],
        "summary": "Get split ledger",
        "description": "View the split distribution ledger for a merchant, showing how each transaction was split among recipients.",
        "operationId": "getSplitLedger",
        "parameters": [
          {
            "name": "merchant_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "transaction_id",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by transaction ID"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Split ledger entries",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/List"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "transaction_id": {
                                "type": "string"
                              },
                              "recipient_type": {
                                "type": "string"
                              },
                              "recipient_name": {
                                "type": "string"
                              },
                              "amount": {
                                "type": "integer",
                                "description": "Amount in cents"
                              },
                              "currency": {
                                "type": "string"
                              },
                              "split_type": {
                                "type": "string"
                              },
                              "status": {
                                "type": "string"
                              },
                              "created_at": {
                                "type": "string",
                                "format": "date-time"
                              }
                            }
                          }
                        },
                        "total": {
                          "type": "integer"
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/analytics/overview": {
      "get": {
        "tags": [
          "Analytics"
        ],
        "summary": "Analytics overview",
        "description": "High-level transaction metrics for the organization including total volume, approval rate, and average ticket size.",
        "operationId": "getAnalyticsOverview",
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 30
            },
            "description": "Number of days to analyze"
          }
        ],
        "responses": {
          "200": {
            "description": "Analytics overview",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "example": "analytics_overview"
                    },
                    "period_days": {
                      "type": "integer"
                    },
                    "total_volume_cents": {
                      "type": "integer"
                    },
                    "total_transactions": {
                      "type": "integer"
                    },
                    "captured_volume_cents": {
                      "type": "integer"
                    },
                    "refunded_volume_cents": {
                      "type": "integer"
                    },
                    "net_volume_cents": {
                      "type": "integer"
                    },
                    "approval_rate_percent": {
                      "type": "number"
                    },
                    "average_ticket_cents": {
                      "type": "integer"
                    },
                    "is_sandbox": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "object": "analytics_overview",
                  "period_days": 30,
                  "total_volume_cents": 15000000,
                  "total_transactions": 342,
                  "captured_volume_cents": 14500000,
                  "refunded_volume_cents": 250000,
                  "net_volume_cents": 14250000,
                  "approval_rate_percent": 96.5,
                  "average_ticket_cents": 43860,
                  "is_sandbox": false
                }
              }
            }
          }
        }
      }
    },
    "/analytics/psp-comparison": {
      "get": {
        "tags": [
          "Analytics"
        ],
        "summary": "PSP comparison",
        "description": "Side-by-side PSP performance metrics including volume, approval rate, and net revenue per processor.",
        "operationId": "getAnalyticsPspComparison",
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 30
            }
          }
        ],
        "responses": {
          "200": {
            "description": "PSP comparison data",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "example": "psp_comparison"
                    },
                    "period_days": {
                      "type": "integer"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "psp": {
                            "type": "string"
                          },
                          "total_transactions": {
                            "type": "integer"
                          },
                          "total_volume_cents": {
                            "type": "integer"
                          },
                          "approval_rate_percent": {
                            "type": "number"
                          },
                          "captured_cents": {
                            "type": "integer"
                          },
                          "refunded_cents": {
                            "type": "integer"
                          },
                          "net_cents": {
                            "type": "integer"
                          }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "object": "psp_comparison",
                  "period_days": 30,
                  "data": [
                    {
                      "psp": "stripe",
                      "total_transactions": 200,
                      "total_volume_cents": 10000000,
                      "approval_rate_percent": 97.5,
                      "captured_cents": 9800000,
                      "refunded_cents": 100000,
                      "net_cents": 9700000
                    },
                    {
                      "psp": "nmi",
                      "total_transactions": 142,
                      "total_volume_cents": 5000000,
                      "approval_rate_percent": 95.1,
                      "captured_cents": 4700000,
                      "refunded_cents": 150000,
                      "net_cents": 4550000
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/analytics/cost-analysis": {
      "get": {
        "tags": [
          "Analytics"
        ],
        "summary": "Cost analysis",
        "description": "Fee breakdown by PSP, card brand, and card type using AI prediction metrics. Shows approval rates, average cost in basis points, and average response times.",
        "operationId": "getAnalyticsCostAnalysis",
        "responses": {
          "200": {
            "description": "Cost analysis data",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "example": "cost_analysis"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "psp": {
                            "type": "string"
                          },
                          "bin_prefix": {
                            "type": "string",
                            "nullable": true
                          },
                          "card_brand": {
                            "type": "string",
                            "nullable": true
                          },
                          "card_type": {
                            "type": "string",
                            "nullable": true
                          },
                          "total_attempts": {
                            "type": "integer"
                          },
                          "approval_rate": {
                            "type": "number"
                          },
                          "decay_weighted_rate": {
                            "type": "number"
                          },
                          "avg_cost_bps": {
                            "type": "number"
                          },
                          "avg_response_ms": {
                            "type": "number"
                          },
                          "total_volume_cents": {
                            "type": "integer"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/analytics/splits": {
      "get": {
        "tags": [
          "Analytics"
        ],
        "summary": "Splits summary",
        "description": "Split settlement summary showing total distributed amounts grouped by recipient type.",
        "operationId": "getAnalyticsSplits",
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 30
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Splits summary",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "example": "splits_summary"
                    },
                    "period_days": {
                      "type": "integer"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "recipient_type": {
                            "type": "string"
                          },
                          "transaction_count": {
                            "type": "integer"
                          },
                          "total_distributed_cents": {
                            "type": "integer"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/routing/rules": {
      "get": {
        "tags": [
          "Routing"
        ],
        "summary": "List routing rules",
        "description": "List all routing rules for the authenticated organization, ordered by priority (ascending).",
        "operationId": "listRoutingRules",
        "responses": {
          "200": {
            "description": "List of routing rules",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/List"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/RoutingRule"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Routing"
        ],
        "summary": "Create a routing rule",
        "description": "Create a new routing rule. Rules are evaluated in priority order (lowest number first). Actions include: route_to_psp (send to specific PSP), split_volume (distribute across PSPs), least_cost (AI selects cheapest PSP).",
        "operationId": "createRoutingRule",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "conditions",
                  "action"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 255
                  },
                  "priority": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1000,
                    "default": 50
                  },
                  "is_active": {
                    "type": "boolean",
                    "default": true
                  },
                  "conditions": {
                    "type": "object",
                    "properties": {
                      "card_type": {
                        "type": "string",
                        "nullable": true
                      },
                      "card_brand": {
                        "type": "string",
                        "nullable": true
                      },
                      "amount_min": {
                        "type": "integer",
                        "minimum": 0,
                        "nullable": true
                      },
                      "amount_max": {
                        "type": "integer",
                        "minimum": 0,
                        "nullable": true
                      },
                      "merchant_id": {
                        "type": "string",
                        "nullable": true
                      },
                      "bin_range": {
                        "type": "string",
                        "nullable": true
                      },
                      "currency": {
                        "type": "string",
                        "minLength": 3,
                        "maxLength": 3,
                        "nullable": true
                      }
                    }
                  },
                  "action": {
                    "type": "string",
                    "enum": [
                      "route_to_psp",
                      "split_volume",
                      "least_cost"
                    ]
                  },
                  "psp_id": {
                    "type": "string",
                    "nullable": true,
                    "description": "Required when action is route_to_psp"
                  },
                  "split_config": {
                    "type": "object",
                    "nullable": true,
                    "additionalProperties": true,
                    "description": "Required when action is split_volume"
                  }
                }
              },
              "example": {
                "name": "High-value Visa to Stripe",
                "priority": 10,
                "conditions": {
                  "card_brand": "visa",
                  "amount_min": 100000
                },
                "action": "route_to_psp",
                "psp_id": "stripe"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Routing rule created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RoutingRule"
                },
                "example": {
                  "id": 1,
                  "object": "routing_rule",
                  "name": "High-value Visa to Stripe",
                  "priority": 10,
                  "is_active": true,
                  "conditions": {
                    "card_brand": "visa",
                    "amount_min": 100000
                  },
                  "action": "route_to_psp",
                  "psp_id": "stripe",
                  "split_config": null,
                  "created_at": "2026-04-09T12:00:00+00:00",
                  "updated_at": "2026-04-09T12:00:00+00:00"
                }
              }
            }
          }
        }
      }
    },
    "/routing/rules/{id}": {
      "delete": {
        "tags": [
          "Routing"
        ],
        "summary": "Delete a routing rule",
        "description": "Delete a routing rule by its ID.",
        "operationId": "deleteRoutingRule",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Routing rule ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Routing rule deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "integer"
                    },
                    "object": {
                      "type": "string",
                      "example": "routing_rule"
                    },
                    "deleted": {
                      "type": "boolean",
                      "example": true
                    }
                  }
                },
                "example": {
                  "id": 1,
                  "object": "routing_rule",
                  "deleted": true
                }
              }
            }
          },
          "404": {
            "description": "Routing rule not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/routing/decision/{payment_id}": {
      "get": {
        "tags": [
          "Routing"
        ],
        "summary": "Get routing decision",
        "description": "Retrieve the routing decision for a specific payment, including which PSP was selected, what rule was applied, and the reasoning.",
        "operationId": "getRoutingDecision",
        "parameters": [
          {
            "name": "payment_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Payment ID (txn_xxx)"
          }
        ],
        "responses": {
          "200": {
            "description": "Routing decision details",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "example": "routing_decision"
                    },
                    "payment_id": {
                      "type": "string"
                    },
                    "psp_used": {
                      "type": "string"
                    },
                    "rule_applied": {
                      "type": "string",
                      "nullable": true
                    },
                    "decision_reason": {
                      "type": "string"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                },
                "example": {
                  "object": "routing_decision",
                  "payment_id": "txn_abc123def456ghi789jklmno",
                  "psp_used": "stripe",
                  "rule_applied": "High-value Visa to Stripe",
                  "decision_reason": "Matched rule: High-value Visa to Stripe (priority 10)",
                  "created_at": "2026-04-09T12:00:00+00:00"
                }
              }
            }
          },
          "404": {
            "description": "Transaction not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/merchant-webhook/config": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Retrieve a merchant's webhook endpoint",
        "description": "Returns the endpoint URL, whether delivery is on, and the last four characters of the signing secret. The full secret is shown only in the response that creates or rotates it.",
        "operationId": "getWebhookConfig",
        "parameters": [
          {
            "name": "X-Penumbra-Merchant-Id",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "The merchant's `id`, as the Merchants API returns it. Required unless you pass `merchant_id` instead."
          },
          {
            "name": "merchant_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "The merchant's `id`, in place of the header."
          }
        ],
        "responses": {
          "200": {
            "description": "The merchant's endpoint",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "webhook_url": {
                      "type": "string",
                      "nullable": true,
                      "description": "Where events are sent. Null until you set one."
                    },
                    "webhook_enabled": {
                      "type": "boolean",
                      "description": "Whether events are being sent."
                    },
                    "secret_last4": {
                      "type": "string",
                      "nullable": true,
                      "description": "The last four characters of the signing secret, to tell secrets apart. Null until one exists."
                    }
                  }
                },
                "example": {
                  "webhook_url": "https://example.com/penumbra/webhooks",
                  "webhook_enabled": true,
                  "secret_last4": "0001"
                }
              }
            }
          },
          "400": {
            "description": "No merchant named, or the merchant is not one of yours. Both get this same answer.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Error code"
                    }
                  }
                },
                "example": {
                  "error": "merchant_required"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Set a merchant's webhook endpoint",
        "description": "Sets the URL, turns delivery on or off, or rotates the signing secret; send only the fields you are changing. The first call for a merchant creates its signing secret and `rotate_secret` replaces it. Either way the response carries the full secret, the only time it is shown, so store it then. A new secret signs every attempt made after it, retries of earlier events included. Delivery is off until you send `webhook_enabled: true`, and events that occur while it is off are not sent later. A new URL applies to new events and to replays; attempts already scheduled finish against the URL they started with. Needs a full-access key.",
        "operationId": "updateWebhookConfig",
        "parameters": [
          {
            "name": "X-Penumbra-Merchant-Id",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "The merchant's `id`, as the Merchants API returns it. Required unless you pass `merchant_id` instead."
          },
          {
            "name": "merchant_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "The merchant's `id`, in place of the header."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "webhook_url": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 512,
                    "nullable": true,
                    "description": "Where to send events. It must be reachable from the public internet over HTTPS or HTTP on port 443, 80, 8443, 8080 or 8000; addresses that resolve to a private or reserved network are refused. Send null to remove it."
                  },
                  "webhook_enabled": {
                    "type": "boolean",
                    "description": "Turns delivery on or off."
                  },
                  "rotate_secret": {
                    "type": "boolean",
                    "description": "Replaces the signing secret. The response carries the new one."
                  }
                }
              },
              "example": {
                "webhook_url": "https://example.com/penumbra/webhooks",
                "webhook_enabled": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "webhook_url": {
                      "type": "string",
                      "nullable": true,
                      "description": "Where events are sent. Null until you set one."
                    },
                    "webhook_enabled": {
                      "type": "boolean",
                      "description": "Whether events are being sent."
                    },
                    "webhook_secret": {
                      "type": "string",
                      "nullable": true,
                      "description": "The full signing secret, only in the response that created or rotated it. Null otherwise."
                    },
                    "secret_last4": {
                      "type": "string",
                      "nullable": true,
                      "description": "The last four characters of the signing secret, to tell secrets apart. Null until one exists."
                    }
                  }
                },
                "example": {
                  "webhook_url": "https://example.com/penumbra/webhooks",
                  "webhook_enabled": true,
                  "webhook_secret": "whsec_0000000000000000000000000000000000000000000000000000000000000001",
                  "secret_last4": "0001"
                }
              }
            }
          },
          "400": {
            "description": "No merchant named, or the merchant is not one of yours. Both get this same answer.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Error code"
                    }
                  }
                },
                "example": {
                  "error": "merchant_required"
                }
              }
            }
          },
          "422": {
            "description": "The URL was refused. One that resolves to a private or reserved address, or uses another port, answers `invalid_webhook_url`; a malformed field answers with a `validation_error` ErrorResponse.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "description": "Error code"
                        },
                        "message": {
                          "type": "string",
                          "description": "Why the URL was refused"
                        }
                      }
                    },
                    {
                      "$ref": "#/components/schemas/ErrorResponse"
                    }
                  ]
                },
                "example": {
                  "error": "invalid_webhook_url",
                  "message": "webhook url port 22 not allowed; use 80, 443, 8080, 8443, or 8000"
                }
              }
            }
          }
        }
      }
    },
    "/merchant-webhook/deliveries": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "List a merchant's webhook deliveries",
        "description": "Newest first. Each delivery is one event sent to the merchant's endpoint, with its status and the result of its last attempt. Page back with `cursor`.",
        "operationId": "listWebhookDeliveries",
        "parameters": [
          {
            "name": "X-Penumbra-Merchant-Id",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "The merchant's `id`, as the Merchants API returns it. Required unless you pass `merchant_id` instead."
          },
          {
            "name": "merchant_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "The merchant's `id`, in place of the header."
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "delivered",
                "dead_letter"
              ]
            },
            "description": "Only deliveries in this state."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 50,
              "minimum": 1,
              "maximum": 200
            },
            "description": "Deliveries per page."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "The `cursor` from the previous page. Returns the deliveries older than it."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of deliveries",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookDelivery"
                      }
                    },
                    "cursor": {
                      "type": "string",
                      "nullable": true,
                      "description": "Pass as `cursor` for the next page. Null on the last page."
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "00000000-0000-4000-8000-000000000001",
                      "target_url": "https://example.com/penumbra/webhooks",
                      "status": "delivered",
                      "attempt_count": 1,
                      "max_attempts": 5,
                      "last_http_status": 200,
                      "last_error": null,
                      "next_retry_at": null,
                      "delivered_at": "2026-09-18 17:02:11",
                      "created_at": "2026-09-18 17:02:10"
                    }
                  ],
                  "cursor": null
                }
              }
            }
          },
          "400": {
            "description": "No merchant named, or the merchant is not one of yours. Both get this same answer.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Error code"
                    }
                  }
                },
                "example": {
                  "error": "merchant_required"
                }
              }
            }
          }
        }
      }
    },
    "/merchant-webhook/deliveries/{id}": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Retrieve a webhook delivery",
        "description": "The delivery, and the event as it was sent.",
        "operationId": "getWebhookDelivery",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The delivery's `id`."
          },
          {
            "name": "X-Penumbra-Merchant-Id",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "The merchant's `id`, as the Merchants API returns it. Required unless you pass `merchant_id` instead."
          },
          {
            "name": "merchant_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "The merchant's `id`, in place of the header."
          }
        ],
        "responses": {
          "200": {
            "description": "The delivery and its event",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "delivery": {
                      "$ref": "#/components/schemas/WebhookDelivery"
                    },
                    "event": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/WebhookEvent"
                        }
                      ],
                      "nullable": true,
                      "description": "The body that was sent."
                    }
                  }
                },
                "example": {
                  "delivery": {
                    "id": "00000000-0000-4000-8000-000000000001",
                    "target_url": "https://example.com/penumbra/webhooks",
                    "status": "delivered",
                    "attempt_count": 1,
                    "max_attempts": 5,
                    "last_http_status": 200,
                    "last_error": null,
                    "next_retry_at": null,
                    "delivered_at": "2026-09-18 17:02:11",
                    "created_at": "2026-09-18 17:02:10"
                  },
                  "event": {
                    "schema_version": "pen.v1",
                    "event_id": "evt_pen_000000000000000000000001",
                    "type": "payment.captured",
                    "occurred_at": "2026-09-18T17:02:10+00:00",
                    "penumbra_txn_id": "txn_000000000000000000000001",
                    "merchant_id": "mer_000000000000000000000001",
                    "psp_used": "stripe",
                    "data": {
                      "amount_cents": 4218,
                      "currency": "USD",
                      "status": "captured"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "No merchant named, or the merchant is not one of yours. Both get this same answer.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Error code"
                    }
                  }
                },
                "example": {
                  "error": "merchant_required"
                }
              }
            }
          },
          "404": {
            "description": "No delivery with that id for this merchant",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Error code"
                    }
                  }
                },
                "example": {
                  "error": "not_found"
                }
              }
            }
          }
        }
      }
    },
    "/merchant-webhook/deliveries/{id}/replay": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Replay a webhook delivery",
        "description": "Sends the delivery again, to the merchant's current URL, with a fresh five attempts. Any delivery can be replayed, including one that already succeeded, so drop duplicates by `X-Penumbra-Event-Id`, which a replay does not change. Needs a full-access key.",
        "operationId": "replayWebhookDelivery",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The delivery's `id`."
          },
          {
            "name": "X-Penumbra-Merchant-Id",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "The merchant's `id`, as the Merchants API returns it. Required unless you pass `merchant_id` instead."
          },
          {
            "name": "merchant_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "The merchant's `id`, in place of the header."
          }
        ],
        "responses": {
          "200": {
            "description": "Queued for delivery",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "replayed": {
                      "type": "boolean"
                    },
                    "delivery_id": {
                      "type": "string",
                      "description": "The delivery's `id`."
                    }
                  }
                },
                "example": {
                  "replayed": true,
                  "delivery_id": "00000000-0000-4000-8000-000000000001"
                }
              }
            }
          },
          "400": {
            "description": "No merchant named, or the merchant is not one of yours. Both get this same answer.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Error code"
                    }
                  }
                },
                "example": {
                  "error": "merchant_required"
                }
              }
            }
          },
          "404": {
            "description": "No delivery with that id for this merchant",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Error code"
                    }
                  }
                },
                "example": {
                  "error": "not_found"
                }
              }
            }
          }
        }
      }
    },
    "/api-keys": {
      "get": {
        "tags": [
          "API Keys"
        ],
        "summary": "List API keys",
        "description": "List all active API keys for the authenticated organization. Only key prefixes and last 4 characters are returned (full keys are only shown at creation time).",
        "operationId": "listApiKeys",
        "responses": {
          "200": {
            "description": "List of API keys",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/List"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "integer"
                              },
                              "name": {
                                "type": "string"
                              },
                              "key_prefix": {
                                "type": "string",
                                "example": "sk_live_"
                              },
                              "key_last4": {
                                "type": "string",
                                "example": "ab12"
                              },
                              "environment": {
                                "type": "string",
                                "enum": [
                                  "test",
                                  "live"
                                ]
                              },
                              "scope": {
                                "type": "string",
                                "enum": [
                                  "full",
                                  "read_only",
                                  "payments_only"
                                ]
                              },
                              "last_used_at": {
                                "type": "string",
                                "format": "date-time",
                                "nullable": true
                              },
                              "created_at": {
                                "type": "string",
                                "format": "date-time"
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "API Keys"
        ],
        "summary": "Create an API key",
        "description": "Generate a new API key. The full key is returned only once in the response. Store it securely, as it cannot be retrieved again. Only a full-access key can create keys.",
        "operationId": "createApiKey",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 255,
                    "description": "Human-readable name for the key"
                  },
                  "environment": {
                    "type": "string",
                    "enum": [
                      "test",
                      "live"
                    ],
                    "default": "test"
                  },
                  "scope": {
                    "type": "string",
                    "enum": [
                      "full",
                      "read_only",
                      "payments_only"
                    ],
                    "default": "full",
                    "description": "What the key may do. `full`: everything. `read_only`: GET requests only. `payments_only`: GET requests plus card payments (`/tokens`, `/payments`, `/checkout`) and sales on a registered reader (`/terminals/{id}/payments`)."
                  }
                }
              },
              "example": {
                "name": "Production Dashboard",
                "environment": "live",
                "scope": "full"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "API key created (key shown only once)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "example": "api_key"
                    },
                    "id": {
                      "type": "integer"
                    },
                    "name": {
                      "type": "string"
                    },
                    "key": {
                      "type": "string",
                      "description": "Full API key. Save this now; it will not be shown again."
                    },
                    "environment": {
                      "type": "string"
                    },
                    "scope": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "object": "api_key",
                  "id": 5,
                  "name": "Production Dashboard",
                  "key": "sk_live_abc123def456ghi789jkl012mno345pqr678",
                  "environment": "live",
                  "scope": "full",
                  "message": "Save this key now. It will not be shown again."
                }
              }
            }
          },
          "403": {
            "description": "The key making the request is not full-access",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "type": "authorization_error",
                    "code": "permission_denied",
                    "message": "This API key is read-only. Use a full-access key to make changes.",
                    "doc_url": "https://penumbrahq.com/docs/errors#permission_denied"
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key authentication. Include your key in the Authorization header as: Bearer sk_live_... (production) or Bearer sk_test_... (sandbox). Keys expire after 180 days. Keys older than 90 days receive a rotation warning header. Each key has a scope. `full` can do everything, including creating and revoking API keys. `read_only` can make GET requests only. `payments_only` can make GET requests and take card payments: create tokens and payments, capture, void and refund them, open checkout sessions, and start, check and cancel sales on a registered card reader. A request outside the key's scope gets `403` with type `authorization_error` and code `permission_denied`."
      }
    },
    "schemas": {
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "type",
              "message"
            ],
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "authentication_error",
                  "authorization_error",
                  "invalid_request_error",
                  "validation_error",
                  "api_error",
                  "compliance_error",
                  "rate_limit_error"
                ],
                "description": "Error type category"
              },
              "code": {
                "type": "string",
                "description": "The specific condition, when the error names one. Each has an entry on the error-code page."
              },
              "message": {
                "type": "string",
                "description": "Human-readable error message"
              },
              "param": {
                "type": "string",
                "nullable": true,
                "description": "The parameter that caused the error, if applicable"
              },
              "errors": {
                "type": "object",
                "nullable": true,
                "additionalProperties": true,
                "description": "Field-level validation errors, if applicable"
              },
              "doc_url": {
                "type": "string",
                "format": "uri",
                "description": "A link to that condition on the error-code page, when a code is present."
              }
            }
          }
        }
      },
      "List": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "example": "list"
          },
          "data": {
            "type": "array",
            "items": {}
          },
          "has_more": {
            "type": "boolean",
            "description": "Whether there are more results available beyond this page"
          }
        }
      },
      "Payment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique payment ID with txn_ prefix",
            "example": "txn_abc123def456ghi789jklmno"
          },
          "object": {
            "type": "string",
            "example": "payment"
          },
          "amount": {
            "type": "integer",
            "description": "Payment amount in cents"
          },
          "currency": {
            "type": "string",
            "example": "USD"
          },
          "status": {
            "type": "string",
            "enum": [
              "authorized",
              "captured",
              "declined",
              "voided",
              "refunded",
              "partially_refunded",
              "failed"
            ]
          },
          "type": {
            "type": "string",
            "example": "charge"
          },
          "merchant_id": {
            "type": "string",
            "nullable": true
          },
          "token_id": {
            "type": "string",
            "nullable": true
          },
          "psp_used": {
            "type": "string",
            "nullable": true,
            "description": "The PSP that processed this payment"
          },
          "authorization_code": {
            "type": "string",
            "nullable": true
          },
          "decline_reason": {
            "type": "string",
            "nullable": true
          },
          "captured_amount": {
            "type": "integer",
            "nullable": true,
            "description": "Amount captured in cents"
          },
          "refunded_amount": {
            "type": "integer",
            "description": "Total amount refunded in cents"
          },
          "routing_decision": {
            "type": "string",
            "nullable": true,
            "description": "Explanation of the routing decision"
          },
          "metadata": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true
          },
          "is_sandbox": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Token": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique token ID with tok_ prefix",
            "example": "tok_abc123def456ghi789jkl012"
          },
          "object": {
            "type": "string",
            "example": "token"
          },
          "type": {
            "type": "string",
            "enum": [
              "card",
              "bank_account",
              "plaid"
            ]
          },
          "last4": {
            "type": "string",
            "description": "Last 4 digits of the card or account number"
          },
          "brand": {
            "type": "string",
            "nullable": true,
            "enum": [
              "visa",
              "mastercard",
              "amex",
              "discover",
              "unknown",
              null
            ]
          },
          "exp_month": {
            "type": "integer",
            "nullable": true,
            "minimum": 1,
            "maximum": 12
          },
          "exp_year": {
            "type": "integer",
            "nullable": true
          },
          "is_active": {
            "type": "boolean"
          },
          "is_sandbox": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Terminal": {
        "type": "object",
        "description": "A card reader registered to your account.",
        "properties": {
          "id": {
            "type": "string",
            "description": "The terminal's ID (`term_...`)."
          },
          "object": {
            "type": "string",
            "enum": [
              "terminal"
            ]
          },
          "manufacturer": {
            "type": "string",
            "enum": [
              "stripe"
            ],
            "description": "`stripe`: a Stripe Terminal reader."
          },
          "model": {
            "type": "string",
            "nullable": true,
            "description": "The reader's model as Stripe reports it, such as `bbpos_wisepos_e`."
          },
          "serial_number": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "active"
            ]
          },
          "location_name": {
            "type": "string",
            "nullable": true
          },
          "capabilities": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "How the reader takes cards: `emv` (chip), `nfc` (tap) and `magstripe` (swipe)."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "online": {
            "type": "boolean",
            "nullable": true,
            "description": "Retrieve only. Whether the reader is online, as Stripe reports it. Null when Stripe cannot say."
          },
          "device": {
            "type": "string",
            "description": "Retrieve only. `stripe_terminal`."
          }
        }
      },
      "TerminalPayment": {
        "type": "object",
        "description": "A sale on a reader. It is also a payment: its `transaction_id` works with the Payments API, for example to refund it.",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "terminal_payment"
            ]
          },
          "terminal_id": {
            "type": "string"
          },
          "transaction_id": {
            "type": "string",
            "description": "The payment's ID (`txn_...`)."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "authorized",
              "captured",
              "declined",
              "voided",
              "failed",
              "refunded",
              "partially_refunded"
            ],
            "description": "`pending` while the reader waits for the card. `captured` once the card is approved and captured. `authorized` if the card was approved but the capture has not gone through yet; checking the sale again retries it. `declined` when the card is declined, `voided` when the sale is cancelled, and `failed` when the reader never took the sale. `refunded` or `partially_refunded` after a refund through the Payments API."
          },
          "approved": {
            "type": "boolean",
            "description": "Whether the card was approved: true for `authorized`, `captured`, `refunded` and `partially_refunded`."
          },
          "amount": {
            "type": "integer",
            "description": "Amount in cents."
          },
          "currency": {
            "type": "string"
          },
          "auth_code": {
            "type": "string",
            "nullable": true,
            "description": "The issuer's authorization code."
          },
          "decline_reason": {
            "type": "string",
            "nullable": true,
            "description": "Why the sale did not complete: the card's decline code (such as `generic_decline` or `insufficient_funds`), or why it ended."
          },
          "psp_used": {
            "type": "string",
            "enum": [
              "stripe"
            ]
          },
          "entry_mode": {
            "type": "string",
            "nullable": true,
            "enum": [
              "chip",
              "contactless",
              "swipe",
              "swipe_fallback"
            ],
            "description": "How the card was read."
          },
          "is_debit": {
            "type": "boolean",
            "nullable": true
          },
          "card_brand": {
            "type": "string",
            "nullable": true
          },
          "card_last4": {
            "type": "string",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Merchant": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique merchant ID with mer_ prefix",
            "example": "mer_abc123def456ghi789jkl012"
          },
          "object": {
            "type": "string",
            "example": "merchant"
          },
          "business_name": {
            "type": "string"
          },
          "dba": {
            "type": "string",
            "nullable": true
          },
          "business_type": {
            "type": "string",
            "enum": [
              "sole_prop",
              "llc",
              "corp",
              "partnership",
              "nonprofit"
            ]
          },
          "address": {
            "type": "object",
            "properties": {
              "line1": {
                "type": "string"
              },
              "line2": {
                "type": "string",
                "nullable": true
              },
              "city": {
                "type": "string"
              },
              "state": {
                "type": "string"
              },
              "zip": {
                "type": "string"
              },
              "country": {
                "type": "string"
              }
            }
          },
          "contact": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "email": {
                "type": "string",
                "format": "email"
              },
              "phone": {
                "type": "string"
              }
            }
          },
          "website": {
            "type": "string",
            "nullable": true
          },
          "mcc": {
            "type": "string",
            "nullable": true,
            "description": "Merchant Category Code"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending_review",
              "approved",
              "rejected",
              "suspended",
              "deactivated"
            ]
          },
          "risk_tier": {
            "type": "string",
            "enum": [
              "low",
              "medium",
              "high",
              "prohibited"
            ]
          },
          "kyc_completed": {
            "type": "boolean"
          },
          "ofac_screened": {
            "type": "boolean"
          },
          "is_sandbox": {
            "type": "boolean"
          },
          "metadata": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Customer": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "object": {
            "type": "string",
            "example": "customer"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "phone": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "inactive",
              "flagged"
            ]
          },
          "merchant_id": {
            "type": "string",
            "nullable": true
          },
          "total_transactions": {
            "type": "integer"
          },
          "total_spent_cents": {
            "type": "integer",
            "description": "Total amount spent in cents"
          },
          "total_refunded_cents": {
            "type": "integer",
            "description": "Total amount refunded in cents"
          },
          "average_ticket_cents": {
            "type": "integer",
            "description": "Average transaction amount in cents"
          },
          "lifetime_value_cents": {
            "type": "integer",
            "description": "Lifetime value in cents (spent minus refunded)"
          },
          "lifetime_value": {
            "type": "string",
            "description": "Formatted lifetime value (e.g., $1,150.00)"
          },
          "is_high_value": {
            "type": "boolean",
            "description": "True if lifetime value exceeds high-value threshold"
          },
          "default_token_id": {
            "type": "string",
            "nullable": true
          },
          "last_transaction_id": {
            "type": "string",
            "nullable": true
          },
          "metadata": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true
          },
          "first_seen_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "last_seen_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CryptoTransfer": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "example": "crypto_transfer"
          },
          "transfer_id": {
            "type": "integer"
          },
          "circle_payout_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "confirmed"
            ]
          },
          "chain": {
            "type": "string",
            "enum": [
              "polygon",
              "ethereum",
              "solana",
              "base",
              "avalanche"
            ]
          },
          "amount_cents": {
            "type": "integer",
            "description": "Amount in cents"
          },
          "currency": {
            "type": "string",
            "example": "USDC"
          },
          "to_address": {
            "type": "string",
            "description": "Destination wallet address"
          },
          "blockchain_tx_hash": {
            "type": "string",
            "nullable": true
          },
          "block_explorer_url": {
            "type": "string",
            "nullable": true
          },
          "memo": {
            "type": "string",
            "nullable": true
          },
          "confirmed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "BankTransfer": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "example": "bank_transfer"
          },
          "id": {
            "type": "string",
            "description": "UUID"
          },
          "from_account_id": {
            "type": "string"
          },
          "to_identifier": {
            "type": "string"
          },
          "amount_cents": {
            "type": "integer",
            "description": "Transfer amount in cents"
          },
          "speed": {
            "type": "string",
            "enum": [
              "ach_standard",
              "ach_same_day",
              "fednow",
              "rtp"
            ]
          },
          "fee_cents": {
            "type": "integer",
            "description": "Transfer fee in cents"
          },
          "memo": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "completed"
            ],
            "description": "`pending` until the rail carrying the transfer confirms settlement, then `completed`."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ForexRate": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "example": "forex_rate"
          },
          "from": {
            "type": "string",
            "description": "Source currency code"
          },
          "to": {
            "type": "string",
            "description": "Target currency code"
          },
          "rate": {
            "type": "number",
            "description": "Exchange rate (6 decimal places)"
          },
          "fee_pct": {
            "type": "number",
            "description": "Conversion fee percentage",
            "example": 0.5
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ForexConversion": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "example": "forex_conversion"
          },
          "id": {
            "type": "string",
            "description": "UUID"
          },
          "from_currency": {
            "type": "string"
          },
          "to_currency": {
            "type": "string"
          },
          "send_amount_cents": {
            "type": "integer",
            "description": "Amount sent in from_currency, in cents"
          },
          "receive_amount_cents": {
            "type": "integer",
            "description": "Amount received in to_currency, in cents"
          },
          "rate": {
            "type": "number",
            "description": "Exchange rate used"
          },
          "fee_pct": {
            "type": "number",
            "description": "Fee percentage applied",
            "example": 0.5
          },
          "fee_amount_cents": {
            "type": "integer",
            "description": "Fee amount in from_currency cents"
          },
          "recipient": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "example": "completed"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SplitConfig": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Split config ID with spl_ prefix",
            "example": "spl_abc123def456ghi789jkl012"
          },
          "object": {
            "type": "string",
            "example": "split_config"
          },
          "merchant_id": {
            "type": "string"
          },
          "version": {
            "type": "integer",
            "description": "Auto-incremented version number"
          },
          "is_active": {
            "type": "boolean"
          },
          "effective_date": {
            "type": "string",
            "format": "date"
          },
          "recipients": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "recipient_type": {
                  "type": "string",
                  "enum": [
                    "platform",
                    "merchant",
                    "agent",
                    "mca_lender",
                    "reserve",
                    "tax",
                    "custom"
                  ]
                },
                "recipient_name": {
                  "type": "string"
                },
                "distribution_type": {
                  "type": "string",
                  "enum": [
                    "percentage",
                    "fixed",
                    "tiered",
                    "conditional"
                  ]
                },
                "distribution_value": {
                  "type": "object",
                  "additionalProperties": true
                },
                "priority": {
                  "type": "integer"
                },
                "is_perpetual": {
                  "type": "boolean"
                },
                "change_of_control_survives": {
                  "type": "boolean"
                }
              }
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "RoutingRule": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "object": {
            "type": "string",
            "example": "routing_rule"
          },
          "name": {
            "type": "string"
          },
          "priority": {
            "type": "integer",
            "description": "Lower number = higher priority"
          },
          "is_active": {
            "type": "boolean"
          },
          "conditions": {
            "type": "object",
            "properties": {
              "card_type": {
                "type": "string",
                "nullable": true
              },
              "card_brand": {
                "type": "string",
                "nullable": true
              },
              "amount_min": {
                "type": "integer",
                "nullable": true
              },
              "amount_max": {
                "type": "integer",
                "nullable": true
              },
              "merchant_id": {
                "type": "string",
                "nullable": true
              },
              "bin_range": {
                "type": "string",
                "nullable": true
              },
              "currency": {
                "type": "string",
                "nullable": true
              }
            }
          },
          "action": {
            "type": "string",
            "enum": [
              "route_to_psp",
              "split_volume",
              "least_cost"
            ]
          },
          "psp_id": {
            "type": "string",
            "nullable": true
          },
          "split_config": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WebhookEvent": {
        "type": "object",
        "description": "The Pen.v1 envelope every webhook delivery carries.",
        "required": [
          "schema_version",
          "event_id",
          "type",
          "occurred_at",
          "data"
        ],
        "properties": {
          "schema_version": {
            "type": "string",
            "enum": [
              "pen.v1"
            ]
          },
          "event_id": {
            "type": "string",
            "description": "Unique per event, prefixed evt_pen_. The same on every retry.",
            "example": "evt_pen_000000000000000000000001"
          },
          "type": {
            "type": "string",
            "description": "The canonical event type.",
            "example": "payment.captured"
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time"
          },
          "penumbra_txn_id": {
            "type": "string",
            "nullable": true,
            "description": "The Penumbra transaction the event concerns, when there is one."
          },
          "merchant_id": {
            "type": "string",
            "nullable": true,
            "description": "The merchant the event concerns."
          },
          "psp_used": {
            "type": "string",
            "nullable": true,
            "description": "The processor that handled it."
          },
          "psp_event_id": {
            "type": "string",
            "nullable": true,
            "description": "The processor's own event ID."
          },
          "psp_event_type": {
            "type": "string",
            "nullable": true,
            "description": "The processor's own event type."
          },
          "data": {
            "type": "object",
            "additionalProperties": true,
            "description": "The fields for this event type."
          },
          "raw_psp_payload": {
            "type": "object",
            "additionalProperties": true,
            "description": "The processor's original event."
          }
        }
      },
      "WebhookDelivery": {
        "type": "object",
        "description": "One event sent to one merchant's endpoint, with the result of its most recent attempt. Timestamps are UTC, formatted `2026-09-18 17:02:11`.",
        "properties": {
          "id": {
            "type": "string",
            "description": "The delivery's ID. Use it to fetch or replay the delivery."
          },
          "target_url": {
            "type": "string",
            "description": "The URL it is sent to."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "delivered",
              "dead_letter"
            ],
            "description": "`pending` is waiting for its first attempt or a retry. `dead_letter` failed all five attempts, or its URL was refused at delivery."
          },
          "attempt_count": {
            "type": "integer",
            "description": "Attempts made so far."
          },
          "max_attempts": {
            "type": "integer",
            "description": "Attempts allowed before it is dead-lettered: 5."
          },
          "last_http_status": {
            "type": "integer",
            "nullable": true,
            "description": "The status code your endpoint returned on the last attempt. Null if it never answered."
          },
          "last_error": {
            "type": "string",
            "nullable": true,
            "description": "The most recent failure to reach your endpoint, such as a timeout or a refused address. A status code your endpoint returned shows in `last_http_status` instead."
          },
          "next_retry_at": {
            "type": "string",
            "nullable": true,
            "description": "When the next attempt is due."
          },
          "delivered_at": {
            "type": "string",
            "nullable": true,
            "description": "When your endpoint accepted it."
          },
          "created_at": {
            "type": "string",
            "description": "When it was queued."
          }
        }
      }
    }
  }
}
