{
  "info": {
    "name": "Key2Pay API",
    "description": "Official Postman collection for the Key2Pay payments API.\n\n**Quickstart**\n1. Import this collection into Postman.\n2. Import one of the matching environments — `Key2Pay (Sandbox)` or `Key2Pay (Production)`.\n3. Open the environment, fill in `apiKey` and `secretKey` from your Key2Pay dashboard, then save.\n4. Pick the environment in the top-right of Postman.\n5. Open **Auth → POST /auth/token** and hit **Send**. The response's `accessToken` is captured automatically into `{{bearerToken}}` and reused by every other request in this collection.\n6. Fire any other request — it'll authenticate with the freshly-minted Bearer token.\n\nTokens are short-lived (1h). When you get `401 token_expired`, just hit `POST /auth/token` again.\n\nFor backward compatibility you can still pass `sk_test_…` or `sk_live_…` directly as the Bearer if you prefer — but the two-credential flow is the recommended path.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
    "_postman_id": "f4a09c11-key2pay-collection-2026"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      { "key": "token", "value": "{{bearerToken}}", "type": "string" }
    ]
  },
  "variable": [
    { "key": "baseUrl", "value": "https://api.key2pays.com/v1", "type": "string", "description": "Override per-environment via the `Key2Pay (Sandbox)` or `Key2Pay (Production)` environment." }
  ],
  "event": [
    {
      "listen": "prerequest",
      "script": {
        "type": "text/javascript",
        "exec": [
          "// Generate a fresh Idempotency-Key for any request that mutates state.",
          "// Override `idempotencyKey` in the environment when you want to replay.",
          "if (!pm.variables.get('idempotencyKey')) {",
          "  pm.variables.set('idempotencyKey', 'idem_' + Math.random().toString(36).slice(2) + Date.now().toString(36));",
          "}"
        ]
      }
    }
  ],
  "item": [
    {
      "name": "Auth",
      "description": "Two-credential token flow. Run `POST /auth/token` first — every other folder in this collection will use the resulting Bearer token automatically.",
      "item": [
        {
          "name": "POST /auth/token — exchange apiKey + secretKey for a Bearer token",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// On a successful issuance, capture the token into the active",
                  "// environment so every subsequent request in the collection",
                  "// authenticates without us having to copy/paste the JWT.",
                  "pm.test('200 OK', () => pm.response.to.have.status(200));",
                  "const json = pm.response.json();",
                  "if (json && json.accessToken) {",
                  "  pm.environment.set('bearerToken', json.accessToken);",
                  "  pm.environment.set('bearerExpiresAt', String(Math.floor(Date.now()/1000) + (json.expiresIn || 3600)));",
                  "  console.log('[key2pay] saved bearerToken (env: ' + (json.environment || '?') + ', expires in ' + (json.expiresIn || '?') + 's)');",
                  "} else {",
                  "  console.warn('[key2pay] token endpoint did not return accessToken:', json);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "auth": { "type": "noauth" },
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json", "type": "text" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"apiKey\":    \"{{apiKey}}\",\n  \"secretKey\": \"{{secretKey}}\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/auth/token",
              "host": ["{{baseUrl}}"],
              "path": ["auth", "token"]
            },
            "description": "Exchange an `apiKey` + `secretKey` pair for a short-lived Bearer access token (1h).\n\n**Body**\n```json\n{\n  \"apiKey\":    \"pk_test_…\" or \"pk_live_…\",\n  \"secretKey\": \"sk_test_…\" or \"sk_live_…\"\n}\n```\n\nBoth keys must come from the same environment — pairing a test apiKey with a live secretKey returns 401.\n\n**Response 200**\n```json\n{\n  \"accessToken\": \"eyJhbGciOi…\",\n  \"tokenType\":   \"Bearer\",\n  \"expiresIn\":   3600,\n  \"environment\": \"sandbox\"\n}\n```\n\nThe **Tests** tab on this request automatically writes `accessToken` to the `{{bearerToken}}` environment variable, so the rest of the collection just works."
          }
        }
      ]
    },
    {
      "name": "Health & Identity",
      "description": "Quick checks you'll run while wiring up your integration.",
      "item": [
        {
          "name": "GET /ping — credential & connectivity check",
          "request": {
            "method": "GET",
            "header": [],
            "url": { "raw": "{{baseUrl}}/ping", "host": ["{{baseUrl}}"], "path": ["ping"] },
            "description": "Lightweight health check that confirms your token, environment and pinned API version. No side effects.\n\n**Response 200**\n```json\n{\n  \"ok\": true,\n  \"environment\": \"sandbox\",\n  \"keyKind\": \"secret\",\n  \"apiVersion\": \"2026-05-01\"\n}\n```"
          }
        },
        {
          "name": "GET /me — your merchant profile",
          "request": {
            "method": "GET",
            "header": [],
            "url": { "raw": "{{baseUrl}}/me", "host": ["{{baseUrl}}"], "path": ["me"] },
            "description": "Returns the authenticated merchant's profile, capabilities and balance. Useful to bootstrap your client app on startup."
          }
        }
      ]
    },
    {
      "name": "Checkout sessions",
      "description": "Hosted checkout flow — easiest way to accept payments without building your own card form.",
      "item": [
        {
          "name": "POST /checkout/sessions — create",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Idempotency-Key", "value": "{{idempotencyKey}}", "type": "text" },
              { "key": "Content-Type", "value": "application/json", "type": "text" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": 4500,\n  \"currency\": \"USD\",\n  \"country\": \"MEX\",\n  \"customer\": {\n    \"email\": \"buyer@example.com\",\n    \"firstName\": \"Carlos\",\n    \"lastName\": \"Pérez\"\n  },\n  \"success_url\": \"https://yoursite.com/orders/1234?paid=1\",\n  \"cancel_url\": \"https://yoursite.com/orders/1234\",\n  \"metadata\": { \"order_id\": \"1234\" }\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/checkout/sessions",
              "host": ["{{baseUrl}}"],
              "path": ["checkout", "sessions"]
            },
            "description": "Create a hosted checkout session. Redirect the customer to `redirect_url`. We handle PAN entry, KYC, retries and provider cascade.\n\n**Required fields**\n- `amount` integer (smallest currency unit)\n- `currency` ISO-4217\n- `success_url`\n- `cancel_url`\n\n**Optional**\n- `country` ISO-3166 alpha-3 (defaults to customer IP)\n- `method` restrict cascade (`card`, `spei`, `pix`, `oxxo`)\n- `customer` object\n- `metadata` up to 50 KV pairs"
          }
        },
        {
          "name": "GET /checkout/sessions/:id — retrieve",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/checkout/sessions/cs_3aB7K8…",
              "host": ["{{baseUrl}}"],
              "path": ["checkout", "sessions", "cs_3aB7K8…"]
            },
            "description": "Retrieve a checkout session. Typically polled from your `success_url` to confirm the linked payment status."
          }
        }
      ]
    },
    {
      "name": "Payments",
      "description": "Direct charge endpoints. Use these when you build your own UI; otherwise use checkout sessions.",
      "item": [
        {
          "name": "POST /payments — create direct charge",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Idempotency-Key", "value": "{{idempotencyKey}}", "type": "text" },
              { "key": "Content-Type", "value": "application/json", "type": "text" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": 12000,\n  \"currency\": \"MXN\",\n  \"method\": \"spei\",\n  \"country\": \"MEX\",\n  \"customer\": {\n    \"email\": \"buyer@example.com\",\n    \"firstName\": \"Carlos\",\n    \"lastName\": \"Pérez\",\n    \"country\": \"MX\"\n  },\n  \"description\": \"Order #1234\",\n  \"metadata\": { \"order_id\": \"1234\" }\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": { "raw": "{{baseUrl}}/payments", "host": ["{{baseUrl}}"], "path": ["payments"] },
            "description": "Create a direct charge against a specific payment method. Use this when you build the customer-facing UI yourself."
          }
        },
        {
          "name": "GET /payments/:id — retrieve",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/payments/pay_3aB81…",
              "host": ["{{baseUrl}}"],
              "path": ["payments", "pay_3aB81…"]
            },
            "description": "Fetch a payment by id."
          }
        },
        {
          "name": "GET /payments — list",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/payments?limit=20",
              "host": ["{{baseUrl}}"],
              "path": ["payments"],
              "query": [
                { "key": "limit", "value": "20", "description": "Page size, max 100." },
                { "key": "starting_after", "value": "", "description": "Cursor — id of the last item from the previous page.", "disabled": true },
                { "key": "status", "value": "succeeded", "description": "Filter: succeeded | processing | failed | refunded.", "disabled": true }
              ]
            },
            "description": "List payments using cursor pagination."
          }
        },
        {
          "name": "POST /payments/:id/refund — refund",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Idempotency-Key", "value": "{{idempotencyKey}}", "type": "text" },
              { "key": "Content-Type", "value": "application/json", "type": "text" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": 12000,\n  \"reason\": \"customer_request\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/payments/pay_3aB81…/refund",
              "host": ["{{baseUrl}}"],
              "path": ["payments", "pay_3aB81…", "refund"]
            },
            "description": "Refund a payment fully or partially. Pass `amount` to issue a partial refund; omit to refund the full amount."
          }
        }
      ]
    },
    {
      "name": "Catalog",
      "description": "Discoverable lookup endpoints.",
      "item": [
        {
          "name": "GET /payment-methods — list available methods",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/payment-methods?country=MEX",
              "host": ["{{baseUrl}}"],
              "path": ["payment-methods"],
              "query": [
                { "key": "country", "value": "MEX", "description": "ISO-3166 alpha-3 — restrict the list to one country." }
              ]
            },
            "description": "List the payment methods enabled for your account, optionally filtered by country. The icon URL on each item is suitable for direct rendering."
          }
        },
        {
          "name": "GET /balance — your funds",
          "request": {
            "method": "GET",
            "header": [],
            "url": { "raw": "{{baseUrl}}/balance", "host": ["{{baseUrl}}"], "path": ["balance"] },
            "description": "Returns your current balance broken down into available, pending, frozen and reserve."
          }
        }
      ]
    },
    {
      "name": "Webhooks",
      "description": "Register a URL that receives signed event deliveries.",
      "item": [
        {
          "name": "POST /webhooks — register endpoint",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json", "type": "text" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"url\": \"https://yoursite.com/webhooks/key2pay\",\n  \"events\": [\n    \"payment.succeeded\",\n    \"payment.failed\",\n    \"payment.refunded\"\n  ]\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": { "raw": "{{baseUrl}}/webhooks", "host": ["{{baseUrl}}"], "path": ["webhooks"] },
            "description": "Register a webhook endpoint. The response includes a per-endpoint signing secret used to verify deliveries — store it securely.\n\n**Verifying deliveries**\nEach POST to your endpoint includes an `X-Key2Pay-Signature` header (HMAC-SHA256 of the raw body, hex-encoded). Compute the same HMAC with the secret you got back here and compare with `crypto.timingSafeEqual`."
          }
        }
      ]
    }
  ]
}
