Skip to content
Draft — not yet reviewedThis article is a starter draft. The steps may not match the product exactly yet.

REST API

The Moja REST API reads and writes the records you otherwise manage in the portal: campaigns, targets, buyers, publishers, routing plans, RTB formulas, numbers and number pools, webhooks, custom tags, audit logs, and reporting.

It is a fifth integration surface, separate from the four documented elsewhere in this section. RTB, webhooks, postbacks, and DNI move call traffic and call data. The REST API manages configuration and reporting. They use different hosts and different authentication, so do not carry credentials or conventions from one to the other.

Base URL https://external-api.moja-ai.com
Path prefix /api/v1 — every operation is under it
Authentication X-API-Key header, or Authorization: Bearer <api_key>
Format JSON request and response bodies
Operations 90 across 55 paths (47 GET, 20 POST, 12 PATCH, 10 DELETE, 1 PUT)
Pagination limit / offset query parameters
Spec OpenAPI 3.1.0 — /api/v1/openapi.json

List your campaigns. If this returns 200 with a data array, your key works and everything else on this page will too.

Terminal window
curl https://external-api.moja-ai.com/api/v1/campaigns \
-H 'X-API-Key: YOUR_API_KEY'

Every page in the endpoint reference carries generated snippets for cURL, JavaScript, Go, and C#, built from that operation’s own parameters and schema.

Two schemes, both carrying the same organization API key. Pick one — they are equivalent, and you do not send both.

Scheme Header
API key X-API-Key: <api_key>
Bearer Authorization: Bearer <api_key>

The key is resolved to an organization on every request, and everything you can read or write is scoped to that organization. There is no per-user identity in v1 and no OAuth flow.

A missing or invalid key returns 401 with the standard error body:

No key supplied
{ "detail": { "code": "moja_api_unauthorized", "message": "A valid API key is required." } }
Key rejected
{ "detail": { "code": "moja_api_unauthorized", "message": "The provided API key is invalid or has been revoked." } }

List endpoints take limit and offset query parameters.

Parameter Default Range
limit 20 1–200 on standard list endpoints; higher on CSV exports
offset 0 0 or greater

Two response envelopes are in use. Check the endpoint’s response schema in the reference rather than assuming.

Most list endpoints return a meta object:

{
"data": [ /* … */ ],
"meta": {
"limit": 20,
"offset": 0,
"count": 20,
"total": 137,
"has_more": true,
"next_offset": 20,
"previous_offset": null
}
}

Page by following meta.next_offset until meta.has_more is false. Do not compute the next offset yourself — that is what the field is for.

Audit logs and number pools return the counts flat on the response instead:

{ "data": [ /* … */ ], "total": 137, "limit": 20, "offset": 0 }

There is no has_more on this shape, so stop when offset + len(data) >= total.

Results are not guaranteed to be stable across pages while records are being created. For an exact snapshot of a busy resource, prefer the CSV export operations where they exist.

Every error carries the same body — a detail object with a machine-readable code and a human-readable message:

{ "detail": { "code": "moja_api_unauthorized", "message": "A valid API key is required." } }

Branch on detail.code, not on detail.message. Messages are written for humans and will be reworded.

Status Meaning What to do
400 Bad Request — the request was malformed or the values conflict with each other Fix the request. Retrying unchanged will not help.
401 Unauthorized — missing, invalid, or revoked API key Check the header. Do not retry with the same key.
404 Not Found — no record with that id in your organization Note that a record belonging to another organization is a 404, not a 403.
409 Conflict — the write collides with existing state, such as a duplicate name Read the current record and reconcile.
422 Validation Error — the body or query failed schema validation See the field-level shape below.
429 Too Many Requests — rate limit exceeded Back off and retry. See Rate limits.
500 Server error Retry with backoff. If it persists, open a support conversation with the request timestamp.
503 Upstream dependency unavailable — returned by the call-life, webhook-request, and RTB life-log endpoints Retry with backoff.

422 is the exception to the shape above. It comes from schema validation and returns an array, one entry per failing field:

{
"detail": [
{
"loc": ["body", "payout_amount"],
"msg": "Input should be a valid number",
"type": "float_parsing"
}
]
}

loc is the path to the offending field — ["body", "payout_amount"] means the payout_amount key of the JSON body, ["query", "limit"] means the limit query parameter.

Rate limits are applied per organization, after the API key authenticates. Every response carries the current window state:

Header Meaning
X-RateLimit-Limit Requests permitted in the window
X-RateLimit-Remaining Requests left in the current window
X-RateLimit-Reset When the window resets
Retry-After Sent on 429 — how long to wait before retrying

Read X-RateLimit-Remaining and slow down before you hit zero rather than after. On a 429, honour Retry-After; retrying immediately extends the block rather than clearing it.

A successful create returns 201 Created with the new record in the body, including its generated id. Read the id from the response rather than looking the record up again by name.

PATCH bodies are partial. Only the fields you send are updated; fields you omit are left alone.

Delete operations exist on buyers, publishers, custom tags, RTB groups, inbound RTB records, webhooks, and number pools. Pausing a target or an RTB formula is a DELETE against its /pause sub-resource — that resumes it. See the Pause group in the reference.

Phone numbers are E.164 — a leading +, country code, then the national number, no spaces or punctuation: +13105559876. This matches the rest of the Developers section.

Money is a JSON number, for example 150.00. Currency-formatted strings such as "$150.00" are rejected.

Identifiers are opaque. Store them as strings and do not parse or generate them.

Timestamps on records — created_at, updated_at, paused_until — are declared date-time in the spec, which is RFC 3339: 2026-08-28T14:05:00Z.

Interactive reference

A live, try-it console at /api/v1/docs, served by the API itself. Useful for firing a real request against a real key. It is not brand-themed and is not search-indexed — the endpoint reference here is the documentation.

Postman collection

/api/v1/postman_collection.json — all 90 requests, named and grouped, generated from the same spec. Import it into Postman and set the X-API-Key header once at the collection level.

OpenAPI document

/api/v1/openapi.json — OpenAPI 3.1.0. Generate a typed client from it: openapi-typescript for TypeScript, openapi-python-client for Python.

The other four surfaces

RTB, webhooks, postbacks, and DNI carry call traffic and call data. Different hosts, different authentication.

Every path is under /api/v1. The reference on this site is generated from a pinned copy of the spec, refreshed deliberately rather than on every API deploy — so what you read here matches a known, reviewed version of the API rather than whatever shipped an hour ago.

Include the request timestamp, the full request and response bodies, and the HTTP status in any support conversation. For reporting and call-log endpoints, include the call_log_id or request_id as well.