{
  "openapi": "3.0.3",
  "info": {
    "title": "SpeedyCoins API",
    "version": "1.0.0",
    "description": "Accept crypto payments with invoices and deposit addresses, pay out with withdrawals, and get signed webhooks. Custodial. Human documentation: https://docs.speedycoins.io",
    "contact": { "url": "https://docs.speedycoins.io" }
  },
  "servers": [{ "url": "https://api.speedycoins.io/v1", "description": "Production (live and test keys)" }],
  "security": [{ "bearerAuth": [] }],
  "tags": [
    { "name": "Invoices" }, { "name": "Deposit addresses" }, { "name": "Deposits" }, { "name": "Withdrawals" }, { "name": "Test mode", "description": "Available to test keys (sc_test_...) only." }
  ],
  "paths": {
    "/invoices": {
      "post": {
        "tags": ["Invoices"], "operationId": "createInvoice", "summary": "Create an invoice",
        "description": "Amount is USD from 0.50 to 100000.00. Returns 201 when created and 200 when an Idempotency-Key repeat returns the original.",
        "parameters": [{ "$ref": "#/components/parameters/IdempotencyKeyOptional" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateInvoice" } } } },
        "responses": {
          "201": { "description": "Created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Invoice" } } } },
          "200": { "description": "Idempotent repeat: the original invoice", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Invoice" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" },
          "409": { "$ref": "#/components/responses/Conflict" }, "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "get": {
        "tags": ["Invoices"], "operationId": "listInvoices", "summary": "List invoices, newest first",
        "parameters": [
          { "name": "status", "in": "query", "schema": { "$ref": "#/components/schemas/InvoiceStatus" } },
          { "name": "order_id", "in": "query", "schema": { "type": "string", "maxLength": 100 } },
          { "$ref": "#/components/parameters/Limit" }
        ],
        "responses": { "200": { "description": "A list", "content": { "application/json": { "schema": { "type": "object", "required": ["data"], "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Invoice" } } } } } } }, "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    },
    "/invoices/{id}": {
      "get": {
        "tags": ["Invoices"], "operationId": "getInvoice", "summary": "Retrieve an invoice",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^inv_[0-9a-f]{32}$" } }],
        "responses": { "200": { "description": "The invoice", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Invoice" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    },
    "/invoices/{id}/cancel": {
      "post": {
        "tags": ["Invoices"], "operationId": "cancelInvoice", "summary": "Cancel an unpaid invoice",
        "description": "Refused with 409 cannot_cancel once any payment has been seen. Takes no body.",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^inv_[0-9a-f]{32}$" } }],
        "responses": { "200": { "description": "The cancelled invoice", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Invoice" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "404": { "$ref": "#/components/responses/NotFound" }, "409": { "$ref": "#/components/responses/Conflict" }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    },
    "/addresses": {
      "post": {
        "tags": ["Deposit addresses"], "operationId": "createAddress", "summary": "Create a deposit address",
        "description": "Using the same label on the same chain again returns the same address with status 200.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateAddress" } } } },
        "responses": {
          "201": { "description": "Created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Address" } } } },
          "200": { "description": "Existing address for that label and chain", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Address" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "409": { "$ref": "#/components/responses/Conflict" }, "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "get": {
        "tags": ["Deposit addresses"], "operationId": "listAddresses", "summary": "List deposit addresses, newest first",
        "parameters": [
          { "name": "label", "in": "query", "schema": { "type": "string", "maxLength": 100 } },
          { "name": "chain", "in": "query", "schema": { "$ref": "#/components/schemas/Chain" } },
          { "$ref": "#/components/parameters/Limit" },
          { "name": "starting_after", "in": "query", "description": "Id of the last address of the previous page.", "schema": { "type": "string" } }
        ],
        "responses": { "200": { "description": "A page", "content": { "application/json": { "schema": { "type": "object", "required": ["data", "has_more"], "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Address" } }, "has_more": { "type": "boolean" } } } } } }, "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    },
    "/addresses/{id}": {
      "get": {
        "tags": ["Deposit addresses"], "operationId": "getAddress", "summary": "Retrieve a deposit address",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^adr_[0-9a-f]{32}$" } }],
        "responses": { "200": { "description": "The address", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Address" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    },
    "/deposits": {
      "get": {
        "tags": ["Deposits"], "operationId": "listDeposits", "summary": "List deposits, newest first",
        "parameters": [
          { "name": "address_id", "in": "query", "schema": { "type": "string", "pattern": "^adr_[0-9a-f]{32}$" } },
          { "name": "status", "in": "query", "schema": { "$ref": "#/components/schemas/DepositStatus" } },
          { "$ref": "#/components/parameters/Limit" }
        ],
        "responses": { "200": { "description": "A list", "content": { "application/json": { "schema": { "type": "object", "required": ["data"], "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Deposit" } } } } } } }, "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    },
    "/withdrawals": {
      "post": {
        "tags": ["Withdrawals"], "operationId": "createWithdrawal", "summary": "Create a withdrawal",
        "description": "Pays from your withdrawal wallet. Idempotency-Key is required. Depending on your settings the withdrawal is sent at once or waits for approval in the dashboard.",
        "parameters": [{ "$ref": "#/components/parameters/IdempotencyKeyRequired" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateWithdrawal" } } } },
        "responses": {
          "201": { "description": "Created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Withdrawal" } } } },
          "200": { "description": "Idempotent repeat: the original withdrawal", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Withdrawal" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" },
          "409": { "$ref": "#/components/responses/Conflict" }, "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "get": {
        "tags": ["Withdrawals"], "operationId": "listWithdrawals", "summary": "List withdrawals, newest first",
        "parameters": [{ "name": "status", "in": "query", "schema": { "$ref": "#/components/schemas/WithdrawalStatus" } }, { "$ref": "#/components/parameters/Limit" }],
        "responses": { "200": { "description": "A list", "content": { "application/json": { "schema": { "type": "object", "required": ["data"], "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Withdrawal" } } } } } } }, "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    },
    "/withdrawals/{id}": {
      "get": {
        "tags": ["Withdrawals"], "operationId": "getWithdrawal", "summary": "Retrieve a withdrawal",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^wd_[0-9a-f]{32}$" } }],
        "responses": { "200": { "description": "The withdrawal", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Withdrawal" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    },
    "/test/payments": {
      "post": {
        "tags": ["Test mode"], "operationId": "simulatePayment", "summary": "Simulate a payment (test keys only)",
        "description": "Creates a deposit against an invoice or deposit address. It is confirmed and settled within a couple of seconds and fires the same webhooks as live mode, with livemode false. Send invoice_id or address_id.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SimulatePayment" } } } },
        "responses": { "2XX": { "description": "The simulated payment was created" }, "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    },
    "/test/fund": {
      "post": {
        "tags": ["Test mode"], "operationId": "fundTestWallet", "summary": "Credit the test withdrawal wallet (test keys only)",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["chain", "asset", "amount"], "properties": { "chain": { "$ref": "#/components/schemas/Chain" }, "asset": { "type": "string", "example": "USDT" }, "amount": { "type": "string", "example": "500" } } } } } },
        "responses": { "2XX": { "description": "Credited" }, "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    }
  },
  "webhooks": {
    "event": {
      "post": {
        "summary": "A signed event sent to your endpoint",
        "description": "Header SpeedyCoins-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\" with your whsec_ secret>. Header SpeedyCoins-Event-Id repeats the event id. Reply 2xx within 10 seconds; failures are retried up to ten times over about two days.",
        "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookEvent" } } } },
        "responses": { "200": { "description": "Acknowledged" } }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": { "type": "http", "scheme": "bearer", "description": "sc_live_<prefix>_<secret> or sc_test_<prefix>_<secret>. Test keys see only test data; live keys only live data." }
    },
    "parameters": {
      "Limit": { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 25 } },
      "IdempotencyKeyOptional": { "name": "Idempotency-Key", "in": "header", "required": false, "schema": { "type": "string", "minLength": 1, "maxLength": 100 } },
      "IdempotencyKeyRequired": { "name": "Idempotency-Key", "in": "header", "required": true, "description": "Your own payout id. Repeating a request with the same key returns the original withdrawal.", "schema": { "type": "string", "minLength": 1, "maxLength": 100 } }
    },
    "responses": {
      "BadRequest": { "description": "Invalid request (invalid_request, invalid_amount, amount_out_of_range, currency_unsupported, invalid_redirect_url, invalid_asset, invalid_address, chain_unsupported, missing_idempotency_key)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Unauthorized": { "description": "Missing, malformed, wrong or revoked API key (invalid_api_key)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Forbidden": { "description": "Account not active (account_unavailable), or a live key used on a test-only endpoint", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "NotFound": { "description": "No such object (not_found)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Conflict": { "description": "Conflicts with current state (idempotency_conflict, cannot_cancel, no_payment_methods, chain_not_enabled, wallet_missing, insufficient_balance)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "RateLimited": { "description": "More than 600 requests per minute per key (rate_limited)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
    },
    "schemas": {
      "Error": { "type": "object", "required": ["error"], "properties": { "error": { "type": "object", "required": ["code", "message"], "properties": { "code": { "type": "string", "example": "invalid_address" }, "message": { "type": "string" } } } } },
      "Chain": { "type": "string", "enum": ["eth", "bsc", "sol", "tron", "btc"] },
      "InvoiceStatus": { "type": "string", "enum": ["pending", "paid", "overpaid", "underpaid", "expired", "cancelled"] },
      "DepositStatus": { "type": "string", "enum": ["detected", "confirmed", "swept", "reorged", "failed"] },
      "WithdrawalStatus": { "type": "string", "enum": ["pending_approval", "approved", "broadcast", "completed", "rejected", "failed"] },
      "CreateInvoice": {
        "type": "object", "required": ["amount", "currency"],
        "properties": {
          "amount": { "type": "string", "pattern": "^\\d{1,12}(\\.\\d{1,2})?$", "description": "USD, 0.50 to 100000.00", "example": "49.00" },
          "currency": { "type": "string", "enum": ["USD"] },
          "order_id": { "type": "string", "maxLength": 100 },
          "description": { "type": "string", "maxLength": 200 },
          "redirect_url": { "type": "string", "maxLength": 500, "format": "uri", "description": "https only" },
          "expires_in": { "type": "integer", "minimum": 60, "maximum": 604800, "default": 3600, "description": "Seconds" },
          "metadata": { "type": "object", "description": "Any JSON up to 2 KB", "additionalProperties": true }
        }
      },
      "Invoice": {
        "type": "object",
        "required": ["id", "status", "amount", "currency", "order_id", "description", "pay_url", "expires_at", "paid_usd", "detected_usd", "late", "payment", "metadata", "created_at", "settled_at"],
        "properties": {
          "id": { "type": "string", "example": "inv_bb470c9f9a914894b5d822b81fd1a0af" },
          "livemode": { "type": "boolean" },
          "status": { "$ref": "#/components/schemas/InvoiceStatus" },
          "amount": { "type": "string", "example": "49.00" },
          "currency": { "type": "string", "example": "USD" },
          "order_id": { "type": "string", "nullable": true },
          "description": { "type": "string", "nullable": true },
          "pay_url": { "type": "string", "format": "uri" },
          "expires_at": { "type": "string", "format": "date-time" },
          "paid_usd": { "type": "string" },
          "detected_usd": { "type": "string" },
          "late": { "type": "boolean" },
          "payment": { "type": "object", "nullable": true, "description": "Set once the customer picks a coin; the quote is valid for 15 minutes.", "properties": { "chain": { "$ref": "#/components/schemas/Chain" }, "asset": { "type": "string" }, "amount": { "type": "string" }, "address": { "type": "string" }, "expires_at": { "type": "string", "format": "date-time" } } },
          "metadata": { "type": "object", "nullable": true, "additionalProperties": true },
          "created_at": { "type": "string", "format": "date-time" },
          "settled_at": { "type": "string", "format": "date-time", "nullable": true },
          "overpaid_usd": { "type": "string", "nullable": true, "description": "Only in invoice.* webhook payloads." }
        }
      },
      "CreateAddress": {
        "type": "object", "required": ["chain"],
        "properties": { "chain": { "$ref": "#/components/schemas/Chain" }, "label": { "type": "string", "minLength": 1, "maxLength": 100 }, "metadata": { "type": "object", "additionalProperties": true, "description": "Up to 2 KB" } }
      },
      "Address": {
        "type": "object", "required": ["id", "chain", "address", "label", "metadata", "created_at"],
        "properties": { "id": { "type": "string", "example": "adr_7c1e90a2b3d44f6e8a1b2c3d4e5f6a7b" }, "livemode": { "type": "boolean" }, "chain": { "$ref": "#/components/schemas/Chain" }, "address": { "type": "string" }, "label": { "type": "string", "nullable": true }, "metadata": { "type": "object", "nullable": true, "additionalProperties": true }, "created_at": { "type": "string", "format": "date-time" } }
      },
      "Deposit": {
        "type": "object", "required": ["id", "address_id", "invoice_id", "label", "chain", "asset", "amount", "usd_value", "tx_hash", "status", "split", "created_at"],
        "properties": {
          "id": { "type": "string", "example": "dep_0f3a9c1e7b2d4a58b6e1f0c9d8a7b6c5" },
          "livemode": { "type": "boolean" },
          "address_id": { "type": "string" },
          "invoice_id": { "type": "string", "nullable": true },
          "label": { "type": "string", "nullable": true },
          "chain": { "$ref": "#/components/schemas/Chain" },
          "asset": { "type": "string", "example": "USDT" },
          "amount": { "type": "string", "nullable": true, "description": "Gross amount in whole units" },
          "usd_value": { "type": "string", "nullable": true },
          "tx_hash": { "type": "string", "nullable": true },
          "status": { "$ref": "#/components/schemas/DepositStatus" },
          "split": { "type": "object", "nullable": true, "description": "Set once swept. The network fee is deducted before payout.", "properties": { "payout": { "type": "string", "nullable": true }, "withdrawal_wallet": { "type": "string", "nullable": true }, "fee": { "type": "string", "nullable": true } } },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "CreateWithdrawal": {
        "type": "object", "required": ["chain", "asset", "to", "amount"],
        "properties": {
          "chain": { "$ref": "#/components/schemas/Chain" },
          "asset": { "type": "string", "description": "native, USDT or USDC (btc: native only)", "example": "USDT" },
          "to": { "type": "string", "maxLength": 100 },
          "amount": { "type": "string", "description": "Decimal in whole units, up to the asset's decimals", "example": "25.00" }
        }
      },
      "Withdrawal": {
        "type": "object", "required": ["id", "chain", "asset", "to", "amount", "usd_value", "network_fee", "status", "tx_hash", "reference", "failure_reason", "created_at"],
        "properties": {
          "id": { "type": "string", "example": "wd_4c1f7a0e9b2d4e6f8a1b2c3d4e5f6a7b" },
          "livemode": { "type": "boolean" },
          "chain": { "$ref": "#/components/schemas/Chain" },
          "asset": { "type": "string" },
          "to": { "type": "string" },
          "amount": { "type": "string", "nullable": true },
          "usd_value": { "type": "string", "nullable": true },
          "network_fee": { "type": "string", "nullable": true },
          "status": { "$ref": "#/components/schemas/WithdrawalStatus" },
          "tx_hash": { "type": "string", "nullable": true },
          "reference": { "type": "string", "nullable": true, "description": "The Idempotency-Key you sent" },
          "failure_reason": { "type": "string", "nullable": true },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "SimulatePayment": {
        "type": "object", "description": "Send invoice_id or address_id.",
        "properties": {
          "invoice_id": { "type": "string" },
          "address_id": { "type": "string" },
          "amount": { "type": "string", "description": "Decimal string in the asset. Default: the exact amount owed." },
          "asset": { "type": "string", "example": "USDT" },
          "chain": { "$ref": "#/components/schemas/Chain" },
          "outcome": { "type": "string", "enum": ["success", "underpaid", "overpaid", "late"], "default": "success" }
        }
      },
      "WebhookEvent": {
        "type": "object", "required": ["id", "type", "created_at", "data"],
        "properties": {
          "id": { "type": "string", "example": "evt_31c0a7e9b24d5f60a1c8e3d2" },
          "livemode": { "type": "boolean" },
          "type": { "type": "string", "enum": ["deposit.detected", "deposit.confirmed", "deposit.reorged", "deposit.settled", "invoice.paid", "invoice.underpaid", "invoice.expired", "withdrawal.pending_approval", "withdrawal.rejected", "withdrawal.completed", "withdrawal.failed", "webhook.test"] },
          "created_at": { "type": "string", "format": "date-time" },
          "data": { "oneOf": [{ "$ref": "#/components/schemas/Deposit" }, { "$ref": "#/components/schemas/Invoice" }, { "$ref": "#/components/schemas/Withdrawal" }, { "type": "object", "properties": { "message": { "type": "string" } } }] }
        }
      }
    }
  }
}
