{
  "openapi": "3.1.0",
  "info": {
    "title": "Laso Finance x402 API",
    "version": "1.0.0",
    "x-docs-revision": "cecfcbe45738",
    "x-docs-manifest": "https://laso.finance/.well-known/docs-version.json",
    "contact": {
      "email": "agents+support@laso.finance"
    },
    "x-guidance": "Laso Finance is a payment-gated (x402) API that lets an AI agent spend USDC on real-world financial products: prepaid cards (U.S. and international), gift cards, push-to-card transfers to USD/EUR/GBP debit cards, and Venmo/PayPal payouts.\n\nPayment: every paid route is an x402 v2 endpoint. Call it with no payment header to receive a 402 challenge listing the accepted networks, then replay with a signed USDC payment. Both Base (eip155:8453) and Solana (solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp) are accepted on every paid route; the caller picks either chain.\n\nIdentity: `GET /auth` is free and identity-only. Prove wallet ownership with a `SIGN-IN-WITH-X` (CAIP-122) header to receive a Firebase id_token, then send that token as a Bearer credential to the authenticated read routes (`get-card-data`, `get-account-balance`, `get-kyc-status`, etc.). Paid routes also return fresh auth credentials in their response, so a payment is never required just to obtain a token.\n\nRecommended flow: (1) `GET /auth` to establish identity, (2) call a paid route (e.g. `GET /get-card`) to purchase a product, paying USDC on Base or Solana, (3) poll the authenticated read routes with the returned Bearer token to fetch the resulting card/transfer details. Full machine-readable instructions live at https://laso.finance/SKILL.md.",
    "description": "Payment-gated API for Laso Finance. All paywalled routes use the x402 protocol — the caller includes a USDC payment header on Base (eip155:8453) or Solana (solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp) and the server verifies payment before processing. Free routes require no payment header.\n\n## Getting started\n\nTo set up a wallet for making x402 payments, choose a provider:\n\n- **Locus** (default): https://paywithlocus.com/SKILL.md\n- **Sponge**: https://wallet.paysponge.com/skill.md — automatic x402 service discovery\n- **Ampersend**: https://www.ampersend.ai/getting-started.md — self-custody on Base or Solana with dual-approval spending limits. Laso Finance is a default skill, so no manual endpoint registration is needed.\n\n## How x402 works\n\n1. Call a paywalled endpoint without a payment header → receive a `402 Payment Required` response containing payment details (price, recipient address, network).\n2. Construct an x402 payment header using the details from the 402 response.\n3. Replay the request with the payment header → the server verifies payment and processes the request.\n\n## Authentication flow\n\n`GET /auth` is free: callers prove wallet ownership by sending a `SIGN-IN-WITH-X` header (CAIP-122 wallet signature). Paywalled routes (`/get-card`, `/order-gift-card`, `/get-push-to-card`, `/order-intl-card`) also return fresh auth credentials in their responses, so a payment is never required just to obtain a token.\n\nMost routes return auth credentials (`id_token`, `refresh_token`, `expires_in`). Use the `id_token` as a Bearer token to call authenticated Laso Finance endpoints like `/get-card-data`. When the `id_token` expires, use `POST /auth` with `grant_type: refresh_token` to get a new one.\n\n## Important notes\n\nThe `/get-card` USA prepaid card endpoint is U.S. only — issued in USD, usable at U.S.-based merchants only, and physical goods must ship to a U.S. address. For non-U.S. merchants or non-USD currencies, use `GET /order-intl-card` instead (international prepaid card, admin-fulfilled within 24 hours). All cards are intended for the caller's own use.\n\nFor step-by-step instructions, read https://laso.finance/SKILL.md"
  },
  "servers": [
    {
      "url": "https://laso.finance",
      "description": "Production"
    }
  ],
  "paths": {
    "/version": {
      "get": {
        "operationId": "getVersion",
        "summary": "Get the current docs version",
        "description": "Returns the current content-hash version of the Laso Finance agent docs (`SKILL.md`, `openapi.json`, `llms.txt`, `llms-full.txt`, `ai-plugin.json`). Free, no auth or payment header.\n\nAgents aggressively cache these docs, so use this to detect a stale cache: if the `docs_version` you last saw differs from the value returned here, refetch the docs. Every API response also carries this value in the `X-Laso-Docs-Version` response header, alongside `X-Laso-Docs-Manifest`, which points at the authoritative freshness beacon at `https://laso.finance/.well-known/docs-version.json`.\n\nThis route is also the `status` target of the API catalog at `https://laso.finance/.well-known/api-catalog` (RFC 9727), which every response advertises via a `Link` header with `rel=\"api-catalog\"`.",
        "responses": {
          "200": {
            "description": "Current docs version",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "docs_version": {
                      "type": "string",
                      "description": "12-character content hash of the agent docs. Changes only when the docs change."
                    },
                    "docs_manifest_url": {
                      "type": "string",
                      "description": "URL of the authoritative freshness beacon, served with no-cache."
                    },
                    "api_catalog_url": {
                      "type": "string",
                      "description": "URL of the RFC 9727 API catalog (`application/linkset+json`) indexing the OpenAPI description, the agent docs, and this status route."
                    },
                    "docs": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "description": "Per-file entries, each with a `url` and a stable `sha256` content identifier. Compare against the values you last saw to tell which file changed."
                    },
                    "note": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "docs_version": "8ea0eaadf372",
                  "docs_manifest_url": "https://laso.finance/.well-known/docs-version.json",
                  "api_catalog_url": "https://laso.finance/.well-known/api-catalog",
                  "docs": {
                    "SKILL.md": {
                      "url": "https://laso.finance/SKILL.md",
                      "sha256": "bb30ea2ee3cbd51b932f7e9fa1df35379ca589e203f23d1d51dd93dc0eb7a06c"
                    }
                  },
                  "note": "If your cached copy of the docs carries a different docs_version, refetch."
                }
              }
            }
          }
        },
        "security": []
      }
    },
    "/auth": {
      "get": {
        "operationId": "getAuth",
        "summary": "Get auth credentials",
        "description": "Free endpoint. Returns an ID token, refresh token, and user ID for the calling wallet. Use the ID token as a Bearer token to call Laso Finance APIs.\n\nProve wallet ownership by sending a `SIGN-IN-WITH-X` header: a base64-encoded CAIP-122 signed message. Build it with `@x402/extensions/sign-in-with-x` (e.g. `wrapFetchWithSIWx` handles the round-trip automatically). Works for any EVM (Base, eip155:8453) or Solana mainnet wallet.\n\nFor ID-token refresh use `POST /auth` with `grant_type=refresh_token`.",
        "parameters": [
          {
            "name": "SIGN-IN-WITH-X",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Base64-encoded CAIP-122 signed message proving wallet ownership."
          }
        ],
        "responses": {
          "200": {
            "description": "Auth credentials and user ID",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "auth": {
                      "$ref": "#/components/schemas/AuthCredentials"
                    },
                    "callable_base_url": {
                      "type": "string",
                      "example": "https://us-central1-kyc-ts.cloudfunctions.net"
                    },
                    "user_id": {
                      "type": "string",
                      "description": "The user's ID (lowercase wallet address)"
                    }
                  }
                }
              }
            }
          },
          "402": {
            "description": "No valid payment or SIGN-IN-WITH-X proof was presented. All SIWX failures (missing or malformed header, invalid or expired signature, reused nonce, domain mismatch) return 402 per the x402 protocol. The response body is an empty JSON object; a fresh challenge (new nonce, payment options, SIWX info) is base64-encoded in the `PAYMENT-REQUIRED` response header. Sign the new challenge and retry. x402 client libraries such as `wrapFetchWithSIWx` handle this automatically. A **second** 402 on the paid retry means something different: the payment header verified but the transfer could not be settled on-chain. That response body is not empty and carries no `accepts` — it is the x402 settlement-failure shape `{\"success\": false, \"errorReason\": \"...\", \"errorMessage\": \"...\"}`, plus `x_laso_guidance` when a concrete next step applies. Distinguish the two by body: a challenge has `accepts`, a failure has `success: false`. The usual cause is an underfunded wallet, and the usual cause of that is the fee being charged **on top of** `amount` (a wallet holding exactly \\$2,000 cannot send a \\$2,000 payment). Nothing is charged for a failed settlement, so retrying with a smaller amount is safe.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "example": {}
                }
              }
            }
          }
        },
        "security": [
          {
            "siwx": []
          }
        ]
      },
      "post": {
        "operationId": "refreshToken",
        "summary": "Refresh an ID token",
        "description": "Exchange a refresh token for a new ID token. This is a free endpoint — no x402 payment required. Uses the standard OAuth2 `grant_type=refresh_token` pattern.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "grant_type",
                  "refresh_token"
                ],
                "properties": {
                  "grant_type": {
                    "type": "string",
                    "enum": [
                      "refresh_token"
                    ],
                    "description": "Must be \"refresh_token\""
                  },
                  "refresh_token": {
                    "type": "string",
                    "description": "The refresh token received from a previous /auth or /get-card call"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Refreshed auth credentials and user ID",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id_token": {
                      "type": "string",
                      "description": "New ID token — use as Bearer token for Laso Finance APIs"
                    },
                    "refresh_token": {
                      "type": "string",
                      "description": "New refresh token — use for the next refresh"
                    },
                    "expires_in": {
                      "type": "string",
                      "description": "Token lifetime in seconds"
                    },
                    "user_id": {
                      "type": "string",
                      "description": "The user's ID (lowercase wallet address)"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid or missing grant_type or refresh_token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Invalid or missing grant_type. Must be \"refresh_token\" for POST /auth."
                }
              }
            }
          },
          "401": {
            "description": "Token is invalid or revoked",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Failed to refresh token. Token may be invalid or revoked."
                }
              }
            }
          }
        },
        "security": []
      }
    },
    "/get-card": {
      "get": {
        "operationId": "getCard",
        "summary": "Order a USA prepaid card",
        "description": "Pay between \\$5–\\$1000 USDC to order a Laso Finance USA prepaid card. By default returns JSON with auth credentials and card info (for AI agents). Pass `format=html` to get an HTML redirect to the card redemption URL (for browser-based flows).\n\nThe JSON response includes a `card.status` of `\"pending\"`. Card details (number, CVV, etc.) take ~7-10 seconds to become available. Poll `getCardData` at the `callableBaseUrl` to check when `status` becomes `\"ready\"`.\n\n**U.S. only.** This card is issued in USD and can only be used for purchases at U.S.-based merchants. Physical goods must ship to a U.S. address. For non-U.S. merchants or non-USD currencies, use `GET /order-intl-card` instead. This endpoint is intended for the caller's own use — the card is non-transferable.",
        "x-x402": [
          {
            "price": "dynamic",
            "priceRange": "$5–$1000",
            "scheme": "exact",
            "network": "eip155:8453",
            "payTo": "0x3291e96b3bff7ed56e3ca8364273c5b4654b2b37"
          },
          {
            "price": "dynamic",
            "priceRange": "$5–$1000",
            "scheme": "exact",
            "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
            "payTo": "3MZVk97x9SeRxbYpc3jhzRfU2fyA3emYutnqfn9kNfYX"
          }
        ],
        "x-payment-info": {
          "price": {
            "mode": "dynamic",
            "currency": "USD",
            "min": "5",
            "max": "1000"
          },
          "protocols": [
            {
              "x402": {}
            }
          ]
        },
        "parameters": [
          {
            "name": "amount",
            "in": "query",
            "required": true,
            "description": "USD amount to load on the card (min \\$5, max \\$1000)",
            "schema": {
              "type": "number",
              "minimum": 5,
              "maximum": 1000
            }
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "description": "Response format. `json` (default) returns auth credentials and card info for agents. `html` returns a redirect page for browsers.",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "html"
              ],
              "default": "json"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Card order response. JSON by default, or HTML redirect if `format=html`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "auth": {
                      "$ref": "#/components/schemas/AuthCredentials"
                    },
                    "callable_base_url": {
                      "type": "string",
                      "example": "https://us-central1-kyc-ts.cloudfunctions.net"
                    },
                    "user_id": {
                      "type": "string",
                      "description": "The user's ID (lowercase wallet address)"
                    },
                    "card": {
                      "$ref": "#/components/schemas/CardOrder"
                    }
                  }
                }
              },
              "text/html": {
                "schema": {
                  "type": "string",
                  "description": "HTML page with meta-refresh redirect to card redemption URL (when format=html)"
                }
              }
            }
          },
          "400": {
            "description": "Invalid or missing amount",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Amount must be at least $5. Received: $2"
                }
              }
            }
          },
          "402": {
            "description": "Payment required. No valid x402 payment header was included. The response body is an empty JSON object; the payment details (price, recipient address, network) are base64-encoded in the `PAYMENT-REQUIRED` response header. x402 client libraries handle this automatically. A **second** 402 on the paid retry means something different: the payment header verified but the transfer could not be settled on-chain. That response body is not empty and carries no `accepts` — it is the x402 settlement-failure shape `{\"success\": false, \"errorReason\": \"...\", \"errorMessage\": \"...\"}`, plus `x_laso_guidance` when a concrete next step applies. Distinguish the two by body: a challenge has `accepts`, a failure has `success: false`. The usual cause is an underfunded wallet, and the usual cause of that is the fee being charged **on top of** `amount` (a wallet holding exactly \\$2,000 cannot send a \\$2,000 payment). Nothing is charged for a failed settlement, so retrying with a smaller amount is safe.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "example": {}
                }
              }
            }
          },
          "403": {
            "description": "Account is frozen. The response includes a `frozen_message` field explaining why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FrozenError"
                },
                "example": {
                  "error": "Account is frozen",
                  "frozen_message": "Your account is frozen pending a compliance review. Contact support@laso.finance."
                }
              }
            }
          }
        }
      }
    },
    "/get-card-data": {
      "get": {
        "operationId": "getCardData",
        "summary": "Get card details",
        "description": "Returns the current status and details of card orders. If `card_id` is provided, returns a single card, looked up across U.S. non-reloadable, international non-reloadable, and reloadable cards. If omitted, returns all cards of the given `card_type` for the authenticated user; `card_type` defaults to `Non-Reloadable U.S.` when omitted, so existing callers see unchanged behavior. Pass `card_type=Non-Reloadable International` to list international cards, or `card_type=Reloadable` to list reloadable cards.\n\nFor U.S. non-reloadable cards, details take ~7-10 seconds to become available after ordering; poll every 2-3 seconds until `status` is `\"ready\"`, then read `card_details`. For international non-reloadable cards, the order is `queued` until an admin fulfills it (typically within 24 hours), after which `card_details` is populated.\n\nFor international cards, the `card_id` returned by `/order-intl-card` is a queue id. After admin fulfillment, the issuer's transaction id becomes the new `card_id` and the original queue id is preserved on the card as `queued_order_card_id`. You can keep polling `/get-card-data?card_id=<original-queue-id>` and it will resolve to the fulfilled card.\n\n**Reloadable cards** are a separate product, set up by the account holder in the Laso dashboard rather than ordered through this API. They are reusable (a `multi_use` card stays open across charges until its limit is spent) and can be topped up, unlike the single-load non-reloadable cards. Listing them returns `balance`, `spend_limit`, `last4`, `expiry`, and `reusable`. If the wallet has no card issuer account linked, the list is empty and a `note` explains how the holder sets one up.\n\nReading a reloadable card's **number and CVV** requires the account holder's approval: request the card by `card_id`, and if the response contains `details_approval.status: \"pending\"`, the holder has been emailed an approve/deny link. Once they approve, retry with `approval_id` to receive `card_details`. Spend the card by entering its number, expiry, CVV, and the returned `billing_address` exactly as given at the merchant's checkout — the address comes from the account holder's identity verification and is AVS-checked, and a mismatch is the most common decline on a card that has funds. A card with `reusable: true` stays open after an approved charge and can be spent again up to its remaining `balance`; a charge larger than the balance is declined in full, as there are no partial approvals.\n\nRequires a Bearer token from `/auth` or `/get-card`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "card_id",
            "in": "query",
            "required": false,
            "description": "The card ID returned from `/get-card` or `/order-intl-card`, or a reloadable card's id from `card_type=Reloadable`. Looked up across all three card types. If omitted, returns all cards of `card_type`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "card_type",
            "in": "query",
            "required": false,
            "description": "When listing all cards (no `card_id`), filters by card type. Defaults to `Non-Reloadable U.S.` if omitted (preserves existing client behavior). Pass `Non-Reloadable International` to list international cards, or `Reloadable` to list reloadable cards.",
            "schema": {
              "type": "string",
              "enum": [
                "Non-Reloadable U.S.",
                "Non-Reloadable International",
                "Reloadable"
              ],
              "default": "Non-Reloadable U.S."
            }
          },
          {
            "name": "approval_id",
            "in": "query",
            "required": false,
            "description": "Reloadable cards only. When a request for a reloadable card's `card_details` returns `details_approval.status: \"pending\"`, the account holder is emailed an approve/deny link. After they approve, retry the same request with the `approval_id` from that response to receive the card number and CVV.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Card status and details. Returns a single `CardData` object when `card_id` is provided, or `{ \"cards\": CardData[] }` when omitted.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/CardData"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "cards": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/CardData"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid card_type. Must be \"Non-Reloadable\" or \"Non-Reloadable International\" when provided.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "card_type must be \"Non-Reloadable\" or \"Non-Reloadable International\" when provided. Received: foo"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Missing or invalid Authorization header"
                }
              }
            }
          },
          "403": {
            "description": "Not authorized to view this card, or the account is frozen (frozen responses include a `frozen_message` field).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not authorized to view this card"
                }
              }
            }
          },
          "404": {
            "description": "Card not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Card not found"
                }
              }
            }
          }
        }
      }
    },
    "/get-account-balance": {
      "get": {
        "operationId": "getAccountBalance",
        "summary": "Get account balance",
        "description": "Returns the current account balance and total deposits for the authenticated user.\n\nRequires a Bearer token from `/auth` or `/get-card`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Account balance information",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AccountBalance"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Invalid or expired token"
                }
              }
            }
          },
          "403": {
            "description": "Account is frozen. The response includes a `frozen_message` field explaining why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FrozenError"
                },
                "example": {
                  "error": "Account is frozen",
                  "frozen_message": "Your account is frozen pending a compliance review. Contact support@laso.finance."
                }
              }
            }
          },
          "404": {
            "description": "Account balance not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Account balance not found"
                }
              }
            }
          }
        }
      }
    },
    "/get-kyc-status": {
      "get": {
        "operationId": "getKycStatus",
        "summary": "Get KYC verification status (optional)",
        "tags": [
          "Verification (optional)"
        ],
        "description": "**KYC is optional.** Most endpoints (cards, gift cards, push-to-card, account balance, withdrawals) do not require verification. It is only needed for certain features such as Venmo/PayPal payouts via `/send-payment`, and may be used for additional controls in the future. If you are not using those features, you can ignore the verification endpoints entirely.\n\nReturns the calling wallet's cached KYC verification status. Use this as a free pre-flight check before paying for `/send-payment`: if `kyc_verified` is `true` the payout will go through. If it is `false`, don't call `/send-payment` yet — that call would not send the payout (it returns `kyc_required` and a `kyc_url`), and the USDC you paid would just land in your account balance, recoverable with `POST /withdraw`. To start verification when not verified, call `/get-kyc-link`.\n\nThis reads the cached status kept up to date by the verification webhook; it does not start verification or return a verification link.\n\nRequires a Bearer token from `/auth` or `/get-card`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "KYC verification status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "user_id": {
                      "type": "string"
                    },
                    "kyc_verified": {
                      "type": "boolean",
                      "description": "Whether the wallet has completed identity verification. When true, /send-payment dispatches the payout. When false, /send-payment does not send: it returns kyc_required and the USDC paid lands in account balance (recoverable via POST /withdraw). Verify before calling /send-payment."
                    },
                    "kyc_review_status": {
                      "type": "string",
                      "nullable": true,
                      "description": "Latest review status from the verification provider, or null if never reviewed."
                    },
                    "kyc_review_answer": {
                      "type": "string",
                      "nullable": true,
                      "description": "Latest review answer (e.g. GREEN for approved, RED for rejected), or null if never reviewed."
                    },
                    "kyc_last_reviewed_at": {
                      "type": "number",
                      "nullable": true,
                      "description": "Epoch milliseconds of the last review, or null if never reviewed."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Invalid or expired token"
                }
              }
            }
          },
          "403": {
            "description": "Account is frozen. The response includes a `frozen_message` field explaining why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FrozenError"
                },
                "example": {
                  "error": "Account is frozen",
                  "frozen_message": "Your account is frozen pending a compliance review. Contact support@laso.finance."
                }
              }
            }
          }
        }
      }
    },
    "/get-kyc-link": {
      "get": {
        "operationId": "getKycLink",
        "summary": "Get a KYC verification link (optional)",
        "tags": [
          "Verification (optional)"
        ],
        "description": "**KYC is optional.** It is only needed for certain features such as Venmo/PayPal payouts via `/send-payment`, and may be used for additional controls in the future. If you are not using those features, you can ignore the verification endpoints.\n\nReturns a one-time identity-verification link (`kyc_url`) for the calling wallet. Open it (or hand it to the wallet owner) to complete verification. The typical flow is: call `/get-kyc-status`, and only if `kyc_verified` is `false` call this to get a link, complete it, then proceed to `/send-payment`.\n\nRequires a Bearer token from `/auth` or `/get-card`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Verification link",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "user_id": {
                      "type": "string"
                    },
                    "kyc_url": {
                      "type": "string",
                      "description": "One-time URL to open to complete identity verification."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Invalid or expired token"
                }
              }
            }
          },
          "403": {
            "description": "Account is frozen. The response includes a `frozen_message` field explaining why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FrozenError"
                },
                "example": {
                  "error": "Account is frozen",
                  "frozen_message": "Your account is frozen pending a compliance review. Contact support@laso.finance."
                }
              }
            }
          }
        }
      }
    },
    "/register-webhook": {
      "post": {
        "operationId": "registerWebhook",
        "summary": "Register a notification webhook (free)",
        "tags": [
          "Notifications"
        ],
        "description": "Registers (or replaces) an HTTPS webhook URL to receive the calling wallet's account notifications as signed POSTs — banking application status changes, bank transfer and payout completions, agent wallet deposits, card orders, and every other event the user is notified about. This closes the polling gap for agents: instead of re-fetching status endpoints, point this at any URL you can receive HTTP on (your harness's inbound webhook endpoint, or a relay you poll).\n\nDeliveries are signed per the Standard Webhooks specification (https://www.standardwebhooks.com/): each POST carries `webhook-id`, `webhook-timestamp`, and `webhook-signature` (`v1,<base64 HMAC-SHA256>`) headers verifiable with any standard-webhooks library using the returned `secret`. The body is `{\"type\": \"notification.<category>\", \"timestamp\": \"<ISO 8601>\", \"data\": {\"user_id\", \"title\", \"text\", \"category\"}}`.\n\nThe `secret` is returned exactly once, by this call. Re-registering rotates the secret, replaces the URL, and re-enables a registration that was auto-disabled after 50 consecutive failed deliveries. Registering also notifies the account owner through their other channels and fires a first signed test delivery (`type` `notification.account`) at the new URL.\n\nDeliveries time out after 10 seconds and are not retried; treat the webhook as a low-latency hint and the status endpoints as the source of truth. The URL must be public HTTPS.\n\nRequires a Bearer token from `/auth` or `/get-card`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "description": "Public HTTPS URL to receive signed notification POSTs. Max 512 characters. Private/internal hosts are rejected."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Webhook registered",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "registered": {
                      "type": "boolean"
                    },
                    "url": {
                      "type": "string"
                    },
                    "secret": {
                      "type": "string",
                      "description": "Standard Webhooks signing secret (`whsec_...`). Shown only in this response — store it now. Rotate by re-registering."
                    },
                    "signing": {
                      "type": "string",
                      "description": "Always `standard-webhooks`."
                    }
                  }
                },
                "example": {
                  "registered": true,
                  "url": "https://agent.example.com/hooks/laso",
                  "secret": "whsec_EXAMPLEONLYnotarealsecretAAAAAAA",
                  "signing": "standard-webhooks"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid `url` (not HTTPS, not public, too long, or malformed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "url must use https"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Invalid or expired token"
                }
              }
            }
          },
          "403": {
            "description": "Account is frozen. The response includes a `frozen_message` field explaining why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FrozenError"
                }
              }
            }
          }
        }
      }
    },
    "/get-webhook": {
      "get": {
        "operationId": "getWebhook",
        "summary": "Get webhook registration & delivery health (free)",
        "tags": [
          "Notifications"
        ],
        "description": "Returns the calling wallet's webhook registration and its delivery health. The signing secret is never returned here — it is shown once by `POST /register-webhook`; re-register to rotate it.\n\nA registration is auto-disabled after 50 consecutive failed deliveries (`disabled` becomes `true`); re-register to re-enable it.\n\nRequires a Bearer token from `/auth` or `/get-card`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Registration state (`registered: false` when none exists)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "registered": {
                      "type": "boolean"
                    },
                    "url": {
                      "type": "string",
                      "nullable": true
                    },
                    "disabled": {
                      "type": "boolean",
                      "description": "True when auto-disabled after repeated delivery failures. Re-register to re-enable."
                    },
                    "disabled_reason": {
                      "type": "string",
                      "nullable": true
                    },
                    "consecutive_failures": {
                      "type": "number"
                    },
                    "last_delivery_status": {
                      "type": "string",
                      "nullable": true,
                      "description": "`delivered` or `failed`, or null before the first delivery."
                    },
                    "last_delivery_timestamp": {
                      "type": "number",
                      "nullable": true,
                      "description": "Epoch milliseconds of the last delivery attempt."
                    },
                    "last_delivery_detail": {
                      "type": "string",
                      "nullable": true,
                      "description": "`HTTP <status>` of the last attempt, or the error message."
                    },
                    "last_delivery_message_id": {
                      "type": "string",
                      "nullable": true,
                      "description": "The `webhook-id` header of the last delivery."
                    }
                  }
                },
                "example": {
                  "registered": true,
                  "url": "https://agent.example.com/hooks/laso",
                  "disabled": false,
                  "disabled_reason": null,
                  "consecutive_failures": 0,
                  "last_delivery_status": "delivered",
                  "last_delivery_timestamp": 1753900000000,
                  "last_delivery_detail": "HTTP 200",
                  "last_delivery_message_id": "msg_a1b2c3d4e5f6a7b8c9d0e1f2"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Invalid or expired token"
                }
              }
            }
          },
          "403": {
            "description": "Account is frozen. The response includes a `frozen_message` field explaining why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FrozenError"
                }
              }
            }
          }
        }
      }
    },
    "/delete-webhook": {
      "post": {
        "operationId": "deleteWebhook",
        "summary": "Delete the notification webhook (free)",
        "tags": [
          "Notifications"
        ],
        "description": "Removes the calling wallet's webhook registration. Notifications keep flowing to the user's other channels (push, SMS, Telegram, in-app); only the webhook deliveries stop. The account owner is notified of the removal.\n\nRequires a Bearer token from `/auth` or `/get-card`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Deletion result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "type": "boolean",
                      "description": "False when there was no registration to delete."
                    }
                  }
                },
                "example": {
                  "deleted": true
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Invalid or expired token"
                }
              }
            }
          },
          "403": {
            "description": "Account is frozen. The response includes a `frozen_message` field explaining why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FrozenError"
                }
              }
            }
          }
        }
      }
    },
    "/withdraw": {
      "post": {
        "operationId": "withdraw",
        "summary": "Withdraw from account balance",
        "description": "Initiate a withdrawal from the user's account balance. The withdrawal is created in a `pending` state and will be processed by the admin flow.\n\nRequires a Bearer token from `/auth` or `/get-card`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "amount"
                ],
                "properties": {
                  "amount": {
                    "type": "number",
                    "minimum": 0.01,
                    "description": "Amount to withdraw in USD"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Withdrawal initiated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "withdrawal": {
                      "$ref": "#/components/schemas/WithdrawalRecord"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid amount or insufficient funds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Insufficient funds. Current balance: $12.50"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Missing or invalid Authorization header"
                }
              }
            }
          },
          "403": {
            "description": "Account is frozen. The response includes a `frozen_message` field explaining why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FrozenError"
                },
                "example": {
                  "error": "Account is frozen",
                  "frozen_message": "Your account is frozen pending a compliance review. Contact support@laso.finance."
                }
              }
            }
          },
          "404": {
            "description": "Account balance not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Account balance not found"
                }
              }
            }
          }
        }
      }
    },
    "/get-withdrawal-status": {
      "get": {
        "operationId": "getWithdrawalStatus",
        "summary": "Get withdrawal statuses",
        "description": "Returns the status of withdrawals for the authenticated user. If `withdrawal_id` is provided, returns a single withdrawal. If omitted, returns all withdrawals.\n\nRequires a Bearer token from `/auth` or `/get-card`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "withdrawal_id",
            "in": "query",
            "required": false,
            "description": "Get a specific withdrawal by ID. If omitted, returns all withdrawals.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Withdrawal status. Returns `{ \"withdrawal\": WithdrawalStatus }` when `withdrawal_id` is provided, or `{ \"withdrawals\": WithdrawalStatus[] }` when omitted.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "properties": {
                        "withdrawal": {
                          "$ref": "#/components/schemas/WithdrawalStatus"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "properties": {
                        "withdrawals": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/WithdrawalStatus"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Invalid or expired token"
                }
              }
            }
          },
          "403": {
            "description": "Account is frozen. The response includes a `frozen_message` field explaining why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FrozenError"
                },
                "example": {
                  "error": "Account is frozen",
                  "frozen_message": "Your account is frozen pending a compliance review. Contact support@laso.finance."
                }
              }
            }
          },
          "404": {
            "description": "Withdrawal not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Withdrawal not found"
                }
              }
            }
          }
        }
      }
    },
    "/refresh-card-data": {
      "post": {
        "operationId": "refreshCardData",
        "summary": "Trigger a card data refresh",
        "description": "Requests an updated balance for a card.\n\nFor U.S. non-reloadable cards (default), the card is re-scraped from the issuer via a retrieval queue and processed asynchronously. Rate limited per card to one request every 5 minutes, and to at most 24 refreshes in any rolling 24-hour period.\n\nFor international non-reloadable cards, a balance update request is recorded; an admin will manually update the balance within 24 hours. While a balance update is already pending for a card, additional requests for that card return 409.\n\nRequires a Bearer token from `/auth` or `/get-card`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "card_id"
                ],
                "properties": {
                  "card_id": {
                    "type": "string",
                    "description": "The card ID to refresh data for"
                  },
                  "card_type": {
                    "type": "string",
                    "description": "The type of card. Defaults to `Non-Reloadable U.S.` if omitted.",
                    "enum": [
                      "Non-Reloadable U.S.",
                      "Non-Reloadable International"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Card refresh requested successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string",
                      "example": "Card refresh requested."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing card_id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "card_id is required in request body"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Missing or invalid Authorization header"
                }
              }
            }
          },
          "403": {
            "description": "Account is frozen. The response includes a `frozen_message` field explaining why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FrozenError"
                },
                "example": {
                  "error": "Account is frozen",
                  "frozen_message": "Your account is frozen pending a compliance review. Contact support@laso.finance."
                }
              }
            }
          },
          "404": {
            "description": "Card not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Card not found"
                }
              }
            }
          },
          "409": {
            "description": "Card already has a pending refresh/balance update request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "A balance update is already pending for this card."
                }
              }
            }
          },
          "429": {
            "description": "Rate limited — either less than 5 minutes since the last refresh of this card, or this card has reached its 24-refresh daily limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Daily card refresh limit reached. You can request at most 24 refreshes for a card in a 24-hour period. Please try again later."
                }
              }
            }
          }
        }
      }
    },
    "/order-intl-card": {
      "get": {
        "operationId": "orderIntlCard",
        "summary": "Order an international prepaid card",
        "description": "Order an international non-reloadable prepaid card (USD). The order is queued and fulfilled by an admin — after fulfillment, card details become available via `GET /get-card-data` with `card_type=Non-Reloadable International`.\n\nThe x402 payment covers the on-card amount plus a 3.8% fee. Minimum \\$100, maximum \\$1,000 on-card amount. Submit a whole dollar amount.\n\nIf you need to cancel a queued order before it is fulfilled, use `POST /cancel-intl-order`.",
        "x-x402": [
          {
            "price": "dynamic",
            "priceRange": "$103.80–$1,038 USD (on-card amount \\$100–\\$1,000 + 3.8% fee)",
            "scheme": "exact",
            "network": "eip155:8453",
            "payTo": "0x3291e96b3bff7ed56e3ca8364273c5b4654b2b37"
          },
          {
            "price": "dynamic",
            "priceRange": "$103.80–$1,038 USD (on-card amount \\$100–\\$1,000 + 3.8% fee)",
            "scheme": "exact",
            "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
            "payTo": "3MZVk97x9SeRxbYpc3jhzRfU2fyA3emYutnqfn9kNfYX"
          }
        ],
        "x-payment-info": {
          "price": {
            "mode": "dynamic",
            "currency": "USD",
            "min": "103.80",
            "max": "1038"
          },
          "protocols": [
            {
              "x402": {}
            }
          ]
        },
        "parameters": [
          {
            "name": "amount",
            "in": "query",
            "required": true,
            "description": "On-card USD amount (min \\$100, max \\$1,000). Must be a whole dollar amount: the issuer only issues whole-dollar cards. The x402 payment is this amount plus a 3.8% fee.",
            "schema": {
              "type": "number",
              "minimum": 100,
              "maximum": 1000
            }
          }
        ],
        "responses": {
          "200": {
            "description": "International card order queued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "auth": {
                      "$ref": "#/components/schemas/AuthCredentials"
                    },
                    "callable_base_url": {
                      "type": "string"
                    },
                    "user_id": {
                      "type": "string"
                    },
                    "intl_card_order": {
                      "type": "object",
                      "properties": {
                        "on_card_usd_amount": {
                          "type": "number"
                        },
                        "charged_usd_amount": {
                          "type": "number"
                        },
                        "status": {
                          "type": "string",
                          "example": "queued"
                        },
                        "timestamp": {
                          "type": "number"
                        }
                      }
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid amount",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Amount must be at least $100. Received: $50"
                }
              }
            }
          },
          "402": {
            "description": "Payment required. No valid x402 payment header was included. The response body is an empty JSON object; the payment details (price, recipient address, network) are base64-encoded in the `PAYMENT-REQUIRED` response header. x402 client libraries handle this automatically. A **second** 402 on the paid retry means something different: the payment header verified but the transfer could not be settled on-chain. That response body is not empty and carries no `accepts` — it is the x402 settlement-failure shape `{\"success\": false, \"errorReason\": \"...\", \"errorMessage\": \"...\"}`, plus `x_laso_guidance` when a concrete next step applies. Distinguish the two by body: a challenge has `accepts`, a failure has `success: false`. The usual cause is an underfunded wallet, and the usual cause of that is the fee being charged **on top of** `amount` (a wallet holding exactly \\$2,000 cannot send a \\$2,000 payment). Nothing is charged for a failed settlement, so retrying with a smaller amount is safe.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "example": {}
                }
              }
            }
          },
          "403": {
            "description": "Account is frozen. The response includes a `frozen_message` field explaining why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FrozenError"
                },
                "example": {
                  "error": "Account is frozen",
                  "frozen_message": "Your account is frozen pending a compliance review. Contact support@laso.finance."
                }
              }
            }
          },
          "500": {
            "description": "Order failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Failed to order international card. Please contact support."
                }
              }
            }
          }
        }
      }
    },
    "/cancel-intl-order": {
      "post": {
        "operationId": "cancelIntlOrder",
        "summary": "Cancel a queued international card order",
        "description": "Cancel a previously queued international non-reloadable card order, as long as it has not yet been fulfilled by an admin (`state=queued`). The charged amount is credited back to the account balance.\n\nRequires a Bearer token from `/auth` or `/get-card`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "card_id"
                ],
                "properties": {
                  "card_id": {
                    "type": "string",
                    "description": "The card ID of the queued order to cancel (returned from `/order-intl-card`)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Order cancelled and charged amount refunded to account balance",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "card_id": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing card_id, or card is not in a queued state",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "card_id is required in request body"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Missing or invalid Authorization header"
                }
              }
            }
          },
          "403": {
            "description": "Account is frozen. The response includes a `frozen_message` field explaining why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FrozenError"
                },
                "example": {
                  "error": "Account is frozen",
                  "frozen_message": "Your account is frozen pending a compliance review. Contact support@laso.finance."
                }
              }
            }
          },
          "404": {
            "description": "Card not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Card not found"
                }
              }
            }
          }
        }
      }
    },
    "/search-merchants": {
      "get": {
        "operationId": "searchMerchants",
        "summary": "Search merchant spend data",
        "description": "Search Laso's merchant database for confirmed spend data for a given card type. Returns whether the card was accepted, not accepted, or unknown at each merchant.\n\nUse `card_type` to search by the USA prepaid card (`Non-Reloadable U.S.`, the default) or the international prepaid card (`Non-Reloadable International`). USA searches exclude merchants with non-US country-code TLDs; international searches do not.\n\n**Important:** This database only contains merchants where Laso users have previously attempted a transaction. A merchant not being listed, or being listed as `unknown`, does NOT mean the card won't work there — it just means it hasn't been tried yet. If a merchant is listed as `accepted`, you can confidently use the card there. If listed as `not_accepted`, the card will fail at that merchant.\n\nRequires a Bearer token from `/auth` or `/get-card`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "Search query — the merchant name to search for (e.g. \"amazon\", \"netflix\")",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "card_type",
            "in": "query",
            "required": false,
            "description": "Which card type to search acceptance for. Defaults to `Non-Reloadable U.S.` if omitted (preserves existing client behavior). Pass `Non-Reloadable International` to search for international prepaid card acceptance.",
            "schema": {
              "type": "string",
              "enum": [
                "Non-Reloadable U.S.",
                "Non-Reloadable International"
              ],
              "default": "Non-Reloadable U.S."
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Merchant search results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "merchants": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MerchantResult"
                      }
                    },
                    "query": {
                      "type": "string",
                      "description": "The search query that was used"
                    },
                    "count": {
                      "type": "integer",
                      "description": "Number of merchants returned"
                    },
                    "card_type": {
                      "type": "string",
                      "description": "The card type results are filtered for",
                      "example": "Non-Reloadable U.S."
                    },
                    "note": {
                      "type": "string",
                      "description": "Important caveat about the data — this database only includes merchants where users have previously attempted transactions"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing search query",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "q query parameter is required"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Missing or invalid Authorization header"
                }
              }
            }
          }
        }
      }
    },
    "/get-auth-link": {
      "get": {
        "operationId": "getAuthLink",
        "summary": "Get a login link for the web dashboard",
        "description": "Returns a URL that a human can open in a browser to log in to the Laso Finance web dashboard as the authenticated user. Useful for humans who want to see what their AI agent has been doing (view cards, transactions, balances, etc.).\n\nThe URL carries a short single-use login code rather than a long token, so relay it to your human verbatim; it passes through tool-output credential filters intact. Do not fetch or open the URL yourself: redeeming the code consumes the human's login.\n\nRequires a Bearer token from `/auth` or `/get-card`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Auth link generated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "auth_url": {
                      "type": "string",
                      "description": "URL to open in a browser to log in to the Laso Finance dashboard. Carries a short single-use login code.",
                      "example": "https://laso.finance/agent/dashboard/auth?code=K7MPQ-W3XZ9"
                    },
                    "user_id": {
                      "type": "string",
                      "description": "The authenticated user's ID (wallet address)",
                      "example": "0xabc..."
                    },
                    "expires_in": {
                      "type": "number",
                      "description": "Seconds until the login code expires",
                      "example": 900
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Missing or invalid Authorization header"
                }
              }
            }
          },
          "403": {
            "description": "Account is frozen. The response includes a `frozen_message` field explaining why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FrozenError"
                },
                "example": {
                  "error": "Account is frozen",
                  "frozen_message": "Your account is frozen pending a compliance review. Contact support@laso.finance."
                }
              }
            }
          }
        }
      }
    },
    "/submit-agent-feedback": {
      "post": {
        "operationId": "submitAgentFeedback",
        "summary": "Send feedback about the API",
        "description": "Send feedback about the Laso API to the humans who run it: what worked, what was confusing, what you wish existed. It lands directly on their dashboard, so it is the best channel for reporting API friction or requesting features.\n\nThis endpoint is served from the Cloud Function URL (`https://us-central1-kyc-ts.cloudfunctions.net/submitAgentFeedback`), not the `https://laso.finance` base URL used by the other routes. Send the `id_token` from `/auth` as a Bearer token.\n\nFeedback is only accepted after the authenticated account has completed at least one real action (a settled deposit, purchase, or withdrawal), and is rate limited to at most 5 entries per 24 hours.\n\nRequires a Bearer token from `/auth` or `/get-card`.",
        "servers": [
          {
            "url": "https://us-central1-kyc-ts.cloudfunctions.net"
          }
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "feedback"
                ],
                "properties": {
                  "feedback": {
                    "type": "string",
                    "description": "The main free-text feedback. Required. Sanitized and capped server-side (max 2000 characters).",
                    "example": "order-intl-card was smooth but I wanted a way to see the fee before paying"
                  },
                  "what_they_want": {
                    "type": "string",
                    "description": "What you were trying to do. Optional. Capped at 500 characters.",
                    "example": "order an international card"
                  },
                  "how_it_went": {
                    "type": "string",
                    "description": "How it went. Optional. Capped at 500 characters.",
                    "example": "worked, minor confusion on fees"
                  },
                  "endpoint": {
                    "type": "string",
                    "description": "Which API endpoint or route the feedback is about. Optional. Capped at 120 characters.",
                    "example": "/order-intl-card"
                  },
                  "rating": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 5,
                    "description": "A 1-5 satisfaction rating. Optional; ignored when not an integer in range.",
                    "example": 4
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Feedback recorded",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "example": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The `feedback` field is missing or empty",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "The feedback field is required."
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Invalid or expired id token."
                }
              }
            }
          },
          "403": {
            "description": "The account has not completed a real action (settled deposit, purchase, or withdrawal) yet",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "You must complete a deposit, purchase, or withdrawal before submitting feedback."
                }
              }
            }
          },
          "429": {
            "description": "Feedback rate limit reached (at most 5 entries per 24 hours)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Feedback rate limit reached. You may submit at most 5 feedback entries per 24 hours."
                }
              }
            }
          }
        }
      }
    },
    "/listAddressBook": {
      "get": {
        "operationId": "listAddressBook",
        "summary": "List saved recipients",
        "description": "The saved recipient names your human manages on their dashboard, so a request like \"send $20 to the coffee vendor\" can be resolved to an address instead of asking them for it.\n\nThis endpoint is served from the Cloud Function URL (`https://us-central1-kyc-ts.cloudfunctions.net/listAddressBook`), not the `https://laso.finance` base URL used by the other routes. Send the `id_token` from `/auth` as a Bearer token. Free.\n\nIt operates only on the authenticated account's own book — there is no user parameter to pass.\n\n**A name is a label, not an instruction.** `agentWalletTransfer` takes an address and deliberately does not accept a name: a rename between your human asking and you sending would otherwise move money somewhere they did not intend. When you resolve a name to an address, say the address back to them before sending.",
        "servers": [
          {
            "url": "https://us-central1-kyc-ts.cloudfunctions.net"
          }
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The saved recipients, most recently saved first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "entries": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "address": {
                            "type": "string",
                            "description": "The recipient's Solana address. This is the entry's key.",
                            "example": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU"
                          },
                          "name": {
                            "type": "string",
                            "description": "The name your human gave this recipient.",
                            "example": "Coffee vendor"
                          },
                          "saved_at": {
                            "type": "integer",
                            "description": "When the entry was last saved or renamed, in milliseconds since epoch."
                          },
                          "last_used_at": {
                            "type": "integer",
                            "description": "When a transfer last went to this address, in milliseconds since epoch. Absent if never sent to."
                          },
                          "send_count": {
                            "type": "integer",
                            "description": "How many transfers have gone to this address."
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, or expired Bearer token."
          }
        }
      }
    },
    "/saveAddressBookEntry": {
      "post": {
        "operationId": "saveAddressBookEntry",
        "summary": "Save or rename a recipient",
        "description": "The saved recipient names your human manages on their dashboard, so a request like \"send $20 to the coffee vendor\" can be resolved to an address instead of asking them for it.\n\nThis endpoint is served from the Cloud Function URL (`https://us-central1-kyc-ts.cloudfunctions.net/saveAddressBookEntry`), not the `https://laso.finance` base URL used by the other routes. Send the `id_token` from `/auth` as a Bearer token. Free.\n\nIt operates only on the authenticated account's own book — there is no user parameter to pass.\n\n**A name is a label, not an instruction.** `agentWalletTransfer` takes an address and deliberately does not accept a name: a rename between your human asking and you sending would otherwise move money somewhere they did not intend. When you resolve a name to an address, say the address back to them before sending.\n\nThe address is the entry's key, so saving one that is already saved renames it rather than creating a duplicate. That makes this safe to retry.",
        "servers": [
          {
            "url": "https://us-central1-kyc-ts.cloudfunctions.net"
          }
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "address",
                  "name"
                ],
                "properties": {
                  "address": {
                    "type": "string",
                    "description": "The recipient's Solana address.",
                    "example": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU"
                  },
                  "name": {
                    "type": "string",
                    "description": "The name to save it under. Sanitized and capped server-side (max 60 characters).",
                    "example": "Coffee vendor"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The saved entry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "address": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "address is not a valid Solana address, or name is missing."
          },
          "401": {
            "description": "Missing, invalid, or expired Bearer token."
          }
        }
      }
    },
    "/deleteAddressBookEntry": {
      "delete": {
        "operationId": "deleteAddressBookEntry",
        "summary": "Remove a saved recipient",
        "description": "The saved recipient names your human manages on their dashboard, so a request like \"send $20 to the coffee vendor\" can be resolved to an address instead of asking them for it.\n\nThis endpoint is served from the Cloud Function URL (`https://us-central1-kyc-ts.cloudfunctions.net/deleteAddressBookEntry`), not the `https://laso.finance` base URL used by the other routes. Send the `id_token` from `/auth` as a Bearer token. Free.\n\nIt operates only on the authenticated account's own book — there is no user parameter to pass.\n\n**A name is a label, not an instruction.** `agentWalletTransfer` takes an address and deliberately does not accept a name: a rename between your human asking and you sending would otherwise move money somewhere they did not intend. When you resolve a name to an address, say the address back to them before sending.\n\nRemoving an entry only forgets the name; it has no effect on past transfers to that address.",
        "servers": [
          {
            "url": "https://us-central1-kyc-ts.cloudfunctions.net"
          }
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "address",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "The address to remove. May be sent here or in the JSON body."
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "address": {
                    "type": "string",
                    "description": "The address to remove."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The removed address.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "address": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "address is required."
          },
          "401": {
            "description": "Missing, invalid, or expired Bearer token."
          }
        }
      }
    },
    "/get-push-to-card": {
      "get": {
        "operationId": "pushToCard",
        "summary": "Send money to a debit card (USD, EUR, GBP)",
        "description": "Pay USDC to initiate a push-to-card transfer. Specify the face value in the target currency (USD, EUR, or GBP); Laso computes the USDC cost using the current exchange rate plus a 4.8% fee (minimum fee of \\$1.50/€1.50/£1.50 in the transfer currency), and that becomes the x402 price. Returns a `redemption_url` that must be opened to complete the transfer. The form requires sender name, debit card number, and cardholder name.\n\n**Supported currencies:**\n- `USD` — U.S. debit cards (U.S. bank accounts only)\n- `EUR` — Eurozone debit cards\n- `GBP` — U.K. debit cards\n\nLaso cannot perform the transfer directly — the redemption URL must be visited and the debit card details entered manually (by the agent or a human).\n\n**Fee:** 4.8%, with a minimum of 1.50 in the transfer currency (included in the USDC price, computed from the requested face value).",
        "x-x402": [
          {
            "price": "dynamic",
            "priceRange": "~$11.50–$10,000 USD (face value 10–9,541.98 in USD/EUR/GBP + 4.8% fee with a 1.50 minimum, converted to USD)",
            "scheme": "exact",
            "network": "eip155:8453",
            "payTo": "0x3291e96b3bff7ed56e3ca8364273c5b4654b2b37"
          },
          {
            "price": "dynamic",
            "priceRange": "~$11.50–$10,000 USD (face value 10–9,541.98 in USD/EUR/GBP + 4.8% fee with a 1.50 minimum, converted to USD)",
            "scheme": "exact",
            "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
            "payTo": "3MZVk97x9SeRxbYpc3jhzRfU2fyA3emYutnqfn9kNfYX"
          }
        ],
        "x-payment-info": {
          "price": {
            "mode": "dynamic",
            "currency": "USD",
            "min": "11.50",
            "max": "10000"
          },
          "protocols": [
            {
              "x402": {}
            }
          ]
        },
        "parameters": [
          {
            "name": "amount",
            "in": "query",
            "required": true,
            "description": "Face value to send to the debit card, denominated in the chosen `currency` (min 10, max 9,541.98). The x402 payment price is this face value converted to USD plus a 4.8% fee (minimum fee of 1.50 in the chosen currency).",
            "schema": {
              "type": "number",
              "minimum": 10,
              "maximum": 9541.98
            }
          },
          {
            "name": "currency",
            "in": "query",
            "required": false,
            "description": "Target currency for the debit card transfer. Defaults to `USD`.",
            "schema": {
              "type": "string",
              "enum": [
                "USD",
                "EUR",
                "GBP"
              ],
              "default": "USD"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Push-to-card transfer initiated. Open the redemption_url to enter debit card details.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "auth": {
                      "$ref": "#/components/schemas/AuthCredentials"
                    },
                    "callable_base_url": {
                      "type": "string",
                      "example": "https://us-central1-kyc-ts.cloudfunctions.net"
                    },
                    "user_id": {
                      "type": "string",
                      "description": "The user's ID (lowercase wallet address)"
                    },
                    "success": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string",
                      "example": "Push-to-card transfer initiated. Open the redemption_url to enter your debit card details and complete the transfer."
                    },
                    "amount": {
                      "type": "number",
                      "description": "The face value sent to the debit card, in the requested `currency`"
                    },
                    "currency": {
                      "type": "string",
                      "enum": [
                        "USD",
                        "EUR",
                        "GBP"
                      ],
                      "description": "The target currency of the debit card transfer"
                    },
                    "redemption_url": {
                      "type": "string",
                      "description": "URL to open and complete the push-to-card transfer. The form requires: sender name, debit card number, and cardholder name."
                    },
                    "note": {
                      "type": "string",
                      "description": "Important information about the currency's regional restriction and required form fields"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid or missing amount, or no payer address",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "currency must be USD, EUR, or GBP. Received: XYZ"
                }
              }
            }
          },
          "402": {
            "description": "Payment required. No valid x402 payment header was included. The response body is an empty JSON object; the payment details (price, recipient address, network) are base64-encoded in the `PAYMENT-REQUIRED` response header. x402 client libraries handle this automatically. A **second** 402 on the paid retry means something different: the payment header verified but the transfer could not be settled on-chain. That response body is not empty and carries no `accepts` — it is the x402 settlement-failure shape `{\"success\": false, \"errorReason\": \"...\", \"errorMessage\": \"...\"}`, plus `x_laso_guidance` when a concrete next step applies. Distinguish the two by body: a challenge has `accepts`, a failure has `success: false`. The usual cause is an underfunded wallet, and the usual cause of that is the fee being charged **on top of** `amount` (a wallet holding exactly \\$2,000 cannot send a \\$2,000 payment). Nothing is charged for a failed settlement, so retrying with a smaller amount is safe.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "example": {}
                }
              }
            }
          },
          "403": {
            "description": "Account is frozen. The response includes a `frozen_message` field explaining why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FrozenError"
                },
                "example": {
                  "error": "Account is frozen",
                  "frozen_message": "Your account is frozen pending a compliance review. Contact support@laso.finance."
                }
              }
            }
          }
        }
      }
    },
    "/send-payment": {
      "get": {
        "operationId": "sendPayment",
        "summary": "Send a payment via Venmo or PayPal",
        "description": "Pay USDC to send money to a Venmo or PayPal recipient. The x402 USDC price is the requested amount plus a 4.9% fee (with a \\$1.50 minimum fee). On-chain payment credits the calling wallet's Laso account balance via the standard deposit webhook; the callable then debits the gross amount and dispatches the payout.\n\n**KYC required.** The first time a wallet sends a Venmo or PayPal payout it must complete identity verification. If the wallet is not yet verified the response returns `kyc_required: true` and a `kyc_url` — open the URL, complete the flow, and retry. If you don't want to proceed, the credited account balance can be withdrawn with `POST /withdraw`.\n\n**Recipient details.** Both Venmo and PayPal require the recipient's first name and last name. For Venmo, `recipient_id` is the recipient's 10-digit U.S. phone number and `recipient_email` is required. For PayPal, `recipient_id` is the recipient's PayPal email, which also serves as `recipient_email` (so `recipient_email` is optional).\n\n**Fee:** 4.9% with a \\$1.50 minimum (included in the USDC price).",
        "x-x402": [
          {
            "price": "dynamic",
            "priceRange": "~$6.50–$1,049 USD (amount $5–$1,000 × 1.049 fee, min $1.50 fee)",
            "scheme": "exact",
            "network": "eip155:8453",
            "payTo": "0x3291e96b3bff7ed56e3ca8364273c5b4654b2b37"
          },
          {
            "price": "dynamic",
            "priceRange": "~$6.50–$1,049 USD (amount $5–$1,000 × 1.049 fee, min $1.50 fee)",
            "scheme": "exact",
            "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
            "payTo": "3MZVk97x9SeRxbYpc3jhzRfU2fyA3emYutnqfn9kNfYX"
          }
        ],
        "x-payment-info": {
          "price": {
            "mode": "dynamic",
            "currency": "USD",
            "min": "6.50",
            "max": "1049"
          },
          "protocols": [
            {
              "x402": {}
            }
          ]
        },
        "parameters": [
          {
            "name": "amount",
            "in": "query",
            "required": true,
            "description": "USD amount to send to the recipient (min \\$5, max \\$1,000). The x402 payment price is this amount plus a 4.9% fee (with a \\$1.50 minimum).",
            "schema": {
              "type": "number",
              "minimum": 5,
              "maximum": 1000
            }
          },
          {
            "name": "platform",
            "in": "query",
            "required": true,
            "description": "Payment platform.",
            "schema": {
              "type": "string",
              "enum": [
                "venmo",
                "paypal"
              ]
            }
          },
          {
            "name": "recipient_id",
            "in": "query",
            "required": true,
            "description": "Recipient identifier. For Venmo: 10-digit U.S. phone number. For PayPal: email address.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "recipient_first_name",
            "in": "query",
            "required": true,
            "description": "Recipient's first name (English letters A-Z and a-z only).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "recipient_last_name",
            "in": "query",
            "required": true,
            "description": "Recipient's last name (English letters A-Z and a-z only).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "recipient_email",
            "in": "query",
            "required": false,
            "description": "Recipient's email address. Required for Venmo. Optional for PayPal, where it defaults to recipient_id (the PayPal email).",
            "schema": {
              "type": "string",
              "format": "email"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Payment dispatched, or KYC required.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "properties": {
                        "auth": {
                          "$ref": "#/components/schemas/AuthCredentials"
                        },
                        "callable_base_url": {
                          "type": "string"
                        },
                        "user_id": {
                          "type": "string"
                        },
                        "success": {
                          "type": "boolean"
                        },
                        "message": {
                          "type": "string"
                        },
                        "platform": {
                          "type": "string",
                          "enum": [
                            "venmo",
                            "paypal"
                          ]
                        },
                        "amount": {
                          "type": "number"
                        },
                        "recipient_id": {
                          "type": "string"
                        },
                        "state": {
                          "type": "string",
                          "description": "Payment lifecycle state (e.g. `in-process`, `queued`)."
                        }
                      }
                    },
                    {
                      "type": "object",
                      "properties": {
                        "auth": {
                          "$ref": "#/components/schemas/AuthCredentials"
                        },
                        "callable_base_url": {
                          "type": "string"
                        },
                        "user_id": {
                          "type": "string"
                        },
                        "kyc_required": {
                          "type": "boolean",
                          "enum": [
                            true
                          ]
                        },
                        "kyc_url": {
                          "type": "string",
                          "description": "URL the wallet owner must open to complete identity verification. No payout was sent, but the x402 payment you made was still credited to your Laso account balance. After verifying, retry /send-payment to dispatch the payout, or call POST /withdraw to pull the credited USDC back to your wallet."
                        },
                        "message": {
                          "type": "string",
                          "description": "Human-readable explanation of the KYC requirement and how to recover the credited funds via retry or POST /withdraw."
                        },
                        "platform": {
                          "type": "string",
                          "enum": [
                            "venmo",
                            "paypal"
                          ]
                        },
                        "amount": {
                          "type": "number"
                        },
                        "recipient_id": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid or missing parameters, or no payer address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Payment required — response body contains x402 payment details. A **second** 402 on the paid retry means something different: the payment header verified but the transfer could not be settled on-chain. That response body is not empty and carries no `accepts` — it is the x402 settlement-failure shape `{\"success\": false, \"errorReason\": \"...\", \"errorMessage\": \"...\"}`, plus `x_laso_guidance` when a concrete next step applies. Distinguish the two by body: a challenge has `accepts`, a failure has `success: false`. The usual cause is an underfunded wallet, and the usual cause of that is the fee being charged **on top of** `amount` (a wallet holding exactly \\$2,000 cannot send a \\$2,000 payment). Nothing is charged for a failed settlement, so retrying with a smaller amount is safe."
          }
        }
      }
    },
    "/send-bank-payment": {
      "get": {
        "operationId": "sendBankPayment",
        "summary": "Send dollars to a bank account",
        "description": "Pay USDC to send dollars to a bank account by ACH. The x402 USDC price is the requested amount plus a 0.25% transfer fee (with a \\$1.50 minimum fee). On-chain payment credits the calling wallet's Laso account balance via the standard deposit webhook; the callable then debits the gross amount and queues the transfer.\n\n**A bank destination is required first.** `destination_id` comes from the banking callables: create the banking profile with `createBankingProfile`, register who is being paid with `createBankingRecipient`, and attach their bank account with `addBankingDestination`, which returns the id. List what you already have at `GET /bank-recipients` (free). See the [bank accounts guide](/guides/agent-bank-accounts) for the full setup.\n\n**Identity verification is required** on the account that owns the banking profile, and only the human owner can complete it. `createBankingProfile` hands back a `kycUrl` when it is outstanding.\n\n**If the payout cannot be fulfilled** (no approved banking profile, a destination that is not yours, an amount out of range), nothing is stranded: the USDC you paid has already credited your account balance. Fix the problem and retry, or recover it with `POST /withdraw`.\n\n**Settlement:** ACH, normally 1-2 business days. Follow it with `listBankingTransactions` / `getBankingTransaction`.\n\n**Fee:** 0.25% with a \\$1.50 minimum (included in the USDC price).",
        "x-x402": [
          {
            "price": "dynamic",
            "priceRange": "~$11.50–$50,125 USD (amount $10–$50,000 plus a 0.25% fee, min $1.50 fee)",
            "scheme": "exact",
            "network": "eip155:8453",
            "payTo": "0x3291e96b3bff7ed56e3ca8364273c5b4654b2b37"
          },
          {
            "price": "dynamic",
            "priceRange": "~$11.50–$50,125 USD (amount $10–$50,000 plus a 0.25% fee, min $1.50 fee)",
            "scheme": "exact",
            "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
            "payTo": "3MZVk97x9SeRxbYpc3jhzRfU2fyA3emYutnqfn9kNfYX"
          }
        ],
        "x-payment-info": {
          "price": {
            "mode": "dynamic",
            "currency": "USD",
            "min": "11.50",
            "max": "10025"
          },
          "protocols": [
            {
              "x402": {}
            }
          ]
        },
        "parameters": [
          {
            "name": "amount",
            "in": "query",
            "required": true,
            "description": "USD delivered to the recipient's bank account (min \\$10, max \\$50,000). The x402 payment price is this amount plus a 0.25% fee (with a \\$1.50 minimum).",
            "schema": {
              "type": "number",
              "minimum": 10,
              "maximum": 50000
            }
          },
          {
            "name": "destination_id",
            "in": "query",
            "required": true,
            "description": "Bank destination to pay out to. Returned by `addBankingDestination`, and listed by `GET /bank-recipients`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Bank payment queued.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "auth": {
                      "$ref": "#/components/schemas/AuthCredentials"
                    },
                    "callable_base_url": {
                      "type": "string"
                    },
                    "user_id": {
                      "type": "string"
                    },
                    "success": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    },
                    "bank_payment": {
                      "type": "object",
                      "properties": {
                        "payout_id": {
                          "type": "string"
                        },
                        "amount": {
                          "type": "number",
                          "description": "USD delivered to the bank account."
                        },
                        "fee_amount": {
                          "type": "number",
                          "description": "Transfer fee charged on top of the amount."
                        },
                        "charged_amount": {
                          "type": "number",
                          "description": "amount + fee_amount, the total debited."
                        },
                        "destination_id": {
                          "type": "string"
                        },
                        "state": {
                          "type": "string",
                          "example": "in-process"
                        },
                        "timestamp": {
                          "type": "number"
                        },
                        "destination": {
                          "type": "object",
                          "description": "The bank account being paid, summarized. The account number is masked to its last four digits.",
                          "properties": {
                            "name": {
                              "type": "string"
                            },
                            "bank_name": {
                              "type": "string"
                            },
                            "account_holder_name": {
                              "type": "string"
                            },
                            "account_number_last4": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing or out-of-range `amount`, or a missing `destination_id`."
          },
          "402": {
            "description": "Payment required. Includes the x402 payment challenge. A **second** 402 on the paid retry means something different: the payment header verified but the transfer could not be settled on-chain. That response body is not empty and carries no `accepts` — it is the x402 settlement-failure shape `{\"success\": false, \"errorReason\": \"...\", \"errorMessage\": \"...\"}`, plus `x_laso_guidance` when a concrete next step applies. Distinguish the two by body: a challenge has `accepts`, a failure has `success: false`. The usual cause is an underfunded wallet, and the usual cause of that is the fee being charged **on top of** `amount` (a wallet holding exactly \\$2,000 cannot send a \\$2,000 payment). Nothing is charged for a failed settlement, so retrying with a smaller amount is safe."
          },
          "403": {
            "description": "Account is frozen."
          },
          "412": {
            "description": "No approved banking profile, or the destination is not one of yours. The USDC paid is credited to your account balance and recoverable with `POST /withdraw`."
          }
        }
      }
    },
    "/bank-recipients": {
      "get": {
        "operationId": "listBankRecipients",
        "summary": "List bank payout recipients (free)",
        "description": "Lists the recipients registered on your banking profile and the destinations attached to each, so you can find the `destination_id` that `GET /send-bank-payment` pays out to. Free — only the payout itself is a paid action.\n\nBank account numbers are returned masked to their last four digits; routing numbers are returned in full. Create recipients and destinations with the `createBankingRecipient` and `addBankingDestination` callables, rename a destination's `nickname` (the only editable field) with `updateBankingDestination`, and remove a recipient with `deleteBankingRecipient` (see the [bank accounts guide](/guides/agent-bank-accounts)).\n\nRequires a Bearer token from `/auth` or `/get-card`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The recipients on this banking profile.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "user_id": {
                      "type": "string"
                    },
                    "recipients": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "recipient_id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string"
                          },
                          "address": {
                            "type": "object",
                            "description": "The payee's postal address, when one is set."
                          },
                          "destinations": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "destination_id": {
                                  "type": "string",
                                  "description": "Pass this as `destination_id` to `GET /send-bank-payment`."
                                },
                                "destination_type": {
                                  "type": "string",
                                  "enum": [
                                    "fiat_us",
                                    "fiat_iban",
                                    "crypto"
                                  ]
                                },
                                "name": {
                                  "type": "string"
                                },
                                "nickname": {
                                  "type": "string",
                                  "description": "User-chosen label, present when one is set. The only editable field on a destination: set it at creation with `addBankingDestination` or later with `updateBankingDestination`."
                                },
                                "bank_name": {
                                  "type": "string"
                                },
                                "account_holder_name": {
                                  "type": "string"
                                },
                                "account_number_last4": {
                                  "type": "string"
                                },
                                "routing_number": {
                                  "type": "string"
                                },
                                "crypto_address": {
                                  "type": "string"
                                },
                                "network_id": {
                                  "type": "string"
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token."
          },
          "403": {
            "description": "Account is frozen."
          },
          "412": {
            "description": "No banking profile yet. Call `createBankingProfile` first."
          }
        }
      }
    },
    "/search-gift-cards": {
      "get": {
        "operationId": "searchGiftCards",
        "summary": "Search the gift card catalog",
        "description": "Browse and search available gift cards. Returns a list of gift card products with pricing, denomination, and catalog information. Use the `laso_server_id` from the results to order a card via `GET /order-gift-card`.\n\nEvery response includes a `facets` object listing the valid values for the `category`, `currency`, and `country` filters. The facets are computed from the full catalog, so a single unfiltered request (no query parameters) is enough to discover everything you can filter on.\n\nThis is a free endpoint — no x402 payment required. Requires a Bearer token from `/auth`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Search query to filter gift cards by name (e.g. \"Amazon\", \"Uber\")",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "country",
            "in": "query",
            "required": false,
            "description": "ISO 3166-1 alpha-2 country code to filter by (e.g. \"US\", \"GB\"). Returns gift cards available in that country, including borderless products that have no country restriction.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "currency",
            "in": "query",
            "required": false,
            "description": "Currency code to filter by (e.g. \"USD\", \"EUR\")",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "Category to filter by (e.g. \"ecommerce\", \"travel\", \"gaming\"). See the `facets.categories` array in any response for the full list of valid values.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of matching gift card products",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "gift_cards": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/GiftCardProduct"
                      }
                    },
                    "count": {
                      "type": "integer",
                      "description": "Number of results returned"
                    },
                    "filters": {
                      "type": "object",
                      "properties": {
                        "query": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "country": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "currency": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "category": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      }
                    },
                    "facets": {
                      "type": "object",
                      "description": "Valid filter values, computed from the full catalog. Use these to discover what you can pass to the `category`, `currency`, and `country` query parameters.",
                      "properties": {
                        "categories": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "All valid `category` filter values."
                        },
                        "currencies": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "All valid `currency` filter values."
                        },
                        "countries": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "All valid `country` filter values (ISO 3166-1 alpha-2 codes)."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Missing or invalid Authorization header"
                }
              }
            }
          }
        }
      }
    },
    "/order-gift-card": {
      "get": {
        "operationId": "orderGiftCard",
        "summary": "Order a gift card",
        "description": "Pay USDC via x402 to order a gift card. First browse the catalog via `GET /search-gift-cards` to find the `laso_server_id` for the card you want, then call this endpoint with the amount and product ID.\n\n**Pricing:** `amount` is the card's face value in the product's own currency, not USD. Laso converts it to USD at the current exchange rate and adds the product's fee (up to 4.8%); that total is the x402 USDC price. A 100 SAR card costs roughly \\$28 USDC, not \\$100. Check the product's `currency` field in `GET /search-gift-cards` to see what `amount` is denominated in.\n\nThe \\$5 minimum and \\$9,000 maximum apply to the **converted USD value**, not the raw amount, so a foreign-currency amount is accepted only when its USD equivalent falls inside that range.\n\nReturns redemption details (URL, code, and/or PIN) depending on the gift card brand.",
        "x-x402": [
          {
            "price": "dynamic",
            "priceRange": "$5–$9,000 USD (converted from the product's own currency)",
            "scheme": "exact",
            "network": "eip155:8453",
            "payTo": "0x3291e96b3bff7ed56e3ca8364273c5b4654b2b37"
          },
          {
            "price": "dynamic",
            "priceRange": "$5–$9,000 USD (converted from the product's own currency)",
            "scheme": "exact",
            "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
            "payTo": "3MZVk97x9SeRxbYpc3jhzRfU2fyA3emYutnqfn9kNfYX"
          }
        ],
        "x-payment-info": {
          "price": {
            "mode": "dynamic",
            "currency": "USD",
            "min": "5",
            "max": "9432"
          },
          "protocols": [
            {
              "x402": {}
            }
          ]
        },
        "parameters": [
          {
            "name": "amount",
            "in": "query",
            "required": true,
            "description": "Gift card face value in the product's own currency (see the product's `currency` field in `GET /search-gift-cards`), not USD. The x402 price is this value converted to USD plus the product fee. After conversion it must be worth at least \\$5 and at most \\$9,000 USD.",
            "schema": {
              "type": "number",
              "exclusiveMinimum": 0
            }
          },
          {
            "name": "laso_server_id",
            "in": "query",
            "required": true,
            "description": "The product identifier from the gift card catalog (GET /search-gift-cards)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "country",
            "in": "query",
            "required": false,
            "description": "ISO 3166-1 alpha-2 country code (defaults to \"US\")",
            "schema": {
              "type": "string",
              "default": "US"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Gift card ordered successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "auth": {
                      "$ref": "#/components/schemas/AuthCredentials"
                    },
                    "callable_base_url": {
                      "type": "string",
                      "example": "https://us-central1-kyc-ts.cloudfunctions.net"
                    },
                    "user_id": {
                      "type": "string",
                      "description": "The user's ID (lowercase wallet address)"
                    },
                    "gift_card": {
                      "$ref": "#/components/schemas/GiftCardOrder"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid or missing parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "laso_server_id query parameter is required"
                }
              }
            }
          },
          "402": {
            "description": "Payment required. No valid x402 payment header was included. The response body is an empty JSON object; the payment details (price, recipient address, network) are base64-encoded in the `PAYMENT-REQUIRED` response header. x402 client libraries handle this automatically. A **second** 402 on the paid retry means something different: the payment header verified but the transfer could not be settled on-chain. That response body is not empty and carries no `accepts` — it is the x402 settlement-failure shape `{\"success\": false, \"errorReason\": \"...\", \"errorMessage\": \"...\"}`, plus `x_laso_guidance` when a concrete next step applies. Distinguish the two by body: a challenge has `accepts`, a failure has `success: false`. The usual cause is an underfunded wallet, and the usual cause of that is the fee being charged **on top of** `amount` (a wallet holding exactly \\$2,000 cannot send a \\$2,000 payment). Nothing is charged for a failed settlement, so retrying with a smaller amount is safe.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "example": {}
                }
              }
            }
          },
          "403": {
            "description": "Account is frozen. The response includes a `frozen_message` field explaining why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FrozenError"
                },
                "example": {
                  "error": "Account is frozen",
                  "frozen_message": "Your account is frozen pending a compliance review. Contact support@laso.finance."
                }
              }
            }
          }
        }
      }
    },
    "/refresh": {
      "post": {
        "operationId": "refreshTokenDeprecated",
        "summary": "Refresh an ID token (deprecated)",
        "description": "**Deprecated:** Use `POST /auth` with `grant_type=refresh_token` instead. This endpoint is kept for backward compatibility.\n\nExchange a refresh token for a new ID token. This is a free endpoint — no x402 payment required.",
        "deprecated": true,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "refresh_token"
                ],
                "properties": {
                  "refresh_token": {
                    "type": "string",
                    "description": "The refresh token received from /auth or a previous refresh call"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Refreshed auth credentials and user ID",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "auth": {
                      "$ref": "#/components/schemas/AuthCredentials"
                    },
                    "user_id": {
                      "type": "string",
                      "description": "The user's ID (lowercase wallet address)"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing refresh_token in request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "refresh_token is required in request body"
                }
              }
            }
          },
          "401": {
            "description": "Token is invalid or revoked",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Failed to refresh token. Token may be invalid or revoked."
                }
              }
            }
          }
        },
        "security": []
      }
    },
    "/createBankingProfile": {
      "post": {
        "operationId": "createBankingProfile",
        "summary": "Create the banking profile",
        "tags": [
          "Banking"
        ],
        "description": "Opens the banking profile that all other banking calls hang off. Identity verification is required first; if the human has not verified, the reply carries `kycRequired: true` and a `kycUrl` to hand them. **This is the only step an agent cannot complete itself.** Retry once they finish.\n\nThis is a Firebase callable served from the Cloud Function URL (`https://us-central1-kyc-ts.cloudfunctions.net/createBankingProfile`), not the `https://laso.finance` base URL used by the paywalled routes. Callables wrap the request in `{\"data\": ...}` and the reply in `{\"result\": ...}`. Send the `id_token` from `/auth` as a Bearer token. Free.",
        "servers": [
          {
            "url": "https://us-central1-kyc-ts.cloudfunctions.net"
          }
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "data"
                ],
                "properties": {
                  "data": {
                    "type": "object",
                    "required": [
                      "userId"
                    ],
                    "properties": {
                      "userId": {
                        "type": "string",
                        "description": "The Laso user id (the `usr_...` value from /auth).",
                        "example": "usr_7aff81763cae58c936aa6de67dd5a95e"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The profile, or a `kycRequired` reply with the link to give the human.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "result": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing or invalid."
          },
          "401": {
            "description": "Missing, invalid, or expired Bearer token."
          },
          "403": {
            "description": "The caller is not this user and is not an admin."
          }
        }
      }
    },
    "/getBankingProfileStatus": {
      "post": {
        "operationId": "getBankingProfileStatus",
        "summary": "Check the banking profile status",
        "tags": [
          "Banking"
        ],
        "description": "Reads the current state of the banking profile: whether it exists, and where its application stands.\n\nThis is a Firebase callable served from the Cloud Function URL (`https://us-central1-kyc-ts.cloudfunctions.net/getBankingProfileStatus`), not the `https://laso.finance` base URL used by the paywalled routes. Callables wrap the request in `{\"data\": ...}` and the reply in `{\"result\": ...}`. Send the `id_token` from `/auth` as a Bearer token. Free.",
        "servers": [
          {
            "url": "https://us-central1-kyc-ts.cloudfunctions.net"
          }
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "data"
                ],
                "properties": {
                  "data": {
                    "type": "object",
                    "required": [
                      "userId"
                    ],
                    "properties": {
                      "userId": {
                        "type": "string",
                        "description": "The Laso user id (the `usr_...` value from /auth).",
                        "example": "usr_7aff81763cae58c936aa6de67dd5a95e"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The profile status.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "result": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing or invalid."
          },
          "401": {
            "description": "Missing, invalid, or expired Bearer token."
          },
          "403": {
            "description": "The caller is not this user and is not an admin."
          }
        }
      }
    },
    "/getBankingApplication": {
      "post": {
        "operationId": "getBankingApplication",
        "summary": "Read the banking application",
        "tags": [
          "Banking"
        ],
        "description": "Reads what the application still needs. Returns `ready` (whether it can be submitted), `applicationStatus`, and the outstanding questions. The identity answers carry over from verification; what remains are non-identity questions such as employment status and source of funds, which an agent can answer itself.\n\nThis is a Firebase callable served from the Cloud Function URL (`https://us-central1-kyc-ts.cloudfunctions.net/getBankingApplication`), not the `https://laso.finance` base URL used by the paywalled routes. Callables wrap the request in `{\"data\": ...}` and the reply in `{\"result\": ...}`. Send the `id_token` from `/auth` as a Bearer token. Free.",
        "servers": [
          {
            "url": "https://us-central1-kyc-ts.cloudfunctions.net"
          }
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "data"
                ],
                "properties": {
                  "data": {
                    "type": "object",
                    "required": [
                      "userId"
                    ],
                    "properties": {
                      "userId": {
                        "type": "string",
                        "description": "The Laso user id (the `usr_...` value from /auth).",
                        "example": "usr_7aff81763cae58c936aa6de67dd5a95e"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The application, its status, and what is outstanding.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "result": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing or invalid."
          },
          "401": {
            "description": "Missing, invalid, or expired Bearer token."
          },
          "403": {
            "description": "The caller is not this user and is not an admin."
          }
        }
      }
    },
    "/updateBankingApplicationDetails": {
      "post": {
        "operationId": "updateBankingApplicationDetails",
        "summary": "Answer application questions",
        "tags": [
          "Banking"
        ],
        "description": "Submits answers to the non-identity application questions read from `getBankingApplication`. Call as many times as needed; each call merges.\n\nThis is a Firebase callable served from the Cloud Function URL (`https://us-central1-kyc-ts.cloudfunctions.net/updateBankingApplicationDetails`), not the `https://laso.finance` base URL used by the paywalled routes. Callables wrap the request in `{\"data\": ...}` and the reply in `{\"result\": ...}`. Send the `id_token` from `/auth` as a Bearer token. Free.",
        "servers": [
          {
            "url": "https://us-central1-kyc-ts.cloudfunctions.net"
          }
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "data"
                ],
                "properties": {
                  "data": {
                    "type": "object",
                    "required": [
                      "userId",
                      "details"
                    ],
                    "properties": {
                      "userId": {
                        "type": "string",
                        "description": "The Laso user id (the `usr_...` value from /auth).",
                        "example": "usr_7aff81763cae58c936aa6de67dd5a95e"
                      },
                      "details": {
                        "type": "object",
                        "description": "The answers to submit, keyed by the field names `getBankingApplication` reported as outstanding."
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated application.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "result": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing or invalid."
          },
          "401": {
            "description": "Missing, invalid, or expired Bearer token."
          },
          "403": {
            "description": "The caller is not this user and is not an admin."
          }
        }
      }
    },
    "/uploadBankingDocument": {
      "post": {
        "operationId": "uploadBankingDocument",
        "summary": "Upload a proof-of-address document",
        "tags": [
          "Banking"
        ],
        "description": "Attaches a proof-of-address document to the banking application, for the cases where the partner asks for one. The application must already exist and have an individual on it, so complete the application first.\n\n**Two things reject uploads that otherwise look fine.** A `.jpg` file must be sent as `fileType: \"jpeg\"` — `\"jpg\"` is refused. And the partner caps the *decoded* size at 10MB, even though their own upload page advertises 20MB; oversized files are rejected here before the round-trip.\n\n`fileContent` accepts either a bare base64 string or a `data:` URL, so a browser `FileReader.readAsDataURL` result can be passed through unchanged.\n\nThis is a Firebase callable served from the Cloud Function URL (`https://us-central1-kyc-ts.cloudfunctions.net/uploadBankingDocument`), not the `https://laso.finance` base URL used by the paywalled routes. Callables wrap the request in `{\"data\": ...}` and the reply in `{\"result\": ...}`. Send the `id_token` from `/auth` as a Bearer token. Free.",
        "servers": [
          {
            "url": "https://us-central1-kyc-ts.cloudfunctions.net"
          }
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "data"
                ],
                "properties": {
                  "data": {
                    "type": "object",
                    "required": [
                      "userId",
                      "documentType",
                      "fileType",
                      "fileContent",
                      "country"
                    ],
                    "properties": {
                      "userId": {
                        "type": "string",
                        "description": "The Laso user id (the `usr_...` value from /auth).",
                        "example": "usr_7aff81763cae58c936aa6de67dd5a95e"
                      },
                      "documentType": {
                        "type": "string",
                        "enum": [
                          "proof_of_address",
                          "bank_statement",
                          "utility_bill"
                        ],
                        "description": "The kind of document.",
                        "example": "utility_bill"
                      },
                      "fileType": {
                        "type": "string",
                        "enum": [
                          "pdf",
                          "jpeg",
                          "png"
                        ],
                        "description": "The file's format. Send a .jpg as `jpeg`.",
                        "example": "jpeg"
                      },
                      "fileContent": {
                        "type": "string",
                        "description": "The file, base64-encoded. A `data:` URL is also accepted. Decoded size must be 10MB or less."
                      },
                      "country": {
                        "type": "string",
                        "description": "The document's country, ISO 3166-1 alpha-2.",
                        "example": "US"
                      },
                      "filename": {
                        "type": "string",
                        "description": "The file's name.",
                        "example": "utility-bill.pdf"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The created document id and its review status.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "result": {
                      "type": "object",
                      "properties": {
                        "documentId": {
                          "type": "string"
                        },
                        "status": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing or invalid."
          },
          "401": {
            "description": "Missing, invalid, or expired Bearer token."
          },
          "403": {
            "description": "The caller is not this user and is not an admin."
          },
          "412": {
            "description": "The application has no individual to attach a document to yet. Complete the application first."
          }
        }
      }
    },
    "/submitBankingApplication": {
      "post": {
        "operationId": "submitBankingApplication",
        "summary": "Submit the application",
        "tags": [
          "Banking"
        ],
        "description": "Submits the completed application for review. If the partner rejects it as incomplete, the reply says what is still missing rather than failing opaquely.\n\nThis is a Firebase callable served from the Cloud Function URL (`https://us-central1-kyc-ts.cloudfunctions.net/submitBankingApplication`), not the `https://laso.finance` base URL used by the paywalled routes. Callables wrap the request in `{\"data\": ...}` and the reply in `{\"result\": ...}`. Send the `id_token` from `/auth` as a Bearer token. Free.",
        "servers": [
          {
            "url": "https://us-central1-kyc-ts.cloudfunctions.net"
          }
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "data"
                ],
                "properties": {
                  "data": {
                    "type": "object",
                    "required": [
                      "userId"
                    ],
                    "properties": {
                      "userId": {
                        "type": "string",
                        "description": "The Laso user id (the `usr_...` value from /auth).",
                        "example": "usr_7aff81763cae58c936aa6de67dd5a95e"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Whether the submission was accepted, and what is missing if not.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "result": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing or invalid."
          },
          "401": {
            "description": "Missing, invalid, or expired Bearer token."
          },
          "403": {
            "description": "The caller is not this user and is not an admin."
          }
        }
      }
    },
    "/createBankingAccount": {
      "post": {
        "operationId": "createBankingAccount",
        "summary": "Open an on-ramp or off-ramp account",
        "tags": [
          "Banking"
        ],
        "description": "Opens a banking rail. An **on-ramp** issues a virtual US bank account: dollars sent to it arrive as USDC in the managed wallet. An **off-ramp** pays out to a bank account you have registered, and is opened automatically by `/send-bank-payment`, so most agents never call this for one directly.\n\nThis is a Firebase callable served from the Cloud Function URL (`https://us-central1-kyc-ts.cloudfunctions.net/createBankingAccount`), not the `https://laso.finance` base URL used by the paywalled routes. Callables wrap the request in `{\"data\": ...}` and the reply in `{\"result\": ...}`. Send the `id_token` from `/auth` as a Bearer token. Free.",
        "servers": [
          {
            "url": "https://us-central1-kyc-ts.cloudfunctions.net"
          }
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "data"
                ],
                "properties": {
                  "data": {
                    "type": "object",
                    "required": [
                      "userId",
                      "accountType"
                    ],
                    "properties": {
                      "userId": {
                        "type": "string",
                        "description": "The Laso user id (the `usr_...` value from /auth).",
                        "example": "usr_7aff81763cae58c936aa6de67dd5a95e"
                      },
                      "accountType": {
                        "type": "string",
                        "enum": [
                          "onramp",
                          "offramp"
                        ],
                        "description": "Which rail to open."
                      },
                      "cryptoAddress": {
                        "type": "string",
                        "description": "On-ramp only. Where the converted USDC should land. Defaults to the managed agent wallet."
                      },
                      "networkId": {
                        "type": "string",
                        "description": "On-ramp only. Defaults to Solana mainnet."
                      },
                      "asset": {
                        "type": "string",
                        "description": "Crypto asset ticker, uppercase.",
                        "example": "USDC"
                      },
                      "fiatDestinationId": {
                        "type": "string",
                        "description": "Off-ramp only. The bank destination payouts go to, from `addBankingDestination`."
                      },
                      "capabilities": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        },
                        "description": "On-ramp only. Bank rails to support. Defaults to ACH and wire."
                      },
                      "rail": {
                        "type": "string",
                        "description": "The payment rail for the account."
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The account, including its bank details for an on-ramp.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "result": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing or invalid."
          },
          "401": {
            "description": "Missing, invalid, or expired Bearer token."
          },
          "403": {
            "description": "The caller is not this user and is not an admin."
          }
        }
      }
    },
    "/listBankingAccounts": {
      "post": {
        "operationId": "listBankingAccounts",
        "summary": "List banking accounts",
        "tags": [
          "Banking"
        ],
        "description": "Lists the account's banking rails and their details — for an on-ramp, the routing and account numbers to give whoever is paying in. The off-ramp deposit address is deliberately withheld: it is internal payout plumbing, not account detail.\n\nThis is a Firebase callable served from the Cloud Function URL (`https://us-central1-kyc-ts.cloudfunctions.net/listBankingAccounts`), not the `https://laso.finance` base URL used by the paywalled routes. Callables wrap the request in `{\"data\": ...}` and the reply in `{\"result\": ...}`. Send the `id_token` from `/auth` as a Bearer token. Free.",
        "servers": [
          {
            "url": "https://us-central1-kyc-ts.cloudfunctions.net"
          }
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "data"
                ],
                "properties": {
                  "data": {
                    "type": "object",
                    "required": [
                      "userId"
                    ],
                    "properties": {
                      "userId": {
                        "type": "string",
                        "description": "The Laso user id (the `usr_...` value from /auth).",
                        "example": "usr_7aff81763cae58c936aa6de67dd5a95e"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The accounts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "result": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing or invalid."
          },
          "401": {
            "description": "Missing, invalid, or expired Bearer token."
          },
          "403": {
            "description": "The caller is not this user and is not an admin."
          }
        }
      }
    },
    "/createBankingRecipient": {
      "post": {
        "operationId": "createBankingRecipient",
        "summary": "Create a payout recipient",
        "tags": [
          "Banking"
        ],
        "description": "Creates a person or company money can be sent to. Bank details are attached separately with `addBankingDestination`.\n\nThis is a Firebase callable served from the Cloud Function URL (`https://us-central1-kyc-ts.cloudfunctions.net/createBankingRecipient`), not the `https://laso.finance` base URL used by the paywalled routes. Callables wrap the request in `{\"data\": ...}` and the reply in `{\"result\": ...}`. Send the `id_token` from `/auth` as a Bearer token. Free.",
        "servers": [
          {
            "url": "https://us-central1-kyc-ts.cloudfunctions.net"
          }
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "data"
                ],
                "properties": {
                  "data": {
                    "type": "object",
                    "required": [
                      "userId",
                      "name"
                    ],
                    "properties": {
                      "userId": {
                        "type": "string",
                        "description": "The Laso user id (the `usr_...` value from /auth).",
                        "example": "usr_7aff81763cae58c936aa6de67dd5a95e"
                      },
                      "name": {
                        "type": "string",
                        "description": "The recipient's name.",
                        "example": "Acme LLC"
                      },
                      "address": {
                        "type": "object",
                        "description": "The recipient's postal address. Required before a bank destination can be attached, so supplying it here avoids a second call."
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The recipient.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "result": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing or invalid."
          },
          "401": {
            "description": "Missing, invalid, or expired Bearer token."
          },
          "403": {
            "description": "The caller is not this user and is not an admin."
          }
        }
      }
    },
    "/addBankingDestination": {
      "post": {
        "operationId": "addBankingDestination",
        "summary": "Attach bank details to a recipient",
        "tags": [
          "Banking"
        ],
        "description": "Attaches a bank account or crypto address to a recipient and returns the `destination_id` that `/send-bank-payment` pays out to.\n\nThis is a Firebase callable served from the Cloud Function URL (`https://us-central1-kyc-ts.cloudfunctions.net/addBankingDestination`), not the `https://laso.finance` base URL used by the paywalled routes. Callables wrap the request in `{\"data\": ...}` and the reply in `{\"result\": ...}`. Send the `id_token` from `/auth` as a Bearer token. Free.",
        "servers": [
          {
            "url": "https://us-central1-kyc-ts.cloudfunctions.net"
          }
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "data"
                ],
                "properties": {
                  "data": {
                    "type": "object",
                    "required": [
                      "userId",
                      "recipientId",
                      "destination"
                    ],
                    "properties": {
                      "userId": {
                        "type": "string",
                        "description": "The Laso user id (the `usr_...` value from /auth).",
                        "example": "usr_7aff81763cae58c936aa6de67dd5a95e"
                      },
                      "recipientId": {
                        "type": "string",
                        "description": "The recipient from `createBankingRecipient`."
                      },
                      "destination": {
                        "type": "object",
                        "description": "The destination. For a US bank account: `destination_type: \"fiat_us\"` plus `aba_routing_number`, `account_number`, `account_type`, `account_holder_name`, and `bank_name`. Also supports `fiat_iban` and `crypto`."
                      },
                      "nickname": {
                        "type": "string",
                        "description": "An optional label. The only field editable later, via `updateBankingDestination`.",
                        "example": "Rent account"
                      },
                      "recipientAddress": {
                        "type": "object",
                        "description": "Sets the recipient's postal address before attaching the destination. Needed when the recipient was created name-only: bank destinations are rejected without one, so supplying it here avoids a dead end."
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The destination, including its `destination_id`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "result": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing or invalid."
          },
          "401": {
            "description": "Missing, invalid, or expired Bearer token."
          },
          "403": {
            "description": "The caller is not this user and is not an admin."
          }
        }
      }
    },
    "/listBankingRecipients": {
      "post": {
        "operationId": "listBankingRecipients",
        "summary": "List recipients and destinations",
        "tags": [
          "Banking"
        ],
        "description": "Lists payout recipients and their destinations, with account numbers masked to the last four digits. This is where `destination_id` values come from. Also available as the free paywalled route `GET /bank-recipients`.\n\nThis is a Firebase callable served from the Cloud Function URL (`https://us-central1-kyc-ts.cloudfunctions.net/listBankingRecipients`), not the `https://laso.finance` base URL used by the paywalled routes. Callables wrap the request in `{\"data\": ...}` and the reply in `{\"result\": ...}`. Send the `id_token` from `/auth` as a Bearer token. Free.",
        "servers": [
          {
            "url": "https://us-central1-kyc-ts.cloudfunctions.net"
          }
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "data"
                ],
                "properties": {
                  "data": {
                    "type": "object",
                    "required": [
                      "userId"
                    ],
                    "properties": {
                      "userId": {
                        "type": "string",
                        "description": "The Laso user id (the `usr_...` value from /auth).",
                        "example": "usr_7aff81763cae58c936aa6de67dd5a95e"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The recipients and their destinations.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "result": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing or invalid."
          },
          "401": {
            "description": "Missing, invalid, or expired Bearer token."
          },
          "403": {
            "description": "The caller is not this user and is not an admin."
          }
        }
      }
    },
    "/updateBankingDestination": {
      "post": {
        "operationId": "updateBankingDestination",
        "summary": "Rename a destination",
        "tags": [
          "Banking"
        ],
        "description": "Sets or clears a destination's nickname. This is the only editable field: bank details are immutable at the partner, so anything else wrong means deleting the recipient and creating a new one.\n\nThis is a Firebase callable served from the Cloud Function URL (`https://us-central1-kyc-ts.cloudfunctions.net/updateBankingDestination`), not the `https://laso.finance` base URL used by the paywalled routes. Callables wrap the request in `{\"data\": ...}` and the reply in `{\"result\": ...}`. Send the `id_token` from `/auth` as a Bearer token. Free.",
        "servers": [
          {
            "url": "https://us-central1-kyc-ts.cloudfunctions.net"
          }
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "data"
                ],
                "properties": {
                  "data": {
                    "type": "object",
                    "required": [
                      "userId",
                      "destinationId",
                      "nickname"
                    ],
                    "properties": {
                      "userId": {
                        "type": "string",
                        "description": "The Laso user id (the `usr_...` value from /auth).",
                        "example": "usr_7aff81763cae58c936aa6de67dd5a95e"
                      },
                      "destinationId": {
                        "type": "string",
                        "description": "The destination to rename."
                      },
                      "nickname": {
                        "type": "string",
                        "description": "The new nickname. An empty string clears it.",
                        "example": "Rent account"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Confirmation.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "result": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing or invalid."
          },
          "401": {
            "description": "Missing, invalid, or expired Bearer token."
          },
          "403": {
            "description": "The caller is not this user and is not an admin."
          }
        }
      }
    },
    "/deleteBankingRecipient": {
      "post": {
        "operationId": "deleteBankingRecipient",
        "summary": "Delete a recipient",
        "tags": [
          "Banking"
        ],
        "description": "Deletes a recipient, its destinations, and any off-ramp account that paid out to it. Irreversible.\n\nThis is a Firebase callable served from the Cloud Function URL (`https://us-central1-kyc-ts.cloudfunctions.net/deleteBankingRecipient`), not the `https://laso.finance` base URL used by the paywalled routes. Callables wrap the request in `{\"data\": ...}` and the reply in `{\"result\": ...}`. Send the `id_token` from `/auth` as a Bearer token. Free.",
        "servers": [
          {
            "url": "https://us-central1-kyc-ts.cloudfunctions.net"
          }
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "data"
                ],
                "properties": {
                  "data": {
                    "type": "object",
                    "required": [
                      "userId",
                      "recipientId"
                    ],
                    "properties": {
                      "userId": {
                        "type": "string",
                        "description": "The Laso user id (the `usr_...` value from /auth).",
                        "example": "usr_7aff81763cae58c936aa6de67dd5a95e"
                      },
                      "recipientId": {
                        "type": "string",
                        "description": "The recipient to delete."
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Confirmation.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "result": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing or invalid."
          },
          "401": {
            "description": "Missing, invalid, or expired Bearer token."
          },
          "403": {
            "description": "The caller is not this user and is not an admin."
          }
        }
      }
    },
    "/createBankingTransfer": {
      "post": {
        "operationId": "createBankingTransfer",
        "summary": "Send a one-off transfer",
        "tags": [
          "Banking"
        ],
        "description": "Sends a one-off transfer from the banking profile to one of your registered destinations. For paying a bank account from the agent wallet, prefer the paywalled `/send-bank-payment` route, which handles the funding leg for you.\n\nThis is a Firebase callable served from the Cloud Function URL (`https://us-central1-kyc-ts.cloudfunctions.net/createBankingTransfer`), not the `https://laso.finance` base URL used by the paywalled routes. Callables wrap the request in `{\"data\": ...}` and the reply in `{\"result\": ...}`. Send the `id_token` from `/auth` as a Bearer token. Free.",
        "servers": [
          {
            "url": "https://us-central1-kyc-ts.cloudfunctions.net"
          }
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "data"
                ],
                "properties": {
                  "data": {
                    "type": "object",
                    "required": [
                      "userId",
                      "amount",
                      "destinationId"
                    ],
                    "properties": {
                      "userId": {
                        "type": "string",
                        "description": "The Laso user id (the `usr_...` value from /auth).",
                        "example": "usr_7aff81763cae58c936aa6de67dd5a95e"
                      },
                      "amount": {
                        "type": "string",
                        "description": "Decimal string in the source asset's unit.",
                        "example": "25.50"
                      },
                      "destinationId": {
                        "type": "string",
                        "description": "The destination to pay."
                      },
                      "sourceNetworkId": {
                        "type": "string",
                        "description": "Defaults to Solana mainnet."
                      },
                      "sourceAsset": {
                        "type": "string",
                        "description": "Defaults to USDC."
                      },
                      "destinationAsset": {
                        "type": "string",
                        "description": "Defaults to USDC."
                      },
                      "paymentReference": {
                        "type": "string",
                        "description": "A reference shown on the payment."
                      },
                      "destinationPaymentRail": {
                        "type": "string",
                        "description": "The rail to pay out over, e.g. `ach` or `fedwire`."
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The transfer.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "result": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing or invalid."
          },
          "401": {
            "description": "Missing, invalid, or expired Bearer token."
          },
          "403": {
            "description": "The caller is not this user and is not an admin."
          }
        }
      }
    },
    "/sendBankingPayout": {
      "post": {
        "operationId": "sendBankingPayout",
        "summary": "Send a bank payout",
        "tags": [
          "Banking"
        ],
        "description": "Sends dollars to a registered bank destination. **Agents should use the paywalled `/send-bank-payment` route instead**: this callable debits the Laso account balance, whereas an agent's funds live in the managed wallet, so calling it directly fails against a zero balance.\n\nThis is a Firebase callable served from the Cloud Function URL (`https://us-central1-kyc-ts.cloudfunctions.net/sendBankingPayout`), not the `https://laso.finance` base URL used by the paywalled routes. Callables wrap the request in `{\"data\": ...}` and the reply in `{\"result\": ...}`. Send the `id_token` from `/auth` as a Bearer token. Free.",
        "servers": [
          {
            "url": "https://us-central1-kyc-ts.cloudfunctions.net"
          }
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "data"
                ],
                "properties": {
                  "data": {
                    "type": "object",
                    "required": [
                      "userId",
                      "amount",
                      "destinationId"
                    ],
                    "properties": {
                      "userId": {
                        "type": "string",
                        "description": "The Laso user id (the `usr_...` value from /auth).",
                        "example": "usr_7aff81763cae58c936aa6de67dd5a95e"
                      },
                      "amount": {
                        "type": "number",
                        "description": "USD to deliver, before the fee.",
                        "example": 250
                      },
                      "destinationId": {
                        "type": "string",
                        "description": "The bank destination to pay."
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The payout.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "result": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing or invalid."
          },
          "401": {
            "description": "Missing, invalid, or expired Bearer token."
          },
          "403": {
            "description": "The caller is not this user and is not an admin."
          }
        }
      }
    },
    "/getBankingTransaction": {
      "post": {
        "operationId": "getBankingTransaction",
        "summary": "Read one transaction",
        "tags": [
          "Banking"
        ],
        "description": "Reads a single banking transaction — an on-ramp arriving or an off-ramp paying out — with its normalized status.\n\nThis is a Firebase callable served from the Cloud Function URL (`https://us-central1-kyc-ts.cloudfunctions.net/getBankingTransaction`), not the `https://laso.finance` base URL used by the paywalled routes. Callables wrap the request in `{\"data\": ...}` and the reply in `{\"result\": ...}`. Send the `id_token` from `/auth` as a Bearer token. Free.",
        "servers": [
          {
            "url": "https://us-central1-kyc-ts.cloudfunctions.net"
          }
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "data"
                ],
                "properties": {
                  "data": {
                    "type": "object",
                    "required": [
                      "userId",
                      "transactionId"
                    ],
                    "properties": {
                      "userId": {
                        "type": "string",
                        "description": "The Laso user id (the `usr_...` value from /auth).",
                        "example": "usr_7aff81763cae58c936aa6de67dd5a95e"
                      },
                      "transactionId": {
                        "type": "string",
                        "description": "The transaction to read."
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The transaction.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "result": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing or invalid."
          },
          "401": {
            "description": "Missing, invalid, or expired Bearer token."
          },
          "403": {
            "description": "The caller is not this user and is not an admin."
          }
        }
      }
    },
    "/listBankingTransactions": {
      "post": {
        "operationId": "listBankingTransactions",
        "summary": "List transactions",
        "tags": [
          "Banking"
        ],
        "description": "Lists banking transactions for the account: money arriving over the on-ramp and paying out over the off-ramp, with normalized statuses and amounts.\n\nThis is a Firebase callable served from the Cloud Function URL (`https://us-central1-kyc-ts.cloudfunctions.net/listBankingTransactions`), not the `https://laso.finance` base URL used by the paywalled routes. Callables wrap the request in `{\"data\": ...}` and the reply in `{\"result\": ...}`. Send the `id_token` from `/auth` as a Bearer token. Free.",
        "servers": [
          {
            "url": "https://us-central1-kyc-ts.cloudfunctions.net"
          }
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "data"
                ],
                "properties": {
                  "data": {
                    "type": "object",
                    "required": [
                      "userId"
                    ],
                    "properties": {
                      "userId": {
                        "type": "string",
                        "description": "The Laso user id (the `usr_...` value from /auth).",
                        "example": "usr_7aff81763cae58c936aa6de67dd5a95e"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The transactions.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "result": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing or invalid."
          },
          "401": {
            "description": "Missing, invalid, or expired Bearer token."
          },
          "403": {
            "description": "The caller is not this user and is not an admin."
          }
        }
      }
    },
    "/agentAuth": {
      "post": {
        "operationId": "agentAuth",
        "summary": "Exchange an API key for a session",
        "tags": [
          "Agent Wallet"
        ],
        "description": "Exchanges a durable `lasoak_` API key for a short-lived `id_token` (one hour) plus a `refresh_token`, and returns an `auth_url` to hand your human so they can watch what you do. This is the first call an agent makes. The reply's `id_token` authenticates every other endpoint here.\n\nIf you have no key, your human creates one at https://laso.finance/agent/dashboard — there is no other way to obtain one.\n\nServed from the Cloud Function URL (`https://us-central1-kyc-ts.cloudfunctions.net/agentAuth`), not the `https://laso.finance` base URL used by the paywalled routes. Send the `id_token` from `/auth` as a Bearer token, with a plain JSON body (no `data` wrapper). Free.",
        "servers": [
          {
            "url": "https://us-central1-kyc-ts.cloudfunctions.net"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "api_key"
                ],
                "properties": {
                  "api_key": {
                    "type": "string",
                    "description": "Your `lasoak_` API key.",
                    "example": "lasoak_..."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "An id_token, a refresh_token, and an auth_url for your human.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing or invalid."
          }
        }
      }
    },
    "/announceAgentConnection": {
      "post": {
        "operationId": "announceAgentConnection",
        "summary": "Announce that you connected",
        "tags": [
          "Agent Wallet"
        ],
        "description": "**Required immediately after sign-in, before anything else.** Your human is on a \"waiting for your agent\" screen that only flips to \"connected\" when this lands; until then a successful sign-in is indistinguishable from a broken one on their side. Skipping it is the most common agent mistake here, and the one humans actually notice.\n\nThe reply also carries the wallet's funding state, so it doubles as a first `getAgentWallet` call.\n\nServed from the Cloud Function URL (`https://us-central1-kyc-ts.cloudfunctions.net/announceAgentConnection`), not the `https://laso.finance` base URL used by the paywalled routes. Send the `id_token` from `/auth` as a Bearer token, with a plain JSON body (no `data` wrapper). Free.",
        "servers": [
          {
            "url": "https://us-central1-kyc-ts.cloudfunctions.net"
          }
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "agentName": {
                    "type": "string",
                    "description": "What to call you on their dashboard.",
                    "example": "Claude"
                  },
                  "message": {
                    "type": "string",
                    "description": "A short free-form note shown alongside the connection."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Confirmation, plus the wallet's current funding state.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing or invalid."
          },
          "401": {
            "description": "Missing, invalid, or expired Bearer token."
          }
        }
      }
    },
    "/getAgentWallet": {
      "get": {
        "operationId": "getAgentWallet",
        "summary": "Read the managed wallet",
        "tags": [
          "Agent Wallet"
        ],
        "description": "Returns the managed wallet's Solana address and USDC balance: `wallet_address`, `balance_usdc`, `needs_funding`, and `has_wallet`.\n\n`wallet_address` is the only place to send funds, and it is **not** your `user_id` — a `usr_...` value is an account id, never a receive address. Poll this to watch a deposit land.\n\nServed from the Cloud Function URL (`https://us-central1-kyc-ts.cloudfunctions.net/getAgentWallet`), not the `https://laso.finance` base URL used by the paywalled routes. Send the `id_token` from `/auth` as a Bearer token, with a plain JSON body (no `data` wrapper). Free.",
        "servers": [
          {
            "url": "https://us-central1-kyc-ts.cloudfunctions.net"
          }
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The wallet address, balance, and funding state.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, or expired Bearer token."
          }
        }
      }
    },
    "/createAgentWallet": {
      "post": {
        "operationId": "createAgentWallet",
        "summary": "Provision a managed wallet",
        "tags": [
          "Agent Wallet"
        ],
        "description": "Creates the Laso-custodied Solana spend wallet for the account, or returns the existing one. Idempotent. Most agents never call this: the wallet is provisioned when the human completes setup on the dashboard.\n\nA Firebase callable served from the Cloud Function URL (`https://us-central1-kyc-ts.cloudfunctions.net/createAgentWallet`), not the `https://laso.finance` base URL used by the paywalled routes. Callables wrap the request in `{\"data\": ...}` and the reply in `{\"result\": ...}`. Send the `id_token` from `/auth` as a Bearer token. Free.",
        "servers": [
          {
            "url": "https://us-central1-kyc-ts.cloudfunctions.net"
          }
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "data"
                ],
                "properties": {
                  "data": {
                    "type": "object",
                    "properties": {
                      "userId": {
                        "type": "string",
                        "description": "The Laso user id (the `usr_...` value from /auth).",
                        "example": "usr_7aff81763cae58c936aa6de67dd5a95e"
                      }
                    },
                    "required": [
                      "userId"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The wallet id and its Solana address.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "result": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing or invalid."
          },
          "401": {
            "description": "Missing, invalid, or expired Bearer token."
          }
        }
      }
    },
    "/agentX402Pay": {
      "post": {
        "operationId": "agentX402Pay",
        "summary": "Pay an x402 endpoint from the managed wallet",
        "tags": [
          "Agent Wallet"
        ],
        "description": "Settles an x402 payment on your behalf, so you never build a payment header or hold a private key. Takes either a Laso `route` name or the full `url` of any external x402 endpoint on the open web.\n\nFor an external `url`, pin `maxAmountUsdc` plus optionally `expectedAsset` / `expectedNetwork` / `expectedPayTo`; a challenge that exceeds or does not match them is refused.\n\nThe call succeeds as long as the request went out, so check the returned `status` — it is the endpoint's status, not the callable's.\n\nA Firebase callable served from the Cloud Function URL (`https://us-central1-kyc-ts.cloudfunctions.net/agentX402Pay`), not the `https://laso.finance` base URL used by the paywalled routes. Callables wrap the request in `{\"data\": ...}` and the reply in `{\"result\": ...}`. Send the `id_token` from `/auth` as a Bearer token. Free.",
        "servers": [
          {
            "url": "https://us-central1-kyc-ts.cloudfunctions.net"
          }
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "data"
                ],
                "properties": {
                  "data": {
                    "type": "object",
                    "properties": {
                      "userId": {
                        "type": "string",
                        "description": "The Laso user id (the `usr_...` value from /auth).",
                        "example": "usr_7aff81763cae58c936aa6de67dd5a95e"
                      },
                      "route": {
                        "type": "string",
                        "description": "A Laso route name, e.g. `send-bank-payment`. Mutually exclusive with `url`."
                      },
                      "url": {
                        "type": "string",
                        "description": "Full URL of an external x402 endpoint. Mutually exclusive with `route`."
                      },
                      "params": {
                        "type": "object",
                        "description": "Query parameters for the route."
                      },
                      "method": {
                        "type": "string",
                        "description": "GET or POST. Defaults to GET."
                      },
                      "maxAmountUsdc": {
                        "type": "number",
                        "description": "Required for an external `url`: the most you will pay.",
                        "example": 5
                      },
                      "expectedAsset": {
                        "type": "string",
                        "description": "Refuse the challenge if the asset differs."
                      },
                      "expectedNetwork": {
                        "type": "string",
                        "description": "Refuse the challenge if the network differs."
                      },
                      "expectedPayTo": {
                        "type": "string",
                        "description": "Refuse the challenge if the payee differs."
                      },
                      "body": {
                        "type": "object",
                        "description": "JSON body for the paid request, when `method` is POST."
                      }
                    },
                    "required": [
                      "userId"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The paid call's `status`, `body`, and an `error` when the endpoint refused.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "result": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing or invalid."
          },
          "401": {
            "description": "Missing, invalid, or expired Bearer token."
          }
        }
      }
    },
    "/agentWalletTransfer": {
      "post": {
        "operationId": "agentWalletTransfer",
        "summary": "Send USDC from the managed wallet",
        "tags": [
          "Agent Wallet"
        ],
        "description": "Moves USDC out of the managed wallet to any Solana address. Laso signs on your behalf.\n\nTakes an address, and deliberately **not** an address-book name: a rename between your human asking and you sending would move money somewhere they did not intend. When you resolve a name via `listAddressBook`, say the address back to them before sending.\n\nA Firebase callable served from the Cloud Function URL (`https://us-central1-kyc-ts.cloudfunctions.net/agentWalletTransfer`), not the `https://laso.finance` base URL used by the paywalled routes. Callables wrap the request in `{\"data\": ...}` and the reply in `{\"result\": ...}`. Send the `id_token` from `/auth` as a Bearer token. Free.",
        "servers": [
          {
            "url": "https://us-central1-kyc-ts.cloudfunctions.net"
          }
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "data"
                ],
                "properties": {
                  "data": {
                    "type": "object",
                    "required": [
                      "userId",
                      "destinationAddress",
                      "amount"
                    ],
                    "properties": {
                      "userId": {
                        "type": "string",
                        "description": "The Laso user id (the `usr_...` value from /auth).",
                        "example": "usr_7aff81763cae58c936aa6de67dd5a95e"
                      },
                      "destinationAddress": {
                        "type": "string",
                        "description": "The recipient's Solana address.",
                        "example": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU"
                      },
                      "amount": {
                        "type": "string",
                        "description": "Decimal USDC string, not lamports.",
                        "example": "5.50"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The `transferId`, `txHash`, and `destinationAddress`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "result": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing or invalid."
          },
          "401": {
            "description": "Missing, invalid, or expired Bearer token."
          }
        }
      }
    },
    "/createAgentApiKey": {
      "post": {
        "operationId": "createAgentApiKey",
        "summary": "Issue an API key",
        "tags": [
          "Agent Wallet"
        ],
        "description": "Issues a durable `lasoak_` API key for the account. **The raw key is returned once** — only its hash is stored, so it cannot be retrieved again. Normally the human does this from the dashboard.\n\nA Firebase callable served from the Cloud Function URL (`https://us-central1-kyc-ts.cloudfunctions.net/createAgentApiKey`), not the `https://laso.finance` base URL used by the paywalled routes. Callables wrap the request in `{\"data\": ...}` and the reply in `{\"result\": ...}`. Send the `id_token` from `/auth` as a Bearer token. Free.",
        "servers": [
          {
            "url": "https://us-central1-kyc-ts.cloudfunctions.net"
          }
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "data"
                ],
                "properties": {
                  "data": {
                    "type": "object",
                    "properties": {
                      "userId": {
                        "type": "string",
                        "description": "The Laso user id (the `usr_...` value from /auth).",
                        "example": "usr_7aff81763cae58c936aa6de67dd5a95e"
                      },
                      "label": {
                        "type": "string",
                        "description": "A label to recognize the key by.",
                        "example": "Claude Code"
                      }
                    },
                    "required": [
                      "userId"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The raw `apiKey` (shown once) and its `keyId`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "result": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing or invalid."
          },
          "401": {
            "description": "Missing, invalid, or expired Bearer token."
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "AuthCredentials": {
        "type": "object",
        "properties": {
          "id_token": {
            "type": "string",
            "description": "ID token — use as Bearer token for Laso Finance APIs"
          },
          "refresh_token": {
            "type": "string",
            "description": "Use with POST /auth (grant_type=refresh_token) to get a new id_token when it expires"
          },
          "expires_in": {
            "type": "string",
            "description": "Token lifetime in seconds"
          }
        }
      },
      "CardOrder": {
        "type": "object",
        "description": "Card order info returned by /get-card. Status is always `pending` initially — poll `/get-card-data` to check when card details are ready.",
        "properties": {
          "card_id": {
            "type": "string"
          },
          "usd_amount": {
            "type": "number"
          },
          "country": {
            "type": "string",
            "example": "US"
          },
          "timestamp": {
            "type": "number"
          },
          "timestamp_readable": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending"
            ],
            "description": "Always `pending` at order time. Poll `/get-card-data` to check for `ready`."
          }
        }
      },
      "CardData": {
        "type": "object",
        "description": "Response from `/get-card-data`. When `status` is `ready`, `card_details` contains the card number, CVV, and expiry. International cards include extra fields (`label`, `charged_usd_amount`, `fees_paid`, `state`, `balance_update_requested_timestamp`, `queued_order_card_id`).",
        "properties": {
          "card_id": {
            "type": "string"
          },
          "card_type": {
            "type": "string",
            "enum": [
              "Non-Reloadable U.S.",
              "Non-Reloadable International"
            ]
          },
          "usd_amount": {
            "type": "number"
          },
          "country": {
            "type": "string",
            "description": "U.S. cards only.",
            "example": "US"
          },
          "label": {
            "type": "string",
            "description": "International cards only. User-supplied label, may be empty.",
            "nullable": true
          },
          "charged_usd_amount": {
            "type": "number",
            "description": "International cards only. Amount the user was charged including fees."
          },
          "fees_paid": {
            "type": "number",
            "description": "International cards only. Fees paid for this card."
          },
          "state": {
            "type": "string",
            "description": "International cards only. Raw card state.",
            "enum": [
              "queued",
              "redeemable",
              "complete",
              "refund-requested",
              "refund-requested-approved-for-queue",
              "archived",
              "refunded"
            ]
          },
          "balance_update_requested_timestamp": {
            "type": "number",
            "nullable": true,
            "description": "International cards only. Unix timestamp (ms) of an outstanding admin balance update request, or null if none is pending."
          },
          "queued_order_card_id": {
            "type": "string",
            "nullable": true,
            "description": "International cards only. The original `card_id` returned by `/order-intl-card`. After admin fulfillment the card is reissued with a new `card_id` (the issuer's transaction id); querying `/get-card-data?card_id=<original>` continues to resolve to the fulfilled card via this field."
          },
          "timestamp": {
            "type": "number"
          },
          "timestamp_readable": {
            "type": "string"
          },
          "last_updated_timestamp": {
            "type": "number",
            "description": "U.S. cards only. Unix timestamp (ms) of the last time card data was refreshed."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "ready",
              "queued",
              "complete",
              "refund-requested",
              "refunded",
              "archived"
            ]
          },
          "card_details": {
            "type": "object",
            "description": "Only present when status is `ready` (U.S.) or `complete` (international).",
            "properties": {
              "card_number": {
                "type": "string"
              },
              "exp_month": {
                "type": "string"
              },
              "exp_year": {
                "type": "string"
              },
              "cvv": {
                "type": "string"
              },
              "available_balance": {
                "type": "number"
              },
              "billing_address": {
                "type": "object",
                "description": "The card billing address to enter when a merchant asks for one at checkout. The billing name is always `Laso Finance`. For USA non-reloadable cards `required` is `false` (any valid U.S. billing address works; this is a known-good default). For international non-reloadable cards `required` is `true` and the merchant AVS check is validated against exactly this address.",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Always `Laso Finance`."
                  },
                  "line_1": {
                    "type": "string"
                  },
                  "line_2": {
                    "type": "string"
                  },
                  "city": {
                    "type": "string"
                  },
                  "state": {
                    "type": "string"
                  },
                  "zip": {
                    "type": "string"
                  },
                  "country": {
                    "type": "string"
                  },
                  "required": {
                    "type": "boolean",
                    "description": "`true` if the card enforces AVS against this exact address (international cards); `false` if any valid U.S. address works (USA cards)."
                  },
                  "note": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "transactions": {
            "type": "array",
            "description": "Card transaction history. U.S. and international cards use slightly different shapes — see `CardTransaction` and `IntlCardTransaction`.",
            "items": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/CardTransaction"
                },
                {
                  "$ref": "#/components/schemas/IntlCardTransaction"
                }
              ]
            }
          }
        }
      },
      "CardTransaction": {
        "type": "object",
        "description": "U.S. prepaid card transaction.",
        "properties": {
          "amount": {
            "type": "number"
          },
          "date": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "is_credit": {
            "type": "boolean"
          }
        }
      },
      "IntlCardTransaction": {
        "type": "object",
        "description": "International prepaid card transaction.",
        "properties": {
          "amount": {
            "type": "number",
            "description": "Transaction amount in USD."
          },
          "date": {
            "type": "number",
            "nullable": true,
            "description": "Unix timestamp (ms) of the transaction."
          },
          "merchant": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "nullable": true,
            "description": "Optional status, e.g. `pending`."
          }
        }
      },
      "AccountBalance": {
        "type": "object",
        "description": "Account balance information.",
        "properties": {
          "user_id": {
            "type": "string",
            "description": "The user's ID (lowercase wallet address)"
          },
          "balance": {
            "type": "number",
            "description": "Current available balance in USD"
          },
          "total_deposits": {
            "type": "number",
            "description": "Total lifetime deposits in USD"
          },
          "created_timestamp": {
            "type": "number"
          },
          "created_timestamp_readable": {
            "type": "string"
          }
        }
      },
      "WithdrawalRecord": {
        "type": "object",
        "description": "A withdrawal record.",
        "properties": {
          "id": {
            "type": "string"
          },
          "amount": {
            "type": "number"
          },
          "state": {
            "type": "string",
            "enum": [
              "pending"
            ]
          },
          "timestamp": {
            "type": "number"
          },
          "timestamp_readable": {
            "type": "string"
          }
        }
      },
      "WithdrawalStatus": {
        "type": "object",
        "description": "A withdrawal status record.",
        "properties": {
          "id": {
            "type": "string"
          },
          "amount": {
            "type": "number"
          },
          "asset": {
            "type": "string",
            "example": "USDC"
          },
          "network": {
            "type": "string",
            "example": "BASE_MAINNET"
          },
          "state": {
            "type": "string"
          },
          "address": {
            "type": "string",
            "description": "The wallet address the withdrawal is sent to"
          },
          "timestamp": {
            "type": "number"
          },
          "timestamp_readable": {
            "type": "string"
          },
          "tx_hash": {
            "type": "string",
            "description": "On-chain transaction hash, present once the withdrawal is processed"
          },
          "tx_url": {
            "type": "string",
            "description": "Block explorer URL for the transaction, present when tx_hash exists"
          }
        }
      },
      "MerchantResult": {
        "type": "object",
        "description": "A merchant from the spend data database with its acceptance status for the Non-Reloadable U.S. card.",
        "properties": {
          "name": {
            "type": "string",
            "description": "Merchant name",
            "example": "Amazon"
          },
          "url": {
            "type": "string",
            "description": "Merchant website URL",
            "example": "amazon.com"
          },
          "status": {
            "type": "string",
            "enum": [
              "accepted",
              "not_accepted",
              "unknown"
            ],
            "description": "Whether the Non-Reloadable U.S. card is accepted at this merchant. `accepted` = confirmed working, `not_accepted` = confirmed failing, `unknown` = card type not yet tried at this merchant."
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "description": "Brief description of the merchant"
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "description": "Additional notes about using the card at this merchant"
          }
        }
      },
      "GiftCardProduct": {
        "type": "object",
        "description": "A gift card product from the catalog.",
        "properties": {
          "laso_server_id": {
            "type": "string",
            "description": "Product identifier to use when ordering via GET /order-gift-card"
          },
          "name": {
            "type": "string",
            "description": "Gift card brand name"
          },
          "description": {
            "type": "string"
          },
          "category": {
            "type": [
              "string",
              "null"
            ],
            "description": "Catalog category for the gift card (e.g. \"ecommerce\", \"travel\")."
          },
          "country": {
            "type": [
              "string",
              "null"
            ],
            "example": "FR",
            "description": "ISO 3166-1 alpha-2 country the gift card belongs to. Null for borderless products available in more than one country."
          },
          "currency": {
            "type": [
              "string",
              "null"
            ],
            "example": "USD",
            "description": "Currency code for the gift card"
          },
          "min": {
            "type": "number",
            "description": "Minimum order amount"
          },
          "max": {
            "type": "number",
            "description": "Maximum order amount"
          },
          "increment": {
            "type": "string",
            "description": "Minimum order increment"
          },
          "denominations": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            },
            "description": "If set, only these exact amounts can be ordered. Null for range-based products."
          },
          "product_image_url": {
            "type": "string",
            "description": "URL of the gift card image"
          },
          "catalog_info": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "brand_description": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "redemption_instructions": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        }
      },
      "GiftCardOrder": {
        "type": "object",
        "description": "Gift card order result with redemption details.",
        "properties": {
          "card_id": {
            "type": "string"
          },
          "laso_server_id": {
            "type": "string"
          },
          "amount": {
            "type": "number",
            "description": "Gift card face value, denominated in the product's own `currency` (not USD)"
          },
          "currency": {
            "type": "string",
            "description": "ISO 4217 code the `amount` is denominated in, e.g. `USD`, `SAR`, `EUR`",
            "example": "USD"
          },
          "country": {
            "type": "string",
            "example": "US"
          },
          "redemption_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "URL to redeem the gift card (if applicable)"
          },
          "redemption_code": {
            "type": [
              "string",
              "null"
            ],
            "description": "Code to redeem the gift card (if applicable)"
          },
          "pin_code": {
            "type": [
              "string",
              "null"
            ],
            "description": "PIN code for the gift card (if applicable)"
          },
          "status": {
            "type": "string",
            "enum": [
              "completed"
            ]
          },
          "timestamp": {
            "type": "number"
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        }
      },
      "FrozenError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "example": "Account is frozen"
          },
          "frozen_message": {
            "type": "string",
            "description": "Human-readable explanation of why the account is frozen"
          }
        }
      }
    },
    "securitySchemes": {
      "BearerAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "Authorization",
        "description": "Firebase ID token from `/auth` or any paid route, sent as a Bearer token: `Authorization: Bearer <id_token>` (the `Bearer ` prefix is required)."
      },
      "siwx": {
        "type": "apiKey",
        "in": "header",
        "name": "SIGN-IN-WITH-X",
        "description": "Sign-In-With-X (CAIP-122) wallet signature proving ownership of the calling EVM (Base) or Solana wallet. Identity only, no payment. Build it with `@x402/extensions/sign-in-with-x` (`wrapFetchWithSIWx`)."
      }
    }
  }
}
