{
  "openapi": "3.0.3",
  "info": {
    "title": "Fundora API",
    "version": "0.3.0",
    "description": "Documentation for Fundora partner integrations. At the current stage, external integrations should use **API Sandbox**.\n\n## LIVE API\n\n**LIVE API is in development.** This section is shown early to keep the future production API clearly separated from Sandbox API.\n\nUse **API Sandbox** until Fundora announces LIVE API access separately.\n\nThe LIVE API contract will later include:\n\n- production API keys;\n- production operations for real client applications;\n- production webhooks;\n- one webhook delivery contract for sandbox/live;\n- access limits and roles;\n- SLA and error-handling rules."
  },
  "servers": [
    {
      "url": "https://partner.fundora.capital",
      "description": "Production domain for the partner API"
    }
  ],
  "security": [
    {
      "SandboxBearer": []
    }
  ],
  "tags": [
    {
      "name": "sandbox-overview",
      "x-page-title": "API Sandbox",
      "description": "Sandbox API is used for partner test integrations. It lets you walk through the full loan journey: calculate terms, receive a test BTC collateral address, confirm collateral, complete a payout simulation, accept USDT repayment, and return collateral. Sandbox does not perform real blockchain transfers: addresses are test addresses, and credits are confirmed by API actions such as `confirm_btc` and `confirm_usdt`.\n\nBase URL: `https://partner.fundora.capital`. Authorization: `Authorization: Bearer sk_sandbox_YOUR_KEY`. All POST requests require `Idempotency-Key`. Rate limit: 120 requests per 60 seconds per Sandbox API key.\n\n## How Sandbox works\n\nSandbox mirrors the main Fundora borrower dashboard flow without real transfers. The partner calls API methods step by step and receives state that can be shown in the partner interface.\n\n1. Get configuration: rates, FX, limits, fees, and risk parameters.\n2. Create a test loan application and receive addresses needed for the flow.\n3. Instead of waiting for a blockchain event, send a confirming API action.\n4. After payout, read the loan state and start repayment.\n5. After full repayment, the API shows the collateral return step.\n\nSteps cannot be executed in arbitrary order. If an action is sent too early, the API returns `409 FLOW_CONFLICT`. In that case, call `GET /api/sandbox/state`, inspect the current step, and continue from there.\n\n## Quick start\n\n1. Get a Sandbox API key in the Fundora partner dashboard.\n2. Call `GET /api/sandbox/config` to load rates, FX, fees, and calculator limits.\n3. Call `GET /api/sandbox/state` to check whether the key already has an active application or loan.\n4. Create an application through `POST /api/sandbox/loans/flow` with `action=start_collateral_transfer`.\n5. Continue the same flow endpoint with the next allowed `action`.\n6. After payout, read the loan through `GET /api/sandbox/loans` or `GET /api/sandbox/state`.\n7. Start repayment through `POST /api/sandbox/loans/{id}/repayment-flow`.\n8. Configure webhooks if your backend needs push events instead of polling.\n\n## Test wallets\n\nAPI returns wallet data in `sandboxFlow`. These are not real custody wallets: `isRealWallet=false`, network is `sandbox`, and confirmation is performed through API actions.\n\nFor loan applications, wallets are located at `sandboxFlow.loanApplications[].wallets`: `collateralDeposit` for BTC collateral, `loanPayout` for loan payout simulation, and `collateralReturn` for the future collateral return.\n\nFor repayments, wallets are located at `sandboxFlow.repaymentOperations[].wallets`: `repayment` for the USDT test payment and `collateralReturn` for collateral return after full repayment.\n\n## Webhooks\n\nSandbox webhooks send push events to a partner HTTPS endpoint when loan flow, repayment flow, or sandbox partner state changes. Delivery is signed with HMAC SHA-256, attempts are written to the delivery log, and failed deliveries can be retried.\n\nCreate or update the endpoint through `POST /api/sandbox/webhooks`. The first response and responses with `rotate_secret=true` return `signingSecret`; store it immediately. Later responses expose only `secretPreview`. Use `POST /api/sandbox/webhooks/test` to send a signed test event.\n\n## Errors and retries\n\nAll errors use one format: `request_id`, `error_code`, and `error`. Use `error_code` for integration logic and treat `error` as human-readable text.\n\nAll POST requests require `Idempotency-Key`, usually a UUID. For a new operation, use a new key. For retry after a network error, send the same key, method, endpoint, and body. If the same key is reused with a different request, API returns `IDEMPOTENCY_KEY_CONFLICT`.\n\nEvery response includes `request_id`. It is visible in partner dashboard logs and helps Fundora support investigate issues quickly."
    },
    {
      "name": "get-config",
      "x-parent": "sandbox-overview",
      "x-page-title": "GET Config",
      "description": "Read Sandbox FX rates, borrow rates, LTV, liquidation settings, terms, and fees."
    },
    {
      "name": "get-state",
      "x-parent": "sandbox-overview",
      "x-page-title": "GET State",
      "description": "Read the current Sandbox state for loans and partner data."
    },
    {
      "name": "get-loans",
      "x-parent": "sandbox-overview",
      "x-page-title": "GET Loans",
      "description": "Read Sandbox loan state."
    },
    {
      "name": "post-loans",
      "x-parent": "sandbox-overview",
      "x-page-title": "POST Loans",
      "description": "Create loans and advance the Sandbox loan lifecycle."
    },
    {
      "name": "post-repayments",
      "x-parent": "sandbox-overview",
      "x-page-title": "POST Repayments",
      "description": "Run full, partial, or interest-only repayments in Sandbox."
    },
    {
      "name": "get-partner",
      "x-parent": "sandbox-overview",
      "x-page-title": "GET Partner",
      "description": "Read the sandbox partner profile, links, and rewards."
    },
    {
      "name": "post-partner",
      "x-parent": "sandbox-overview",
      "x-page-title": "POST Partner",
      "description": "Create or update the sandbox partner profile."
    },
    {
      "name": "get-webhooks",
      "x-parent": "sandbox-overview",
      "x-page-title": "GET Webhooks",
      "description": "Read the webhook endpoint, subscribed events, and recent delivery attempts."
    },
    {
      "name": "post-webhooks",
      "x-parent": "sandbox-overview",
      "x-page-title": "POST Webhooks",
      "description": "Create or update a sandbox webhook endpoint."
    },
    {
      "name": "delete-webhooks",
      "x-parent": "sandbox-overview",
      "x-page-title": "DELETE Webhooks",
      "description": "Disable the sandbox webhook endpoint without deleting the delivery log."
    },
    {
      "name": "post-webhook-test",
      "x-parent": "sandbox-overview",
      "x-page-title": "POST Webhook Test",
      "description": "Create and send a sandbox.webhook.test event."
    },
    {
      "name": "post-webhook-retry",
      "x-parent": "sandbox-overview",
      "x-page-title": "POST Webhook Retry",
      "description": "Retry one webhook delivery by ID or retry a batch."
    }
  ],
  "paths": {
    "/api/sandbox/config": {
      "get": {
        "operationId": "getSandboxConfig",
        "tags": [
          "get-config"
        ],
        "summary": "GET /api/sandbox/config",
        "description": "Returns the current Sandbox configuration: market FX rates, the fixed borrow rate, risk settings, payment terms, and the loan origination fee.\n\nUse this endpoint first. It tells the partner backend which values to show in its calculator and which limits to apply before creating a loan.\n\nImportant response fields:\n\n- `rates` - BTC/USD, BTC/RUB, and USD/RUB. RUB includes market, payout, and loan/obligation rates.\n- `borrow_rates.fixed` - fixed loan rate.\n- `risk` - maximum LTV, liquidation threshold, and liquidation penalty.\n- `terms` - interest and principal payment terms in months.\n- `fees.loan_origination_percent` - platform fee charged on loan origination.\n\n`Idempotency-Key` is not required for GET requests.\n\nExample:\n```bash\ncurl https://partner.fundora.capital/api/sandbox/config \\\n  -H \"Authorization: Bearer sk_sandbox_YOUR_KEY\"\n```",
        "responses": {
          "200": {
            "description": "Sandbox configuration",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SandboxConfigResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/sandbox/state": {
      "get": {
        "operationId": "getSandboxState",
        "tags": [
          "get-state"
        ],
        "summary": "GET /api/sandbox/state",
        "description": "Returns a full Sandbox snapshot for the current API key: the `loans` block with loans and flow state plus the `partner` block with partner profile, links, and rewards.\n\nUse this endpoint to synchronize your UI after any POST request or after a `FLOW_CONFLICT` error. In Sandbox, each API key sees only its own test state.\n\n`Idempotency-Key` is not required for GET requests, and `idempotency_replayed` is not returned.\n\nExample:\n```bash\ncurl https://partner.fundora.capital/api/sandbox/state \\\n  -H \"Authorization: Bearer sk_sandbox_YOUR_KEY\"\n```",
        "responses": {
          "200": {
            "description": "Sandbox state",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SandboxStateResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/sandbox/loans/flow": {
      "post": {
        "operationId": "runSandboxLoanFlow",
        "tags": [
          "post-loans"
        ],
        "summary": "POST /api/sandbox/loans/flow",
        "description": "Advances one step of the cabinet-compatible loan issuance flow. This is the main endpoint for integrations that want to reproduce the same journey users follow in the Fundora borrower dashboard.\n\nActions must be executed in strict order:\n\n1. `start_collateral_transfer` - create an application draft, calculate loan parameters, and receive sandbox wallets.\n2. `confirm_btc` - confirm the test BTC collateral credit with `tx_hash` and `confirmations`.\n3. `run_kyt` - simulate a KYT check.\n4. `complete_payout` - complete payout simulation and turn the application into an active loan.\n\nYou cannot execute a step too early. API returns `409 FLOW_CONFLICT` when the state does not allow the requested action. To recover, call `GET /api/sandbox/state` and continue from the current step.\n\nResponse wallets are located at `sandboxFlow.loanApplications[].wallets`. They are deterministic test addresses: `collateralDeposit` for collateral, `loanPayout` for payout, and `collateralReturn` for future collateral return. Production transactions will be tracked from on-chain credits, but Sandbox does not require external chain polling: confirmation is simulated by the `confirm_btc` POST action.\n\nPOST requests must include `Idempotency-Key` so a retry does not create a second draft or duplicate a state transition.\n\nExample:\n```bash\ncurl https://partner.fundora.capital/api/sandbox/loans/flow \\\n  -H \"Authorization: Bearer sk_sandbox_YOUR_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Idempotency-Key: 0d66a118-e544-4a82-94de-35c9f28a0f38\" \\\n  -d '{\"action\":\"start_collateral_transfer\",\"collateral_btc\":0.1,\"loan_amount\":3000,\"loan_currency\":\"USDT\",\"external_client_id\":\"client-42\"}'\n```",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SandboxLoanFlowRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated Sandbox loan state",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SandboxLoansStateResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/sandbox/loans": {
      "get": {
        "operationId": "listSandboxLoans",
        "tags": [
          "get-loans"
        ],
        "summary": "GET /api/sandbox/loans",
        "responses": {
          "200": {
            "description": "Current Sandbox loan state",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SandboxLoansStateResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "description": "Returns only the loan state for the current Sandbox API key, without the partner block.\n\nThe response contains the simulator wallet, active loans, loan history, current draft, and `sandboxFlow` with loan and repayment operations. Use `GET /api/sandbox/state` for a full snapshot that also includes partner data.\n\n`Idempotency-Key` is not required for GET requests."
      },
      "post": {
        "operationId": "createSandboxLoan",
        "tags": [
          "post-loans"
        ],
        "summary": "POST /api/sandbox/loans",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSandboxLoanRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sandbox loan created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SandboxLoansStateResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "description": "Creates a Sandbox loan directly, without the step-by-step cabinet-compatible flow. This endpoint is useful for quick backend tests. To simulate the real borrower dashboard, use `POST /api/sandbox/loans/flow`.\n\nThe request body defines BTC collateral, loan amount, loan currency, and optional partner client identifiers. `external_client_id` and `client_reference` help group deals in the admin interface and match them to a client in the partner system.\n\nPOST requests must include `Idempotency-Key` so a retry does not create a second loan."
      }
    },
    "/api/sandbox/loans/{id}/action": {
      "post": {
        "operationId": "runSandboxLoanAction",
        "tags": [
          "post-loans"
        ],
        "summary": "POST /api/sandbox/loans/{id}/action",
        "parameters": [
          {
            "$ref": "#/components/parameters/LoanId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SandboxLoanActionRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated Sandbox loan state",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SandboxLoansStateResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "description": "Runs a direct action on an active Sandbox loan. This is a low-level shortcut for post-issuance operations: interest repayment, principal repayment, adding collateral, removing part of collateral, or increasing the loan.\n\nFor the cabinet-compatible repayment path, use `POST /api/sandbox/loans/{id}/repayment-flow`. This endpoint remains useful for simple business-logic and balance tests.\n\nPOST requests must include `Idempotency-Key` so a retry does not apply the same action twice."
      }
    },
    "/api/sandbox/loans/{id}/repayment-flow": {
      "post": {
        "operationId": "runSandboxRepaymentFlow",
        "tags": [
          "post-repayments"
        ],
        "summary": "POST /api/sandbox/loans/{id}/repayment-flow",
        "description": "Advances one step of the cabinet-compatible repayment flow. Use it to simulate full, partial, or interest-only repayment with a test payment wallet, KYT, settlement, and collateral return.\n\nActions must be executed in strict order:\n\n1. `start_payment` - create a repayment operation and receive sandbox wallets.\n2. `confirm_usdt` - confirm the test payment credit with `tx_hash` and `confirmations`.\n3. `run_kyt` - simulate a KYT check for the payment.\n4. `complete_settlement` - settle interest or principal debt.\n5. `complete_collateral_return` - return collateral after full repayment.\n\nYou cannot execute a step too early. API returns `409 FLOW_CONFLICT` when the state does not allow the requested action. To recover, call `GET /api/sandbox/state`.\n\nResponse wallets are located at `sandboxFlow.repaymentOperations[].wallets`: `repayment` for the payment and `collateralReturn` for collateral return after full repayment. Sandbox does not require external chain polling: confirmation is simulated by the `confirm_usdt` POST action.\n\nPOST requests must include `Idempotency-Key` so a retry does not create a second repayment operation or duplicate a state transition.\n\nExample:\n```bash\ncurl https://partner.fundora.capital/api/sandbox/loans/LOAN_ID/repayment-flow \\\n  -H \"Authorization: Bearer sk_sandbox_YOUR_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Idempotency-Key: b6dc3b48-8a3b-4c19-9c7c-365809712665\" \\\n  -d '{\"action\":\"start_payment\",\"repayment_kind\":\"full_principal\",\"full_repayment\":true}'\n```",
        "parameters": [
          {
            "$ref": "#/components/parameters/LoanId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SandboxRepaymentFlowRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated Sandbox loan state",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SandboxLoansStateResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/sandbox/partner": {
      "get": {
        "operationId": "getSandboxPartner",
        "tags": [
          "get-partner"
        ],
        "summary": "GET /api/sandbox/partner",
        "responses": {
          "200": {
            "description": "Sandbox partner state",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SandboxPartnerResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "description": "Returns the sandbox partner for the current API key: profile, default link, list of links, direct subpartners, and reward totals.\n\nUse this endpoint if your partner interface needs to show a partner link, invite code, partner level, or accrued rewards.\n\n`Idempotency-Key` is not required for GET requests."
      },
      "post": {
        "operationId": "upsertSandboxPartner",
        "tags": [
          "post-partner"
        ],
        "summary": "POST /api/sandbox/partner",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpsertSandboxPartnerRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sandbox partner state",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SandboxPartnerResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "description": "Creates or updates the sandbox partner profile for the current API key.\n\nIf the partner does not exist yet, pass `invite_code` to attach it to an upstream sandbox partner. Maximum network depth is 5 levels; if the limit is exceeded, API returns `409 FLOW_CONFLICT`.\n\nPOST requests must include `Idempotency-Key` so a retry does not create a duplicate operation."
      }
    },
    "/api/sandbox/webhooks": {
      "get": {
        "operationId": "getSandboxWebhooks",
        "tags": [
          "get-webhooks"
        ],
        "summary": "GET /api/sandbox/webhooks",
        "responses": {
          "200": {
            "description": "Webhook endpoint and delivery attempts",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SandboxWebhooksResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "description": "Returns the webhook endpoint for the current Sandbox API key and recent delivery attempts.\n\n`signingSecret` is not returned by GET; use `secretPreview` for comparison. `Idempotency-Key` is not required for GET requests."
      },
      "post": {
        "operationId": "upsertSandboxWebhook",
        "tags": [
          "post-webhooks"
        ],
        "summary": "POST /api/sandbox/webhooks",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpsertSandboxWebhookRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Webhook endpoint created or updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SandboxWebhooksResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "description": "Creates or updates the sandbox webhook endpoint for the current API key. URL must be public `https://`, without credentials and without a local or private host.\n\nThe first create response and responses with `rotate_secret=true` return `signingSecret`; store it immediately. POST requests require `Idempotency-Key`."
      },
      "delete": {
        "operationId": "disableSandboxWebhook",
        "tags": [
          "delete-webhooks"
        ],
        "summary": "DELETE /api/sandbox/webhooks",
        "responses": {
          "200": {
            "description": "Webhook endpoint disabled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SandboxWebhooksResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "description": "Disables the webhook endpoint for the current API key. Delivery history remains available through GET Webhooks."
      }
    },
    "/api/sandbox/webhooks/test": {
      "post": {
        "operationId": "sendSandboxWebhookTest",
        "tags": [
          "post-webhook-test"
        ],
        "summary": "POST /api/sandbox/webhooks/test",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Test webhook event created and sent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SandboxWebhookTestResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "description": "Creates `sandbox.webhook.test`, signs the payload, and tries to send it to the enabled endpoint. If the endpoint is not configured, API returns `409 WEBHOOK_ENDPOINT_REQUIRED`."
      }
    },
    "/api/sandbox/webhooks/retry": {
      "post": {
        "operationId": "retrySandboxWebhookDeliveries",
        "tags": [
          "post-webhook-retry"
        ],
        "summary": "POST /api/sandbox/webhooks/retry",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SandboxWebhookRetryRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Webhook deliveries retried",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SandboxWebhookRetryResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "description": "Retries a specific delivery by `delivery_id` or a batch of pending/retrying/failed deliveries for the current API key. Already delivered deliveries are not retried."
      }
    }
  },
  "components": {
    "securitySchemes": {
      "SandboxBearer": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "sk_sandbox"
      }
    },
    "parameters": {
      "LoanId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        },
        "description": "Sandbox loan ID from activeLoans[].id or loanHistory[].id."
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "description": "Required unique key for a POST operation, usually a UUID. Repeating the same POST to the same endpoint with the same body returns the cached result with idempotency_replayed=true. The same key with a different body or endpoint returns IDEMPOTENCY_KEY_CONFLICT. Missing key returns IDEMPOTENCY_KEY_REQUIRED.",
        "schema": {
          "type": "string",
          "maxLength": 255,
          "example": "0d66a118-e544-4a82-94de-35c9f28a0f38"
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Sandbox API key is missing or invalid",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "Conflict": {
        "description": "The flow cannot continue from the current state, a balance/limit is insufficient, or Idempotency-Key conflicts",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "NotFound": {
        "description": "Loan was not found",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "BadRequest": {
        "description": "Invalid request: invalid body, missing Idempotency-Key for POST, unknown action, or invalid amount",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Sandbox API rate limit exceeded",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      }
    },
    "schemas": {
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "request_id": {
            "type": "string",
            "description": "Request ID for support and log lookup."
          },
          "error_code": {
            "type": "string",
            "description": "Formal error code for integration logic.",
            "example": "FLOW_CONFLICT",
            "enum": [
              "SANDBOX_API_KEY_REQUIRED",
              "SANDBOX_API_KEY_INVALID",
              "SANDBOX_API_KEY_DATABASE_REQUIRED",
              "SANDBOX_SESSION_REQUIRED",
              "SANDBOX_RATE_LIMIT_EXCEEDED",
              "INVALID_REQUEST",
              "INVALID_ACTION",
              "INVALID_SCOPE",
              "LOAN_NOT_FOUND",
              "WALLET_NOT_FOUND",
              "FLOW_CONFLICT",
              "LIMIT_EXCEEDED",
              "INSUFFICIENT_BALANCE",
              "IDEMPOTENCY_KEY_REQUIRED",
              "IDEMPOTENCY_KEY_CONFLICT",
              "IDEMPOTENCY_KEY_IN_PROGRESS",
              "DATABASE_UNAVAILABLE",
              "INTERNAL_ERROR"
            ]
          },
          "error": {
            "type": "string",
            "description": "Human-readable error message. Do not use it for integration logic; use error_code instead."
          }
        },
        "required": [
          "request_id",
          "error_code",
          "error"
        ]
      },
      "SandboxConfigResponse": {
        "type": "object",
        "properties": {
          "request_id": {
            "type": "string",
            "description": "Request ID for support and log lookup."
          },
          "success": {
            "type": "boolean",
            "example": true,
            "description": "Success flag."
          },
          "rates": {
            "$ref": "#/components/schemas/RateSnapshot"
          },
          "borrow_rates": {
            "$ref": "#/components/schemas/BorrowRates"
          },
          "risk": {
            "$ref": "#/components/schemas/RiskSettings"
          },
          "terms": {
            "$ref": "#/components/schemas/RepaymentTerms"
          },
          "fees": {
            "$ref": "#/components/schemas/FeeSettings"
          }
        },
        "required": [
          "request_id",
          "success",
          "rates",
          "borrow_rates",
          "risk",
          "terms",
          "fees"
        ]
      },
      "SandboxStateResponse": {
        "type": "object",
        "description": "Full Sandbox snapshot: loans plus partner data.",
        "properties": {
          "request_id": {
            "type": "string",
            "description": "Request ID for support and log lookup."
          },
          "success": {
            "type": "boolean",
            "example": true,
            "description": "Success flag."
          },
          "loans": {
            "$ref": "#/components/schemas/SandboxLoansState"
          },
          "partner": {
            "$ref": "#/components/schemas/SandboxPartnerState"
          }
        },
        "required": [
          "request_id",
          "success",
          "loans",
          "partner"
        ]
      },
      "SandboxPartnerResponse": {
        "type": "object",
        "properties": {
          "request_id": {
            "type": "string",
            "description": "Request ID for support and log lookup."
          },
          "idempotency_replayed": {
            "type": "boolean",
            "description": "true when a POST response was replayed from the Idempotency-Key cache. Omitted for GET responses."
          },
          "success": {
            "type": "boolean",
            "example": true,
            "description": "Success flag."
          },
          "partner": {
            "$ref": "#/components/schemas/SandboxPartnerProfile"
          },
          "rewards": {
            "$ref": "#/components/schemas/SandboxPartnerRewards"
          },
          "subpartners": {
            "type": "array",
            "description": "Direct subpartners of the current partner.",
            "items": {
              "$ref": "#/components/schemas/SandboxSubpartner"
            }
          },
          "links": {
            "type": "array",
            "description": "Partner links of the current partner.",
            "items": {
              "$ref": "#/components/schemas/SandboxPartnerLink"
            }
          }
        },
        "required": [
          "request_id",
          "success"
        ]
      },
      "SandboxLoanFlowRequest": {
        "type": "object",
        "properties": {
          "action": {
            "type": "string",
            "enum": [
              "start_collateral_transfer",
              "confirm_btc",
              "run_kyt",
              "complete_payout"
            ],
            "description": "Loan-flow action. Order: start_collateral_transfer -> confirm_btc -> run_kyt -> complete_payout."
          },
          "collateral_btc": {
            "type": "number",
            "example": 0.1,
            "description": "BTC collateral amount. Required for start_collateral_transfer."
          },
          "loan_amount": {
            "type": "number",
            "example": 3000,
            "description": "Loan amount. Required for start_collateral_transfer."
          },
          "loan_currency": {
            "type": "string",
            "enum": [
              "RUB",
              "USDT"
            ],
            "example": "USDT",
            "description": "Loan currency. Defaults to RUB when omitted."
          },
          "partner_link_slug": {
            "type": "string",
            "nullable": true,
            "description": "Partner-link slug used to calculate partner margin/rewards."
          },
          "external_client_id": {
            "type": "string",
            "nullable": true,
            "description": "External client ID in the partner system. Used to group Sandbox loans in the admin interface.",
            "example": "client-42"
          },
          "client_reference": {
            "type": "string",
            "nullable": true,
            "description": "Human-readable reference to the client or deal in the partner system.",
            "example": "ACME treasury desk"
          },
          "tx_hash": {
            "type": "string",
            "nullable": true,
            "description": "Test transaction hash. Pass it on confirm_btc to simulate BTC credit."
          },
          "confirmations": {
            "type": "number",
            "example": 1,
            "description": "Number of test confirmations. For confirm_btc, 1 is usually enough."
          },
          "kyt_report": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "Arbitrary KYT payload for run_kyt. Stored in Sandbox as a test report."
          }
        },
        "description": "Request body for one loan-flow step. action may be omitted; start_collateral_transfer is used by default."
      },
      "CreateSandboxLoanRequest": {
        "type": "object",
        "properties": {
          "collateral_btc": {
            "type": "number",
            "example": 0.1,
            "description": "BTC collateral amount."
          },
          "loan_amount": {
            "type": "number",
            "example": 3000,
            "description": "Loan amount."
          },
          "loan_currency": {
            "type": "string",
            "enum": [
              "RUB",
              "USDT"
            ],
            "example": "USDT",
            "description": "Loan currency. Defaults to RUB when omitted."
          },
          "partner_link_slug": {
            "type": "string",
            "nullable": true,
            "description": "Partner-link slug used to calculate partner margin/rewards."
          },
          "external_client_id": {
            "type": "string",
            "nullable": true,
            "description": "External client ID in the partner system. Used to group Sandbox loans in the admin interface.",
            "example": "client-42"
          },
          "client_reference": {
            "type": "string",
            "nullable": true,
            "description": "Human-readable reference to the client or deal in the partner system.",
            "example": "ACME treasury desk"
          }
        },
        "description": "Body for direct Sandbox loan creation without the step-by-step flow.",
        "required": [
          "collateral_btc",
          "loan_amount"
        ]
      },
      "SandboxLoanActionRequest": {
        "type": "object",
        "properties": {
          "action": {
            "type": "string",
            "example": "repay_interest",
            "enum": [
              "repay_interest",
              "repay_principal",
              "add_collateral",
              "remove_collateral",
              "borrow_more"
            ],
            "description": "Direct action on an active Sandbox loan."
          },
          "amount": {
            "type": "number",
            "example": 100,
            "description": "Operation amount. May be omitted for full repayment."
          },
          "fullRepayment": {
            "type": "boolean",
            "description": "true for full principal repayment through the shortcut action.",
            "example": true
          }
        }
      },
      "SandboxRepaymentFlowRequest": {
        "type": "object",
        "properties": {
          "action": {
            "type": "string",
            "enum": [
              "start_payment",
              "confirm_usdt",
              "run_kyt",
              "complete_settlement",
              "complete_collateral_return"
            ],
            "description": "Repayment-flow action. Order: start_payment -> confirm_usdt -> run_kyt -> complete_settlement -> complete_collateral_return."
          },
          "operation_id": {
            "type": "string",
            "nullable": true,
            "description": "Repayment operation ID from sandboxFlow.repaymentOperations[].id. Required after start_payment."
          },
          "repayment_kind": {
            "type": "string",
            "enum": [
              "interest",
              "partial_principal",
              "full_principal"
            ],
            "example": "full_principal",
            "description": "Repayment type for start_payment: interest, partial_principal, or full_principal."
          },
          "amount": {
            "type": "number",
            "nullable": true,
            "description": "Partial repayment or interest-payment amount. May be omitted for full_principal when full_repayment=true."
          },
          "full_repayment": {
            "type": "boolean",
            "example": true,
            "description": "true for full principal repayment followed by collateral return."
          },
          "tx_hash": {
            "type": "string",
            "nullable": true,
            "description": "Test transaction hash. Pass it on confirm_usdt to simulate payment credit."
          },
          "confirmations": {
            "type": "number",
            "example": 1,
            "description": "Number of test confirmations. For confirm_usdt, 1 is usually enough."
          },
          "kyt_report": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "Arbitrary KYT payload for run_kyt. Stored in Sandbox as a test report."
          }
        },
        "description": "Request body for one repayment-flow step. action may be omitted; start_payment is used by default."
      },
      "UpsertSandboxPartnerRequest": {
        "type": "object",
        "properties": {
          "display_name": {
            "type": "string",
            "example": "Sandbox Partner",
            "description": "Partner display name."
          },
          "fixed_margin_percent": {
            "type": "number",
            "example": 0,
            "description": "Deprecated field; currently not used for Sandbox link calculation."
          },
          "phone": {
            "type": "string",
            "nullable": true,
            "description": "Partner phone number.",
            "example": "+79990000000"
          },
          "invite_code": {
            "type": "string",
            "nullable": true,
            "description": "Invite code of the upstream sandbox partner used to build the network.",
            "example": "sp123abc"
          }
        },
        "description": "Body for creating or updating a sandbox partner."
      },
      "RateSnapshot": {
        "type": "object",
        "description": "Rates used by Sandbox calculator and loan flow.",
        "properties": {
          "btc_usd": {
            "type": "number",
            "description": "Market BTC/USD rate.",
            "example": 68000
          },
          "btc_rub": {
            "type": "number",
            "description": "Calculated BTC/RUB rate.",
            "example": 6120000
          },
          "usd_rub": {
            "type": "number",
            "description": "Base USD/RUB rate kept for compatibility.",
            "example": 90
          },
          "usd_rub_market": {
            "type": "number",
            "description": "Market USD/RUB rate.",
            "example": 90
          },
          "usd_rub_payout": {
            "type": "number",
            "description": "USD/RUB rate for RUB loan payout, including sandbox FX margin.",
            "example": 87.75
          },
          "usd_rub_obligation": {
            "type": "number",
            "description": "USD/RUB rate for RUB loan/repayment obligations, including sandbox FX margin.",
            "example": 92.25
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "Time when rates were last updated."
          }
        }
      },
      "BorrowRates": {
        "type": "object",
        "properties": {
          "fixed": {
            "type": "object",
            "description": "Fixed loan rate.",
            "properties": {
              "enabled": {
                "type": "boolean",
                "description": "Whether the fixed rate is available.",
                "example": true
              },
              "annual_percent": {
                "type": "number",
                "description": "Annual borrower rate in percent.",
                "example": 19
              }
            }
          }
        }
      },
      "RiskSettings": {
        "type": "object",
        "properties": {
          "max_ltv": {
            "type": "number",
            "description": "Maximum LTV for loan creation. 0.73 means 73%.",
            "example": 0.73
          },
          "liquidation_threshold": {
            "type": "number",
            "description": "Liquidation threshold. 0.775 means 77.5%.",
            "example": 0.775
          },
          "liquidation_penalty": {
            "type": "number",
            "description": "Liquidation penalty. 0.05 means 5%.",
            "example": 0.05
          }
        }
      },
      "RepaymentTerms": {
        "type": "object",
        "properties": {
          "interest_payment_months": {
            "type": "integer",
            "description": "Number of months before the interest payment is due.",
            "example": 1
          },
          "principal_payment_months": {
            "type": "integer",
            "description": "Number of months before the principal payment is due.",
            "example": 12
          }
        }
      },
      "FeeSettings": {
        "type": "object",
        "properties": {
          "loan_origination_percent": {
            "type": "number",
            "description": "Loan origination fee in percent.",
            "example": 1.5
          }
        }
      },
      "SandboxTestWallet": {
        "type": "object",
        "description": "Deterministic test wallet/address for Sandbox flow. Not a real custody wallet.",
        "properties": {
          "purpose": {
            "type": "string",
            "enum": [
              "collateral_deposit",
              "loan_payout",
              "repayment",
              "collateral_return"
            ],
            "description": "Test wallet purpose within the flow."
          },
          "asset": {
            "type": "string",
            "enum": [
              "BTC",
              "USDT"
            ],
            "description": "Wallet asset."
          },
          "network": {
            "type": "string",
            "enum": [
              "sandbox"
            ],
            "description": "Always sandbox: the address is not intended for a real blockchain transfer."
          },
          "chainCode": {
            "type": "string",
            "enum": [
              "SANDBOX_BTC",
              "SANDBOX_USDT"
            ],
            "description": "Test network code."
          },
          "address": {
            "type": "string",
            "description": "Test address that the partner shows to the client or uses in the simulation.",
            "example": "sandbox:collateral_deposit:client42"
          },
          "isRealWallet": {
            "type": "boolean",
            "enum": [
              false
            ],
            "description": "Always false: Sandbox does not create a real custody wallet."
          },
          "confirmationMode": {
            "type": "string",
            "enum": [
              "api_simulated"
            ],
            "description": "Confirmation is performed through an API action, not external blockchain polling."
          },
          "requiredConfirmations": {
            "type": "integer",
            "description": "Number of confirmations to pass in the confirm action.",
            "example": 1
          }
        },
        "required": [
          "purpose",
          "asset",
          "network",
          "chainCode",
          "address",
          "isRealWallet",
          "confirmationMode",
          "requiredConfirmations"
        ]
      },
      "SandboxFlowStep": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Machine-readable flow step ID."
          },
          "title": {
            "type": "string",
            "description": "Step title for display."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "processing",
              "completed"
            ],
            "description": "Current step status."
          }
        }
      },
      "SandboxLoanApplicationFlow": {
        "type": "object",
        "description": "Current loan application flow for loan issuance.",
        "properties": {
          "id": {
            "type": "string",
            "description": "Draft/application flow ID."
          },
          "loanId": {
            "type": "string",
            "nullable": true,
            "description": "Created loan ID. Can be null before complete_payout."
          },
          "externalClientId": {
            "type": "string",
            "nullable": true,
            "description": "Client ID in the partner system."
          },
          "clientReference": {
            "type": "string",
            "nullable": true,
            "description": "Human-readable reference to the client or deal."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "completed"
            ],
            "description": "Loan application flow status."
          },
          "currentStepId": {
            "type": "string",
            "nullable": true,
            "enum": [
              "calculator",
              "collateral_transfer",
              "btc_confirmation",
              "kyt",
              "payout"
            ],
            "description": "Next or current step that must be completed."
          },
          "collateralRequiredConfirmations": {
            "type": "integer",
            "description": "Number of confirmations to pass in confirm_btc.",
            "example": 1
          },
          "payoutRequiredConfirmations": {
            "type": "integer",
            "description": "Number of confirmations expected for the test payout.",
            "example": 1
          },
          "wallets": {
            "type": "object",
            "description": "Test wallets for loan flow.",
            "properties": {
              "collateralDeposit": {
                "$ref": "#/components/schemas/SandboxTestWallet"
              },
              "loanPayout": {
                "$ref": "#/components/schemas/SandboxTestWallet"
              },
              "collateralReturn": {
                "$ref": "#/components/schemas/SandboxTestWallet"
              }
            }
          },
          "steps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SandboxFlowStep"
            }
          }
        }
      },
      "SandboxRepaymentOperationFlow": {
        "type": "object",
        "description": "Repayment operation flow for interest, partial, or full repayment.",
        "properties": {
          "id": {
            "type": "string",
            "description": "Repayment operation ID. Pass it as operation_id on following steps."
          },
          "loanId": {
            "type": "string",
            "nullable": true,
            "description": "ID of the loan this repayment operation belongs to."
          },
          "kind": {
            "type": "string",
            "enum": [
              "interest",
              "partial_principal",
              "full_principal"
            ],
            "description": "Repayment type."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "completed"
            ],
            "description": "Repayment flow status."
          },
          "currentStepId": {
            "type": "string",
            "nullable": true,
            "enum": [
              "payment",
              "usdt_confirmation",
              "kyt",
              "interest_settlement",
              "debt_update",
              "collateral_return"
            ],
            "description": "Next or current step of the repayment flow."
          },
          "requiredConfirmations": {
            "type": "integer",
            "description": "Number of confirmations to pass in confirm_usdt.",
            "example": 1
          },
          "wallets": {
            "type": "object",
            "description": "Test wallets for repayment flow.",
            "properties": {
              "repayment": {
                "$ref": "#/components/schemas/SandboxTestWallet"
              },
              "collateralReturn": {
                "nullable": true,
                "allOf": [
                  {
                    "$ref": "#/components/schemas/SandboxTestWallet"
                  }
                ],
                "description": "Collateral-return wallet. Filled for full repayment."
              }
            }
          },
          "steps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SandboxFlowStep"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "Time when the repayment operation was created."
          }
        }
      },
      "SandboxLoanFlowState": {
        "type": "object",
        "properties": {
          "loanApplications": {
            "type": "array",
            "description": "All active and completed loan application flows.",
            "items": {
              "$ref": "#/components/schemas/SandboxLoanApplicationFlow"
            }
          },
          "repaymentOperations": {
            "type": "array",
            "description": "All repayment flows for loans.",
            "items": {
              "$ref": "#/components/schemas/SandboxRepaymentOperationFlow"
            }
          }
        }
      },
      "SandboxWalletState": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Simulator sandbox wallet ID."
          },
          "virtualBtcBalance": {
            "type": "string",
            "description": "Available test BTC balance."
          },
          "virtualUsdBalance": {
            "type": "string",
            "description": "Available test USD/USDT balance."
          },
          "virtualRubBalance": {
            "type": "string",
            "description": "Available test RUB balance."
          },
          "lockedBtcBalance": {
            "type": "string",
            "description": "BTC locked as collateral."
          },
          "investedRubBalance": {
            "type": "string",
            "description": "RUB used in the investment scenario."
          }
        }
      },
      "SandboxLoanRecord": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Sandbox loan ID."
          },
          "status": {
            "type": "string",
            "description": "Loan status.",
            "example": "active"
          },
          "collateral_btc": {
            "type": "number",
            "description": "Total BTC collateral."
          },
          "collateral_received": {
            "type": "number",
            "description": "Actually confirmed BTC collateral."
          },
          "collateral_address": {
            "type": "string",
            "nullable": true,
            "description": "Collateral address in the simulator."
          },
          "external_client_id": {
            "type": "string",
            "nullable": true,
            "description": "Client ID in the partner system."
          },
          "client_reference": {
            "type": "string",
            "nullable": true,
            "description": "Human-readable reference to the client or deal."
          },
          "loan_amount": {
            "type": "number",
            "description": "Original loan amount."
          },
          "loan_currency": {
            "type": "string",
            "enum": [
              "RUB",
              "USDT"
            ],
            "description": "Loan currency."
          },
          "debt_amount": {
            "type": "number",
            "description": "Original principal debt."
          },
          "annual_rate": {
            "type": "number",
            "description": "Annual loan rate in percent."
          },
          "btc_rate": {
            "type": "number",
            "description": "BTC rate fixed at creation time."
          },
          "usd_rub_rate": {
            "type": "number",
            "description": "USD/RUB rate fixed at creation time."
          },
          "current_collateral_usd": {
            "type": "number",
            "description": "Current collateral value in USD."
          },
          "current_collateral_rub": {
            "type": "number",
            "description": "Current collateral value in RUB."
          },
          "current_debt": {
            "type": "number",
            "description": "Current debt after operations."
          },
          "health_factor": {
            "type": "number",
            "description": "Loan health indicator: higher values are safer."
          },
          "accrued_interest": {
            "type": "number",
            "description": "Accrued interest."
          },
          "max_ltv": {
            "type": "number",
            "description": "Maximum LTV for the loan."
          },
          "liquidation_threshold": {
            "type": "number",
            "description": "Liquidation threshold for the loan."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "Time when the loan was created."
          },
          "funded_at": {
            "type": "string",
            "format": "date-time",
            "description": "Time when the loan was funded."
          },
          "interest_due_at": {
            "type": "string",
            "nullable": true,
            "format": "date-time",
            "description": "Nearest interest payment due date."
          },
          "principal_due_at": {
            "type": "string",
            "nullable": true,
            "format": "date-time",
            "description": "Principal payment due date."
          }
        }
      },
      "SandboxLoanHistoryEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "History event ID."
          },
          "type": {
            "type": "string",
            "enum": [
              "opened",
              "collateral",
              "loan",
              "interest_paid",
              "closed"
            ],
            "description": "Event type."
          },
          "title": {
            "type": "string",
            "description": "Event title for UI."
          },
          "amountLabel": {
            "type": "string",
            "description": "Primary display amount."
          },
          "amountSubLabel": {
            "type": "string",
            "nullable": true,
            "description": "Secondary display amount."
          },
          "rateLabel": {
            "type": "string",
            "nullable": true,
            "description": "Rate or FX value for display."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "Time when the event happened."
          },
          "loanId": {
            "type": "string",
            "description": "Related loan ID."
          },
          "canRemoveCollateral": {
            "type": "boolean",
            "description": "Whether part of collateral can be removed after this event."
          }
        }
      },
      "SandboxLoanDraft": {
        "type": "object",
        "nullable": true,
        "properties": {
          "id": {
            "type": "string",
            "description": "Draft application ID."
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "completed",
              "discarded"
            ],
            "description": "Draft application status."
          },
          "currentStep": {
            "type": "string",
            "enum": [
              "contract",
              "kyt",
              "collateral",
              "cfa"
            ],
            "description": "Current draft step in the legacy simulator flow."
          },
          "loanId": {
            "type": "string",
            "nullable": true,
            "description": "Created loan ID if the draft is already completed."
          },
          "loanParams": {
            "type": "object",
            "properties": {
              "collateral_btc": {
                "type": "number",
                "description": "BTC collateral of the draft application."
              },
              "loan_amount": {
                "type": "number",
                "description": "Loan amount of the draft application."
              },
              "loan_currency": {
                "type": "string",
                "enum": [
                  "RUB",
                  "USDT"
                ],
                "description": "Loan currency of the draft application."
              }
            }
          },
          "contractAddress": {
            "type": "string",
            "nullable": true,
            "description": "Test contract/collateral address."
          },
          "kytReport": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "KYT payload passed to run_kyt."
          },
          "collateralPosted": {
            "type": "boolean",
            "description": "Whether collateral is confirmed."
          },
          "cfaIssued": {
            "type": "boolean",
            "description": "Whether loan payout is completed."
          },
          "collateralPostedAt": {
            "type": "string",
            "nullable": true,
            "format": "date-time"
          },
          "cfaIssuedAt": {
            "type": "string",
            "nullable": true,
            "format": "date-time"
          },
          "completedAt": {
            "type": "string",
            "nullable": true,
            "format": "date-time"
          },
          "discardedAt": {
            "type": "string",
            "nullable": true,
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SandboxLoansState": {
        "type": "object",
        "properties": {
          "activatedScenarios": {
            "type": "object",
            "description": "Sandbox scenarios enabled for the current user/API key.",
            "properties": {
              "miner": {
                "type": "boolean",
                "description": "Whether the borrower/miner scenario is enabled."
              },
              "investor": {
                "type": "boolean",
                "description": "Whether the investor scenario is enabled."
              }
            }
          },
          "wallet": {
            "nullable": true,
            "allOf": [
              {
                "$ref": "#/components/schemas/SandboxWalletState"
              }
            ],
            "description": "Simulator test wallet."
          },
          "activeLoans": {
            "type": "array",
            "description": "Active Sandbox loans.",
            "items": {
              "$ref": "#/components/schemas/SandboxLoanRecord"
            }
          },
          "loanHistory": {
            "type": "array",
            "description": "Closed or historical Sandbox loans.",
            "items": {
              "$ref": "#/components/schemas/SandboxLoanRecord"
            }
          },
          "loanHistoryEvents": {
            "type": "array",
            "description": "Loan history events for UI/timeline rendering.",
            "items": {
              "$ref": "#/components/schemas/SandboxLoanHistoryEvent"
            }
          },
          "loanDraft": {
            "$ref": "#/components/schemas/SandboxLoanDraft"
          },
          "sandboxFlow": {
            "$ref": "#/components/schemas/SandboxLoanFlowState"
          }
        }
      },
      "SandboxLoansStateResponse": {
        "type": "object",
        "properties": {
          "request_id": {
            "type": "string",
            "description": "Request ID for support and log lookup."
          },
          "idempotency_replayed": {
            "type": "boolean",
            "description": "true when a POST response was replayed from the Idempotency-Key cache. Omitted for GET responses."
          },
          "success": {
            "type": "boolean",
            "example": true,
            "description": "Success flag."
          },
          "activatedScenarios": {
            "type": "object",
            "description": "Sandbox scenarios enabled for the current user/API key.",
            "properties": {
              "miner": {
                "type": "boolean",
                "description": "Whether the borrower/miner scenario is enabled."
              },
              "investor": {
                "type": "boolean",
                "description": "Whether the investor scenario is enabled."
              }
            }
          },
          "wallet": {
            "nullable": true,
            "allOf": [
              {
                "$ref": "#/components/schemas/SandboxWalletState"
              }
            ],
            "description": "Simulator test wallet."
          },
          "activeLoans": {
            "type": "array",
            "description": "Active Sandbox loans.",
            "items": {
              "$ref": "#/components/schemas/SandboxLoanRecord"
            }
          },
          "loanHistory": {
            "type": "array",
            "description": "Closed or historical Sandbox loans.",
            "items": {
              "$ref": "#/components/schemas/SandboxLoanRecord"
            }
          },
          "loanHistoryEvents": {
            "type": "array",
            "description": "Loan history events for UI/timeline rendering.",
            "items": {
              "$ref": "#/components/schemas/SandboxLoanHistoryEvent"
            }
          },
          "loanDraft": {
            "$ref": "#/components/schemas/SandboxLoanDraft"
          },
          "sandboxFlow": {
            "$ref": "#/components/schemas/SandboxLoanFlowState"
          }
        },
        "required": [
          "request_id",
          "success"
        ]
      },
      "SandboxPartnerLink": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Partner link ID."
          },
          "slug": {
            "type": "string",
            "description": "Link slug that can be passed as partner_link_slug when creating a loan."
          },
          "label": {
            "type": "string",
            "description": "Link label."
          },
          "marginPercent": {
            "type": "number",
            "description": "Partner margin/rate for the link in percent. Sandbox defaults to 4%."
          },
          "disabledAt": {
            "type": "string",
            "nullable": true,
            "format": "date-time",
            "description": "Time when the link was disabled."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "Time when the link was created."
          }
        }
      },
      "SandboxPartnerProfile": {
        "type": "object",
        "nullable": true,
        "properties": {
          "id": {
            "type": "string",
            "description": "Sandbox partner ID."
          },
          "displayName": {
            "type": "string",
            "description": "Partner display name."
          },
          "phone": {
            "type": "string",
            "nullable": true,
            "description": "Partner phone number, if provided."
          },
          "inviteCode": {
            "type": "string",
            "description": "Invite code used to connect subpartners."
          },
          "partnerLevel": {
            "type": "integer",
            "description": "Partner level in the sandbox network."
          },
          "parentPartnerId": {
            "type": "string",
            "nullable": true,
            "description": "Upstream partner ID."
          },
          "status": {
            "type": "string",
            "description": "Partner status.",
            "example": "active"
          },
          "defaultLink": {
            "nullable": true,
            "allOf": [
              {
                "$ref": "#/components/schemas/SandboxPartnerLink"
              }
            ],
            "description": "Primary partner link."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "Time when the partner was created."
          }
        }
      },
      "SandboxPartnerRewards": {
        "type": "object",
        "properties": {
          "totalUsd": {
            "type": "number",
            "description": "Total sandbox rewards in USD."
          },
          "directUsd": {
            "type": "number",
            "description": "Direct rewards in USD."
          },
          "networkUsd": {
            "type": "number",
            "description": "Network rewards in USD."
          }
        }
      },
      "SandboxSubpartner": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "ID subpartner."
          },
          "displayName": {
            "type": "string",
            "description": "Subpartner name."
          },
          "inviteCode": {
            "type": "string",
            "description": "Invite code subpartner."
          },
          "partnerLevel": {
            "type": "integer",
            "description": "Subpartner level."
          },
          "rewardUsd": {
            "type": "number",
            "description": "Reward from this subpartner in USD."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "Time when the subpartner was created."
          }
        }
      },
      "SandboxPartnerState": {
        "type": "object",
        "properties": {
          "partner": {
            "$ref": "#/components/schemas/SandboxPartnerProfile"
          },
          "rewards": {
            "$ref": "#/components/schemas/SandboxPartnerRewards"
          },
          "subpartners": {
            "type": "array",
            "description": "Direct subpartners of the current partner.",
            "items": {
              "$ref": "#/components/schemas/SandboxSubpartner"
            }
          },
          "links": {
            "type": "array",
            "description": "Partner links of the current partner.",
            "items": {
              "$ref": "#/components/schemas/SandboxPartnerLink"
            }
          }
        }
      },
      "SandboxWebhookEventType": {
        "type": "string",
        "enum": [
          "sandbox.loan_application.updated",
          "sandbox.loan.issued",
          "sandbox.loan.updated",
          "sandbox.repayment.updated",
          "sandbox.repayment.settled",
          "sandbox.collateral.returned",
          "sandbox.partner.updated",
          "sandbox.webhook.test"
        ],
        "description": "Webhook event type."
      },
      "SandboxWebhookEndpoint": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "environment": {
            "type": "string",
            "enum": [
              "sandbox"
            ]
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Public HTTPS partner endpoint."
          },
          "enabled": {
            "type": "boolean"
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SandboxWebhookEventType"
            }
          },
          "secretPreview": {
            "type": "string",
            "description": "Masked signing secret. The full secret is returned only on create/rotation."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "disabledAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "required": [
          "id",
          "environment",
          "url",
          "enabled",
          "events",
          "secretPreview",
          "createdAt",
          "updatedAt",
          "disabledAt"
        ]
      },
      "SandboxWebhookDelivery": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "eventId": {
            "type": "string",
            "format": "uuid"
          },
          "eventType": {
            "$ref": "#/components/schemas/SandboxWebhookEventType"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "processing",
              "delivered",
              "retrying",
              "failed"
            ]
          },
          "attemptCount": {
            "type": "integer",
            "minimum": 0
          },
          "nextAttemptAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "lastAttemptAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "lastStatusCode": {
            "type": "integer",
            "nullable": true
          },
          "lastError": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "eventId",
          "eventType",
          "status",
          "attemptCount",
          "createdAt",
          "updatedAt"
        ]
      },
      "SandboxWebhookEvent": {
        "type": "object",
        "description": "JSON body that Fundora sends to the partner webhook endpoint.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "$ref": "#/components/schemas/SandboxWebhookEventType"
          },
          "environment": {
            "type": "string",
            "enum": [
              "sandbox"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "request_id": {
            "type": "string",
            "nullable": true
          },
          "idempotency_key": {
            "type": "string",
            "nullable": true
          },
          "data": {
            "type": "object",
            "additionalProperties": true
          }
        },
        "required": [
          "id",
          "type",
          "environment",
          "created_at",
          "data"
        ]
      },
      "SandboxWebhooksResponse": {
        "type": "object",
        "properties": {
          "request_id": {
            "type": "string"
          },
          "idempotency_replayed": {
            "type": "boolean"
          },
          "success": {
            "type": "boolean",
            "example": true
          },
          "webhook": {
            "nullable": true,
            "allOf": [
              {
                "$ref": "#/components/schemas/SandboxWebhookEndpoint"
              }
            ]
          },
          "deliveries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SandboxWebhookDelivery"
            }
          },
          "signingSecret": {
            "type": "string",
            "description": "Full signing secret. Returned only when the endpoint is created or rotate_secret=true."
          }
        },
        "required": [
          "request_id",
          "success",
          "webhook",
          "deliveries"
        ]
      },
      "UpsertSandboxWebhookRequest": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Public HTTPS endpoint. Local/private hosts are rejected."
          },
          "enabled": {
            "type": "boolean",
            "default": true
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SandboxWebhookEventType"
            },
            "description": "Non-empty event list. If omitted on first create, the endpoint subscribes to all events."
          },
          "rotate_secret": {
            "type": "boolean",
            "default": false,
            "description": "If true, API creates a new signing_secret and returns it in the response."
          }
        },
        "required": [
          "url"
        ]
      },
      "SandboxWebhookRetryRequest": {
        "type": "object",
        "properties": {
          "delivery_id": {
            "type": "string",
            "format": "uuid",
            "description": "If provided, retries this specific delivery."
          },
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 25,
            "default": 5,
            "description": "If delivery_id is omitted, retries a batch of pending/retrying/failed deliveries."
          }
        }
      },
      "SandboxWebhookRetryResponse": {
        "type": "object",
        "properties": {
          "request_id": {
            "type": "string"
          },
          "idempotency_replayed": {
            "type": "boolean"
          },
          "success": {
            "type": "boolean",
            "example": true
          },
          "retried": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SandboxWebhookDelivery"
            }
          },
          "webhook": {
            "nullable": true,
            "allOf": [
              {
                "$ref": "#/components/schemas/SandboxWebhookEndpoint"
              }
            ]
          },
          "deliveries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SandboxWebhookDelivery"
            }
          }
        },
        "required": [
          "request_id",
          "success",
          "retried",
          "webhook",
          "deliveries"
        ]
      },
      "SandboxWebhookTestResponse": {
        "type": "object",
        "properties": {
          "request_id": {
            "type": "string"
          },
          "idempotency_replayed": {
            "type": "boolean"
          },
          "success": {
            "type": "boolean",
            "example": true
          },
          "delivery": {
            "type": "object",
            "properties": {
              "delivery": {
                "nullable": true,
                "allOf": [
                  {
                    "$ref": "#/components/schemas/SandboxWebhookDelivery"
                  }
                ]
              },
              "dispatched": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/SandboxWebhookDelivery"
                }
              }
            }
          },
          "webhook": {
            "nullable": true,
            "allOf": [
              {
                "$ref": "#/components/schemas/SandboxWebhookEndpoint"
              }
            ]
          },
          "deliveries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SandboxWebhookDelivery"
            }
          }
        },
        "required": [
          "request_id",
          "success",
          "delivery",
          "webhook",
          "deliveries"
        ]
      }
    }
  }
}
