> For the complete documentation index, see [llms.txt](https://docs.allscale.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.allscale.io/allscale-checkout/quickstart-with-postman/allscale_openapi.postman_collection-json.md).

# AllScale\_OpenAPI.postman\_collection.json

```
{
  "info": {
    "_postman_id": "a11sc4le-0pen-4p1-0000-000000000001",
    "name": "AllScale OpenAPI (v1)",
    "description": "Ready-to-use Postman collection for AllScale's third-party OpenAPI.\n\n_Aligned with docs/openapi (Create v4 / Get v5 / Get-Status v2 — 2026-05-06)._\n\n**Setup**\n\n1. Import this collection **and** the companion environment file (`AllScale_OpenAPI.postman_environment.json`).\n2. Pick the environment (top-right dropdown).\n3. Fill in `api_key` and `api_secret` (shown once in the merchant dashboard — store them in the environment, not the collection).\n4. `base_url` defaults to the **sandbox** host; switch to the production host when ready.\n\n**How signing works**\n\nThe collection has a pre-request script at the root that runs before every request. It:\n\n- Reads `api_key` / `api_secret` from the active environment\n- Builds the canonical string `METHOD\\nPATH\\nQUERY\\nTIMESTAMP\\nNONCE\\nBODY_SHA256`\n- Signs it with HMAC-SHA256 + Base64\n- Upserts `X-API-Key`, `X-Timestamp`, `X-Nonce`, `X-Signature: v1=<sig>` headers\n\nYou should not need to touch the script for normal QA usage — just run a request.\n\n**Ordered happy-path**\n\n1. `Test / GET /v1/test/ping` — confirms connectivity + signing.\n2. `Test / POST /v1/test/post` — confirms body hashing.\n3. `Checkout Intent / Create — fiat` (or `Create — native stable-coin`) — creates an intent and auto-captures `checkout_intent_id` into the environment.\n4. `Checkout Intent / GET /v1/checkout_intents/{id}/status` — polls status.\n5. `Checkout Intent / GET /v1/checkout_intents/{id}` — full payload.\n\n**Pricing modes on Create** (pick exactly one per request):\n\n- `currency` (fiat IntEnum, Appendix A) → server FX-converts to the resolved settlement coin (USDT or USDC); use `accepted_stable_coins` to pick which.\n- `stable_coin` (IntEnum, Appendix B) → `1` (USDT) and `2` (USDC) are accepted; amount is stable-coin cents, no FX.\n\nSending both, or neither, returns `10001`.\n\n**Settlement:** USDT or USDC. `stable_coin_type` in the response matches the resolved coin (defaults to `1` USDT when neither `stable_coin` nor `accepted_stable_coins` is set).\n\n**Error codes** (from the server's unified envelope):\n\n| Code  | Meaning |\n|-------|---------|\n| 0     | Success |\n| 10001 | Validation error |\n| 20001 | Missing auth headers |\n| 20002 | Bad signature |\n| 30001 | Forbidden (IP / ownership) |\n| 40001 | Rate limit exceeded |\n| 50001 | Checkout intent not found |\n| 50002 | Create checkout intent error (e.g. amount at or below the 0.1-coin floor) |\n| 90000 | Internal server error |\n",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "noauth"
  },
  "event": [
    {
      "listen": "prerequest",
      "script": {
        "type": "text/javascript",
        "exec": [
          "// AllScale HMAC-SHA256 request signing.",
          "// See docs/openapi/docs/API Doc - Auth.md for the canonical-string format.",
          "",
          "const apiKey = pm.environment.get('api_key') || pm.collectionVariables.get('api_key') || '';",
          "const apiSecret = (pm.environment.get('api_secret') || pm.collectionVariables.get('api_secret') || '').trim();",
          "",
          "if (!apiKey || !apiSecret) {",
          "    console.warn('[AllScale] api_key or api_secret is empty — requests will be rejected (20001/20002).');",
          "}",
          "",
          "// Method",
          "const method = (pm.request.method || 'GET').toUpperCase();",
          "",
          "// Path — resolve {{var}} placeholders and :path params via pm.variables.replaceIn.",
          "const urlObj = pm.request.url;",
          "const rawPathSegments = (urlObj.path || []).map(function (seg) {",
          "    return pm.variables.replaceIn(String(seg));",
          "});",
          "let path = '/' + rawPathSegments.join('/');",
          "// Postman treats a trailing slash in the URL as an empty segment; keep it so the canonical path",
          "// matches what Starlette sees on the server (e.g. /v1/checkout_intents/).",
          "",
          "// Query — use Postman's resolved query string so encoding matches what actually goes on the wire.",
          "let query = '';",
          "try {",
          "    query = urlObj.getQueryString({ ignoreDisabled: true }) || '';",
          "} catch (e) {",
          "    // Older Postman runtimes — fall back to manual build.",
          "    const parts = [];",
          "    (urlObj.query || []).each(function (q) {",
          "        if (q.disabled) return;",
          "        const k = pm.variables.replaceIn(q.key || '');",
          "        const v = pm.variables.replaceIn(q.value || '');",
          "        parts.push(k + '=' + v);",
          "    });",
          "    query = parts.join('&');",
          "}",
          "",
          "// Body — only raw JSON bodies are signed with content. Other modes sign an empty body.",
          "let body = '';",
          "if (pm.request.body && pm.request.body.mode === 'raw' && pm.request.body.raw) {",
          "    body = pm.variables.replaceIn(pm.request.body.raw);",
          "}",
          "",
          "// Canonical pieces",
          "const bodyHash = CryptoJS.SHA256(body).toString(CryptoJS.enc.Hex);",
          "const timestamp = Math.floor(Date.now() / 1000).toString();",
          "",
          "function uuidv4() {",
          "    if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {",
          "        return crypto.randomUUID();",
          "    }",
          "    // RFC 4122 v4 fallback",
          "    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {",
          "        const r = Math.random() * 16 | 0;",
          "        const v = c === 'x' ? r : (r & 0x3 | 0x8);",
          "        return v.toString(16);",
          "    });",
          "}",
          "const nonce = uuidv4();",
          "",
          "const canonical = [method, path, query, timestamp, nonce, bodyHash].join('\\n');",
          "const signature = CryptoJS.HmacSHA256(canonical, apiSecret).toString(CryptoJS.enc.Base64);",
          "",
          "pm.request.headers.upsert({ key: 'X-API-Key', value: apiKey });",
          "pm.request.headers.upsert({ key: 'X-Timestamp', value: timestamp });",
          "pm.request.headers.upsert({ key: 'X-Nonce', value: nonce });",
          "pm.request.headers.upsert({ key: 'X-Signature', value: 'v1=' + signature });",
          "",
          "// Uncomment for debugging:",
          "// console.log('[AllScale canonical]\\n' + canonical);",
          "// console.log('[AllScale signature] v1=' + signature);",
          ""
        ]
      }
    },
    {
      "listen": "test",
      "script": {
        "type": "text/javascript",
        "exec": [
          "// Collection-wide sanity checks.",
          "pm.test('Response is JSON', function () {",
          "    pm.response.to.be.withBody;",
          "    pm.response.to.be.json;",
          "});",
          "",
          "pm.test('Envelope has `code` and `request_id`', function () {",
          "    const body = pm.response.json();",
          "    pm.expect(body).to.have.property('code');",
          "    pm.expect(body).to.have.property('request_id');",
          "});",
          ""
        ]
      }
    }
  ],
  "variable": [
    {
      "key": "base_url",
      "value": "{{base_url}}",
      "type": "string"
    }
  ],
  "item": [
    {
      "name": "Test",
      "description": "Smoke-test endpoints. Run these first to validate credentials, signing, and body hashing before touching real payment flows.",
      "item": [
        {
          "name": "GET /v1/test/ping",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/v1/test/ping",
              "host": ["{{base_url}}"],
              "path": ["v1", "test", "ping"]
            },
            "description": "Health-check. Verifies that:\n\n- `api_key` / `api_secret` are valid\n- Your clock is in sync (±5 min window)\n- The signing pre-request script is producing a correct signature\n\nExpected: `{ \"code\": 0, \"payload\": { \"pong\": \"ok\" }, ... }`"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('code == 0', function () {",
                  "    pm.expect(pm.response.json().code).to.eql(0);",
                  "});",
                  "pm.test('payload.pong == \"ok\"', function () {",
                  "    pm.expect(pm.response.json().payload.pong).to.eql('ok');",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/test/fail",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/v1/test/fail",
              "host": ["{{base_url}}"],
              "path": ["v1", "test", "fail"]
            },
            "description": "Always returns `10001 Validation error`. Use it to verify your error-handling branch parses the envelope correctly."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('code == 10001 (validation error)', function () {",
                  "    pm.expect(pm.response.json().code).to.eql(10001);",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/test/post",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"everything\": \"is_fine\"\n}",
              "options": {
                "raw": { "language": "json" }
              }
            },
            "url": {
              "raw": "{{base_url}}/v1/test/post",
              "host": ["{{base_url}}"],
              "path": ["v1", "test", "post"]
            },
            "description": "Echo endpoint. Verifies body-hash signing — if this passes but real endpoints return `20002`, the client almost certainly isn't hashing the body the same way the server does.\n\nThe server echoes the body back under `payload.your_request_body`."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('code == 0', function () {",
                  "    pm.expect(pm.response.json().code).to.eql(0);",
                  "});",
                  "pm.test('body was echoed', function () {",
                  "    pm.expect(pm.response.json().payload.your_request_body.everything).to.eql('is_fine');",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "Checkout Intent",
      "description": "End-to-end checkout-intent lifecycle. Create → poll status → fetch full detail. `Create` captures the returned intent id into the `checkout_intent_id` environment variable so the two GETs work without manual copy-paste.\n\n**Two create flavors** (pick exactly one on each request):\n\n- **Fiat pricing** — send `currency` (IntEnum, Appendix A). The server FX-converts to the resolved settlement coin (USDT or USDC) at the current rate; `currency_rate` in the response is non-null. Use `accepted_stable_coins` (first entry wins) to pick the settlement coin; defaults to USDT.\n- **Native stable-coin pricing** — send `stable_coin` (IntEnum, Appendix B). `amount_cents` is stable-coin cents (`1000` = `10.00` of the chosen coin). No FX happens, so `currency_rate` in the response is `null`. Both `stable_coin = 1` (USDT) and `stable_coin = 2` (USDC) are accepted.\n\nSending both — or neither — of `currency`/`stable_coin` returns `10001`.",
      "item": [
        {
          "name": "POST /v1/checkout_intents/  (Create — fiat)",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"currency\": 1,\n  \"amount_cents\": 100,\n  \"order_id\": \"qa_order_{{$timestamp}}\",\n  \"order_description\": \"QA test order\",\n  \"user_id\": \"qa_user_001\",\n  \"user_name\": \"QA Tester\",\n  \"redirect_url\": \"https://example.com/checkout/allscale\",\n  \"extra\": {\n    \"source\": \"postman\"\n  }\n}",
              "options": {
                "raw": { "language": "json" }
              }
            },
            "url": {
              "raw": "{{base_url}}/v1/checkout_intents/",
              "host": ["{{base_url}}"],
              "path": ["v1", "checkout_intents", ""]
            },
            "description": "Create a checkout intent priced in fiat. Server FX-converts the amount into the resolved settlement coin (USDT or USDC) at the current rate.\n\n**Field notes**\n\n- `currency` is the fiat IntEnum (1 = USD, 44 = EUR, …). Full table in `API Doc - Checkout Intent Routes - Create Checkout Intent.md` → Appendix A.\n- `amount_cents` is integer **fiat** cents. The resulting stable-coin amount must be greater than `0.1` (USDT or USDC); orders at or below the floor are rejected with `50002`.\n- Do **not** also send `stable_coin` on this request — sending both returns `10001`.\n- `redirect_url` must be `https://…` in production; sandbox also allows `http://`.\n- Settlement coin is **USDT or USDC**. To pin it on a fiat-priced order, send `accepted_stable_coins: [1]` (USDT) or `[2]` (USDC); the first entry picks the settlement coin used for FX conversion. `stable_coin_type` in the response matches whichever was resolved (defaults to `1` USDT).\n\nThe post-response test captures `payload.allscale_checkout_intent_id` into `{{checkout_intent_id}}` for the follow-up GETs."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const body = pm.response.json();",
                  "pm.test('code == 0', function () {",
                  "    pm.expect(body.code).to.eql(0);",
                  "});",
                  "pm.test('payload.allscale_checkout_intent_id exists', function () {",
                  "    pm.expect(body.payload).to.have.property('allscale_checkout_intent_id');",
                  "    pm.expect(body.payload.allscale_checkout_intent_id).to.be.a('string');",
                  "});",
                  "",
                  "if (body && body.payload && body.payload.allscale_checkout_intent_id) {",
                  "    pm.environment.set('checkout_intent_id', body.payload.allscale_checkout_intent_id);",
                  "    console.log('[AllScale] captured checkout_intent_id = ' + body.payload.allscale_checkout_intent_id);",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "POST /v1/checkout_intents/  (Create — native stable-coin)",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"stable_coin\": 1,\n  \"amount_cents\": 1000,\n  \"order_id\": \"qa_stable_{{$timestamp}}\",\n  \"order_description\": \"QA native USDT order\",\n  \"user_id\": \"qa_user_001\",\n  \"user_name\": \"QA Tester\",\n  \"redirect_url\": \"https://example.com/checkout/allscale\",\n  \"extra\": {\n    \"source\": \"postman\",\n    \"pricing\": \"native_stable_coin\"\n  }\n}",
              "options": {
                "raw": { "language": "json" }
              }
            },
            "url": {
              "raw": "{{base_url}}/v1/checkout_intents/",
              "host": ["{{base_url}}"],
              "path": ["v1", "checkout_intents", ""]
            },
            "description": "Create a checkout intent priced **natively in a stable coin** — no FX happens.\n\n**Field notes**\n\n- `stable_coin` is the StableCoin IntEnum (see Appendix B). Both `1` (USDT) and `2` (USDC) are accepted; other values are reserved and will be rejected.\n- `amount_cents` is **stable-coin cents** of the chosen `stable_coin`: `1000` = `10.00` of that coin. The example above (`stable_coin: 1`) produces a `10 USDT` order; switch to `stable_coin: 2` for the equivalent USDC order.\n- Do **not** also send `currency` — sending both, or neither, returns `10001`.\n- The response's `currency_rate` will be `null` (no FX). In the full GET, the fiat fields (`currency`, `currency_symbol`, `amount_cents`, `currency_rate`) will all be `null`; the authoritative amount is `amount_coins`.\n\nThe post-response test captures `payload.allscale_checkout_intent_id` into `{{checkout_intent_id}}` just like the fiat flavor."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const body = pm.response.json();",
                  "pm.test('code == 0', function () {",
                  "    pm.expect(body.code).to.eql(0);",
                  "});",
                  "pm.test('payload.allscale_checkout_intent_id exists', function () {",
                  "    pm.expect(body.payload).to.have.property('allscale_checkout_intent_id');",
                  "});",
                  "pm.test('rate is null for native stable-coin pricing', function () {",
                  "    pm.expect(body.payload.rate).to.be.oneOf([null, undefined]);",
                  "});",
                  "",
                  "if (body && body.payload && body.payload.allscale_checkout_intent_id) {",
                  "    pm.environment.set('checkout_intent_id', body.payload.allscale_checkout_intent_id);",
                  "    console.log('[AllScale] captured checkout_intent_id = ' + body.payload.allscale_checkout_intent_id);",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/checkout_intents/{id}/status",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/v1/checkout_intents/{{checkout_intent_id}}/status",
              "host": ["{{base_url}}"],
              "path": ["v1", "checkout_intents", "{{checkout_intent_id}}", "status"]
            },
            "description": "Poll-friendly lightweight status check. `payload` is the status integer (see status enum in the doc).\n\nUse this instead of the full GET when all you need is the lifecycle state."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const body = pm.response.json();",
                  "pm.test('code == 0', function () {",
                  "    pm.expect(body.code).to.eql(0);",
                  "});",
                  "pm.test('payload is integer status', function () {",
                  "    pm.expect(body.payload).to.be.a('number');",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "GET /v1/checkout_intents/{id}",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/v1/checkout_intents/{{checkout_intent_id}}",
              "host": ["{{base_url}}"],
              "path": ["v1", "checkout_intents", "{{checkout_intent_id}}"]
            },
            "description": "Full checkout intent object — order metadata, currency/coin fields, on-chain tx hashes when payment has settled. Ownership is enforced: the intent must belong to the store identified by `api_key`, otherwise `30001 Forbidden`."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const body = pm.response.json();",
                  "pm.test('code == 0', function () {",
                  "    pm.expect(body.code).to.eql(0);",
                  "});",
                  "pm.test('payload has core fields', function () {",
                  "    const p = body.payload;",
                  "    pm.expect(p).to.have.property('all_scale_checkout_intent_id');",
                  "    pm.expect(p).to.have.property('currency');",
                  "    pm.expect(p).to.have.property('amount_cents');",
                  "    pm.expect(p).to.have.property('status');",
                  "    pm.expect(p).to.have.property('tx_to');",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    }
  ]
}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.allscale.io/allscale-checkout/quickstart-with-postman/allscale_openapi.postman_collection-json.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
