Integrate with Reach
Reach is a multi-tenant transactional email API. Your customers are tenants: create one per customer, verify their domain, then send as them.
Two ways to authenticate
Both reach the same /v1 API. Both are scoped to exactly one organisation and project. Pick by who holds the credential.
API key
For your own backend sending on behalf of your own customers. You create a project API key (rk_…) in the console. It holds every scope and does not expire until you revoke it.
OAuth
For a platform connecting its customers' Reach organisations. An admin approves a set of scopes on the consent page; you receive a 1-hour access token (ro_at_…) and a rotating refresh token (ro_rt_…). Authorization Code + PKCE (S256), confidential clients only.
/v1, an API key or OAuth token is never accepted on /account/*, and a request that carries two credentials is rejected, not resolved by precedence.Base URLs
Two public hosts. https://api.tryreach.email serves /v1, /oauth/token, /oauth/revoke, /health and /openapi.json. https://tryreach.email is the console and serves the OAuth consent page. The console's /account endpoints are internal to the console and are not on the public API host. The examples use these hosts directly; keep them in environment variables or settings in your own code.
# Keep the base URLs in configuration rather than scattering them through your code.
export REACH_API_URL="https://api.tryreach.email" # where /v1 and /oauth/* live
export REACH_CONSOLE_URL="https://tryreach.email" # where the consent page lives (OAuth only)
# API key integration (your own backend)
export REACH_API_KEY="rk_..."
# OAuth integration (a platform connecting its customers' organisations)
export REACH_CLIENT_ID="rc_..."
export REACH_CLIENT_SECRET="..."Conventions that hold everywhere
| Rule | Detail |
|---|---|
Authorization: Bearer … | Exactly one credential on /v1: an rk_ key or an ro_at_ access token. |
| Errors | Always { "error": { "code", "message", "details?", "requestId" } }. Branch on code, never on the message. Quote requestId to support. |
| Lists | Return { "rows", "total" }, scoped to the caller's project, with limit and offset. |
| Unknown query parameter | A 422, never ignored. A filter that was dropped and one that was applied must never look the same. |
| Other project's resource | 404, never 403. |
| JSON in, JSON out | Except /oauth/token and /oauth/revoke, which take application/x-www-form-urlencoded (RFC 6749). |
Postman collection
A Postman v2.1 collection generated from the same OpenAPI document as these docs: every /v1 operation grouped by tag, the OAuth flow (PKCE pre-request script, token requests that store the tokens for you), and the health check (the console-internal /account endpoints are not included). Import it, then set the baseUrl, consoleUrl and apiKey collection variables. It holds no secrets; baseUrl and consoleUrl default to the hosts above.
Where to go next
- Quickstart with an API key from a new key to a verified domain, a sent email and signed webhooks.
- OAuth integration authorize, exchange, refresh, narrow, revoke.
- Errors and rate limits.