{
  "openapi": "3.1.0",
  "info": {
    "title": "EasyCryptoPay API",
    "version": "1.1.0",
    "summary": "Accept crypto payments with USD-denominated links and signed webhooks.",
    "description": "Bearer-authenticated JSON REST API. POST a USD amount, redirect the customer to the hosted checkout, and receive an HMAC-signed webhook when the payment lands. Test mode (`ecp_test_` keys) drives the full lifecycle without real crypto. Full guide: https://docs.easycryptopay.xyz.",
    "contact": {
      "name": "EasyCryptoPay",
      "url": "https://docs.easycryptopay.xyz"
    }
  },
  "servers": [
    {
      "url": "https://easycryptopay.xyz",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "Developer documentation",
    "url": "https://docs.easycryptopay.xyz"
  },
  "tags": [
    {
      "name": "Deposits",
      "description": "Create payments and poll their status."
    },
    {
      "name": "Invoices",
      "description": "Shareable payment requests."
    },
    {
      "name": "Payments",
      "description": "Read received payments."
    },
    {
      "name": "Customers",
      "description": "People who have paid you."
    },
    {
      "name": "Account",
      "description": "Balance and supported currencies."
    },
    {
      "name": "Payouts",
      "description": "Send crypto out. Requires payouts enabled + a key with the `payouts` scope."
    },
    {
      "name": "Checkout (public)",
      "description": "Unauthenticated endpoints powering the hosted /pay page."
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/api/crypto/deposits": {
      "post": {
        "tags": [
          "Deposits"
        ],
        "summary": "Create a payment",
        "description": "POST a USD amount; returns a hosted `paymentLink`. The customer picks the coin + network on that page. Accepts an optional `Idempotency-Key` header (same key + body within 24h returns the cached response).",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "amount"
                ],
                "properties": {
                  "amount": {
                    "type": "number",
                    "minimum": 0,
                    "exclusiveMinimum": true,
                    "description": "Amount in USD."
                  },
                  "metadata": {
                    "$ref": "#/components/schemas/Metadata"
                  }
                }
              },
              "example": {
                "amount": 99.99,
                "metadata": {
                  "order_id": "order_12345"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Payment created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deposit"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/api/crypto/deposits/{idOrToken}/check": {
      "get": {
        "tags": [
          "Deposits"
        ],
        "summary": "Get deposit status",
        "description": "Poll a deposit by its id or payment token. Prefer webhooks at scale.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdOrToken"
          }
        ],
        "responses": {
          "200": {
            "description": "Current status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DepositStatus"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/crypto/deposits/{idOrToken}/test-complete": {
      "post": {
        "tags": [
          "Deposits"
        ],
        "summary": "Complete a test deposit",
        "description": "Sandbox only — drives a deposit created with a `ecp_test_` key to `completed` and fires the full webhook sequence. Rejects live-mode deposits with 400.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdOrToken"
          }
        ],
        "responses": {
          "200": {
            "description": "Completed.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DepositStatus"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "isTest": {
                          "type": "boolean"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/invoices": {
      "get": {
        "tags": [
          "Invoices"
        ],
        "summary": "List invoices",
        "parameters": [
          {
            "$ref": "#/components/parameters/StatusFilter"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          }
        ],
        "responses": {
          "200": {
            "description": "Page of invoices.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "invoices": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/InvoiceRow"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "stats": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "tags": [
          "Invoices"
        ],
        "summary": "Create an invoice",
        "description": "Both camelCase and snake_case request keys are accepted (snake wins when both present).",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "title",
                  "amount_usd",
                  "description"
                ],
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "amount_usd": {
                    "type": "number",
                    "description": "Invoice total in USD (alias: amountUsd)."
                  },
                  "description": {
                    "type": "string"
                  },
                  "recipient_email": {
                    "type": "string",
                    "format": "email",
                    "description": "Required if send=true (alias: recipientEmail)."
                  },
                  "send": {
                    "type": "boolean",
                    "description": "Email the customer a checkout link immediately."
                  },
                  "expires_in_days": {
                    "type": "integer",
                    "description": "1–365, or 0 for no expiry. Default 7 (alias: expiresInDays)."
                  },
                  "expires_at": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Specific expiry; takes precedence (alias: expiresAt)."
                  },
                  "metadata": {
                    "$ref": "#/components/schemas/Metadata"
                  }
                }
              },
              "example": {
                "title": "Website Development",
                "amount_usd": 2500,
                "description": "Phase 1",
                "recipient_email": "client@example.com",
                "send": true,
                "metadata": {
                  "project_id": "proj_42"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Invoice created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceCreateResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/api/invoices/{idOrToken}": {
      "get": {
        "tags": [
          "Invoices"
        ],
        "summary": "Get an invoice",
        "description": "By invoice id or payment token. Test keys resolve only test invoices.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdOrToken"
          }
        ],
        "responses": {
          "200": {
            "description": "Invoice.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceRow"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/payments": {
      "get": {
        "tags": [
          "Payments"
        ],
        "summary": "List payments",
        "parameters": [
          {
            "$ref": "#/components/parameters/StatusFilter"
          },
          {
            "name": "currency",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by coin ticker, e.g. USDT."
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          }
        ],
        "responses": {
          "200": {
            "description": "Page of payments with aggregate stats.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "payments": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Payment"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "stats": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/payments/{idOrToken}": {
      "get": {
        "tags": [
          "Payments"
        ],
        "summary": "Get a payment",
        "description": "Full deposit record (incl. the autoConvert sub-object) by id or payment token.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdOrToken"
          }
        ],
        "responses": {
          "200": {
            "description": "Payment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payment"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/customers": {
      "get": {
        "tags": [
          "Customers"
        ],
        "summary": "List customers",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "inactive"
              ]
            },
            "description": "active = paid within 30 days."
          },
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Case-insensitive email/name match."
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          }
        ],
        "responses": {
          "200": {
            "description": "Page of customers.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "customers": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Customer"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "stats": {
                      "type": "object",
                      "properties": {
                        "total": {
                          "type": "integer"
                        },
                        "active": {
                          "type": "integer"
                        },
                        "totalRevenue": {
                          "type": "number"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/balance": {
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "Get balance",
        "description": "Settled balance plus lifetime volume/fees. Excludes test-mode deposits.",
        "responses": {
          "200": {
            "description": "Balance snapshot.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Balance"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/crypto/currencies": {
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "List currencies",
        "description": "Coins + networks your connected exchange supports. `exchangeConnected:false` (empty list) until you connect one — check this before creating payments.",
        "responses": {
          "200": {
            "description": "Supported currencies.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "currencies": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    },
                    "exchangeConnected": {
                      "type": "boolean"
                    },
                    "exchangeKey": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "tradingApiAvailable": {
                      "type": "boolean"
                    },
                    "useAutoRanking": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/crypto/withdrawals": {
      "get": {
        "tags": [
          "Payouts"
        ],
        "summary": "List withdrawals",
        "description": "Payouts for the active business. Reachable by any key on the business (no scope required for reads).",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "approved",
                "processing",
                "completed",
                "failed",
                "cancelled"
              ]
            },
            "description": "Filter by status."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 50,
              "minimum": 1,
              "maximum": 100
            },
            "description": "Page size (max 100)."
          },
          {
            "$ref": "#/components/parameters/Offset"
          }
        ],
        "responses": {
          "200": {
            "description": "Page of withdrawals.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "withdrawals": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Withdrawal"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/PayoutsForbidden"
          }
        }
      },
      "post": {
        "tags": [
          "Payouts"
        ],
        "summary": "Create a withdrawal",
        "description": "Records a PENDING payout **intent** — the on-chain send happens in a background worker, never in this request. **Requires the `payouts` key scope** (deposit-only keys are rejected). Amount is in USD and priced to crypto at market. Test keys (`ecp_test_`) create rows the sender skips.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "amount",
                  "currency",
                  "network",
                  "destinationAddress"
                ],
                "properties": {
                  "amount": {
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "description": "Amount in USD (aliases: amountUsd, amount_usd)."
                  },
                  "currency": {
                    "type": "string",
                    "example": "USDT"
                  },
                  "network": {
                    "type": "string",
                    "example": "TRC20"
                  },
                  "destinationAddress": {
                    "type": "string"
                  },
                  "destinationTag": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Memo/tag for chains that need one."
                  },
                  "metadata": {
                    "$ref": "#/components/schemas/Metadata"
                  }
                }
              },
              "example": {
                "amount": 250,
                "currency": "USDT",
                "network": "TRC20",
                "destinationAddress": "T...",
                "metadata": {
                  "payout_id": "po_1"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Withdrawal intent created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Withdrawal"
                }
              }
            }
          },
          "400": {
            "description": "Validation / INVALID_ADDRESS.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message."
                    },
                    "code": {
                      "type": "string",
                      "description": "Stable machine code for cases the client must branch on (e.g. IDEMPOTENCY_CONFLICT, IDEMPOTENCY_IN_PROGRESS, PRO_REQUIRED, TRADING_PERMISSION_REQUIRED, STEPUP_REQUIRED)."
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/PayoutsForbidden"
          },
          "409": {
            "description": "PAYOUT_LIMIT_EXCEEDED (rolling 24h ceiling).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message."
                    },
                    "code": {
                      "type": "string",
                      "description": "Stable machine code for cases the client must branch on (e.g. IDEMPOTENCY_CONFLICT, IDEMPOTENCY_IN_PROGRESS, PRO_REQUIRED, TRADING_PERMISSION_REQUIRED, STEPUP_REQUIRED)."
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "503": {
            "description": "Upstream exchange unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message."
                    },
                    "code": {
                      "type": "string",
                      "description": "Stable machine code for cases the client must branch on (e.g. IDEMPOTENCY_CONFLICT, IDEMPOTENCY_IN_PROGRESS, PRO_REQUIRED, TRADING_PERMISSION_REQUIRED, STEPUP_REQUIRED)."
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/crypto/withdrawals/{id}/approve": {
      "post": {
        "tags": [
          "Payouts"
        ],
        "summary": "Approve a withdrawal",
        "description": "Atomic PENDING→APPROVED. **Requires the `payouts` key scope.** Separation-of-duties: a non-owner can't approve their own request.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Approved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Withdrawal"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/PayoutsForbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/crypto/withdrawals/{id}/check": {
      "get": {
        "tags": [
          "Payouts"
        ],
        "summary": "Get withdrawal status",
        "description": "Current status (PENDING→APPROVED→PROCESSING→COMPLETED/FAILED/CANCELLED). No scope required for reads.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Withdrawal.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Withdrawal"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/pay/{id}": {
      "get": {
        "tags": [
          "Checkout (public)"
        ],
        "summary": "Get payment details (public)",
        "description": "Unauthenticated. Powers the hosted /pay page. CORS-enabled.",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Payment token."
          }
        ],
        "responses": {
          "200": {
            "description": "Payment details.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/pay/{id}/currencies": {
      "get": {
        "tags": [
          "Checkout (public)"
        ],
        "summary": "List currencies for a payment (public)",
        "description": "Unauthenticated. CORS-enabled. Returns the coins/networks the merchant can accept for this payment.",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Payment token."
          }
        ],
        "responses": {
          "200": {
            "description": "Currencies.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "currencies": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/pay/{id}/create-deposit": {
      "post": {
        "tags": [
          "Checkout (public)"
        ],
        "summary": "Mint a deposit for a payment (public)",
        "description": "Unauthenticated (the payment token is the authorization). CORS-enabled, so it can be called from the customer's browser. Creates the on-chain deposit once the customer picks a coin + network.",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Payment token."
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "currency",
                  "networkCode"
                ],
                "properties": {
                  "currency": {
                    "type": "string",
                    "example": "USDT"
                  },
                  "networkCode": {
                    "type": "string",
                    "example": "TRC20"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Deposit minted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "503": {
            "description": "No exchange connected / temporarily unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message."
                    },
                    "code": {
                      "type": "string",
                      "description": "Stable machine code for cases the client must branch on (e.g. IDEMPOTENCY_CONFLICT, IDEMPOTENCY_IN_PROGRESS, PRO_REQUIRED, TRADING_PERMISSION_REQUIRED, STEPUP_REQUIRED)."
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "deposit.completed": {
      "post": {
        "summary": "Deposit fully confirmed and credited",
        "description": "Signed POST to your configured webhook URL. Verify the `X-ECP-Signature: t=<unix>,v1=<hex>` header (HMAC-SHA256 of `${t}.${rawBody}`) and the `X-ECP-Event` header. Other events share this envelope: deposit.{pending,confirming,expired,failed}, deposit.auto_converted, invoice.{paid,expired}, withdrawal.{pending,approved,processing,completed,failed}, and webhook.test.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge with any 2xx."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "`Authorization: Bearer ecp_live_…` (production) or `ecp_test_…` (sandbox)."
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string"
        },
        "description": "Same key + body within 24h returns the cached response; different body → 409."
      },
      "IdOrToken": {
        "name": "idOrToken",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        },
        "description": "Resource id or payment token."
      },
      "StatusFilter": {
        "name": "status",
        "in": "query",
        "required": false,
        "schema": {
          "type": "string",
          "enum": [
            "pending",
            "awaiting_confirmation",
            "completed",
            "expired",
            "failed"
          ]
        },
        "description": "Filter by status."
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "schema": {
          "type": "integer",
          "default": 50,
          "minimum": 1,
          "maximum": 200
        },
        "description": "Page size (max 200)."
      },
      "Offset": {
        "name": "offset",
        "in": "query",
        "required": false,
        "schema": {
          "type": "integer",
          "default": 0,
          "minimum": 0
        },
        "description": "Zero-based offset."
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Validation failed.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "error": {
                  "type": "string",
                  "description": "Human-readable error message."
                },
                "code": {
                  "type": "string",
                  "description": "Stable machine code for cases the client must branch on (e.g. IDEMPOTENCY_CONFLICT, IDEMPOTENCY_IN_PROGRESS, PRO_REQUIRED, TRADING_PERMISSION_REQUIRED, STEPUP_REQUIRED)."
                }
              },
              "required": [
                "error"
              ]
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing/invalid API key.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "error": {
                  "type": "string",
                  "description": "Human-readable error message."
                },
                "code": {
                  "type": "string",
                  "description": "Stable machine code for cases the client must branch on (e.g. IDEMPOTENCY_CONFLICT, IDEMPOTENCY_IN_PROGRESS, PRO_REQUIRED, TRADING_PERMISSION_REQUIRED, STEPUP_REQUIRED)."
                }
              },
              "required": [
                "error"
              ]
            }
          }
        }
      },
      "NotFound": {
        "description": "Not found, or belongs to another business / mode.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "error": {
                  "type": "string",
                  "description": "Human-readable error message."
                },
                "code": {
                  "type": "string",
                  "description": "Stable machine code for cases the client must branch on (e.g. IDEMPOTENCY_CONFLICT, IDEMPOTENCY_IN_PROGRESS, PRO_REQUIRED, TRADING_PERMISSION_REQUIRED, STEPUP_REQUIRED)."
                }
              },
              "required": [
                "error"
              ]
            }
          }
        }
      },
      "Conflict": {
        "description": "Idempotency-Key conflict or in-progress.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "error": {
                  "type": "string",
                  "description": "Human-readable error message."
                },
                "code": {
                  "type": "string",
                  "description": "Stable machine code for cases the client must branch on (e.g. IDEMPOTENCY_CONFLICT, IDEMPOTENCY_IN_PROGRESS, PRO_REQUIRED, TRADING_PERMISSION_REQUIRED, STEPUP_REQUIRED)."
                }
              },
              "required": [
                "error"
              ]
            }
          }
        }
      },
      "PayoutsForbidden": {
        "description": "Payouts disabled for this business (PAYOUTS_DISABLED) or the key lacks the `payouts` scope.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "error": {
                  "type": "string",
                  "description": "Human-readable error message."
                },
                "code": {
                  "type": "string",
                  "description": "Stable machine code for cases the client must branch on (e.g. IDEMPOTENCY_CONFLICT, IDEMPOTENCY_IN_PROGRESS, PRO_REQUIRED, TRADING_PERMISSION_REQUIRED, STEPUP_REQUIRED)."
                }
              },
              "required": [
                "error"
              ]
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable error message."
          },
          "code": {
            "type": "string",
            "description": "Stable machine code for cases the client must branch on (e.g. IDEMPOTENCY_CONFLICT, IDEMPOTENCY_IN_PROGRESS, PRO_REQUIRED, TRADING_PERMISSION_REQUIRED, STEPUP_REQUIRED)."
          }
        },
        "required": [
          "error"
        ]
      },
      "Metadata": {
        "type": "object",
        "description": "Free-form JSON, max 50 keys, 500 chars/value. Echoed on every webhook.",
        "additionalProperties": {
          "type": [
            "string",
            "number",
            "boolean",
            "null"
          ]
        }
      },
      "Deposit": {
        "type": "object",
        "description": "Response from POST /api/crypto/deposits (a payment intent).",
        "properties": {
          "invoiceId": {
            "type": "string"
          },
          "paymentToken": {
            "type": "string",
            "example": "inv_xK9mP2rT"
          },
          "expectedAmountUsd": {
            "type": "number"
          },
          "expectedAmountUSD": {
            "type": "number",
            "deprecated": true,
            "description": "Legacy all-caps alias of expectedAmountUsd."
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "awaiting_confirmation",
              "completed",
              "expired",
              "failed"
            ]
          },
          "feePercent": {
            "type": "number"
          },
          "feeUsd": {
            "type": "number"
          },
          "isTest": {
            "type": "boolean"
          },
          "metadata": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Metadata"
              },
              {
                "type": "null"
              }
            ]
          },
          "paymentLink": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "DepositStatus": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "awaiting_confirmation",
              "completed",
              "expired",
              "failed"
            ]
          },
          "confirmations": {
            "type": "integer"
          },
          "txId": {
            "type": [
              "string",
              "null"
            ]
          },
          "amountUsd": {
            "type": "number"
          },
          "totalCreditedUsd": {
            "type": "number"
          },
          "amountUSD": {
            "type": "number",
            "deprecated": true,
            "description": "Legacy all-caps alias of amountUsd."
          },
          "totalCreditedUSD": {
            "type": "number",
            "deprecated": true,
            "description": "Legacy all-caps alias of totalCreditedUsd."
          }
        }
      },
      "InvoiceCreateResponse": {
        "type": "object",
        "properties": {
          "invoice": {
            "$ref": "#/components/schemas/InvoiceRow"
          },
          "emailSent": {
            "type": "boolean"
          },
          "emailError": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "InvoiceRow": {
        "type": "object",
        "description": "Dual-emitted (camelCase canonical + snake_case legacy). Camel shown here.",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "amountUsd": {
            "type": "number"
          },
          "currency": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "sent",
              "paid",
              "expired"
            ]
          },
          "paymentToken": {
            "type": "string"
          },
          "paymentLink": {
            "type": "string",
            "format": "uri"
          },
          "recipientEmail": {
            "type": [
              "string",
              "null"
            ]
          },
          "feeUsd": {
            "type": "number"
          },
          "feePercent": {
            "type": "number"
          },
          "isTest": {
            "type": "boolean"
          },
          "metadata": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Metadata"
              },
              {
                "type": "null"
              }
            ]
          },
          "expiresAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "paidAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Payment": {
        "type": "object",
        "description": "A received deposit (dual-emitted; camelCase shown).",
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "awaiting_confirmation",
              "completed",
              "expired",
              "failed"
            ]
          },
          "currency": {
            "type": "string"
          },
          "network": {
            "type": "string"
          },
          "networkDisplayName": {
            "type": "string"
          },
          "amount": {
            "type": "number",
            "description": "Crypto received (0 until confirmed)."
          },
          "amountUsd": {
            "type": "number"
          },
          "expectedAmountUsd": {
            "type": "number"
          },
          "expectedCryptoAmount": {
            "type": "number"
          },
          "depositAddress": {
            "type": "string"
          },
          "depositTag": {
            "type": [
              "string",
              "null"
            ]
          },
          "txId": {
            "type": [
              "string",
              "null"
            ]
          },
          "confirmations": {
            "type": "integer"
          },
          "requiredConfirmations": {
            "type": "integer"
          },
          "feeUsd": {
            "type": "number"
          },
          "feePercent": {
            "type": "number"
          },
          "totalAmountUsd": {
            "type": [
              "number",
              "null"
            ]
          },
          "isTest": {
            "type": "boolean"
          },
          "metadata": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Metadata"
              },
              {
                "type": "null"
              }
            ]
          },
          "paymentToken": {
            "type": "string"
          },
          "paymentLink": {
            "type": "string",
            "format": "uri"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "completedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "autoConvert": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "Customer": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "name": {
            "type": "string",
            "description": "Never null — falls back to the email local-part."
          },
          "totalPaidUsd": {
            "type": "number"
          },
          "transactionCount": {
            "type": "integer"
          },
          "lastCurrency": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "inactive"
            ]
          },
          "lastActivity": {
            "type": "string",
            "format": "date-time",
            "description": "Never null — falls back to createdAt."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "transactions": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          }
        }
      },
      "Balance": {
        "type": "object",
        "properties": {
          "balance": {
            "type": "number"
          },
          "totalVolumeUsd": {
            "type": "number"
          },
          "totalReceived": {
            "type": "number"
          },
          "totalFees": {
            "type": "number"
          },
          "pendingCount": {
            "type": "integer"
          },
          "currency": {
            "type": "string",
            "const": "USD"
          }
        }
      },
      "Withdrawal": {
        "type": "object",
        "description": "A payout. Camel fields are canonical; a few snake aliases are also emitted.",
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "approved",
              "processing",
              "completed",
              "failed",
              "cancelled"
            ]
          },
          "currency": {
            "type": "string"
          },
          "network": {
            "type": "string"
          },
          "networkDisplayName": {
            "type": "string"
          },
          "amountUsd": {
            "type": "number"
          },
          "cryptoAmount": {
            "type": "number"
          },
          "destinationAddress": {
            "type": "string"
          },
          "destinationTag": {
            "type": [
              "string",
              "null"
            ]
          },
          "exchangeWithdrawId": {
            "type": [
              "string",
              "null"
            ]
          },
          "txId": {
            "type": [
              "string",
              "null"
            ]
          },
          "networkFee": {
            "type": [
              "number",
              "null"
            ]
          },
          "isTest": {
            "type": "boolean"
          },
          "metadata": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Metadata"
              },
              {
                "type": "null"
              }
            ]
          },
          "requestedAt": {
            "type": "string",
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WebhookEvent": {
        "type": "object",
        "properties": {
          "apiVersion": {
            "type": "string",
            "example": "1",
            "description": "Contract discriminator; additive data changes don't bump it."
          },
          "event": {
            "type": "string",
            "example": "deposit.completed"
          },
          "timestamp": {
            "type": "integer",
            "description": "Unix seconds."
          },
          "data": {
            "type": "object",
            "additionalProperties": true,
            "description": "Event-specific payload; always echoes your metadata. Deposit events also carry an `autoConvert` object (`{enabled:false}` unless auto-convert ran)."
          }
        },
        "required": [
          "apiVersion",
          "event",
          "timestamp",
          "data"
        ]
      }
    }
  }
}