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');",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    }
  ]
}

Last updated