{
  "info": {
    "name": "Myela Payments — /v1",
    "description": "# Myela Payments — `/v1`\n\nOne API. Which provider settles a merchant's money is a property of their\naccount, never of the request, so **testing a different provider is a change of\nKEY, never of request or environment**.\n\n## Setup\n\n1. Import this collection and `myela-v1-local.postman_environment.json`.\n2. Put your key in the environment's `MYELA_API_KEY` variable. Keys look like\n   `myela_sk_test_…` and are issued by Myela.\n3. Set `BASE_URL` for the environment you are pointed at.\n\nAuth is set once on the collection (Bearer) and every request inherits it.\n\n## Use a TEST key here\n\nLive keys must HMAC-sign every request — a live key sends bearer auth only and\nis refused with `401 This key requires request signing`. That is the\nenforcement working, not a broken collection: signing means a per-request\ntimestamp, nonce and body hash, which is a job for a client library rather than\na saved request. See `docs/gateway/api/V1_REQUEST_SIGNING.md`.\n\n## Money\n\nAmounts are **integers in the currency's minor unit**. 4999 is $49.99. Sending\n`49.99` is rejected rather than silently charged as a different figure.\n\n## Pagination\n\nLists are cursor-paginated. Pass `limit` (default 10, max 100), and follow\n`nextCursor` by sending it as `startingAfter`. `hasMore` tells you whether\nto continue. A cursor that names no row is a `422`, not a silent restart at\npage one.\n\n## Errors\n\nEvery error has the same shape:\n\n```json\n{ \"error\": { \"code\": \"invalid_request\", \"message\": \"…\" } }\n```\n\n**Branch on `code`, never on `message`.** Codes are a contract; messages are\nprose and get improved. Full list: `docs/gateway/api/V1_ERROR_CATALOGUE.md`.\n\n## A note on card capture\n\nNo request here accepts a card number, and none ever will. Cards are captured in\nthe browser and reach this API only as a single-use `paymentToken`. Set\n`PAYMENT_TOKEN` in your environment from a browser capture before running the\npayment requests.\n\n**A token is single-use and expires in about two minutes.** So a run of the whole\ncollection charges at most once: whichever card request runs first consumes the\ntoken, and the others report `invalid_request`. That is the processor's rule,\nnot a fault in this collection — mint a fresh token per card request, or drive\nthem one at a time.\n\nRequests that need a token, or an id only a token can produce, SKIP rather than\nfail when it is absent, so a run without one still reads cleanly.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{MYELA_API_KEY}}",
        "type": "string"
      }
    ]
  },
  "item": [
    {
      "name": "00 · Start here",
      "description": "Run this first. It answers what this key may do, and it is the only route that is not entitlement-gated — asking what you may do must never itself require permission.",
      "item": [
        {
          "name": "Capabilities",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/v1/capabilities",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "capabilities"
              ]
            },
            "description": "Every operation, and whether it is `active`, `unavailable` (this key lacks the entitlement) or `not_supported` (the account's provider cannot do it). Branch on this rather than discovering a 403 in production."
          }
        }
      ]
    },
    {
      "name": "01 · Payments",
      "description": "The money-moving surface. Amounts are integers in the minor unit — 4999 is $49.99. A decimal is rejected rather than silently charged as a different figure.\n\nA payment is created against exactly ONE funding source: a `paymentToken` from browser capture, or a stored `paymentMethodId`. Sending both is refused, because which card was charged is not a question to answer afterwards.",
      "item": [
        {
          "name": "Create payment (sale)",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const needed = [\"PAYMENT_TOKEN\"];",
                  "const missing = needed.filter((v) => !pm.variables.get(v));",
                  "if (missing.length) {",
                  "  console.log('SKIPPED — needs ' + missing.join(', '));",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});",
                  "// Chain the id forward so the next request needs no copy-paste.",
                  "if (pm.response.code < 300) {",
                  "  const body = pm.response.json();",
                  "  const value = body?.[\"id\"];",
                  "  if (value) pm.collectionVariables.set(\"PAYMENT_ID\", value);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": 1299,\n  \"currency\": \"USD\",\n  \"paymentToken\": \"{{PAYMENT_TOKEN}}\",\n  \"capture\": true\n}"
            },
            "url": {
              "raw": "{{BASE_URL}}/v1/payments",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "payments"
              ]
            },
            "description": "**Entitlement:** `payments.create`\n\n**Needs:** PAYMENT_TOKEN\n\n`capture: true` authorizes and captures in one call. Set an `Idempotency-Key` header on retries: the same key returns the original result rather than charging twice.\n\nAmounts are integers in the minor unit — 4999 is $49.99. A decimal is rejected rather than silently charged as a different figure."
          }
        },
        {
          "name": "Create payment (auth only)",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const needed = [\"PAYMENT_TOKEN\"];",
                  "const missing = needed.filter((v) => !pm.variables.get(v));",
                  "if (missing.length) {",
                  "  console.log('SKIPPED — needs ' + missing.join(', '));",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});",
                  "// Chain the id forward so the next request needs no copy-paste.",
                  "if (pm.response.code < 300) {",
                  "  const body = pm.response.json();",
                  "  const value = body?.[\"id\"];",
                  "  if (value) pm.collectionVariables.set(\"AUTH_ID\", value);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": 2500,\n  \"currency\": \"USD\",\n  \"paymentToken\": \"{{PAYMENT_TOKEN}}\",\n  \"capture\": false\n}"
            },
            "url": {
              "raw": "{{BASE_URL}}/v1/payments",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "payments"
              ]
            },
            "description": "**Entitlement:** `payments.create`\n\n**Needs:** PAYMENT_TOKEN\n\nHolds funds without taking them. Capture it later, or void it. An uncaptured authorization expires on the provider's schedule, not ours."
          }
        },
        {
          "name": "Capture a payment",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const needed = [\"AUTH_ID\"];",
                  "const missing = needed.filter((v) => !pm.variables.get(v));",
                  "if (missing.length) {",
                  "  console.log('SKIPPED — needs ' + missing.join(', '));",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": 2500\n}"
            },
            "url": {
              "raw": "{{BASE_URL}}/v1/payments/{{AUTH_ID}}/capture",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "payments",
                "{{AUTH_ID}}",
                "capture"
              ]
            },
            "description": "**Entitlement:** `payments.capture`\n\n**Needs:** AUTH_ID\n\nOnly valid on an open authorization. Omit `amount` to capture the full authorized amount. Whether a PARTIAL capture is permitted depends on the provider behind the account — check `capabilities` rather than assuming."
          }
        },
        {
          "name": "Void a payment",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const needed = [\"PAYMENT_ID\"];",
                  "const missing = needed.filter((v) => !pm.variables.get(v));",
                  "if (missing.length) {",
                  "  console.log('SKIPPED — needs ' + missing.join(', '));",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/v1/payments/{{PAYMENT_ID}}/void",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "payments",
                "{{PAYMENT_ID}}",
                "void"
              ]
            },
            "description": "**Entitlement:** `payments.void`\n\n**Needs:** PAYMENT_ID\n\nCancels an unsettled transaction. Once settled, a void is refused and a refund is the correct operation — the state machine enforces this rather than letting the provider decide."
          }
        },
        {
          "name": "Refund a payment",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const needed = [\"PAYMENT_ID\"];",
                  "const missing = needed.filter((v) => !pm.variables.get(v));",
                  "if (missing.length) {",
                  "  console.log('SKIPPED — needs ' + missing.join(', '));",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 409\", () => {",
                  "  pm.expect([200,409]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": 500\n}"
            },
            "url": {
              "raw": "{{BASE_URL}}/v1/payments/{{PAYMENT_ID}}/refund",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "payments",
                "{{PAYMENT_ID}}",
                "refund"
              ]
            },
            "description": "**Entitlement:** `payments.refund`\n\n**Needs:** PAYMENT_ID\n\nReturns money on a captured transaction. Omit `amount` for a full refund. Partial refunds may be repeated up to the captured total."
          }
        },
        {
          "name": "Retrieve a payment",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const needed = [\"PAYMENT_ID\"];",
                  "const missing = needed.filter((v) => !pm.variables.get(v));",
                  "if (missing.length) {",
                  "  console.log('SKIPPED — needs ' + missing.join(', '));",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/v1/payments/{{PAYMENT_ID}}",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "payments",
                "{{PAYMENT_ID}}"
              ]
            },
            "description": "**Entitlement:** `payments.retrieve`\n\n**Needs:** PAYMENT_ID\n\nThe authoritative state. `status` reflects settlement as reported by the provider, not an optimistic local guess."
          }
        },
        {
          "name": "List payments",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/v1/payments?limit=10&startingAfter=&customerId=",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "payments"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "10"
                },
                {
                  "key": "startingAfter",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "customerId",
                  "value": "",
                  "disabled": true
                }
              ]
            },
            "description": "**Entitlement:** `payments.list`\n\nCursor-paginated — see the collection description. `startingAfter` takes the id of the last row you saw."
          }
        }
      ]
    },
    {
      "name": "02 · Customers",
      "description": "A customer is the thing cards and subscriptions attach to. Creating one reserves it with the provider as well, so the same customer works for one-off charges and for recurring billing.",
      "item": [
        {
          "name": "Create customer",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});",
                  "// Chain the id forward so the next request needs no copy-paste.",
                  "if (pm.response.code < 300) {",
                  "  const body = pm.response.json();",
                  "  const value = body?.[\"id\"];",
                  "  if (value) pm.collectionVariables.set(\"CUSTOMER_ID\", value);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"{{$guid}}@example.com\",\n  \"name\": \"Ada Lovelace\",\n  \"phone\": \"+15555550123\"\n}"
            },
            "url": {
              "raw": "{{BASE_URL}}/v1/customers",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "customers"
              ]
            },
            "description": "**Entitlement:** `customers.create`\n\nOnly `email` is required. `name` falls back to the local part of the email when omitted."
          }
        },
        {
          "name": "Retrieve customer",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const needed = [\"CUSTOMER_ID\"];",
                  "const missing = needed.filter((v) => !pm.variables.get(v));",
                  "if (missing.length) {",
                  "  console.log('SKIPPED — needs ' + missing.join(', '));",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/v1/customers/{{CUSTOMER_ID}}",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "customers",
                "{{CUSTOMER_ID}}"
              ]
            },
            "description": "**Entitlement:** `customers.retrieve`\n\n**Needs:** CUSTOMER_ID\n\nA customer id from another merchant is `not_found`, never `permission_denied` — confirming existence would leak it."
          }
        },
        {
          "name": "Update customer",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const needed = [\"CUSTOMER_ID\"];",
                  "const missing = needed.filter((v) => !pm.variables.get(v));",
                  "if (missing.length) {",
                  "  console.log('SKIPPED — needs ' + missing.join(', '));",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Ada King\"\n}"
            },
            "url": {
              "raw": "{{BASE_URL}}/v1/customers/{{CUSTOMER_ID}}",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "customers",
                "{{CUSTOMER_ID}}"
              ]
            },
            "description": "**Entitlement:** `customers.update`\n\n**Needs:** CUSTOMER_ID\n\nPOST, not PATCH — the update convention across this API. Only the fields you send change."
          }
        },
        {
          "name": "List customers",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/v1/customers?limit=10&startingAfter=",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "customers"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "10"
                },
                {
                  "key": "startingAfter",
                  "value": "",
                  "disabled": true
                }
              ]
            },
            "description": "**Entitlement:** `customers.list`\n\nCursor-paginated."
          }
        }
      ]
    },
    {
      "name": "03 · Payment methods",
      "description": "Cards on file. The card itself is captured in the browser and never reaches your server — you exchange a single-use token for a reusable payment method here.",
      "item": [
        {
          "name": "Create payment method (vault a card)",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const needed = [\"CUSTOMER_ID\",\"PAYMENT_TOKEN\"];",
                  "const missing = needed.filter((v) => !pm.variables.get(v));",
                  "if (missing.length) {",
                  "  console.log('SKIPPED — needs ' + missing.join(', '));",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});",
                  "// Chain the id forward so the next request needs no copy-paste.",
                  "if (pm.response.code < 300) {",
                  "  const body = pm.response.json();",
                  "  const value = body?.[\"id\"];",
                  "  if (value) pm.collectionVariables.set(\"PAYMENT_METHOD_ID\", value);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"customerId\": \"{{CUSTOMER_ID}}\",\n  \"paymentToken\": \"{{PAYMENT_TOKEN}}\"\n}"
            },
            "url": {
              "raw": "{{BASE_URL}}/v1/payment_methods",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "payment_methods"
              ]
            },
            "description": "**Entitlement:** `paymentMethods.create`\n\n**Needs:** CUSTOMER_ID, PAYMENT_TOKEN\n\nConsumes the single-use token and returns a reusable id. A card stored through `/v1` with no stated schedule is credential-on-file `unscheduled` — the merchant charges it when they charge it."
          }
        },
        {
          "name": "List payment methods",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const needed = [\"CUSTOMER_ID\"];",
                  "const missing = needed.filter((v) => !pm.variables.get(v));",
                  "if (missing.length) {",
                  "  console.log('SKIPPED — needs ' + missing.join(', '));",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/v1/payment_methods?customerId={{CUSTOMER_ID}}&limit=10",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "payment_methods"
              ],
              "query": [
                {
                  "key": "customerId",
                  "value": "{{CUSTOMER_ID}}"
                },
                {
                  "key": "limit",
                  "value": "10"
                }
              ]
            },
            "description": "**Entitlement:** `paymentMethods.list`\n\n**Needs:** CUSTOMER_ID\n\n`customerId` is required — cards are always listed for one customer. The default card sorts first."
          }
        },
        {
          "name": "Set default payment method",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const needed = [\"PAYMENT_METHOD_ID\"];",
                  "const missing = needed.filter((v) => !pm.variables.get(v));",
                  "if (missing.length) {",
                  "  console.log('SKIPPED — needs ' + missing.join(', '));",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/v1/payment_methods/{{PAYMENT_METHOD_ID}}/default",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "payment_methods",
                "{{PAYMENT_METHOD_ID}}",
                "default"
              ]
            },
            "description": "**Entitlement:** `paymentMethods.setDefault`\n\n**Needs:** PAYMENT_METHOD_ID\n\nWhich card is charged when a request names none — including a scheduled invoice collecting itself."
          }
        }
      ]
    },
    {
      "name": "04 · Addresses",
      "description": "Billing and shipping addresses, held by Myela and mirrored to the provider where it can express them — so an address survives a change of provider.",
      "item": [
        {
          "name": "Create address",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const needed = [\"CUSTOMER_ID\"];",
                  "const missing = needed.filter((v) => !pm.variables.get(v));",
                  "if (missing.length) {",
                  "  console.log('SKIPPED — needs ' + missing.join(', '));",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});",
                  "// Chain the id forward so the next request needs no copy-paste.",
                  "if (pm.response.code < 300) {",
                  "  const body = pm.response.json();",
                  "  const value = body?.[\"id\"];",
                  "  if (value) pm.collectionVariables.set(\"ADDRESS_ID\", value);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"customerId\": \"{{CUSTOMER_ID}}\",\n  \"firstName\": \"Ada\",\n  \"lastName\": \"Lovelace\",\n  \"line1\": \"12 Marylebone Road\",\n  \"city\": \"London\",\n  \"postalCode\": \"NW1 5JD\",\n  \"country\": \"GB\"\n}"
            },
            "url": {
              "raw": "{{BASE_URL}}/v1/addresses",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "addresses"
              ]
            },
            "description": "**Entitlement:** `addresses.create`\n\n**Needs:** CUSTOMER_ID\n\nFields are allow-listed: anything else you send is not stored."
          }
        },
        {
          "name": "List addresses",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const needed = [\"CUSTOMER_ID\"];",
                  "const missing = needed.filter((v) => !pm.variables.get(v));",
                  "if (missing.length) {",
                  "  console.log('SKIPPED — needs ' + missing.join(', '));",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/v1/addresses?customerId={{CUSTOMER_ID}}",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "addresses"
              ],
              "query": [
                {
                  "key": "customerId",
                  "value": "{{CUSTOMER_ID}}"
                }
              ]
            },
            "description": "**Entitlement:** `addresses.list`\n\n**Needs:** CUSTOMER_ID\n\nScoped to one customer."
          }
        },
        {
          "name": "Retrieve address",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const needed = [\"ADDRESS_ID\"];",
                  "const missing = needed.filter((v) => !pm.variables.get(v));",
                  "if (missing.length) {",
                  "  console.log('SKIPPED — needs ' + missing.join(', '));",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/v1/addresses/{{ADDRESS_ID}}",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "addresses",
                "{{ADDRESS_ID}}"
              ]
            },
            "description": "**Entitlement:** `addresses.retrieve`\n\n**Needs:** ADDRESS_ID"
          }
        },
        {
          "name": "Update address",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const needed = [\"ADDRESS_ID\"];",
                  "const missing = needed.filter((v) => !pm.variables.get(v));",
                  "if (missing.length) {",
                  "  console.log('SKIPPED — needs ' + missing.join(', '));",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"city\": \"Manchester\"\n}"
            },
            "url": {
              "raw": "{{BASE_URL}}/v1/addresses/{{ADDRESS_ID}}",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "addresses",
                "{{ADDRESS_ID}}"
              ]
            },
            "description": "**Entitlement:** `addresses.update`\n\n**Needs:** ADDRESS_ID\n\nOnly the fields you send change."
          }
        },
        {
          "name": "Set default address",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const needed = [\"ADDRESS_ID\"];",
                  "const missing = needed.filter((v) => !pm.variables.get(v));",
                  "if (missing.length) {",
                  "  console.log('SKIPPED — needs ' + missing.join(', '));",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"type\": \"billing\"\n}"
            },
            "url": {
              "raw": "{{BASE_URL}}/v1/addresses/{{ADDRESS_ID}}/default",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "addresses",
                "{{ADDRESS_ID}}",
                "default"
              ]
            },
            "description": "**Entitlement:** `addresses.setDefault`\n\n**Needs:** ADDRESS_ID\n\n`type` is required and is either `billing` or `shipping` — a customer has one default of each, so a request that does not say which is refused rather than guessed."
          }
        }
      ]
    },
    {
      "name": "05 · Plans and subscriptions",
      "description": "A plan is the price and cadence; a subscription binds a customer, a plan and a card. Amounts are integers in the minor unit — 4999 is $49.99. A decimal is rejected rather than silently charged as a different figure.",
      "item": [
        {
          "name": "Create plan",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});",
                  "// Chain the id forward so the next request needs no copy-paste.",
                  "if (pm.response.code < 300) {",
                  "  const body = pm.response.json();",
                  "  const value = body?.[\"id\"];",
                  "  if (value) pm.collectionVariables.set(\"PLAN_ID\", value);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Standard monthly\",\n  \"amount\": 4999,\n  \"currency\": \"USD\",\n  \"interval\": \"month\",\n  \"intervalCount\": 1\n}"
            },
            "url": {
              "raw": "{{BASE_URL}}/v1/plans",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "plans"
              ]
            },
            "description": "**Entitlement:** `plans.create`\n\n`interval` is one of `day`, `week`, `month`, `year`. An unknown interval is refused here rather than upstream."
          }
        },
        {
          "name": "Retrieve plan",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const needed = [\"PLAN_ID\"];",
                  "const missing = needed.filter((v) => !pm.variables.get(v));",
                  "if (missing.length) {",
                  "  console.log('SKIPPED — needs ' + missing.join(', '));",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/v1/plans/{{PLAN_ID}}",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "plans",
                "{{PLAN_ID}}"
              ]
            },
            "description": "**Entitlement:** `plans.retrieve`\n\n**Needs:** PLAN_ID"
          }
        },
        {
          "name": "List plans",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/v1/plans?limit=10",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "plans"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "10"
                }
              ]
            },
            "description": "**Entitlement:** `plans.list`\n\nCursor-paginated."
          }
        },
        {
          "name": "Create subscription",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const needed = [\"CUSTOMER_ID\",\"PLAN_ID\",\"PAYMENT_METHOD_ID\"];",
                  "const missing = needed.filter((v) => !pm.variables.get(v));",
                  "if (missing.length) {",
                  "  console.log('SKIPPED — needs ' + missing.join(', '));",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});",
                  "// Chain the id forward so the next request needs no copy-paste.",
                  "if (pm.response.code < 300) {",
                  "  const body = pm.response.json();",
                  "  const value = body?.[\"id\"];",
                  "  if (value) pm.collectionVariables.set(\"SUBSCRIPTION_ID\", value);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"customerId\": \"{{CUSTOMER_ID}}\",\n  \"planId\": \"{{PLAN_ID}}\",\n  \"paymentMethodId\": \"{{PAYMENT_METHOD_ID}}\",\n  \"startAt\": \"2026-09-01\",\n  \"timezone\": \"America/New_York\"\n}"
            },
            "url": {
              "raw": "{{BASE_URL}}/v1/subscriptions",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "subscriptions"
              ]
            },
            "description": "**Entitlement:** `subscriptions.create`\n\n**Needs:** CUSTOMER_ID, PLAN_ID, PAYMENT_METHOD_ID\n\nWhich calendar day a charge lands on is a timezone question, so `timezone` is explicit and defaults to UTC rather than being guessed."
          }
        },
        {
          "name": "Retrieve subscription",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const needed = [\"SUBSCRIPTION_ID\"];",
                  "const missing = needed.filter((v) => !pm.variables.get(v));",
                  "if (missing.length) {",
                  "  console.log('SKIPPED — needs ' + missing.join(', '));",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/v1/subscriptions/{{SUBSCRIPTION_ID}}",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "subscriptions",
                "{{SUBSCRIPTION_ID}}"
              ]
            },
            "description": "**Entitlement:** `subscriptions.retrieve`\n\n**Needs:** SUBSCRIPTION_ID\n\n`nextBillingAt` is absent once cancelled — a stale date there is the shape of every \"we cancelled but it billed again\"."
          }
        },
        {
          "name": "List subscriptions",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/v1/subscriptions?limit=10",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "subscriptions"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "10"
                }
              ]
            },
            "description": "**Entitlement:** `subscriptions.list`\n\nCursor-paginated."
          }
        },
        {
          "name": "Cancel subscription",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const needed = [\"SUBSCRIPTION_ID\"];",
                  "const missing = needed.filter((v) => !pm.variables.get(v));",
                  "if (missing.length) {",
                  "  console.log('SKIPPED — needs ' + missing.join(', '));",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/v1/subscriptions/{{SUBSCRIPTION_ID}}/cancel",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "subscriptions",
                "{{SUBSCRIPTION_ID}}",
                "cancel"
              ]
            },
            "description": "**Entitlement:** `subscriptions.cancel`\n\n**Needs:** SUBSCRIPTION_ID\n\nCancels immediately."
          }
        }
      ]
    },
    {
      "name": "06 · Invoices and scheduled collection",
      "description": "An invoice can be sent for someone to pay, or scheduled to collect itself from a saved card on a date. The schedule is Myela's own: no provider behind this API has a collect-on-a-date primitive.",
      "item": [
        {
          "name": "Create invoice",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const needed = [\"CUSTOMER_ID\"];",
                  "const missing = needed.filter((v) => !pm.variables.get(v));",
                  "if (missing.length) {",
                  "  console.log('SKIPPED — needs ' + missing.join(', '));",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});",
                  "// Chain the id forward so the next request needs no copy-paste.",
                  "if (pm.response.code < 300) {",
                  "  const body = pm.response.json();",
                  "  const value = body?.[\"id\"];",
                  "  if (value) pm.collectionVariables.set(\"INVOICE_ID\", value);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"customerId\": \"{{CUSTOMER_ID}}\",\n  \"currency\": \"USD\",\n  \"amount\": 150000,\n  \"description\": \"Consulting, August\",\n  \"dueAt\": \"2026-09-01T00:00:00.000Z\"\n}"
            },
            "url": {
              "raw": "{{BASE_URL}}/v1/invoices",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "invoices"
              ]
            },
            "description": "**Entitlement:** `invoices.create`\n\n**Needs:** CUSTOMER_ID\n\nThe invoice total, in minor units — there is no line-item array on this endpoint. Amounts are integers in the minor unit — 4999 is $49.99. A decimal is rejected rather than silently charged as a different figure."
          }
        },
        {
          "name": "Retrieve invoice",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const needed = [\"INVOICE_ID\"];",
                  "const missing = needed.filter((v) => !pm.variables.get(v));",
                  "if (missing.length) {",
                  "  console.log('SKIPPED — needs ' + missing.join(', '));",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/v1/invoices/{{INVOICE_ID}}",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "invoices",
                "{{INVOICE_ID}}"
              ]
            },
            "description": "**Entitlement:** `invoices.retrieve`\n\n**Needs:** INVOICE_ID\n\n`url` is a Myela-hosted payment page on our own domain, safe to send to a cardholder."
          }
        },
        {
          "name": "List invoices",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/v1/invoices?limit=10",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "invoices"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "10"
                }
              ]
            },
            "description": "**Entitlement:** `invoices.list`\n\nCursor-paginated."
          }
        },
        {
          "name": "Schedule invoice collection",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const needed = [\"INVOICE_ID\",\"PAYMENT_METHOD_ID\"];",
                  "const missing = needed.filter((v) => !pm.variables.get(v));",
                  "if (missing.length) {",
                  "  console.log('SKIPPED — needs ' + missing.join(', '));",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"collectAt\": \"2026-09-01T09:00:00.000Z\",\n  \"paymentMethodId\": \"{{PAYMENT_METHOD_ID}}\"\n}"
            },
            "url": {
              "raw": "{{BASE_URL}}/v1/invoices/{{INVOICE_ID}}/schedule",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "invoices",
                "{{INVOICE_ID}}",
                "schedule"
              ]
            },
            "description": "**Entitlement:** `invoices.schedule`\n\n**Needs:** INVOICE_ID, PAYMENT_METHOD_ID\n\nCharges the named card on that date. Omit `paymentMethodId` to use the customer's default. Failed attempts retry on a backoff and are visible under Attempts."
          }
        },
        {
          "name": "Cancel scheduled collection",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const needed = [\"INVOICE_ID\"];",
                  "const missing = needed.filter((v) => !pm.variables.get(v));",
                  "if (missing.length) {",
                  "  console.log('SKIPPED — needs ' + missing.join(', '));",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/v1/invoices/{{INVOICE_ID}}/cancel_schedule",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "invoices",
                "{{INVOICE_ID}}",
                "cancel_schedule"
              ]
            },
            "description": "**Entitlement:** `invoices.schedule`\n\n**Needs:** INVOICE_ID\n\nLeaves the invoice open but stops it collecting itself."
          }
        },
        {
          "name": "List collection attempts",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const needed = [\"INVOICE_ID\"];",
                  "const missing = needed.filter((v) => !pm.variables.get(v));",
                  "if (missing.length) {",
                  "  console.log('SKIPPED — needs ' + missing.join(', '));",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/v1/invoices/{{INVOICE_ID}}/attempts",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "invoices",
                "{{INVOICE_ID}}",
                "attempts"
              ]
            },
            "description": "**Entitlement:** `invoices.attempts`\n\n**Needs:** INVOICE_ID\n\nEvery attempt made to collect it, in order — the answer to \"why has this not been paid\"."
          }
        },
        {
          "name": "Void invoice",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const needed = [\"INVOICE_ID\"];",
                  "const missing = needed.filter((v) => !pm.variables.get(v));",
                  "if (missing.length) {",
                  "  console.log('SKIPPED — needs ' + missing.join(', '));",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/v1/invoices/{{INVOICE_ID}}/void",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "invoices",
                "{{INVOICE_ID}}",
                "void"
              ]
            },
            "description": "**Entitlement:** `invoices.void`\n\n**Needs:** INVOICE_ID\n\nTerminal. A voided invoice cannot be collected or reopened."
          }
        }
      ]
    },
    {
      "name": "07 · Webhook endpoints and deliveries",
      "description": "Where your payment events leave Myela. Entitlements here are separate from the rest of `/v1` on purpose: a key that may read invoices should not be able to point that stream somewhere new.\n\nVerify every delivery signature before acting on it — see `docs/gateway/api/WEBHOOK_SIGNATURE.md`.",
      "item": [
        {
          "name": "Create webhook endpoint",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});",
                  "// Chain the id forward so the next request needs no copy-paste.",
                  "if (pm.response.code < 300) {",
                  "  const body = pm.response.json();",
                  "  const value = body?.[\"id\"];",
                  "  if (value) pm.collectionVariables.set(\"WEBHOOK_ENDPOINT_ID\", value);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"url\": \"https://example.com/webhooks/myela\",\n  \"events\": [\n    \"invoice.paid\",\n    \"payment.settled\",\n    \"payment.refunded\"\n  ]\n}"
            },
            "url": {
              "raw": "{{BASE_URL}}/v1/webhook_endpoints",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "webhook_endpoints"
              ]
            },
            "description": "**Entitlement:** `webhookEndpoints.create`\n\nThe signing secret is returned once, at creation, and never again."
          }
        },
        {
          "name": "List webhook endpoints",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/v1/webhook_endpoints",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "webhook_endpoints"
              ]
            },
            "description": "**Entitlement:** `webhookEndpoints.list`"
          }
        },
        {
          "name": "Retrieve webhook endpoint",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const needed = [\"WEBHOOK_ENDPOINT_ID\"];",
                  "const missing = needed.filter((v) => !pm.variables.get(v));",
                  "if (missing.length) {",
                  "  console.log('SKIPPED — needs ' + missing.join(', '));",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/v1/webhook_endpoints/{{WEBHOOK_ENDPOINT_ID}}",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "webhook_endpoints",
                "{{WEBHOOK_ENDPOINT_ID}}"
              ]
            },
            "description": "**Entitlement:** `webhookEndpoints.retrieve`\n\n**Needs:** WEBHOOK_ENDPOINT_ID"
          }
        },
        {
          "name": "Update webhook endpoint",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const needed = [\"WEBHOOK_ENDPOINT_ID\"];",
                  "const missing = needed.filter((v) => !pm.variables.get(v));",
                  "if (missing.length) {",
                  "  console.log('SKIPPED — needs ' + missing.join(', '));",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"events\": [\n    \"invoice.paid\"\n  ]\n}"
            },
            "url": {
              "raw": "{{BASE_URL}}/v1/webhook_endpoints/{{WEBHOOK_ENDPOINT_ID}}",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "webhook_endpoints",
                "{{WEBHOOK_ENDPOINT_ID}}"
              ]
            },
            "description": "**Entitlement:** `webhookEndpoints.update`\n\n**Needs:** WEBHOOK_ENDPOINT_ID"
          }
        },
        {
          "name": "Delete webhook endpoint",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const needed = [\"WEBHOOK_ENDPOINT_ID\"];",
                  "const missing = needed.filter((v) => !pm.variables.get(v));",
                  "if (missing.length) {",
                  "  console.log('SKIPPED — needs ' + missing.join(', '));",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/v1/webhook_endpoints/{{WEBHOOK_ENDPOINT_ID}}",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "webhook_endpoints",
                "{{WEBHOOK_ENDPOINT_ID}}"
              ]
            },
            "description": "**Entitlement:** `webhookEndpoints.delete`\n\n**Needs:** WEBHOOK_ENDPOINT_ID"
          }
        },
        {
          "name": "List deliveries",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});",
                  "// Chain the id forward so the next request needs no copy-paste.",
                  "if (pm.response.code < 300) {",
                  "  const body = pm.response.json();",
                  "  const value = body?.[\"data\"]?.[\"0\"]?.[\"id\"];",
                  "  if (value) pm.collectionVariables.set(\"WEBHOOK_DELIVERY_ID\", value);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/v1/webhook_deliveries?limit=10",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "webhook_deliveries"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "10"
                }
              ]
            },
            "description": "**Entitlement:** `webhookDeliveries.list`\n\nEvery attempt to reach your endpoint, with the response we got."
          }
        },
        {
          "name": "Retrieve delivery",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const needed = [\"WEBHOOK_DELIVERY_ID\"];",
                  "const missing = needed.filter((v) => !pm.variables.get(v));",
                  "if (missing.length) {",
                  "  console.log('SKIPPED — needs ' + missing.join(', '));",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/v1/webhook_deliveries/{{WEBHOOK_DELIVERY_ID}}",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "webhook_deliveries",
                "{{WEBHOOK_DELIVERY_ID}}"
              ]
            },
            "description": "**Entitlement:** `webhookDeliveries.retrieve`\n\n**Needs:** WEBHOOK_DELIVERY_ID"
          }
        },
        {
          "name": "Replay delivery",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const needed = [\"WEBHOOK_DELIVERY_ID\"];",
                  "const missing = needed.filter((v) => !pm.variables.get(v));",
                  "if (missing.length) {",
                  "  console.log('SKIPPED — needs ' + missing.join(', '));",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200 or 201 or 204\", () => {",
                  "  pm.expect([200,201,204]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/v1/webhook_deliveries/{{WEBHOOK_DELIVERY_ID}}/replay",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "webhook_deliveries",
                "{{WEBHOOK_DELIVERY_ID}}",
                "replay"
              ]
            },
            "description": "**Entitlement:** `webhookDeliveries.retrieve`\n\n**Needs:** WEBHOOK_DELIVERY_ID\n\nRe-sends the FROZEN payload, not a rebuilt one — a replay must deliver what the event said when it happened."
          }
        }
      ]
    },
    {
      "name": "08 · Myela Elements (browser)",
      "description": "The only two routes a merchant's own checkout page calls, and the only two authenticated by the PUBLISHABLE key (`myela_pk_`) rather than the secret one. Set MYELA_PUBLISHABLE_KEY in the environment; the secret key must never reach a browser.\n\nA session can only be started from an origin on that key's allow-list, which the merchant manages. An empty list refuses every origin — \"not configured yet\" fails closed, because a checkout that 401s until someone adds a domain is recoverable and one open to the internet is not.\n\nThe hosted frame at js.myela.com is not serving yet, so the middle step — mounting the fields and getting an upstream token — cannot be done from these two requests alone. The server contract below is live and can be exercised today.",
      "item": [
        {
          "name": "Create checkout session",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 201\", () => {",
                  "  pm.expect([201]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{MYELA_PUBLISHABLE_KEY}}",
                  "type": "string"
                }
              ]
            },
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"intent\": \"one_time\"\n}"
            },
            "url": {
              "raw": "{{BASE_URL}}/v1/checkout_sessions",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "checkout_sessions"
              ]
            },
            "description": "Opens a checkout session from the merchant's page. `intent` is `one_time` (charge now) or `store` (vault the card); the whole body is optional and defaults to `one_time`.\n\nReturns `sessionId`, a `mountUrl` to iframe, `parentOrigin`, and an opaque `capture` object. Nothing in the response names a provider or differs by which one settles the account — that uniformity is the contract, not an implementation detail.\n\n**401** means the calling origin is not on this key's allow-list."
          }
        },
        {
          "name": "Exchange for a payment token",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const needed = [\"CHECKOUT_SESSION_ID\",\"UPSTREAM_TOKEN\"];",
                  "const missing = needed.filter((v) => !pm.variables.get(v));",
                  "if (missing.length) {",
                  "  console.log('SKIPPED — needs ' + missing.join(', '));",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 201\", () => {",
                  "  pm.expect([201]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{MYELA_PUBLISHABLE_KEY}}",
                  "type": "string"
                }
              ]
            },
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"sessionId\": \"{{CHECKOUT_SESSION_ID}}\",\n  \"upstreamToken\": \"{{UPSTREAM_TOKEN}}\"\n}"
            },
            "url": {
              "raw": "{{BASE_URL}}/v1/tokens",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "v1",
                "tokens"
              ]
            },
            "description": "**Needs:** CHECKOUT_SESSION_ID, UPSTREAM_TOKEN\n\nExchanges the provider token the fields produced for an opaque `mtok_`, which is what `POST /v1/payments` accepts as `paymentToken`.\n\nThe session id is the capability — there is no key on this call beyond the publishable one. A session exchanges ONCE; a second attempt is refused, and so is a replay of the resulting `mtok_`.\n\nBoth variables come from the frame, so this request cannot be driven from the collection alone until js.myela.com is serving."
          }
        }
      ]
    }
  ],
  "variable": [
    {
      "key": "PAYMENT_ID",
      "value": ""
    },
    {
      "key": "AUTH_ID",
      "value": ""
    },
    {
      "key": "CUSTOMER_ID",
      "value": ""
    },
    {
      "key": "PAYMENT_METHOD_ID",
      "value": ""
    },
    {
      "key": "ADDRESS_ID",
      "value": ""
    },
    {
      "key": "PLAN_ID",
      "value": ""
    },
    {
      "key": "SUBSCRIPTION_ID",
      "value": ""
    },
    {
      "key": "INVOICE_ID",
      "value": ""
    },
    {
      "key": "WEBHOOK_ENDPOINT_ID",
      "value": ""
    },
    {
      "key": "WEBHOOK_DELIVERY_ID",
      "value": ""
    }
  ]
}
