An API that does not surprise you.
One base URL, one key, two response shapes. Everything the console does, your code does too.
curl "https://nodium.io/api/v1/conversations?limit=20" \ -H "Authorization: Bearer $NODIUM_KEY" -iX-RateLimit-Limit: 600X-RateLimit-Remaining: 599{ "data": { "items": [ { "id": "c81d…", "status": "open" } ], "nextCursor": "MjAyNi0wOS0yOFQw…", "hasMore": true }}The basics
What to know before the first call.
A versioned base URL
Every route lives under /api/v1. A published version does not change shape.
https://nodium.io/api/v1One key per account
The key acts as itself and opens the whole API: on your own company, on the client it is pinned to, or on the one named by the Nodium-Tenant header. It is created in the console or through the API.
Authorization: Bearer nod_…One envelope
A successful response sits under data; a refusal, under error. Never both.
{ "data": … }Stable errors
Test the code, never the text. Every refusal carries the request id.
{ "error": { "code", "requestId" } }Cursor pagination
Send back nextCursor until it is null. Cursors are opaque.
?limit=100&cursor=…No double sends
Every send carries your idempotency key: replayed after a timeout, it leaves once.
"idempotencyKey": "order-1042"
Webhooks
Subscribe an https URL to events. Nodium signs every call and retries failures.
- Answer 2xx within 10 seconds, do the work afterwards.
- Six attempts: after 1 min, 5 min, 30 min, 2 h and 12 h.
- In the order of each conversation; ignore a duplicate thanks to the event id.
- Log, redelivery, test call, secret rotation and pause, through the API.
message.receivedmessage.importedmessage.echoedmessage.editedmessage.revokedmessage.sentmessage.deliveredmessage.readmessage.failedconversation.openedcontact.opted_incontact.opted_outtemplate.status_changedchannel.connectedchannel.connect_failedchannel.health_changedhistory.progress
import crypto from 'node:crypto'// X-Nodium-Signature: t=<seconds>,v1=<hex>[,v1=<hex>]export function verifyNodium(rawBody, header, secret) { const parts = header.split(',') const t = parts.find(p => p.startsWith('t='))?.slice(2) const sigs = parts.filter(p => p.startsWith('v1=')).map(p => p.slice(3)) if (!t || Math.abs(Date.now() / 1000 - Number(t)) > 300) return false const expected = crypto.createHmac('sha256', secret) .update(`${t}.${rawBody}`).digest('hex') return sigs.some(s => s.length === expected.length && crypto.timingSafeEqual(Buffer.from(s), Buffer.from(expected)))}One key for all your customers
One client per end customer, driven with a single key.
- One key acts on every client of your account; the Nodium-Tenant header names which one.
- Your customer id is enough to name a client: replaying the creation makes no duplicate.
- A hosted, single-use connect link brings your customer back to you once their number is connected.
- A webhook on your own company tells you about every client, and every time a number is connected.
# One account key adds and drives every clientcurl https://nodium.io/api/v1/tenants \ -H "Authorization: Bearer $NODIUM_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Optique Martin", "externalId": "cust_1042" }'# Then act for that client with the Nodium-Tenant headercurl https://nodium.io/api/v1/conversations \ -H "Authorization: Bearer $NODIUM_KEY" \ -H "Nodium-Tenant: cust_1042"Limits
Every response tells you what is left: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset.
600 / min
Calls per client
Above that, 429 and a Retry-After header in seconds.
600 / min
Replies per minute
On free-text sends (text and files), per key and per client. For volume, queue your sends.
6
Attempts per webhook
Then the delivery is marked failed; it can be redelivered on demand.
For your tools and your agents
Everything is drawn from the code, never copied by hand.
OpenAPI 3.1
The full description, to generate a client or import into your API tool.
openapi.jsonllms.txt
Every published route, in one file, for a language model.
llms.txtAGENTS.md
An Agent Skill: the routes, the signature and the pitfalls, for your coding assistant.
AGENTS.mdThe status page
The state of the API, machine-readable.
status.json
Your first key, in five minutes.
Create a free account, then a key in the developer portal.