Overview
Authenticating an HTTP call, what capability each endpoint checks, CORS, and the error codes.
Two ways to authenticate
| what you hold | header |
|---|---|
| an API key — your server | Basic base64("secret.keyId") |
| a token — a browser, or a server acting as one client | Bearer <blackevin-jwt> |
The Basic value is the key verbatim, base64'd. Not user:password. Ably
writes base64("keyName:keySecret") with a colon; a Blackevin key already carries
both halves in secret.keyId, so there is nothing to join. The colon form is
rejected as an invalid credential — a 401, not a 500 and not a silent success.
Anything else — Digest, a bare token with no scheme, no header at all — is a
401. GET /health is the one open endpoint.
curl
KEY='ck_live_7BZ882hK4pQm1x.k1'
AUTH="Basic $(printf '%s' "$KEY" | base64)"
BLACKEVIN='https://api.blackevin.com'curl -X POST "$BLACKEVIN/api/channels/orders%3Anew/publish" \
-H "authorization: $AUTH" \
-H "content-type: application/json" \
-d '{"name":"order.created","data":{"id":7}}'curl "$BLACKEVIN/api/channels/orders%3Anew/history?limit=50" -H "authorization: $AUTH"curl "$BLACKEVIN/api/channels/ops/presence" -H "authorization: $AUTH"Channel names are URL-encoded: : is %3A. A channel is a path segment, so
an unencoded colon or slash silently addresses a different channel — no error,
just the wrong data.
With a token instead:
curl "$BLACKEVIN/api/channels/ops/presence" -H "authorization: Bearer $TOKEN"From the SDK
import * as Blackevin from '@blackevin/client';
const rest = new Blackevin.Rest({ key: process.env.BLACKEVIN_KEY });
const asClient = new Blackevin.Rest({ token });
await rest.channels.get('orders:new').publish('order.created', { id: 7 });
await rest.channels.get('orders:new').history({ limit: 50 });
await rest.channels.get('ops').presence.get();Encoding is handled — pass the channel name as you wrote it.
Blackevin.Realtime attaches the same header for its own REST calls, derived from
key / token / authCallback. Its presence.get() is the exception: the
socket is already open and already authenticated, so it asks over that instead.
What each endpoint checks
Channel endpoints check a capability on that channel, so a
token scoped to orders:* => [subscribe] can read history there and publish
nowhere.
| endpoint | capability |
|---|---|
POST /api/channels/:channel/publish | publish |
GET /api/channels/:channel/history | history |
GET /api/channels/:channel/presence | presence |
GET /api/channels/:channel/presence/history | history |
/api/queues and everything under it | amqp-subscribe |
GET /api/channels, /api/connections, /api/presence | authenticated only |
GET /api/metrics/series | authenticated only |
GET /api/{webhooks,amqp,lambda,sns} | authenticated only — read-only, with secrets redacted |
POST /keys/:keyName/requestToken takes no Authorization header: the
TokenRequest is itself signed, and that signature is the credential.
There are no write endpoints for integration config. The console is the only author — the node reads that configuration from the database rather than being pushed it.
Errors
| status | means |
|---|---|
| 401 | missing, malformed, unknown or revoked credential |
| 403 | authenticated, but the capability does not cover this channel and operation |
| 503 | the feature is not wired on this node — e.g. publish with no gateway |
The body is JSON: { "error": "…" }.
401 is deliberately the same for "malformed" and "wrong". Whether the format parsed is not something a caller should learn from the response.
CORS
Every response carries access-control-allow-origin: *, and OPTIONS is
answered 204 with the same headers before any credential is read — a browser
preflight sends no Authorization at all, so authenticating it would reject every
cross-origin call before the real one was made.
* is safe here and deliberate: this API authenticates by the Authorization
header and never by a cookie, so there is no ambient credential a hostile page
could ride. An origin allowlist would need configuring per customer and per
environment, and would fail closed the first time someone added a preview
deployment.
The headers are on error responses too. Without them a 401 reaches the browser as an opaque CORS failure and the app never learns the key was wrong — the most confusing way possible to surface a bad credential.
Health
curl https://api.blackevin.com/healthokPlain text, 200, no auth. It is deliberately outside the JSON API router so it answers even when that router cannot.
Tenancy
With auth on, every channel name is scoped to the calling key's account before it reaches a store. There is no way to ask for another account's channel — the scope comes from the credential, never from the request.