Overview

Authenticating an HTTP call, what capability each endpoint checks, CORS, and the error codes.

Two ways to authenticate

what you holdheader
an API key — your serverBasic base64("secret.keyId")
a token — a browser, or a server acting as one clientBearer <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.

endpointcapability
POST /api/channels/:channel/publishpublish
GET /api/channels/:channel/historyhistory
GET /api/channels/:channel/presencepresence
GET /api/channels/:channel/presence/historyhistory
/api/queues and everything under itamqp-subscribe
GET /api/channels, /api/connections, /api/presenceauthenticated only
GET /api/metrics/seriesauthenticated 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

statusmeans
401missing, malformed, unknown or revoked credential
403authenticated, but the capability does not cover this channel and operation
503the 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/health
ok

Plain 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.

On this page