{
  "info": {
    "name": "Bitalcer Partner API",
    "description": "API servidor-a-servidor para empresas partner de Bitalcer (Custody-as-a-Service).\n\nAutenticación: HMAC-SHA256 con 3 headers (x-api-key, x-timestamp, x-signature).\n\nAmbientes:\n• sandbox — BSC/Polygon Amoy, BTC testnet\n• production — mainnet real\n\nCredenciales distintas por ambiente. Obtenlas desde el dashboard del partner tras aprobar el KYB de tu empresa.\n\n⚠️ La firma HMAC se calcula automáticamente en el pre-request script.\nSolo necesitas configurar las variables api_key y hmac_secret en esta colección.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
    "_postman_id": "bitalcer-partner-api-v1"
  },
  "auth": {
    "type": "noauth"
  },
  "variable": [
    {
      "key": "base_url",
      "value": "https://api.bitalcer.com",
      "type": "string"
    },
    {
      "key": "api_key",
      "value": "",
      "type": "string",
      "description": "Tu API key (empieza con bak_...)"
    },
    {
      "key": "hmac_secret",
      "value": "",
      "type": "string",
      "description": "Tu HMAC secret (64 caracteres hex)"
    }
  ],
  "event": [
    {
      "listen": "prerequest",
      "script": {
        "id": "hmac-signing",
        "type": "text/javascript",
        "exec": [
          "// HMAC-SHA256 signing for Bitalcer Partner API",
          "const apiKey = pm.collectionVariables.get('api_key');",
          "const hmacSecret = pm.collectionVariables.get('hmac_secret');",
          "",
          "if (!apiKey || !hmacSecret) {",
          "    console.log('⚠️  Configura api_key y hmac_secret en las variables de la colección');",
          "    return;",
          "}",
          "",
          "const timestamp = String(Date.now());",
          "const rawBody = pm.request.body ? (pm.request.body.raw || '') : '';",
          "",
          "// Calculate HMAC-SHA256: hex(hmac_sha256(\"{timestamp}:{rawBody}\", secret))",
          "const signature = CryptoJS.HmacSHA256(timestamp + ':' + rawBody, hmacSecret).toString(CryptoJS.enc.Hex);",
          "",
          "// Set headers",
          "pm.request.headers.add({ key: 'x-api-key', value: apiKey });",
          "pm.request.headers.add({ key: 'x-timestamp', value: timestamp });",
          "pm.request.headers.add({ key: 'x-signature', value: signature });",
          "",
          "// Content-Type for requests with body",
          "if (rawBody) {",
          "    pm.request.headers.add({ key: 'Content-Type', value: 'application/json' });",
          "}",
          "",
          "console.log(`Firma HMAC: timestamp=${timestamp}, signature=${signature.substring(0,16)}...`);"
        ]
      }
    }
  ],
  "item": [
    {
      "name": "00 — Health",
      "description": "Verificación de conectividad y credenciales",
      "item": [
        {
          "name": "Ping",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}/partner/v1/ping",
              "host": ["{{base_url}}"],
              "path": ["partner", "v1", "ping"]
            },
            "description": "Verifica que tus credenciales HMAC son válidas y que la API está operativa.\n\n📌 Este debería ser el primer request que ejecutes. Si falla con 401, revisa:\n• api_key correcta (sin espacios extra)\n• hmac_secret correcto\n• El reloj de tu sistema está sincronizado (±5 min del servidor)"
          },
          "response": [
            {
              "name": "200 — OK",
              "status": "OK",
              "code": 200,
              "header": [
                { "key": "Content-Type", "value": "application/json" },
                { "key": "x-request-id", "value": "uuid-v4" }
              ],
              "body": "{\n  \"status\": \"ok\",\n  \"partner_id\": 7,\n  \"environment\": \"sandbox\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "01 — Usuarios",
      "description": "Gestión de usuarios del partner. Cada usuario debe registrarse con su external_user_id antes de poder crearle wallets o ejecutar operaciones.",
      "item": [
        {
          "name": "Crear / Obtener usuario",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 201) {",
                  "    const json = pm.response.json();",
                  "    pm.collectionVariables.set('external_user_id', json.external_user_id);",
                  "    console.log('Usuario:', json.external_user_id);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "url": {
              "raw": "{{base_url}}/partner/v1/users",
              "host": ["{{base_url}}"],
              "path": ["partner", "v1", "users"]
            },
            "header": [
              { "key": "Content-Type", "value": "application/json" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"external_user_id\": \"cust-00321\"\n}"
            },
            "description": "Registra un nuevo usuario final en tu cuenta de partner. Este endpoint es **idempotente**: si el external_user_id ya existe, devuelve el registro existente.\n\nEl external_user_id es el identificador que tú usas en tu sistema (UUID, ID de BD, etc.).\n\n⚠️ Este paso es obligatorio antes de cualquier operación con ese usuario (wallets, transacciones, conversión)."
          },
          "response": [
            {
              "name": "201 — Creado / Existente",
              "status": "Created",
              "code": 201,
              "body": "{\n  \"id\": 45,\n  \"partner_id\": 7,\n  \"external_user_id\": \"cust-00321\",\n  \"internal_user_id\": null,\n  \"status\": \"active\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "02 — Wallets",
      "description": "Creación y consulta de wallets on-chain (BSC, Polygon, BTC) para usuarios del partner.",
      "item": [
        {
          "name": "Crear wallet",
          "request": {
            "method": "POST",
            "url": {
              "raw": "{{base_url}}/partner/v1/wallets",
              "host": ["{{base_url}}"],
              "path": ["partner", "v1", "wallets"]
            },
            "header": [
              { "key": "Content-Type", "value": "application/json" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"external_user_id\": \"cust-00321\",\n  \"chain\": \"bsc\"\n}"
            },
            "description": "Crea una wallet on-chain para un usuario ya registrado.\n\nCadenas soportadas: bsc, polygon, btc (default: bsc).\n\n📌 Requisito previo: el usuario debe existir (POST /partner/v1/users).\n📌 Se dispara el webhook wallet.created."
          },
          "response": [
            {
              "name": "201 — Wallet creada",
              "status": "Created",
              "code": 201,
              "body": "{\n  \"external_user_id\": \"cust-00321\",\n  \"address\": \"0x4b934FAa1234567890abcdef1234567890abcdef\",\n  \"chain\": \"bsc\"\n}"
            }
          ]
        },
        {
          "name": "Consultar saldo",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}/partner/v1/wallets/cust-00321/balance?token=USDT&chain=bsc",
              "host": ["{{base_url}}"],
              "path": ["partner", "v1", "wallets", "cust-00321", "balance"],
              "query": [
                { "key": "token", "value": "USDT" },
                { "key": "chain", "value": "bsc" }
              ]
            },
            "description": "Consulta el saldo de un token específico en la wallet de un usuario."
          },
          "response": [
            {
              "name": "200 — Saldo",
              "code": 200,
              "body": "{\n  \"external_user_id\": \"cust-00321\",\n  \"token\": \"USDT\",\n  \"chain\": \"bsc\",\n  \"balance\": \"100.00000000\",\n  \"address\": \"0x4b934FAa...\"\n}"
            }
          ]
        },
        {
          "name": "Obtener dirección de depósito",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}/partner/v1/wallets/cust-00321/address?chain=bsc",
              "host": ["{{base_url}}"],
              "path": ["partner", "v1", "wallets", "cust-00321", "address"],
              "query": [
                { "key": "chain", "value": "bsc" }
              ]
            },
            "description": "Devuelve la dirección de depósito. Si el usuario no tiene wallet en esa cadena, la crea automáticamente."
          },
          "response": [
            {
              "name": "200 — Dirección",
              "code": 200,
              "body": "{\n  \"external_user_id\": \"cust-00321\",\n  \"address\": \"0x4b934FAa1234567890abcdef1234567890abcdef\",\n  \"chain\": \"bsc\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "03 — Transacciones",
      "description": "Retiros a dirección externa y transferencias internas entre usuarios del mismo partner.",
      "item": [
        {
          "name": "Retirar (withdraw)",
          "request": {
            "method": "POST",
            "url": {
              "raw": "{{base_url}}/partner/v1/transactions/withdraw",
              "host": ["{{base_url}}"],
              "path": ["partner", "v1", "transactions", "withdraw"]
            },
            "header": [
              { "key": "Content-Type", "value": "application/json" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"external_user_id\": \"cust-00321\",\n  \"to_address\": \"0x9f2cD1E31aB5F6d7890a1b2C3d4E5f678901234A\",\n  \"amount\": \"100.00\",\n  \"token\": \"USDT\",\n  \"chain\": \"bsc\"\n}"
            },
            "description": "Retira fondos de un usuario hacia una dirección externa.\n\n📌 Se descuenta del Partner Treasury (tu saldo prepagado en USD).\n📌 Cadenas soportadas: bsc, polygon. BTC devuelve 501 por ahora.\n\nPosibles errores:\n• 402 — INSUFFICIENT_PARTNER_TREASURY (fondea tu tesorería)\n• 429 — DAILY_VOLUME_LIMIT_EXCEEDED (límite diario de tu tier)\n• 501 — CHAIN_NOT_YET_SUPPORTED (BTC no disponible aún)"
          },
          "response": [
            {
              "name": "201 — Retiro ejecutado",
              "status": "Created",
              "code": 201,
              "body": "{\n  \"withdraw_id\": \"uuid-...\",\n  \"tx_hash\": \"0xdef789...\",\n  \"status\": \"pending\",\n  \"amount\": \"100.00\",\n  \"token\": \"USDT\",\n  \"chain\": \"bsc\"\n}"
            }
          ]
        },
        {
          "name": "Transferir entre usuarios",
          "request": {
            "method": "POST",
            "url": {
              "raw": "{{base_url}}/partner/v1/transactions/transfer",
              "host": ["{{base_url}}"],
              "path": ["partner", "v1", "transactions", "transfer"]
            },
            "header": [
              { "key": "Content-Type", "value": "application/json" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"sender_external_user_id\": \"cust-00321\",\n  \"receiver_external_user_id\": \"cust-00322\",\n  \"amount\": \"25.00\",\n  \"token\": \"USDT\"\n}"
            },
            "description": "Transferencia interna entre dos usuarios tuyos. No sale on-chain — es instantánea.\n\n📌 Ambos usuarios deben existir y tener wallet en la misma cadena.\n📌 Se descuenta del Partner Treasury.\n📌 Se disparan los webhooks transfer.completed para ambos usuarios."
          },
          "response": [
            {
              "name": "201 — Transferencia completada",
              "status": "Created",
              "code": 201,
              "body": "{\n  \"transfer_id\": \"uuid-...\",\n  \"status\": \"completed\",\n  \"amount\": \"25.00\",\n  \"token\": \"USDT\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "04 — Conversión (FX)",
      "description": "Convierte Quetzales (GTQ) a dólares digitales (USDT/USDC) y viceversa.\n\nFlujo de 4 pasos:\n1. Quote — previsualiza precio sin crear orden\n2. Create order — crea orden en estado quoted\n3. Execute — ejecuta la conversión\n4. Status — consulta el estado final",
      "item": [
        {
          "name": "1. Cotizar (quote)",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}/partner/v1/conversion/quote?external_user_id=cust-00321&base_asset=GTQ&quote_asset=USDT&amount=750",
              "host": ["{{base_url}}"],
              "path": ["partner", "v1", "conversion", "quote"],
              "query": [
                { "key": "external_user_id", "value": "cust-00321" },
                { "key": "base_asset", "value": "GTQ" },
                { "key": "quote_asset", "value": "USDT" },
                { "key": "amount", "value": "750" }
              ]
            },
            "description": "Paso 1/4: Previsualiza el precio y el monto estimado de salida. No crea ninguna orden en base de datos."
          },
          "response": [
            {
              "name": "200 — Cotización",
              "code": 200,
              "body": "{\n  \"pair\": \"GTQUSDT\",\n  \"requested_amount\": 750,\n  \"estimated_amount_out\": 100.0,\n  \"price_applied\": 7.5,\n  \"fee_amount\": 0,\n  \"fee_bps\": 0\n}"
            }
          ]
        },
        {
          "name": "2. Crear orden",
          "request": {
            "method": "POST",
            "url": {
              "raw": "{{base_url}}/partner/v1/conversion/orders",
              "host": ["{{base_url}}"],
              "path": ["partner", "v1", "conversion", "orders"]
            },
            "header": [
              { "key": "Content-Type", "value": "application/json" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"external_user_id\": \"cust-00321\",\n  \"base_asset\": \"GTQ\",\n  \"quote_asset\": \"USDT\",\n  \"requested_amount\": 750,\n  \"side\": \"buy\"\n}"
            },
            "description": "Paso 2/4: Crea la orden de conversión en estado quoted. Obtén el order_id para ejecutarla.\n\n📌 La cotización expira — ejecuta antes de quote_expires_at."
          },
          "response": [
            {
              "name": "201 — Orden creada",
              "status": "Created",
              "code": 201,
              "body": "{\n  \"order_id\": \"uuid-1234-5678-abcd\",\n  \"status\": \"quoted\",\n  \"quote_expires_at\": \"2026-07-01 10:35:00\"\n}"
            }
          ]
        },
        {
          "name": "3. Ejecutar orden",
          "request": {
            "method": "POST",
            "url": {
              "raw": "{{base_url}}/partner/v1/conversion/execute",
              "host": ["{{base_url}}"],
              "path": ["partner", "v1", "conversion", "execute"]
            },
            "header": [
              { "key": "Content-Type", "value": "application/json" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"external_user_id\": \"cust-00321\",\n  \"order_id\": \"uuid-1234-5678-abcd\"\n}"
            },
            "description": "Paso 3/4: Ejecuta la conversión con el order_id obtenido en el paso anterior.\n\n📌 Requiere que el usuario tenga saldo suficiente del activo base.\n📌 Se dispara el webhook conversion.executed."
          },
          "response": [
            {
              "name": "200 — Ejecutada",
              "code": 200,
              "body": "{\n  \"order_id\": \"uuid-1234-5678-abcd\",\n  \"status\": \"executed\",\n  \"executed_at\": \"2026-06-30 15:42:00\"\n}"
            }
          ]
        },
        {
          "name": "4. Consultar estado",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}/partner/v1/conversion/status?external_user_id=cust-00321&order_id=uuid-1234-5678-abcd",
              "host": ["{{base_url}}"],
              "path": ["partner", "v1", "conversion", "status"],
              "query": [
                { "key": "external_user_id", "value": "cust-00321" },
                { "key": "order_id", "value": "uuid-1234-5678-abcd" }
              ]
            },
            "description": "Paso 4/4: Consulta el estado final de cualquier orden de conversión."
          },
          "response": [
            {
              "name": "200 — Estado",
              "code": 200,
              "body": "{\n  \"order_id\": \"uuid-1234-5678-abcd\",\n  \"status\": \"executed\",\n  \"base_asset\": \"GTQ\",\n  \"quote_asset\": \"USDT\",\n  \"requested_amount\": 750,\n  \"executed_amount\": 100.0,\n  \"price_applied\": 7.5\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "05 — Partner Treasury",
      "description": "Consulta y fondeo del saldo prepagado de tu empresa (Partner Treasury).",
      "item": [
        {
          "name": "Consultar saldo de tesorería",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base_url}}/partner/v1/treasury/balance",
              "host": ["{{base_url}}"],
              "path": ["partner", "v1", "treasury", "balance"]
            },
            "description": "Consulta el saldo disponible y reservado de tu Partner Treasury.\n\n📌 Los retiros y transferencias descuentan de este saldo. Si llega a cero, las operaciones son rechazadas con 402.\n\nEl fondeo se coordina con tu ejecutivo de cuenta (wire transfer bancario o depósito on-chain de USDT)."
          },
          "response": [
            {
              "name": "200 — Saldo",
              "code": 200,
              "body": "{\n  \"partner_id\": 7,\n  \"available_balance_usd\": 4250.0,\n  \"reserved_balance_usd\": 0,\n  \"tier\": \"tier_1\"\n}"
            }
          ]
        }
      ]
    }
  ]
}
