API getting started
The REST API lives under /api/v1. The web client uses the same endpoints as integrators and no private ones, so anything the admin console can do is available through the API.
The reference is generated from the code: the OpenAPI 3.1 document at /api/v1/openapi.json and the Scalar UI at /api/docs. By default both require an authenticated caller (http.docs can be authenticated, public or disabled). Every operation lists the permission it needs as its scope.
Authentication
There are two kinds of credential. Both are sent the same way:
Authorization: Bearer <access token or API key>
Access token (users)
Sign in with e-mail and password:
curl -sS https://pbx.example.com/api/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"ada@example.com","password":"...","client":"my script"}'
The response:
{
"access_token": "eyJhbGciOiJFZERTQSIs...",
"token_type": "Bearer",
"expires_in": 900,
"refresh_token": "mpbx_rt_...",
"refresh_expires_at": 1790000000,
"session_id": "0199b5a2-6c1e-7d3a-9f00-3c8e1b2a4d55"
}
- The access token is a short-lived EdDSA JWT (15 minutes by default).
- If the account has two-factor authentication enabled and you sent no
totp_code, you get401with codeMPBX-AUTH-TOTP-REQUIREDafter a correct password. Repeat the request with"totp_code":"123456". - Refresh with
POST /api/v1/auth/refreshand{"refresh_token":"..."}. Each refresh token works once. Presenting a spent one ends the whole session (MPBX-AUTH-REFRESH-REUSED), so run at most one refresh at a time per session and always store the newest token. POST /api/v1/auth/logoutends the session.- Browsers can ask for
"refresh_delivery":"cookie", which keeps the refresh token in an HttpOnly cookie instead of the response body.
API keys (integrations)
An API key has the form mpbx_k1_<company>_<id>_<secret> and is sent directly as the bearer token. There is no key-to-token exchange. A key acts for the user who created it and holds only the scopes it was issued, intersected with that user's current permissions, so demoting the user shrinks the key at once. Keys cannot create keys. The full key is shown once at creation.
Who am I
GET /api/v1/me returns the caller, the company, and every permission held with its reach (the whole company or a list of departments). Use it to check what a credential can do.
Company selection
The company always comes from the credential, never from the URL or the body. A system administrator (a System Owner or System Admin, with a system-scope token) selects the company to work in with the X-Mpbx-Tenant: <company id> header. That action is audited.
Conventions
- Pagination:
?limit=50&cursor=....limitis 1 to 200 (default 50), an out-of-range value is a 400. The response is{"items":[...],"next_cursor":"..."}, andnext_cursoris absent on the last page. Cursors are bound to the filter and sort they were issued for. - Filtering and sorting:
filter=field:op:value, repeatable, with operatorseq ne gt gte lt lte contains starts_with in, andsort=-created_at,name. Only fields the endpoint declares are accepted. - Optimistic concurrency:
GETof one resource returns anETag.PATCH,PUTandDELETErequireIf-Matchwith it: missing is428(MPBX-PRECONDITION-REQUIRED), stale is412(MPBX-PRECONDITION-FAILED). PATCH: only the fields present change.nullmeans unchanged, exactly like an absent field. To clear a text field send an empty string.- Idempotency: send
Idempotency-Key(1 to 255 visible ASCII characters, a UUID is fine) on aPOSTto make it safe to retry. A repeat by the same caller with the same body replays the stored2xxresponse withIdempotent-Replayed: true. The same key with a different request is422(MPBX-IDEMPOTENCY-KEY-REUSED). The same key while the first request still runs is409(MPBX-IDEMPOTENCY-IN-PROGRESS). Routes that carry secrets (all/auth/*routes, creating users, password resets, certificate uploads) ignore the header.
Errors
Every error is application/problem+json (RFC 9457):
{
"type": "https://mpbx.example/errors/MPBX-NOT-FOUND",
"title": "Not Found",
"status": 404,
"detail": "extension 1001 does not exist",
"code": "MPBX-NOT-FOUND",
"errors": [{"field": "filter", "message": "unknown field `colour`"}]
}
code is the stable machine-readable value. Branch on code, not on detail. errors appears only when fields are invalid. The type URI uses a placeholder host (mpbx.example) until the real domain is decided. Retry-After is sent with 429 and 503. Internal errors never include their message. See Error codes for the codes of every area.
Examples you will meet first:
| Code | Status | Meaning |
|---|---|---|
MPBX-AUTH-INVALID-CREDENTIALS | 401 | Login failed. One code for every reason, so it does not reveal which accounts exist. |
MPBX-AUTH-TOKEN-EXPIRED | 401 | The access token expired. Refresh it. |
MPBX-APIKEY-INVALID | 401 | The API key is unknown, revoked, expired or wrong. |
MPBX-RBAC-FORBIDDEN | 403 | You lack the permission the operation needs. GET /api/v1/me lists what you hold. |
MPBX-RATE-LIMITED | 429 | Too many sign-in, refresh or TOTP attempts. Wait for Retry-After. |
MPBX-INVALID-QUERY, MPBX-INVALID-CURSOR | 400 | A filter, sort, limit or cursor is invalid. |
Rate limits
Authentication routes are rate limited per client network and per minute: 10 logins, 60 refreshes and 10 TOTP calls by default (http.login_rate_per_min, http.refresh_rate_per_min, http.totp_rate_per_min). Accounts are also locked after repeated failed logins (5 failures in 15 minutes by default). A few other routes have their own limits, for example 10 click-to-call requests a minute per user, 60 call-control requests a minute per user and 120 chat messages a minute per user. Every limit answers 429 with MPBX-RATE-LIMITED and a Retry-After header. Most other routes have no per-key or per-user limit; an administrator can restrict who reaches the API by address (see Security and network access).
Events and integrations
- Outbound webhooks: MonoPbx can call your URL on events, with signed requests, retries and dead letters. See Webhooks.
- Events WebSocket: for a live stream in a client, see the events WebSocket.
- Click-to-call:
POST /api/v1/calls/click-to-callrings one or all of the signed-in user's devices and, when one answers, dials the destination as that user through the dial plan. Send anIdempotency-Key.
What is not available yet
- Client SDKs are not published. TypeScript and Dart clients are generated from the OpenAPI document inside the repository.