{
  "info": {
    "_postman_id": "7d3f6c52-5e0a-4b52-9a39-3c1f2a8d6e10",
    "name": "reach API",
    "description": "Multi-tenant transactional email API.\n\nConventions that hold across every endpoint:\n- Authentication on `/v1` is `Authorization: Bearer <credential>` with exactly one of: a project API key (`rk_…`, holds every scope) or an OAuth access token (`ro_at_…`, 1 hour, holds the scopes a human approved). Both are scoped to one organisation and project. A token lacking the scope an endpoint needs gets **403 `insufficient_scope`**. A refresh token (`ro_rt_…`) or a console session is never accepted here, and a request carrying two credentials is rejected.\n- A resource belonging to another project returns **404**, never 403.\n- Errors always have `{ error: { code, message, details?, requestId } }`; branch on `code`.\n- List endpoints return `{ rows, total }` and are always scoped to the caller's project.\n- An **unknown query parameter is a 422**, never ignored: a filter that was dropped and one that was applied must never look the same to you.\n- This document is generated from the same JSON schemas the server validates with, so it cannot drift from the implementation.\n\nGenerated from the service's OpenAPI document. `baseUrl` and `consoleUrl` (collection variables) default to the public Reach hosts; no key or secret is stored in this file.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://api.tryreach.email",
      "type": "default",
      "description": "Base URL of the public Reach API (no trailing slash): /v1, /oauth/token, /oauth/revoke, /health."
    },
    {
      "key": "consoleUrl",
      "value": "https://tryreach.email",
      "type": "default",
      "description": "Base URL of the Reach console, where the OAuth consent page lives."
    },
    {
      "key": "apiKey",
      "value": "",
      "type": "secret",
      "description": "Project API key (rk_...). Create one in the console; it is shown once."
    },
    {
      "key": "clientId",
      "value": "",
      "type": "default",
      "description": "OAuth client id (rc_...), issued by the Reach operator."
    },
    {
      "key": "clientSecret",
      "value": "",
      "type": "secret",
      "description": "OAuth client secret, issued once by the Reach operator. Never commit it."
    },
    {
      "key": "redirectUri",
      "value": "",
      "type": "default",
      "description": "Your registered redirect URI, byte for byte."
    },
    {
      "key": "scope",
      "value": "emails:send tenants:read",
      "type": "default",
      "description": "Space-delimited scopes to request at consent."
    },
    {
      "key": "state",
      "value": "",
      "type": "default",
      "description": "Set by the authorize pre-request script. Compare it with the state on the redirect."
    },
    {
      "key": "codeVerifier",
      "value": "",
      "type": "secret",
      "description": "Set by the authorize pre-request script (PKCE)."
    },
    {
      "key": "codeChallenge",
      "value": "",
      "type": "default",
      "description": "Set by the authorize pre-request script (PKCE, S256)."
    },
    {
      "key": "authorizationCode",
      "value": "",
      "type": "default",
      "description": "Paste the code from the redirect here. Single use, valid for 60 seconds."
    },
    {
      "key": "accessToken",
      "value": "",
      "type": "secret",
      "description": "Set by the token requests' test script."
    },
    {
      "key": "refreshToken",
      "value": "",
      "type": "secret",
      "description": "Set by the token requests' test script. Rotated on every refresh."
    },
    {
      "key": "tenantId",
      "value": "",
      "type": "default",
      "description": "Set by the Create tenant test script (or paste a tenant id)."
    },
    {
      "key": "redirectUriEncoded",
      "value": "",
      "type": "default",
      "description": "Set by the authorize pre-request script: redirectUri, URL-encoded for the authorize URL."
    },
    {
      "key": "scopeEncoded",
      "value": "",
      "type": "default",
      "description": "Set by the authorize pre-request script: scope, URL-encoded for the authorize URL."
    }
  ],
  "item": [
    {
      "name": "API key (/v1)",
      "description": "For your own backend. Authorization: Bearer {{apiKey}}. A project API key holds every scope. A console session is never accepted here, and a request carrying two credentials is rejected.\n\nTypical order: Create tenant, Add domain (publish the DNS records, then poll Read domain until `verified`), Send email, Read email.",
      "auth": {
        "type": "bearer",
        "bearer": [
          {
            "key": "token",
            "value": "{{apiKey}}",
            "type": "string"
          }
        ]
      },
      "item": [
        {
          "name": "emails",
          "description": "Send and inspect transactional email.",
          "item": [
            {
              "name": "Query the email log",
              "request": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/v1/emails",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "emails"
                  ],
                  "query": [
                    {
                      "key": "tenant",
                      "value": "",
                      "disabled": true
                    },
                    {
                      "key": "status",
                      "value": "",
                      "disabled": true,
                      "description": "`held` means the email was accepted and recorded but deliberately not dispatched: this organisation’s first send is waiting on a review (see `GET /account/organizations/{id}/sending-policy`)."
                    },
                    {
                      "key": "to",
                      "value": "",
                      "disabled": true,
                      "description": "Recipient address, exact match."
                    },
                    {
                      "key": "from_date",
                      "value": "",
                      "disabled": true,
                      "description": "Accepted at or after this instant."
                    },
                    {
                      "key": "to_date",
                      "value": "",
                      "disabled": true,
                      "description": "Accepted at or before this instant."
                    },
                    {
                      "key": "limit",
                      "value": "",
                      "disabled": true
                    },
                    {
                      "key": "offset",
                      "value": "",
                      "disabled": true
                    }
                  ]
                },
                "description": "**Query the email log**\n\nAlways scoped to the calling project. `tenant` narrows it further; omitting `tenant` never widens it beyond the project. The acceptance-date bounds are `from_date` / `to_date` — `from` is the sender on a send and is not a filter here. An unknown query parameter is rejected with 422 `validation_failed` rather than ignored, so a filter that was dropped can never look like one that was applied."
              },
              "response": []
            },
            {
              "name": "Accept a transactional email for delivery",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/emails",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "emails"
                  ]
                },
                "description": "**Accept a transactional email for delivery**\n\nReturns 202 as soon as the email is recorded and queued; delivery happens in a worker. A recipient on the tenant’s suppression list also returns 202, with status `suppressed` and no provider call. Subject to the tenant’s per-tenant send rate limit (PRD A8: burst/hour/day, see `GET /v1/tenants/:id`’s `limits`) — an exceeded window returns `429 rate_limited` with `Retry-After` and an `error.details[0]` entry naming which window (`burst_per_second` | `per_hour` | `per_day`) was exceeded, and never blocks a retry of an already-accepted idempotency key.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"tenant\": \"{{tenantId}}\",\n  \"from\": \"billing@acme.com\",\n  \"to\": \"customer@example.com\",\n  \"subject\": \"Your invoice is ready\",\n  \"html\": \"<p>Hi, your invoice is ready.</p>\",\n  \"idempotencyKey\": \"invoice-2026-09-cust_8f2e1a\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            },
            {
              "name": "Read one email with its ordered event history",
              "request": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/v1/emails/:id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "emails",
                    ":id"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "<id>"
                    }
                  ]
                },
                "description": "**Read one email with its ordered event history**"
              },
              "response": []
            }
          ]
        },
        {
          "name": "tenants",
          "description": "Your customers, as first-class resources.",
          "item": [
            {
              "name": "List the calling project’s tenants",
              "request": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/v1/tenants",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "tenants"
                  ],
                  "query": [
                    {
                      "key": "limit",
                      "value": "",
                      "disabled": true
                    },
                    {
                      "key": "offset",
                      "value": "",
                      "disabled": true
                    }
                  ]
                },
                "description": "**List the calling project’s tenants**"
              },
              "response": []
            },
            {
              "name": "Create a tenant (idempotent on externalId)",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('tenant created or already existed', () => pm.expect([200, 201]).to.include(pm.response.code));",
                      "if ([200, 201].includes(pm.response.code)) {",
                      "  pm.collectionVariables.set('tenantId', pm.response.json().id);",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/tenants",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "tenants"
                  ]
                },
                "description": "**Create a tenant (idempotent on externalId)**\n\nCreates a tenant and its 1:1 provider-side twin. A tenant whose provider mapping is unconfirmed is returned with status `provisioning` or `provision_failed` and cannot send.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"name\": \"Acme Corp\",\n  \"externalId\": \"cust_8f2e1a\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            },
            {
              "name": "Read a tenant",
              "request": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/v1/tenants/:id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "tenants",
                    ":id"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "<id>"
                    }
                  ]
                },
                "description": "**Read a tenant**\n\nA tenant of another project returns 404 — existence is not disclosed."
              },
              "response": []
            },
            {
              "name": "Suspend or reinstate a tenant",
              "request": {
                "method": "PATCH",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/tenants/:id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "tenants",
                    ":id"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "<id>"
                    }
                  ]
                },
                "description": "**Suspend or reinstate a tenant**\n\nSuspending stops sending immediately. Reinstating requires the provider-side mapping to confirm; if it does not, the tenant is left unable to send rather than silently divergent.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"status\": \"suspended\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            },
            {
              "name": "Delete a tenant",
              "request": {
                "method": "DELETE",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/v1/tenants/:id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "tenants",
                    ":id"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "<id>"
                    }
                  ]
                },
                "description": "**Delete a tenant**\n\nThe tenant stops being readable and stops being able to send. Its historical email rows are retained for the retention window — deletion never destroys the audit trail."
              },
              "response": []
            }
          ]
        },
        {
          "name": "domains",
          "description": "Sending domains and their DNS verification (H-006). A domain only sends once `verified`.",
          "item": [
            {
              "name": "List a tenant’s sending domains",
              "request": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/v1/domains?tenant={{tenantId}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "domains"
                  ],
                  "query": [
                    {
                      "key": "tenant",
                      "value": "{{tenantId}}",
                      "disabled": false,
                      "description": "Required."
                    },
                    {
                      "key": "limit",
                      "value": "",
                      "disabled": true
                    },
                    {
                      "key": "offset",
                      "value": "",
                      "disabled": true
                    }
                  ]
                },
                "description": "**List a tenant’s sending domains**"
              },
              "response": []
            },
            {
              "name": "Add a sending domain and get its DNS records",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/domains",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "domains"
                  ]
                },
                "description": "**Add a sending domain and get its DNS records**\n\nReturns the DKIM CNAMEs, the custom MAIL FROM records, and a recommended DMARC record — each copy-pasteable, with both a fully-qualified and a host-only form of the name. The domain starts `pending`; a scheduled check verifies it once the records resolve (within 5 minutes) or fails it after 72 hours.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"tenant\": \"{{tenantId}}\",\n  \"domain\": \"acme.com\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            },
            {
              "name": "Read a sending domain, its DNS records and verification status",
              "request": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/v1/domains/:id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "domains",
                    ":id"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "<id>"
                    }
                  ]
                },
                "description": "**Read a sending domain, its DNS records and verification status**\n\nA domain of another project returns 404 — existence is not disclosed. `records[].status` is the per-record diagnosis: `missing`, `mismatch` or `ok`, never a generic failure."
              },
              "response": []
            }
          ]
        },
        {
          "name": "suppressions",
          "description": "Per-tenant suppression list (H-007). Populated automatically from bounces and complaints; a suppressed address is never mailed again until it is released here.",
          "item": [
            {
              "name": "List a tenant’s suppressed addresses",
              "request": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/v1/suppressions?tenant={{tenantId}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "suppressions"
                  ],
                  "query": [
                    {
                      "key": "tenant",
                      "value": "{{tenantId}}",
                      "disabled": false,
                      "description": "Required."
                    },
                    {
                      "key": "reason",
                      "value": "",
                      "disabled": true,
                      "description": "`bounce` and `complaint` are written automatically from provider feedback (H-005)."
                    },
                    {
                      "key": "from_date",
                      "value": "",
                      "disabled": true,
                      "description": "Suppressed at or after this instant."
                    },
                    {
                      "key": "to_date",
                      "value": "",
                      "disabled": true,
                      "description": "Suppressed at or before this instant."
                    },
                    {
                      "key": "limit",
                      "value": "",
                      "disabled": true
                    },
                    {
                      "key": "offset",
                      "value": "",
                      "disabled": true
                    }
                  ]
                },
                "description": "**List a tenant’s suppressed addresses**\n\nFilterable by `reason` and by the date the address was suppressed. Every entry here is skipped on the send path (H-003 CA4) — a `202` with status `suppressed`, never an error."
              },
              "response": []
            },
            {
              "name": "Release a suppressed address for one tenant",
              "request": {
                "method": "DELETE",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/v1/suppressions?tenant={{tenantId}}&address=user@example.com",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "suppressions"
                  ],
                  "query": [
                    {
                      "key": "tenant",
                      "value": "{{tenantId}}",
                      "disabled": false,
                      "description": "Required."
                    },
                    {
                      "key": "address",
                      "value": "user@example.com",
                      "disabled": false,
                      "description": "Required."
                    }
                  ]
                },
                "description": "**Release a suppressed address for one tenant**\n\nA suppression created in error would otherwise be permanent. The release is recorded with the calling API key and a timestamp, and the address is mailable again immediately. `404` when nothing was suppressed under this tenant and address. `409 suppression_renewed` when a suppression exists but was (re-)created after this request was issued — a bounce or complaint that arrived while the release was in flight is not undone by it."
              },
              "response": []
            }
          ]
        },
        {
          "name": "webhooks",
          "description": "Signed, retried outbound event webhooks (H-010). A subscription receives every email and domain event for its tenant — there is no per-event-type filter in this version. Every delivery is signed with HMAC-SHA256 over `${timestamp}.${rawBody}` and carries `x-reach-signature` (secret `rsec_…`) plus `x-reach-timestamp`, `x-reach-event-type` and `x-reach-delivery-id`. Retried up to 6 times, exponential with jitter, over roughly 24 hours; never left `pending` once the budget is exhausted.",
          "item": [
            {
              "name": "List webhook subscriptions",
              "request": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/v1/webhook-subscriptions",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "webhook-subscriptions"
                  ],
                  "query": [
                    {
                      "key": "tenant",
                      "value": "",
                      "disabled": true
                    },
                    {
                      "key": "limit",
                      "value": "",
                      "disabled": true
                    },
                    {
                      "key": "offset",
                      "value": "",
                      "disabled": true
                    }
                  ]
                },
                "description": "**List webhook subscriptions**"
              },
              "response": []
            },
            {
              "name": "Register a webhook subscription for a tenant",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/webhook-subscriptions",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "webhook-subscriptions"
                  ]
                },
                "description": "**Register a webhook subscription for a tenant**\n\nThe response includes the signing secret (`rsec_…`) exactly once — no other endpoint ever returns it again. The URL must be `https://` and must not resolve to a private or reserved address (SSRF).",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"tenant\": \"{{tenantId}}\",\n  \"url\": \"https://your-app.example/webhooks/reach\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            },
            {
              "name": "Read a webhook subscription",
              "request": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/v1/webhook-subscriptions/:id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "webhook-subscriptions",
                    ":id"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "<id>"
                    }
                  ]
                },
                "description": "**Read a webhook subscription**\n\nNever includes the secret — see `POST /webhook-subscriptions` and the rotate endpoint."
              },
              "response": []
            },
            {
              "name": "Enable or disable a webhook subscription",
              "request": {
                "method": "PATCH",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/webhook-subscriptions/:id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "webhook-subscriptions",
                    ":id"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "<id>"
                    }
                  ]
                },
                "description": "**Enable or disable a webhook subscription**",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"status\": \"disabled\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            },
            {
              "name": "Rotate a webhook subscription’s signing secret",
              "request": {
                "method": "POST",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/v1/webhook-subscriptions/:id/rotate-secret",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "webhook-subscriptions",
                    ":id",
                    "rotate-secret"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "<id>"
                    }
                  ]
                },
                "description": "**Rotate a webhook subscription’s signing secret**\n\nThe new secret is returned exactly once, here. The old secret stops working the instant this returns — there is no grace window (CA7)."
              },
              "response": []
            },
            {
              "name": "Query webhook deliveries",
              "request": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/v1/webhook-deliveries",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "webhook-deliveries"
                  ],
                  "query": [
                    {
                      "key": "tenant",
                      "value": "",
                      "disabled": true
                    },
                    {
                      "key": "subscription_id",
                      "value": "",
                      "disabled": true
                    },
                    {
                      "key": "status",
                      "value": "",
                      "disabled": true,
                      "description": "Never left `pending` forever (CA4): a delivery becomes `succeeded` on a 2xx response or `failed` once the six-attempt budget is exhausted."
                    },
                    {
                      "key": "limit",
                      "value": "",
                      "disabled": true
                    },
                    {
                      "key": "offset",
                      "value": "",
                      "disabled": true
                    }
                  ]
                },
                "description": "**Query webhook deliveries**\n\nA delivery is never left `pending` forever (CA4) — it becomes `succeeded` or `failed` once the six-attempt budget is exhausted, including when the dispatcher itself throws."
              },
              "response": []
            },
            {
              "name": "Read one delivery and its ordered attempt history",
              "request": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/v1/webhook-deliveries/:id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "webhook-deliveries",
                    ":id"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "<id>"
                    }
                  ]
                },
                "description": "**Read one delivery and its ordered attempt history**\n\nEach attempt carries its status code, latency and a truncated response body (CA3)."
              },
              "response": []
            }
          ]
        }
      ]
    },
    {
      "name": "OAuth (platforms connecting customers)",
      "description": "Authorization Code + PKCE (S256) for operator-registered, confidential clients. Run the requests in order: 1 builds the consent URL, 2 exchanges the code, 3 refreshes, 4 revokes.\n\nBasic auth here is Postman's built-in, which does not urlencode: if your client id or secret contains reserved characters, urlencode them into the variables first (or use client_secret_post).\n\nRefresh tokens rotate on every use. Run request 3 once at a time: two refreshes racing each other look like token reuse and revoke the whole grant.",
      "auth": {
        "type": "basic",
        "basic": [
          {
            "key": "username",
            "value": "{{clientId}}",
            "type": "string"
          },
          {
            "key": "password",
            "value": "{{clientSecret}}",
            "type": "string"
          }
        ]
      },
      "item": [
        {
          "name": "1. Authorize URL (open in a browser)",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// PKCE (RFC 7636), S256 only. Uses CryptoJS, which ships in Postman's sandbox.",
                  "const b64url = (wordArray) =>",
                  "  CryptoJS.enc.Base64.stringify(wordArray).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');",
                  "const verifier = b64url(CryptoJS.lib.WordArray.random(32)); // 43 chars of [A-Za-z0-9_-]",
                  "const challenge = b64url(CryptoJS.SHA256(verifier));",
                  "pm.collectionVariables.set('codeVerifier', verifier);",
                  "pm.collectionVariables.set('codeChallenge', challenge);",
                  "pm.collectionVariables.set('state', b64url(CryptoJS.lib.WordArray.random(16)));",
                  "// The authorize URL is a query string: encode what contains reserved characters.",
                  "pm.collectionVariables.set('redirectUriEncoded', encodeURIComponent(pm.collectionVariables.get('redirectUri') || ''));",
                  "pm.collectionVariables.set('scopeEncoded', encodeURIComponent(pm.collectionVariables.get('scope') || ''));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "auth": {
              "type": "noauth"
            },
            "header": [],
            "url": {
              "raw": "{{consoleUrl}}/oauth/authorize?response_type=code&client_id={{clientId}}&redirect_uri={{redirectUriEncoded}}&scope={{scopeEncoded}}&state={{state}}&code_challenge={{codeChallenge}}&code_challenge_method=S256",
              "host": [
                "{{consoleUrl}}"
              ],
              "path": [
                "oauth",
                "authorize"
              ],
              "query": [
                {
                  "key": "response_type",
                  "value": "code"
                },
                {
                  "key": "client_id",
                  "value": "{{clientId}}"
                },
                {
                  "key": "redirect_uri",
                  "value": "{{redirectUriEncoded}}"
                },
                {
                  "key": "scope",
                  "value": "{{scopeEncoded}}"
                },
                {
                  "key": "state",
                  "value": "{{state}}"
                },
                {
                  "key": "code_challenge",
                  "value": "{{codeChallenge}}"
                },
                {
                  "key": "code_challenge_method",
                  "value": "S256"
                }
              ]
            },
            "description": "A console page, not an API call: Send is not what you want here. The pre-request script generates a fresh PKCE verifier, challenge and state into the collection variables. Send once (it will land on the sign-in or consent page), then open the resolved URL in a browser, or hover the URL and copy the resolved value.\n\nAfter approval the browser comes back to your redirect_uri with `code` and `state`. Check that `state` matches the collection variable, paste the code into `authorizationCode`, and run request 2 within 60 seconds. A denial returns `error=access_denied`."
          },
          "response": []
        },
        {
          "name": "2. Exchange the code for tokens",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('token response is 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 200) {",
                  "  const body = pm.response.json();",
                  "  pm.collectionVariables.set('accessToken', body.access_token);",
                  "  // Rotation: the refresh token you just sent is dead. Keep only the new one.",
                  "  pm.collectionVariables.set('refreshToken', body.refresh_token);",
                  "  pm.test('bearer token with scope', () => {",
                  "    pm.expect(body.token_type).to.eql('Bearer');",
                  "    pm.expect(body.scope).to.be.a('string');",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "auth": {
              "type": "basic",
              "basic": [
                {
                  "key": "username",
                  "value": "{{clientId}}",
                  "type": "string"
                },
                {
                  "key": "password",
                  "value": "{{clientSecret}}",
                  "type": "string"
                }
              ]
            },
            "header": [
              {
                "key": "Content-Type",
                "value": "application/x-www-form-urlencoded"
              }
            ],
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                {
                  "key": "grant_type",
                  "value": "authorization_code"
                },
                {
                  "key": "code",
                  "value": "{{authorizationCode}}"
                },
                {
                  "key": "redirect_uri",
                  "value": "{{redirectUri}}",
                  "description": "Identical to the one used at consent."
                },
                {
                  "key": "code_verifier",
                  "value": "{{codeVerifier}}"
                }
              ]
            },
            "url": {
              "raw": "{{baseUrl}}/oauth/token",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "oauth",
                "token"
              ]
            },
            "description": "**Exchange an authorization code, or rotate a refresh token**\n\nBody is `application/x-www-form-urlencoded`. Authenticate the client with HTTP Basic or `client_secret_post` (not both). `authorization_code` requires `code`, `redirect_uri` (byte-for-byte what consent used) and `code_verifier`. `refresh_token` returns a new pair; **store the new refresh token atomically and never reuse the old one — presenting a rotated token revokes the whole grant.** A code redeemed twice revokes everything issued from it."
          },
          "response": []
        },
        {
          "name": "3. Refresh (rotates the refresh token)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('token response is 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 200) {",
                  "  const body = pm.response.json();",
                  "  pm.collectionVariables.set('accessToken', body.access_token);",
                  "  // Rotation: the refresh token you just sent is dead. Keep only the new one.",
                  "  pm.collectionVariables.set('refreshToken', body.refresh_token);",
                  "  pm.test('bearer token with scope', () => {",
                  "    pm.expect(body.token_type).to.eql('Bearer');",
                  "    pm.expect(body.scope).to.be.a('string');",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "auth": {
              "type": "basic",
              "basic": [
                {
                  "key": "username",
                  "value": "{{clientId}}",
                  "type": "string"
                },
                {
                  "key": "password",
                  "value": "{{clientSecret}}",
                  "type": "string"
                }
              ]
            },
            "header": [
              {
                "key": "Content-Type",
                "value": "application/x-www-form-urlencoded"
              }
            ],
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                {
                  "key": "grant_type",
                  "value": "refresh_token"
                },
                {
                  "key": "refresh_token",
                  "value": "{{refreshToken}}"
                },
                {
                  "key": "scope",
                  "value": "{{scope}}",
                  "description": "Optional. May only repeat or narrow the grant. Disable to keep the full grant."
                }
              ]
            },
            "url": {
              "raw": "{{baseUrl}}/oauth/token",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "oauth",
                "token"
              ]
            },
            "description": "Every successful refresh kills the refresh token you sent and returns a new one; the test script stores it. Never reuse the old one, and never run two refreshes at once: reuse revokes the whole grant (`invalid_grant`) and is a security event, not something to retry."
          },
          "response": []
        },
        {
          "name": "4. Revoke (disconnect)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('revoke answers 200 with an empty body', () => {",
                  "  pm.response.to.have.status(200);",
                  "  pm.expect(pm.response.text()).to.eql('');",
                  "});",
                  "if (pm.response.code === 200) {",
                  "  // Revoking a refresh token ends the whole grant.",
                  "  pm.collectionVariables.set('accessToken', '');",
                  "  pm.collectionVariables.set('refreshToken', '');",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "auth": {
              "type": "basic",
              "basic": [
                {
                  "key": "username",
                  "value": "{{clientId}}",
                  "type": "string"
                },
                {
                  "key": "password",
                  "value": "{{clientSecret}}",
                  "type": "string"
                }
              ]
            },
            "header": [
              {
                "key": "Content-Type",
                "value": "application/x-www-form-urlencoded"
              }
            ],
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                {
                  "key": "token",
                  "value": "{{refreshToken}}",
                  "description": "A refresh token ends the whole grant; an access token ends only itself."
                },
                {
                  "key": "token_type_hint",
                  "value": "refresh_token"
                }
              ]
            },
            "url": {
              "raw": "{{baseUrl}}/oauth/revoke",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "oauth",
                "revoke"
              ]
            },
            "description": "**Revoke a refresh or access token (RFC 7009)**\n\nBody is `application/x-www-form-urlencoded`. Client authentication as for `/oauth/token`. Revoking a refresh token revokes the grant and every token of it. Always `200`."
          },
          "response": []
        },
        {
          "name": "Call /v1 with the access token (example: list tenants)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/tenants",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "tenants"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "offset",
                  "value": "",
                  "disabled": true
                }
              ]
            },
            "description": "**List the calling project’s tenants**",
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        }
      ]
    },
    {
      "name": "Operations",
      "item": [
        {
          "name": "Component-level health",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/health",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "health"
              ]
            },
            "description": "**Component-level health**\n\nReports API, data store, queue, email provider, inbound event transport and rate limiter reachability independently. 200 for ok and degraded, 503 for unhealthy.",
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        }
      ]
    }
  ]
}
