Getting started
Authentication
Authenticate Ray API requests with a workspace API key as a bearer token, and learn the read and write scopes and auth errors.
Every Ray API request authenticates with a workspace API key sent as a bearer token. The API base URL is https://ray-api.gege.mn.
Authorization: Bearer ck_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
API keys
- Create keys in the dashboard at https://ray.gege.mn under API keys.
- A key is the prefix
ck_live_followed by 32 URL-safe base64 characters (40 characters total). - Ray stores only an HMAC hash of the key. The full key is shown once, at creation. If you lose it, create a new key and revoke the old one.
- The dashboard lists each key by name, scopes and its first 12 characters, with a last-used time.
- Revoking a key takes effect immediately: requests with it get
401. - Keys of an archived workspace stop authenticating.
Keep keys server-side. Read them from an environment variable (these docs use RAY_API_KEY), never ship them in a browser or mobile app.
Scopes
A key carries one or both scopes.
| Scope | Grants |
|---|---|
read |
All GET endpoints: /me, /channels, /templates, /sends/{sendId}, /notifications, /clicks, /tenant-webhooks, /billing/subscription. |
write |
Everything that creates or changes state: POST /send, template create, update, publish, test-send, archive and unarchive, and delivery webhook create, update and delete. |
GET endpoints accept any valid key. Mutating endpoints check for write; a key without it gets 400 with "error": "validation_error" and "message": "write scope required".
A typical setup is one read+write key for your backend and, if a separate service only reads feeds or send status, a read-only key for it.
Workspace scoping
Every request is scoped to the key's workspace. Ids from another workspace behave as if they don't exist: you get 404, never 403.
Verify a key
GET /me returns the context a key resolves to.
curl https://ray-api.gege.mn/me \
-H "Authorization: Bearer $RAY_API_KEY"
{
"tenantId": "0f9e8d7c-6b5a-4c3d-9e2f-1a0b9c8d7e6f",
"apiKeyId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"scopes": ["read", "write"]
}
const res = await fetch("https://ray-api.gege.mn/me", {
headers: { Authorization: `Bearer ${process.env.RAY_API_KEY}` },
});
if (res.status === 401) throw new Error("Ray API key is missing, malformed or revoked");
const me = (await res.json()) as { tenantId: string; apiKeyId: string; scopes: string[] };
Authentication errors
All errors use the JSON envelope { "error": "<code>", "message": "<text>" }.
| Status | error |
message |
Cause |
|---|---|---|---|
401 |
unauthorized |
missing or invalid Authorization header |
No Authorization header, or it doesn't start with Bearer . |
401 |
unauthorized |
invalid api key |
Wrong format, unknown, revoked, or the workspace is archived. |
400 |
validation_error |
write scope required |
A mutating endpoint called with a key that lacks write. |
429 |
rate_limit_exceeded |
rate limit exceeded |
Too many requests. See Rate limits. |
A per-IP rate limit applies before authentication, so repeated bad keys from one address are throttled.
See Errors for the full list.