Bolrach Push API

Send a notification to a person, not to a token: target a user and Bolrach Push fans out to every device they have registered.

Quick start

Base URL: https://api.bolrach.io/push/v1

NODE
import { BolrachPush } from '@bolrach/push';

const push = new BolrachPush({ apiKey: process.env.BOLRACH_PUSH_KEY });

// One send reaches every device the person has registered.
const message = await push.send({
  app_id: process.env.BOLRACH_PUSH_APP_ID,
  target: { type: 'user', id: 'user_8123' },
  title: 'Your order shipped',
  body: 'Track it in the app.',
  data: { order_id: '8123' },
  priority: 1,
}, { idempotencyKey: 'order-8123-shipped' });

console.log(message.id, message.status);
PYTHON
import os
from bolrach_push import BolrachPush

push = BolrachPush(api_key=os.environ["BOLRACH_PUSH_KEY"])

message = push.send(
    {
        "app_id": os.environ["BOLRACH_PUSH_APP_ID"],
        "target": {"type": "user", "id": "user_8123"},
        "title": "Your order shipped",
        "body": "Track it in the app.",
        "data": {"order_id": "8123"},
        "priority": 1,
    },
    idempotency_key="order-8123-shipped",
)
print(message["id"], message["status"])

Authentication

A server key (bt_live_…) from the API keys page of push.bolrach.io/console. Send it as a bearer token:

curl https://api.bolrach.io/push/v1/... \
  -H "authorization: Bearer $YOUR_KEY"
A server key belongs on a server. Anything shipped to a browser is public the moment it loads, so keys never go in front-end code. Where a call genuinely has to happen in a page, it uses a separate public key that can do only that one thing.

SDKs

Official clients for Node and Python. Both retry on 429 and 5xx with exponential backoff, obey Retry-After, accept idempotency keys on writes, and raise a typed BolrachPushError carrying status, code, message and requestId — so you branch on the failure instead of parsing a string.

NODE
npm install @bolrach/push
PYTHON
pip install bolrach-push

Every endpoint below lists its SDK method name. Anything not yet wrapped is still reachable without waiting for a release:

await client.request('GET', '/some/new/endpoint', { query: { days: 7 } });   // node
client.request('GET', '/some/new/endpoint', query={'days': 7})               # python

Errors and retries

Errors are JSON: {"error": {"code": "...", "message": "..."}}. The HTTP status carries the category, the code carries the specific reason.

STATUSCODEMEANING
400bad_requestThe body or a query parameter was missing or malformed. The message names the field.
401unauthorizedNo key, or a key this API does not recognise.
403forbiddenA valid key without the scope this call needs, or a key not bound to the resource.
404not_foundNo such resource — or one that belongs to somebody else. The two are deliberately indistinguishable.
409conflictThe same idempotency key was reused with a different body.
429rate_limitedToo many calls. Retry after the seconds in the Retry-After header — the SDKs already do.
5xxserver_errorSomething failed on our side. Safe to retry; the SDKs retry twice with backoff.
Retry safely. Every write accepts an Idempotency-Key header. Reusing a key returns the original result rather than applying the change twice — so a timeout you never saw the answer to is safe to repeat.

Endpoints

POST /push/v1/messages send() · send()

Send a message. Pass idempotency_key to make a retry safe.

Idempotent. Pass an idempotency key and a retry returns the original result instead of applying the change twice.

NODE
await client.send({ /* body */ });
PYTHON
client.send({...})
GET /push/v1/messages/{message_id} getMessage() · get_message()

Status of one message.

NODE
await client.getMessage(messageId);
PYTHON
client.get_message(message_id)
GET /push/v1/messages/{message_id}/trace messageTrace() · message_trace()

Every delivery attempt for one message, with provider outcomes.

NODE
await client.messageTrace(messageId);
PYTHON
client.message_trace(message_id)
POST /push/v1/installations registerInstallation() · register_installation()

Register a device. Returns the installation id, plus stream credentials when push_provider is "sse".

NODE
await client.registerInstallation({ /* body */ });
PYTHON
client.register_installation({...})
POST /push/v1/users registerUser() · register_user()

Map your own user id onto a Bolrach Push user.

NODE
await client.registerUser({ /* body */ });
PYTHON
client.register_user({...})
POST /push/v1/receipts receipt() · receipt()

Report what happened on the device: delivered, displayed, opened.

NODE
await client.receipt({ /* body */ });
PYTHON
client.receipt({...})
GET /push/v1/apps listApps() · list_apps()

Apps in this workspace.

NODE
await client.listApps();
PYTHON
client.list_apps()
POST /push/v1/apps createApp() · create_app()

Create an app.

NODE
await client.createApp({ /* body */ });
PYTHON
client.create_app({...})
GET /push/v1/apps/{app_id} getApp() · get_app()

One app.

NODE
await client.getApp(appId);
PYTHON
client.get_app(app_id)
GET /push/v1/apps/{app_id}/stats appStats() · app_stats()

Device counts and 24-hour send volume.

NODE
await client.appStats(appId);
PYTHON
client.app_stats(app_id)
GET /push/v1/apps/{app_id}/insights appInsights() · app_insights()

Delivery insights: totals, daily series, and breakdowns by status, provider, platform and failure reason.

Query parameters: days.

NODE
await client.appInsights(appId);
PYTHON
client.app_insights(app_id)
GET /push/v1/apps/{app_id}/installations listInstallations() · list_installations()

Devices registered for an app.

NODE
await client.listInstallations(appId);
PYTHON
client.list_installations(app_id)
GET /push/v1/apps/{app_id}/deliveries listDeliveries() · list_deliveries()

Recent deliveries for an app.

NODE
await client.listDeliveries(appId);
PYTHON
client.list_deliveries(app_id)
GET /push/v1/webhooks listWebhooks() · list_webhooks()

Webhook endpoints.

NODE
await client.listWebhooks();
PYTHON
client.list_webhooks()
POST /push/v1/webhooks createWebhook() · create_webhook()

Add a webhook endpoint.

NODE
await client.createWebhook({ /* body */ });
PYTHON
client.create_webhook({...})
POST /push/v1/webhooks/{webhook_id}/test testWebhook() · test_webhook()

Fire a signed test event at one endpoint and report what it answered - status, latency and body head.

NODE
await client.testWebhook(webhookId);
PYTHON
client.test_webhook(webhook_id)
GET /push/v1/templates listTemplates() · list_templates()

Message templates.

NODE
await client.listTemplates();
PYTHON
client.list_templates()
POST /push/v1/templates createTemplate() · create_template()

Create a message template.

NODE
await client.createTemplate({ /* body */ });
PYTHON
client.create_template({...})