{
  "openapi": "3.0.3",
  "info": {
    "title": "IMBA Agent API",
    "version": "0.1.0",
    "description": "Permissionless prepaid agents: Ed25519 register, USDT TRC-20 fund, retail catalog buy. Not Partner B2B (/api/partner, ledger 2403). Not a human Wallet HS256 session. Tier 0 (default): no withdraw, C2C, convert. Tier 1+ is operator reputation after catalog spend, not a self-serve cash-out switch. Convert / fx_quote / fx_execute return 403 not_allowed until a later release."
  },
  "servers": [
    { "url": "https://imbawallet.com" }
  ],
  "paths": {
    "/auth/v1/agent/register": {
      "post": {
        "summary": "Identity-only register",
        "description": "Creates core.client (is_agent) and Ed25519 credential. Does not assign a TRON address or credit 2401. Optional email is the 3-D Secure mailbox (AhaSend), not SMS. HMAC plaintext only if callback_url is accepted. Caution medium or agent/enabled=false → 503.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["public_key"],
                "properties": {
                  "public_key": { "type": "string", "description": "Ed25519 public key (PEM or base64)" },
                  "tos_hash": { "type": "string" },
                  "callback_url": { "type": "string", "format": "uri", "description": "HTTPS webhook URL" },
                  "jwe_public_key": { "type": "string", "description": "X25519 public key for JWE card secrets" },
                  "email": { "type": "string", "format": "email", "description": "Optional mailbox for 3-D Secure OTP (AhaSend). Not SMS. Not cardholder KYC." }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "client_id, kid, optional hmac_secret (once), optional email" },
          "400": { "description": "invalid_request" },
          "429": { "description": "rate_limited" },
          "503": { "description": "unavailable (caution medium/under_attack or kill-switch)" }
        }
      }
    },
    "/auth/v1/agent/token": {
      "post": {
        "summary": "Exchange assertion for access JWT",
        "description": "Short-lived EdDSA assertion (aud=imba-agent-api, iss=decimal client_id, ttl≤60s). Access token typ=agent, client_id, same JWKS as Partner. under_attack or agent/enabled=false → 503. 30 assertions/min/kid and 60/min/IP (0 = kill).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["assertion"],
                "properties": { "assertion": { "type": "string" } }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "access_token Bearer ~900s" },
          "401": { "description": "invalid_assertion" },
          "429": { "description": "rate_limited" },
          "503": { "description": "unavailable (under_attack or kill-switch, including token)" }
        }
      }
    },
    "/auth/v1/agent/webhook": {
      "patch": {
        "security": [{ "bearerAuth": [] }],
        "summary": "Set webhook URL / JWE key / email (IMBA generates HMAC)",
        "description": "Email-only PATCH does not require a webhook row. Email is the 3-D Secure mailbox.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "callback_url": { "type": "string", "format": "uri" },
                  "jwe_public_key": { "type": "string" },
                  "email": { "type": "string", "format": "email" }
                }
              }
            }
          }
        }
      }
    },
    "/auth/v1/agent/hmac/rotate": {
      "post": {
        "security": [{ "bearerAuth": [] }],
        "summary": "Rotate HMAC; previous secret dies immediately. Requires callback_url first; otherwise 400 callback_url required before rotate."
      }
    },
    "/api/deposit_address": {
      "post": {
        "security": [{ "bearerAuth": [] }],
        "summary": "TRON USDT deposit address",
        "description": "Agents: 24h unfunded hold then pool; after first deposit_client sticky. Always re-fetch before send. Expired unfunded holds of any agent return to the pool. Empty pool → 503 unavailable (not an empty HTTP 500). Humans keep ~30d rotation. Partner uses /api/partner/*.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": { "blockchain": { "type": "string", "example": "tron" } }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "address plus agent fields",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "address": { "type": "string" },
                    "blockchain": { "type": "string" },
                    "sticky": { "type": "boolean" },
                    "unfunded_release_at": { "type": "string", "nullable": true },
                    "confirmation_blocks": { "type": "integer" }
                  }
                }
              }
            }
          },
          "503": { "description": "{\"error\":\"unavailable\"} — TRON user pool has no free slot (needs SQL pack 20260826_1200 and PC 503 snake_case body)" }
        }
      }
    },
    "/api/balance": {
      "get": {
        "security": [{ "bearerAuth": [] }],
        "summary": "Ledger balances",
        "description": "GET or POST. Spendable catalog currency is USDT (2401 after a confirmed TRC-20 deposit). Empty/unfunded agent: USDT 0."
      },
      "post": {
        "security": [{ "bearerAuth": [] }],
        "summary": "Ledger balances (POST alias of GET)"
      }
    },
    "/api/card_products": {
      "post": {
        "security": [{ "bearerAuth": [] }],
        "summary": "Virtual card products and prices",
        "description": "Use id as imba_product_id for create_card. GET /api/client/card_products is the same handler. Space catalog reads ~1s apart (429 catalog_gap otherwise)."
      }
    },
    "/api/client/card_products": {
      "get": {
        "security": [{ "bearerAuth": [] }],
        "summary": "Virtual card products (Wallet path)"
      }
    },
    "/api/offers": {
      "post": {
        "security": [{ "bearerAuth": [] }],
        "summary": "Gift catalog",
        "description": "Optional body.query. Then POST /api/purchase with offer_id + ext_id. Space catalog reads ~1s apart."
      }
    },
    "/api/esim/plans": {
      "get": {
        "security": [{ "bearerAuth": [] }],
        "summary": "eSIM plans",
        "description": "Query esim_provider=yesim and optional country. POST with the same JSON body also works."
      },
      "post": {
        "security": [{ "bearerAuth": [] }],
        "summary": "eSIM plans (POST body)"
      }
    },
    "/api/esim/list": {
      "post": {
        "security": [{ "bearerAuth": [] }],
        "summary": "List this agent's eSIM profiles"
      }
    },
    "/api/cards": {
      "post": {
        "security": [{ "bearerAuth": [] }],
        "summary": "List this agent's cards"
      }
    },
    "/api/pin/create_card": {
      "post": {
        "security": [{ "bearerAuth": [] }],
        "summary": "Issue card from 2401 USDT (ext_id required for agents)"
      }
    },
    "/api/pin/topup_card": {
      "post": {
        "security": [{ "bearerAuth": [] }],
        "summary": "Top up card from 2401 USDT"
      }
    },
    "/api/purchase": {
      "post": {
        "security": [{ "bearerAuth": [] }],
        "summary": "Gift catalog. payment_source=stars → 403 for agents"
      }
    },
    "/api/esim/new_plan5": {
      "post": {
        "security": [{ "bearerAuth": [] }],
        "summary": "Buy eSIM from 2401 USDT"
      }
    },
    "/api/kyt_quote": {
      "post": {
        "security": [{ "bearerAuth": [] }],
        "summary": "Optional paid address screen quote",
        "description": "Same price as retail. Does not gate deposit. Requires didit.enabled and didit.agent_api. Debits 2401 only on kyt_check, not here. under_attack → 503."
      }
    },
    "/api/kyt_check": {
      "post": {
        "security": [{ "bearerAuth": [] }],
        "summary": "Optional paid Didit screen (2401 USDT)",
        "description": "ext_id recommended. Result never auto-bans and never blocks TRON ingest. Not platform AML."
      }
    },
    "/api/kyt_check_get": {
      "post": {
        "security": [{ "bearerAuth": [] }],
        "summary": "Get own KYT check by id"
      }
    },
    "/api/kyt_checks": {
      "post": {
        "security": [{ "bearerAuth": [] }],
        "summary": "List own KYT checks"
      }
    },
    "/x402/{path}": {
      "post": {
        "summary": "x402 stub",
        "description": "HTTP 402 + PAYMENT-REQUIRED. Not /api/pin. USDC vault 1315; cannot spend until x402_credit_tron is live.",
        "parameters": [
          { "name": "path", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": { "402": { "description": "payment_required" } }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "EdDSA access token, typ=agent, client_id. Same JWKS as GET /auth/v1/partner/jwks."
      }
    }
  }
}
