Référence de l'API
Cette page est générée à partir du code (backend/tools/generate_api_docs.py), ne pas la modifier à la main. Les descriptions proviennent des docstrings du backend et sont en anglais. Les concepts (authentification, multi-domaine, rôles, erreurs) sont décrits dans la documentation API ; la spécification OpenAPI interactive est servie sur /docs par l'API.
513 opérations, 42 groupes.
Sommaire
- Authentification (21 opérations)
- Second facteur et clés d'accès (13 opérations)
- Compte (préférences, suppression, inscription) (9 opérations)
- Organisations (18 opérations)
- Équipes (12 opérations)
- SMS et conversations (15 opérations)
- Notes de conversation (4 opérations)
- Numéros de téléphone (10 opérations)
- Achat de numéros (4 opérations)
- Portage de numéros (7 opérations)
- Portage de numéros (domaine) (7 opérations)
- Pool et fournisseur de numéros (13 opérations)
- Webhook du fournisseur de numéros (1 opérations)
- Fournisseurs SMS (7 opérations)
- Invitations (6 opérations)
- Utilisateurs (8 opérations)
- Clés API (5 opérations)
- Widget de site web (6 opérations)
- Intégrations (9 opérations)
- Widget de site web (public) (2 opérations)
- Notifications push (3 opérations)
- Voix (VoXoS) (63 opérations)
- Fonctions voix des numéros (15 opérations)
- Réglages voix du domaine (16 opérations)
- Voix (interne) (3 opérations)
- Métriques (7 opérations)
- Facturation (33 opérations)
- Essai gratuit, état du compte et réglages (6 opérations)
- Facturation : cartes, devis et reçus (14 opérations)
- Facturation d'un domaine (19 opérations)
- Notifications de paiement (2 opérations)
- Domaines (63 opérations)
- Système (21 opérations)
- Comptes fournisseurs SMS de la plateforme (11 opérations)
- Comptes fournisseurs SMS d'un domaine (12 opérations)
- Fournisseurs de numéros de la plateforme (5 opérations)
- Fournisseurs de la plateforme (7 opérations)
- Portage de numéros (plateforme) (5 opérations)
- Trunks voix de la plateforme (10 opérations)
- Profils et permissions (11 opérations)
- Fichiers (3 opérations)
- Webhooks entrants (7 opérations)
- Clés API : routes fermées
Authentification
Connexion, inscription par invitation, profil, OAuth (Google, Microsoft, Apple).
POST /auth/forgot-password
Request a password reset.
Rate limited to 5 per hour per e-mail address (per client address without an e-mail), whatever the X-API-Key or Authorization headers.
The body must be a JSON object (else 400 validation_error); an email that is missing or not a string gets 400 validation_error too.
The link goes through the SMTP server of the account's own domain and leads to that domain's first production hostname, whatever X-Frontend-Host: the single domain that holds every organization of the account (for a domain_admin, also every domain it administers, and only when the request comes from that domain). A super_admin, or an account spread over several domains or with none, gets the platform's server and FRONTEND_URL (SMTP_* environment variables); without a configured server nothing is sent. The answer is the same in every case.
Accès : public (aucune authentification)
Limite : 5 per hour
Corps de la requête
| Nom | Description |
|---|---|
email |
User email address. |
Réponse : Success message (always returns success to prevent email enumeration).
POST /auth/login
Login with email and password.
Rate limited by the FAILED sign-ins only (a good sign-in is never counted, so nobody can lock an account out with the owner's own successes, and the owner's sign-ins do not eat the budget): 30 failures per 15 minutes per e-mail address (RATELIMIT_LOGIN_ACCOUNT), then 429 rate_limited with Retry-After, even for the right password (the budget bounds the guesses). Where the client's address is known (not behind the TLS demultiplexer, see "Limits" in the API concepts) two more budgets apply: 10 failures per 15 minutes per e-mail address and client address, and 100 per 15 minutes per client address, so one address cannot lock an account out for the others nor spray many accounts. The answers to the 4th and later failures of an account are slowed (0.5 s, doubling, up to 4 s). A request refused with 400 validation_error is not counted. The bucket is the e-mail address, whatever the X-API-Key and Authorization headers.
The body must be a JSON object (else 400 validation_error); an email or a password that is missing or not a string gets 400 validation_error too.
Session length: the access token lasts 1 hour. The refresh token lasts 30 days (JWT_REFRESH_TOKEN_EXPIRES), renewed at each POST /auth/refresh. With remember false it lasts only 12 hours (JWT_REFRESH_TOKEN_EXPIRES_SHORT, JWT_REFRESH_SHORT_HOURS), still renewed at each refresh: the session ends after 12 hours without a refresh. The choice is kept by the rotation of the refresh token and by the new tokens of PUT /auth/me/password and PUT /auth/me/email.
Accès : public (aucune authentification)
Limite : 30 per 15 minutes; 10 per 15 minutes; 100 per 15 minutes
Corps de la requête
| Nom | Description |
|---|---|
email |
User email address. |
password |
User password. |
remember |
Optional boolean, default true (30-day refresh token, the behavior before this option existed). false opens a short session (12 hours). Another type gets 400 validation_error with field remember. |
any_domain |
Optional, default false. Sign-in from a client that does not know the domain of the account (the mobile app when nobody typed another domain): the answer then carries domain, the domain the account works in, whose host the client adopts as X-Frontend-Host. The domain of X-Frontend-Host when the account works there, otherwise the only domain the account works in (with several, the one of its current organization). A failed sign-in is answered exactly as without it (same 401, same budgets, same delay): it tells nothing about the existence of an account. Ignored when the request carries an X-API-Key header. Another type gets 400 validation_error with field any_domain. Second factor: when the account has one (authenticator app, passkey), the right password does not open the session; the answer is mfa_required with an mfa_token (5 minutes, single use) to send with a code or a passkey assertion to POST /auth/mfa/verify. When the account must have one (platform and domain admins, and organization admins where the domain asks) and its grace period is over, the answer is mfa_enrollment_required with an mfa_token (15 minutes) for the enrollment routes (header X-MFA-Token). Today the platform does not enforce it (MFA_ENFORCE off): such an account without a second factor gets its session with mfa_setup (deadline null), never mfa_enrollment_required; once enforced, mfa_setup carries the deadline of the grace period (14 days). |
Réponse : user: The user (as GET /auth/me). access_token: Access JWT (1 hour). Sent as Authorization: Bearer. refresh_token: Refresh JWT (30 days, 12 hours when remember is false), renewed at each POST /auth/refresh. domain: Only with any_domain: {id, name, host} of the domain to use (host: first public hostname, as domain_host of GET /api/v1/organizations/my), null when the account works in no domain and the host is unknown. mfa_setup: Only for an account that should have a second factor and has none (recommended while not enforced, or during the grace period): {required, reason (platform_admin, domain_admin or organization_admin), deadline (null while not enforced)}. mfa_required (boolean): Instead of the session: the account has a second factor; with mfa_token, methods (totp, webauthn, backup_code) and expires_in (seconds). mfa_enrollment_required (boolean): Instead of the session: the account must add a second factor first; with mfa_token, reason, deadline and expires_in. mfa_token: The token of the second step (never a session token).
POST /auth/logout
Log out: revoke the refresh token given in the Authorization header and the whole session it belongs to: the access tokens still alive are refused at once (401), those of the earlier rotations included. Other sessions of the user are not touched. (A session opened before this change keeps its access token until it expires, within the hour.)
Rate limited like POST /auth/refresh: per user of the refresh token.
Accès : JWT de rafraîchissement
Réponse : success: True.
GET /auth/me
Get current authenticated user.
The user object (also returned by login, registration, OAuth and the user routes) has these fields.
With the header X-Admin-Organization (super admin or domain admin, see "Administration context" in the API concepts) the user is shown working in that organization: organization_id is the administered one, team_id is null, the role is the platform role, and the response also carries admin_context and admin_organization_id. Nothing is written on the account.
In a session an administrator opened as this user (POST /api/v1/organizations/<org_id>/members/<user_id>/impersonate), the user also carries impersonated_by {id, name, email}: the administrator.
Accès : JWT d'accès
Réponse : user: mfa (second factor of the account: enabled, methods, totp, passkeys, backup_codes_left, required, reason, deadline, enforced), id, email, name, first_name, last_name, phone (null, or an object with country, number in E.164 and verified_at), timezone (IANA name, null until chosen), language (fr or en), role (agent, manager, admin, domain_admin or super_admin: a copy derived from the profiles and platform permissions, never what decides), permissions (former permission names given by the platform permissions or the profiles in the current organization, plus those granted to the account such as support), organization_id, team_id, oauth_provider, email_verified (false only for a free sign-up whose address was not proved yet), is_active, is_service, created_at, last_login, default_organization_id, default_organization_ids, domain_ids, platform_permissions (platform_admin, domain_admin, support: the permissions of the account), organization_permissions (the permissions of the user in their current organization, given by their profiles; every one for an administrator of the platform or of its domain), profile_ids (the profiles of their membership there), dnd (the do not disturb of the account: {active, until, set_at}, as GET /auth/me/dnd). Accounts created before first_name and last_name existed get them deduced from name. admin_context: true, only while the header X-Admin-Organization is honored. admin_organization_id: The administered organization, with admin_context.
PATCH /auth/me
Update current user profile.
The body must be a JSON object (else 400 validation_error). A field set to null is left unchanged, except timezone, phone and default_organization_id, which null clears.
Changing name alone rewrites first_name and last_name (first word, then the rest); changing first_name or last_name alone rewrites name; sending name with either keeps first_name and last_name as sent.
The profile cannot change the e-mail address (PUT /auth/me/email), grant permissions (an account never grants itself any) or verify a phone number (PUT /auth/me/phone). Deprecated: changing the password here (use PUT /auth/me/password); it now requires current_password and, like the new route, ends the other sessions and follows the same strength rule (400 validation_error with field password). The requests that carry a password are limited to 10 per hour per user (429 rate_limited), like PUT /auth/me/password: they are guesses at the current password for whoever holds a stolen token. The other updates keep the default limits.
Accès : JWT d'accès
Limite : 10 per hour
Corps de la requête
| Nom | Description |
|---|---|
name |
New display name (optional, a non-empty string). |
first_name |
First name (optional, at most 100 characters). |
last_name |
Last name (optional, at most 100 characters; may be empty). |
language |
Language preference (optional, fr or en). |
timezone |
IANA time zone such as America/Toronto (optional; null clears it). |
phone |
Phone number as an object with number and optional country (ISO code; for a North American number it is deduced from the area code, CA or US, when absent; other countries must be allowed by the domain and need the international format), or null to remove it. Normalized to E.164; verified_at is null until PUT /auth/me/phone proves it, and an unchanged number keeps its verified_at. Invalid number gets 400 invalid_phone. |
permissions |
Refused with 403 forbidden unless identical to the current list. |
password |
Deprecated, see PUT /auth/me/password (optional; the strength rule of that route). |
current_password |
Current password, required with password (401 invalid_password if wrong). |
default_organization_id |
Default organization for the current domain (optional, a string; null clears it). The organization must belong to the domain of the request (X-Frontend-Host) and the user must be a member of it, unless the account has the platform permission platform_admin. A field of another type, or a shorter password, gets 400 validation_error with field. |
Réponse : Updated user data. default_organization_id is the default for the current domain, default_organization_ids maps every domain to its default. Only when the password changed, also access_token and refresh_token of a new session (the current one stays signed in, the others end).
PUT /auth/me/email
Change the e-mail address (the sign-in identifier) of the current user.
Needs the proof that the user controls the new address: POST /auth/verifications with channel email, then POST /auth/verifications/<id>/confirm, whose verified_token is sent here (single use, valid 10 minutes). The current password is required too. Rate limited to 10 per hour per user. The body must be a JSON object.
The address is the key of the sign-in: every session ends (token_version is incremented) except the current one, which continues on the new tokens returned here. Pending password reset links are cancelled and the former address gets a notice (best effort).
Accès : JWT d'accès
Limite : 10 per hour
Corps de la requête
| Nom | Description |
|---|---|
email |
The new address. |
password |
The current password (not needed by an account without password). |
verified_token |
Token of a confirmed verification of this address (purpose email_change). |
Réponse : user: The user with the new address. access_token: New access token. refresh_token: New refresh token, to store in place of the old one.
Errors 400 validation_error (field email or password), 400 verification_required (no verified_token), 400 invalid_code (token unknown, already used or for another address), 400 code_expired (token older than 10 minutes), 401 invalid_password, 409 email_in_use, 429 rate_limited.
PUT /auth/me/password
Change the password of the current user.
Replaces PATCH /auth/me with a password, which is now deprecated. Rate limited to 10 per hour per user. The body must be a JSON object (else 400 validation_error).
The new password must have 8 to 128 characters (72 bytes in UTF-8 at most), mix at least 2 of lower case, upper case, digits and other characters, not be a very common password, not be the e-mail address or its local part and differ from the current password.
Every other session ends (token_version is incremented, so their access and refresh tokens are refused with 401). The current session continues on the new tokens returned here, which keep its remember choice: the client must store them in place of the old ones. An account without a password (OAuth sign-in only) does not send current_password and gets its first password this way.
Accès : JWT d'accès
Limite : 10 per hour
Corps de la requête
| Nom | Description |
|---|---|
current_password |
The password in use (old_password is accepted as a synonym). |
new_password |
The new password, see the rule above. |
Réponse : success: True. user: The user. access_token: New access token. refresh_token: New refresh token, to store in place of the old one.
Errors 400 validation_error with field new_password when the rule is not met, and with field current_password when it is missing. 401 invalid_password when current_password is wrong. 429 rate_limited.
PUT /auth/me/phone
Set the verified phone number of the current user.
Needs the proof that the user receives texts on this number: POST /auth/verifications with channel sms, then POST /auth/verifications/<id>/confirm, whose verified_token is sent here (single use, valid 10 minutes). The current password is required too. Rate limited to 10 per hour per user. The body must be a JSON object. The sessions are not affected.
Accès : JWT d'accès
Limite : 10 per hour
Corps de la requête
| Nom | Description |
|---|---|
country |
ISO code of the country (deduced from the area code, CA or US, for a North American number; other countries must be allowed by the domain and need the international format). |
number |
The number, E.164 or national digits; stored in E.164. |
verified_token |
Token of a confirmed verification of this number (purpose phone_change). |
password |
The current password (not needed by an account without password). |
Réponse : user: The user, with phone set to the number and verified_at to now.
Errors 400 invalid_phone (bad number, country not allowed), 400 validation_error (field password), 400 verification_required (no verified_token), 400 invalid_code (token unknown, already used or for another number), 400 code_expired (token older than 10 minutes), 401 invalid_password, 429 rate_limited.
GET /auth/oauth/apple
Get the Sign in with Apple authorization URL.
Public, never rate limited. Apple must be enabled for the domain of the request with every credential (client ID, team ID, key ID, private key: the domain's, else the platform's), else 400 oauth_error. Apple sends the browser back with a POST (form_post, required to receive the name and the address) to the API's POST /auth/oauth/apple/return, which sends it on to redirect_uri with code and state (and user, the first time). The state is signed by the server (10 minutes, single use, bound to Apple, this domain and redirect_uri) and its nonce must come back in Apple's id_token. The client keeps state and refuses a return whose state differs.
Accès : public (aucune authentification)
Limite : exemptée de la limite générale par adresse
Paramètres de requête
| Nom | Description |
|---|---|
redirect_uri |
The web page the browser ends on (an address of a known domain), which sends the code to POST /auth/oauth/apple/callback. |
Réponse : auth_url: The Apple consent URL to send the browser to. state: The state in that URL (to be checked at the callback; the callback requires it).
POST /auth/oauth/apple/callback
Handle the Sign in with Apple callback.
Rate limited by the default limits, counted per authorization code (per client address without a code), whatever the X-API-Key or Authorization headers. The body must be a JSON object (else 400 validation_error).
Apple must be enabled with every credential for the domain of the request, else 400 oauth_error and nothing is sent to Apple. The token exchanges with Apple are also limited to 30 per minute for the whole platform: beyond, 429 rate_limited. Each request to Apple waits at most 10 seconds.
The state of GET /auth/oauth/apple must come back with the code (else 400 oauth_error, before anything is sent to Apple); it works once. Apple's id_token is checked (signature with Apple's public keys, issuer, client ID, expiry, nonce of the state). The account is the one linked to the Apple sub; an existing account with the same address is linked (Apple vouches for the address, a private relay address included) unless it is linked to another Apple identity; an account linked to Google or Microsoft gets the Apple identity next to it. A new account is created only where sign-up is open on the domain. Then the second step, as POST /auth/login (mfa_required, mfa_enrollment_required). Every failure after the state is the same 400 oauth_error with a generic message.
Accès : public (aucune authentification)
Corps de la requête
| Nom | Description |
|---|---|
code |
Authorization code from Apple (query of the web page). |
state |
The state returned by GET /auth/oauth/apple. |
user |
The user value Apple sent the first time (its name), optional. |
any_domain |
As POST /auth/login. |
Réponse : user: The user (as GET /auth/me). access_token: Access JWT (1 hour). refresh_token: Refresh JWT (30 days). domain: Only with any_domain, as POST /auth/login. mfa_setup, mfa_required, mfa_enrollment_required, mfa_token: As POST /auth/login (second factor).
POST /auth/oauth/apple/return
Where Apple posts the browser back (form_post): sends it on to the web page of the state.
Public, never rate limited. The default limits counted it per client address, that is one counter for every Apple sign-in of the platform behind the TLS demultiplexer (a form body has no target the anonymous routes are keyed by). A per-state key would protect nothing: anyone can post made-up states, each one a new counter, and each costs a signature check only. The route sends nothing to Apple and spends nothing: the signed state (10 minutes, bound to Apple and to the redirect_uri given to GET /auth/oauth/apple, a page of a known domain) only lets it send the browser on to that page; the code is exchanged, once, by POST /auth/oauth/apple/callback, which has its limits. The form (code, state, user the first time, error) comes from Apple through the browser. Nothing is exchanged here: the state is checked (signature, Apple, not expired) without being spent, then 303 to the redirect_uri given to GET /auth/oauth/apple with code, state and user in the query (or error). A state that does not check gets 400 oauth_error.
Accès : public (aucune authentification)
Limite : exemptée de la limite générale par adresse
Corps de la requête
| Nom | Description |
|---|---|
code |
Authorization code (form field). |
state |
The state of GET /auth/oauth/apple (form field). |
user |
JSON with the name, the first time only (form field). |
error |
Apple's error, for example user_cancelled_authorize (form field). |
GET /auth/oauth/google
Get Google OAuth authorization URL.
Public, never rate limited. The URL carries a state made and signed by the server (valid 10 minutes, single use, bound to this provider, this domain and redirect_uri) and a PKCE challenge (S256) whose verifier only the server knows. The client keeps state (for example in sessionStorage) before redirecting the browser, refuses a return whose state differs, and sends it to POST /auth/oauth/google/callback.
Accès : public (aucune authentification)
Limite : exemptée de la limite générale par adresse
Paramètres de requête
| Nom | Description |
|---|---|
redirect_uri |
Callback URL after authorization. |
Réponse : auth_url: The Google consent URL to send the browser to. state: The state in that URL (to be checked at the callback; the callback requires it).
POST /auth/oauth/google/callback
Handle Google OAuth callback.
Rate limited by the default limits, counted per authorization code (per client address without a code), whatever the X-API-Key or Authorization headers. The body must be a JSON object and its fields strings (else 400 validation_error).
Google must be enabled, with a client ID, in the OAuth settings of the domain of the request, and the platform's Google credentials configured: else 400 oauth_error, and nothing is sent to Google. The token exchanges with Google are also limited to 30 per minute for the whole platform (only the exchanges actually sent count): beyond, 429 rate_limited. Each request to Google waits at most 10 seconds for an answer.
The state of GET /auth/oauth/google must come back with the code (else 400 oauth_error, before anything is sent to Google); it works once. The account is the one linked to the Google sub; an existing account with the same address is linked only when Google says the address is verified (email_verified) and the account is not linked to another Google identity. A new account is created only where sign-up is open on the domain (settings.signup.enabled), in that domain, as the admin of its own organization. Every failure after the state is the same 400 oauth_error with a generic message.
Accès : public (aucune authentification)
Corps de la requête
| Nom | Description |
|---|---|
code |
Authorization code from Google. |
state |
The state returned by GET /auth/oauth/google. |
redirect_uri |
Callback URL used in authorization (must be the one of the state). |
any_domain |
As POST /auth/login (the answer then carries domain). |
Réponse : user: The user (as GET /auth/me). access_token: Access JWT (1 hour). refresh_token: Refresh JWT (30 days). domain: Only with any_domain, as POST /auth/login. mfa_setup, mfa_required, mfa_enrollment_required, mfa_token: As POST /auth/login (second factor).
GET /auth/oauth/microsoft
Get Microsoft OAuth authorization URL.
Public, never rate limited. The URL carries a state made and signed by the server (valid 10 minutes, single use, bound to this provider, this domain and redirect_uri) and a PKCE challenge (S256) whose verifier only the server knows. The client keeps state (for example in sessionStorage) before redirecting the browser, refuses a return whose state differs, and sends it to POST /auth/oauth/microsoft/callback.
Accès : public (aucune authentification)
Limite : exemptée de la limite générale par adresse
Paramètres de requête
| Nom | Description |
|---|---|
redirect_uri |
Callback URL after authorization. |
Réponse : auth_url: The Microsoft consent URL to send the browser to. state: The state in that URL (to be checked at the callback; the callback requires it).
POST /auth/oauth/microsoft/callback
Handle Microsoft OAuth callback.
Rate limited by the default limits, counted per authorization code (per client address without a code), whatever the X-API-Key or Authorization headers. The body must be a JSON object and its fields strings (else 400 validation_error).
Microsoft must be enabled, with a client ID, in the OAuth settings of the domain of the request, and the platform's Microsoft credentials configured: else 400 oauth_error, and nothing is sent to Microsoft. The token exchanges with Microsoft are also limited to 30 per minute for the whole platform (only the exchanges actually sent count): beyond, 429 rate_limited. Each request to Microsoft waits at most 10 seconds for an answer.
The state of GET /auth/oauth/microsoft must come back with the code (else 400 oauth_error, before anything is sent to Microsoft); it works once. The account is the one linked to the Microsoft object id. Microsoft vouches for no mail address, so an existing account is never linked by address (it signs in with its password; an account created by Microsoft sign-in is marked oauth_email_verified false), and a pending domain admin invitation is not applied. A new account is created only where sign-up is open on the domain (settings.signup.enabled), in that domain. Every failure after the state is the same 400 oauth_error with a generic message.
Accès : public (aucune authentification)
Corps de la requête
| Nom | Description |
|---|---|
code |
Authorization code from Microsoft. |
state |
The state returned by GET /auth/oauth/microsoft. |
redirect_uri |
Callback URL used in authorization (must be the one of the state). |
any_domain |
As POST /auth/login (the answer then carries domain). |
Réponse : user: The user (as GET /auth/me). access_token: Access JWT (1 hour). refresh_token: Refresh JWT (30 days). domain: Only with any_domain, as POST /auth/login. mfa_setup, mfa_required, mfa_enrollment_required, mfa_token: As POST /auth/login (second factor).
GET /auth/oauth/providers
Get list of enabled OAuth providers for current domain.
Public, never rate limited (a cheap read of the domain settings, loaded by every login page).
A provider is offered only when its callback would accept a sign-in (the same rule, AuthService.oauth_provider_enabled): Google and Microsoft when the domain turns them on and the platform's client ID and secret for them are configured (sign-in always uses them); Apple when the domain enables it and its four credentials (client ID, team ID, key ID, private key: the domain's, each missing one taken from the platform's) are known. A button is never shown that would end in 400 oauth_error.
Accès : public (aucune authentification)
Limite : exemptée de la limite générale par adresse
Réponse : providers: {google, microsoft, apple}, true for each provider offered on the domain of the request.
POST /auth/refresh
Refresh the session.
Requires the refresh token in the Authorization header. The refresh token is rotated: the returned one replaces it (the old one stays valid 60 seconds, for tabs that refresh at once). The tokens of one sign-in form a family (claim fam, kept by the rotation): a refresh token already rotated and presented again after those 60 seconds is a copy, and the whole family is revoked (401 for it, the newest tokens and the access tokens included: the owner signs in again). A disabled account gets 401. Once the second factor is enforced on the platform (MFA_ENFORCE), a session that never proved one, of an account that must have one and whose grace period is over, is no longer renewed (401: sign in again, with the second factor); never today. The new tokens keep the claims of the session (remember, family, second factor proved, impersonation). Rate limited by the default limits, counted per user (the identity of the refresh token; per client address when the token is invalid).
Accès : JWT de rafraîchissement
Réponse : access_token: New access token. refresh_token: New refresh token, to store in place of the old one.
POST /auth/register
Register a new user with an invitation token.
Rate limited to 10 per hour per invitation token (per client address without a token), whatever the X-API-Key or Authorization headers.
The body must be a JSON object and its fields strings: anything else gets 400 validation_error.
Accès : public (aucune authentification)
Limite : 10 per hour
Corps de la requête
| Nom | Description |
|---|---|
token |
Invitation token (required). |
password |
User password. It follows the rule of PUT /auth/me/password (8 to 128 characters, at least 2 of lower case, upper case, digits and symbols, not a very common password, not the e-mail address): else 400 validation_error with field password. |
name |
User display name. |
language |
Preferred language (optional). |
Réponse : user: The new user (as GET /auth/me). access_token: Access JWT (1 hour). Sent as Authorization: Bearer. refresh_token: Refresh JWT, to renew the session with POST /auth/refresh.
Errors 402 plan_limit: the invitation gives the messages (messages.read or messages.send) and the organization's messaging plan has no room left for one more user (its place was kept while it was pending: this happens when the plan changed since); {limit (users), plan, upgrade, current}. No account is created and the invitation stays usable.
POST /auth/reset-password
Reset password using a reset token.
Rate limited to 10 per hour per reset token (per client address without a token), whatever the X-API-Key or Authorization headers.
The body must be a JSON object (else 400 validation_error); a token or a password that is missing or not a string gets 400 validation_error too. The new password follows the same rule as PUT /auth/me/password (8 to 128 characters, 2 character classes out of 4, not a very common password, not the e-mail address: 400 validation_error with field password).
A link works once and is taken atomically before the password changes: two simultaneous requests with the same link cannot both succeed (the second gets 400 validation_error "Invalid or expired reset token"). A successful reset ends every session of the account (token_version) and cancels the account's other pending links. An account created by a sign-in whose address the provider did not vouch for (oauth_email_verified false) loses its OAuth link: the reset proves the mailbox is the person's.
Accès : public (aucune authentification)
Limite : 10 per hour
Corps de la requête
| Nom | Description |
|---|---|
token |
Password reset token. |
password |
New password. It follows the rule of PUT /auth/me/password (8 to 128 characters, at least 2 of lower case, upper case, digits and symbols, not a very common password, not the e-mail address): else 400 validation_error with field password, and the token stays usable. |
Réponse : Success message.
POST /auth/verifications
Send a verification code to an e-mail address or a phone number.
The first step of PUT /auth/me/email and PUT /auth/me/phone. A code has 6 digits, is valid 10 minutes and is stored hashed. An e-mail goes through the SMTP server of the user's domain, a text message through the SMS provider of that domain, from a number of its pool that no customer holds (or the one named by the domain setting verification.sms_from); it is not charged to the organization.
Asking again for the same target resends a fresh code on the same verification (same id, attempts reset, previous code void). Limits per hour: 3 codes per user and target, 10 per user, 6 per mailbox whoever asked (the spellings of one mailbox count as one: the +tag of an address and the dots of a Gmail address); at least 60 seconds between two codes (resend_after); 30 requests per hour per user. Beyond, 429 rate_limited with retry_after seconds (and a Retry-After header). The body must be a JSON object.
Accès : JWT d'accès
Limite : 30 per hour
Corps de la requête
| Nom | Description |
|---|---|
channel |
email or sms (required). |
target |
The e-mail address, or the phone number (E.164 or national digits). |
purpose |
email_change (the default for email), phone_change (the default for sms) or email_verify (proof of the address the account has, for a free sign-up whose address is not proved yet: no target, or the account's own address; 409 already_verified once proved; confirming it grants the starting credit that waited); the purpose must match the channel. |
country |
ISO code of the country of an sms target (deduced from the area code, CA or US, for a North American number). |
Réponse : verification: Object with id, channel, purpose, target_masked (the target with its middle hidden), expires_in (seconds the code is valid) and resend_after (seconds before another code can be asked). Status 202.
Errors 400 validation_error (field channel, target or purpose), 400 invalid_phone, 429 rate_limited, 502 send_failed (the provider or the mail server refused), 503 email_unavailable (no SMTP server for the domain), 503 sms_unavailable (the domain has no SMS number or provider to send the code with).
POST /auth/verifications/<verification_id>/confirm
Confirm a verification code and get the verified token.
A code allows 5 attempts and 10 minutes; after that, ask for a new code with POST /auth/verifications. Rate limited to 60 per hour per user. The body must be a JSON object. A verification of another user is a 404.
Accès : JWT d'accès
Limite : 60 per hour
Paramètres de chemin
| Nom | Description |
|---|---|
verification_id |
Verification ID (the id returned by POST /auth/verifications). |
Corps de la requête
| Nom | Description |
|---|---|
code |
The 6 digits received. |
Réponse : verification: Object with id and verified (true). verified_token: Single-use token proving the target, valid 10 minutes, to send to PUT /auth/me/email or PUT /auth/me/phone (already spent for the purpose email_verify). expires_in: Seconds the verified_token is valid. email_verified: true, only for the purpose email_verify (the account is marked verified). credit_granted: The starting credit granted by this confirmation, only when there was one.
Errors 400 invalid_code with attempts_left (wrong code), 400 code_expired (code older than 10 minutes), 404 not_found, 409 already_verified (confirmed already, ask for a new code), 429 too_many_attempts (5 attempts used) or rate_limited.
Second facteur et clés d'accès
Deuxième étape de la connexion, application d'authentification (TOTP), codes de secours, clés d'accès (WebAuthn : empreinte, visage, clé de sécurité) et connexion sans mot de passe. Fermé aux clés API.
GET /auth/mfa
The second factor of the account: what is set up, and whether it must have one.
Session, or X-MFA-Token (enrollment).
Accès : public (aucune authentification)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Réponse : mfa: enabled, methods, totp, passkeys (count), backup_codes_left, required, reason (platform_admin, domain_admin, organization_admin), deadline, enforced. passkeys: The passkeys of the account: id, name, created_at, last_used_at, rp_id (the site it works on), backed_up, transports.
POST /auth/mfa/backup-codes
Replace the backup codes (the old ones stop working).
Session only, for an account with a second factor (else 409 mfa_not_enabled). Proof: a TOTP code or a backup code; the password for an account with passkeys only and no code left.
Accès : public (aucune authentification)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Limite : 20 per 15 minutes
Corps de la requête
| Nom | Description |
|---|---|
code |
A TOTP code or a backup code. |
password |
The password (only for an account with passkeys alone). |
Réponse : backup_codes: The 10 new codes, shown once.
POST /auth/mfa/totp/confirm
Confirm the authenticator app with a code it shows: it becomes a second factor.
Session, or X-MFA-Token (enrollment: the answer then opens the session). 400 invalid_code, 409 not_started. Rate limited: 20 failures per 15 minutes per account.
Accès : public (aucune authentification)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Limite : 20 per 15 minutes
Corps de la requête
| Nom | Description |
|---|---|
code |
The 6-digit code (required). |
Réponse : mfa: The status (as GET /auth/mfa). backup_codes: The 10 backup codes, shown once, when the account had none. access_token, refresh_token: The session, marked as having proved a second factor (new tokens of the current session; with X-MFA-Token, also user and domain as POST /auth/login).
POST /auth/mfa/totp/disable
Remove the authenticator app.
Session only. Proof: a code of the app or a backup code. 409 mfa_required when it is the last factor of an account that must have one.
Accès : public (aucune authentification)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Limite : 20 per 15 minutes
Corps de la requête
| Nom | Description |
|---|---|
code |
A TOTP code or a backup code (required). |
Réponse : mfa: The status after the change.
POST /auth/mfa/totp/setup
Start setting up an authenticator app (TOTP, 6 digits, 30 seconds).
Session, or X-MFA-Token (enrollment). A new secret each time, kept pending until confirmed; 409 already_enabled when the account has one. An account that already has a second factor adds another one only from a session that proved one (else 403 second_factor_required).
Accès : public (aucune authentification)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Réponse : secret: The secret in base32 (to type by hand). otpauth_uri: The otpauth:// URI. qr_svg: The QR code of that URI (data:image/svg+xml URI).
POST /auth/mfa/verify
Second step of a sign-in: prove the second factor and open the session.
Public. The mfa_token of POST /auth/login (or of an OAuth callback) answered mfa_required: 5 minutes, single use, refused (401 invalid_mfa_token) once the password of the account changed. Rate limited: 10 failures per 15 minutes per account (whatever the mfa_token), then 429.
Accès : public (aucune authentification)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Limite : 10 per 15 minutes
Corps de la requête
| Nom | Description |
|---|---|
mfa_token |
The token of mfa_required (required). |
method |
totp, backup_code or webauthn (required). |
code |
totp and backup_code: the 6-digit code of the authenticator app, or a backup code (single use). |
challenge_id |
webauthn: the challenge_id of POST /auth/mfa/webauthn/options. |
credential |
webauthn: the PublicKeyCredential of navigator.credentials.get (JSON). |
Réponse : user, access_token, refresh_token, domain: As POST /auth/login. The session carries the second factor. backup_codes_left: After a backup code, how many are left.
POST /auth/mfa/webauthn/options
Options of navigator.credentials.get for the second step of a sign-in (the passkeys of the account).
Public. Rate limited: 60 per 15 minutes per account of the mfa_token (the requests with a token that does not read share one counter of their own), never per address. The Origin of the request must be a known domain (the mobile app sends no Origin but X-Client-App: ios or android with X-Frontend-Host); else 400 origin_not_allowed.
Accès : public (aucune authentification)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Limite : 60 per 15 minutes
Corps de la requête
| Nom | Description |
|---|---|
mfa_token |
The token of mfa_required (required). |
Réponse : challenge_id: To send back with the credential (single use, 5 minutes). options: The PublicKeyCredentialRequestOptions (JSON, binary fields in base64url).
PATCH /auth/webauthn/credentials/<credential_id>
Rename a passkey of the account.
Session only.
Accès : public (aucune authentification)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
credential_id |
Id of the passkey (GET /auth/mfa). |
Corps de la requête
| Nom | Description |
|---|---|
name |
The new name (60 characters). |
Réponse : passkey: The passkey.
POST /auth/webauthn/credentials/<credential_id>/remove
Remove a passkey of the account.
Session only. Proof: a TOTP code or a backup code; the password for an account with passkeys only and no code left. 409 mfa_required when it is the last factor of an account that must have one.
Accès : public (aucune authentification)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Limite : 20 per 15 minutes
Paramètres de chemin
| Nom | Description |
|---|---|
credential_id |
Id of the passkey. |
Corps de la requête
| Nom | Description |
|---|---|
code |
A TOTP code or a backup code. |
password |
The password (only for an account with passkeys alone). |
Réponse : mfa: The status after the change.
POST /auth/webauthn/login
Sign in with a passkey (fingerprint, face, PIN of the device, security key).
Public. The authenticator must have verified the person (user verification): the sign-in counts as a multi-factor one, the session opens at once (claim mfa). Unknown passkey, passkey of another site or refused signature: 401. Rate limited, failures (401) only: 30 per 15 minutes per passkey presented (credential id), and, when the client's address is known, 30 per 15 minutes per address; never one counter for the whole platform. Then 429 rate_limited.
Accès : public (aucune authentification)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Limite : 30 per 15 minutes; 30 per 15 minutes
Corps de la requête
| Nom | Description |
|---|---|
challenge_id |
The challenge_id of POST /auth/webauthn/login/options (required). |
credential |
The PublicKeyCredential of navigator.credentials.get (JSON, required). |
remember |
As POST /auth/login. |
any_domain |
As POST /auth/login. |
Réponse : user, access_token, refresh_token, domain: As POST /auth/login.
POST /auth/webauthn/login/options
Options of navigator.credentials.get for a sign-in with a passkey (no address, no password).
Public. Rate limited: 60 per 15 minutes per client address when that address is known; not limited when it is not (a per-address counter would then be one for the whole platform: one person would stop everybody's passkey sign-in). The challenge lives 5 minutes. The Origin of the request must be a known domain (the mobile app sends no Origin but X-Client-App: ios or android with X-Frontend-Host: the passkeys of that site); else 400 origin_not_allowed. The browser offers the passkeys it has for this site.
Accès : public (aucune authentification)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Limite : 60 per 15 minutes
Réponse : challenge_id: To send back with the credential (single use, 5 minutes). options: The PublicKeyCredentialRequestOptions (JSON, user verification required).
POST /auth/webauthn/register
Add a passkey (the answer of navigator.credentials.create).
Session, or X-MFA-Token (enrollment: the answer then opens the session). 400 invalid_challenge or invalid_credential, 409 already_registered.
Accès : public (aucune authentification)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Limite : 20 per 15 minutes
Corps de la requête
| Nom | Description |
|---|---|
challenge_id |
The challenge_id of POST /auth/webauthn/register/options (required). |
credential |
The PublicKeyCredential (JSON, required). |
name |
A name to recognize it (60 characters, default "Clé d'accès"). |
Réponse : passkey: The passkey (as in GET /auth/mfa). backup_codes: The 10 backup codes, shown once, when the account had none. access_token, refresh_token: As POST /auth/mfa/totp/confirm. mfa: The status after the change.
POST /auth/webauthn/register/options
Options of navigator.credentials.create to add a passkey to the account.
Session, or X-MFA-Token (enrollment). The Origin of the request must be a known domain: the passkey works on that site (the mobile app sends X-Client-App: ios or android with X-Frontend-Host instead; else 400 origin_not_allowed). 409 too_many_passkeys beyond 20. An account that already has a second factor adds another one only from a session that proved one (else 403 second_factor_required).
Accès : public (aucune authentification)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Réponse : challenge_id: To send back with the credential (single use, 5 minutes). options: The PublicKeyCredentialCreationOptions (JSON, binary fields in base64url).
Compte (préférences, suppression, inscription)
Préférences de notification, préférences libres, suppression de son compte et inscription libre.
DELETE /auth/me
Delete one's own account.
What happens, organization by organization: when the account is the only member, the organization closes with it (refused with 409 balance_negative while its balance is below zero); its API keys, integrations, widgets, invitations, SIP endpoints and call routing are removed and its phone numbers go back to the pool of the domain. When the account shares an organization, it leaves it (refused with 403 last_admin when it is the last member holding members.admin, such as the Admin profile, in an organization that still has other members: give members.admin to someone else first). Every session of the account ends. A confirmation is e-mailed.
The account stays recoverable for a grace period (settings.account_deletion.grace_days of the domain, 10 days by default, 0 for none): signing in again with the password during it cancels the deletion (the numbers still free in the pool come back); afterwards the account is deleted for good.
Refused in the administration context (X-Admin-Organization, 403 forbidden: it is a personal action), and for super admins and domain admins (403 forbidden).
Accès : JWT d'accès
Limite : 5 per hour
Corps de la requête
| Nom | Description |
|---|---|
password |
The password of the account (not needed for an account that signs in with Google or Microsoft only). 401 invalid_password when wrong. |
confirm |
Must be true (400 validation_error, field confirm). |
Réponse : success: true. deletion: {state: scheduled (recoverable until purge_at) or deleted, purge_at: UTC date or null, numbers: the phone numbers released}.
GET /auth/me/dnd
The do not disturb of the account (the mobile app's switch).
While it is on: no push for new text messages nor for missed calls, the voicemail push arrives silent, and every endpoint of the account (in all its organizations) is in do not disturb (its calls go to its busy handling). The e-mails are not affected. It ends by itself at until.
Accès : JWT d'accès
Réponse : dnd: {active (boolean), until (ISO 8601 in UTC with Z, or null: no end), set_at (ISO 8601 in UTC with Z: when it was turned on, null when off)}. An expired one is {active: false, until: null, set_at: null}.
PUT /auth/me/dnd
Turn the do not disturb of the account on or off.
Every endpoint of the account, in all its organizations, follows: in do not disturb until until (or without end), or out of it when turned off. The other sessions of the account get the Socket.IO event dnd_update {dnd}.
Accès : JWT d'accès
Corps de la requête
| Nom | Description |
|---|---|
active |
true to turn it on, false to turn it off (required). Anything else: 400 validation_error with field active. |
until |
With active true: the end, an ISO 8601 date and time with its time zone (Z or +hh:mm), in the future and at most 7 days from now (otherwise 400 validation_error with field until); null or absent: no end. Ignored with active false. |
Réponse : dnd: The state after the change, as GET /auth/me/dnd.
GET /auth/me/notifications
The notification preferences of the account: which e-mails and pushes it receives.
The platform defaults apply to what the person never changed (a domain can set its own defaults, settings.notifications.defaults). Messages that carry a security notice, a requested code, an invitation or a service notice (password reset, verification codes, the negative balance notices, the end of a trial...) are always sent and have no switch.
Accès : JWT d'accès
Réponse : notifications: email ({updates, survey, purchase, login, low_balance}: booleans; low_balance_threshold: the balance under which the low-balance e-mail is sent, null to use the one of the organization) and push ({sms, missed_call, voicemail}: booleans).
PATCH /auth/me/notifications
Change the notification preferences (partial: only the keys sent change).
Accès : JWT d'accès
Corps de la requête
| Nom | Description |
|---|---|
email |
Optional object with any of updates, survey, purchase, login, low_balance (booleans) and low_balance_threshold (a number from 0 to 100000, or null to use the organization's threshold). |
push |
Optional object with any of sms, missed_call, voicemail (booleans). An unknown channel or key, a value of the wrong type, or a body that is not an object gets 400 validation_error with field (for example email.login). |
Réponse : notifications: All the preferences after the change, as GET /auth/me/notifications.
GET /auth/me/preferences
The free preferences of the account: a small key/value store for the apps (display choices, last used filters...). Nothing in it changes what the server does.
Accès : JWT d'accès
Réponse : preferences: The object, empty until something is stored.
PATCH /auth/me/preferences
Change the free preferences: every key of the body replaces the stored one, a null value removes it, the other keys are left alone.
Accès : JWT d'accès
Corps de la requête
| Nom | Description |
|---|---|
<key> |
Any JSON value (keys of 1 to 64 letters, digits, _ or -; values nested four levels at most, with keys of the same kind). At most 50 keys and 8 KB in all. A key or a value that is not acceptable, or too much data, gets 400 validation_error with field. |
Réponse : preferences: The whole object after the change.
POST /auth/signup
Create an account, its organization and its default team (free sign-up).
Closed by default: a domain admin opens it (settings.signup.enabled of the domain, 403 signup_disabled otherwise). Protections: a captcha when the domain set a provider, at most 5 accounts per client address and per day (setting max_per_ip_per_day), disposable e-mail providers refused when the domain lists them, 10 attempts per hour and e-mail address (and 20 per hour and client address). The "client address" is the real one only when the infrastructure passes it on (see "Limits" in the API concepts): behind the TLS demultiplexer of the production host it is unknown, the per-address cap and bucket do not apply, and the sign-ups share a ceiling per domain and day of 100 or 10 times max_per_ip_per_day. The phone, if given, is not verified (confirm it with /auth/verifications); a person with no phone number can sign in.
The e-mail address is not proved at sign-up (the user gets a session at once; user.email_verified is false). The starting credit (setting initial_credit) is therefore NOT granted now: it is granted, once, when the address is verified with POST /auth/verifications (purpose email_verify, no target) then POST /auth/verifications/<id>/confirm, or by a verified change of address. A 409 email_in_use tells that an address has an account: the sign-up form needs it, the captcha and the limits above bound the probing.
Accès : public (aucune authentification)
Limite : 10 per hour; 20 per hour
Corps de la requête
| Nom | Description |
|---|---|
email |
E-mail address (required, it is the sign-in). |
password |
Password (required; 8 to 128 characters, 2 classes of characters, not a common password nor the e-mail). |
first_name |
First name (required). |
last_name |
Last name (required). |
phone |
Optional object {number, country?}, normalized to E.164 and unverified (invalid: 400 invalid_phone). |
language |
fr or en (default fr). |
accept_terms |
Must be true (400 validation_error, field accept_terms). |
captcha_token |
The answer of the captcha widget (required when the domain set a provider; 400 captcha_failed when wrong). |
Réponse : 201 with user, access_token and refresh_token, like a login. Welcome e-mail (setting notifications.welcome_email) as the domain decided; the starting credit (setting initial_credit of the domain) waits for the verified e-mail address (see above).
Errors 404 domain_not_found (no domain matches X-Frontend-Host), 403 signup_disabled, 403 email_domain_blocked, 400 validation_error / invalid_phone / captcha_failed, 409 email_in_use, 429 rate_limited, 503 captcha_unavailable (the provider could not be asked).
GET /auth/signup/config
What the sign-up page needs to know about the domain: whether sign-up is open and the captcha to show. Public.
Accès : public (aucune authentification)
Limite : exemptée de la limite générale par adresse
Réponse : signup: {enabled, captcha: {provider (none, recaptcha or turnstile), site_key}, terms_url}. Sign-up is closed (enabled false) unless a domain admin opened it.
Errors 404 domain_not_found: no domain matches X-Frontend-Host.
Organisations
Organisations de l'utilisateur et administration des organisations du domaine.
GET /api/v1/organizations
Search and list the organizations of the current domain (platform_admin or domain_admin).
The current domain is the one serving the frontend host (e.g. other.telma.ca), so each whitelabel site only shows its own organizations. Sorted by name (case and accents ignored). A platform_admin can search every domain with all_domains.
Accès : permission de plateforme platform_admin ou domain_admin
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de requête
| Nom | Description |
|---|---|
q |
Free text, searched anywhere in the organization name or in the e-mail address of its owner, ignoring case and accents (optional, 100 characters at most, else 400 validation_error with field q). |
skip |
Pagination offset (default 0). Not an integer: 400 validation_error with field skip. |
limit |
Page size (default 50, max 100; a larger value is brought back to 100). Not an integer: 400 validation_error with field limit. |
all_domains |
true to search every domain instead of the current one (platform_admin only, 403 forbidden for a domain_admin; true or false, else 400 validation_error). |
Réponse : organizations: The organizations of the page, each with member_count, team_count, owner_email (null without owner) and, with all_domains, domain_name (display name of its domain) and domain_host (hostname to use for that domain); domain_id is always there. total: Number of organizations matching the filters, before skip and limit. skip: The offset applied. limit: The page size applied.
POST /api/v1/organizations
Create a new organization in the current domain (platform_admin, or domain_admin of that domain).
Accès : permission de plateforme platform_admin ou domain_admin
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Corps de la requête
| Nom | Description |
|---|---|
name |
Organization name (required, a string of 1 to 120 characters once trimmed: 400 validation_error otherwise). |
owner_email |
Email of the owner (optional). For a domain admin, only an account that already belongs to this domain (a membership in one of its organizations) is taken; any other address is ignored, without telling whether it exists. |
Réponse : organization: The created organization. message: Confirmation.
DELETE /api/v1/organizations/<org_id>
Delete organization (platform_admin, or domain_admin of its domain).
Refused (400) while the organization has members. Every other access goes with it: its integrations and their API keys, website widgets, pending invitations and voice endpoints; its phone numbers go back to the domain. Its teams and conversations stay stored, out of reach.
Accès : permission de plateforme platform_admin ou domain_admin
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
org_id |
Organization ID. |
Réponse : Success message.
GET /api/v1/organizations/<org_id>
Get organization details.
platform_admin: any organization; domain_admin: those of their domains; a member holding members.admin or organization.settings: their current organization only.
Not available to restricted API keys (a key limited to a team, or an integration's key): 403 with error forbidden.
Accès : authentifié (JWT ou clé API); platform_admin, domain_admin of the domain of the organization, or members.admin or organization.settings in that organization when it is the current one (checked in the function: 403 forbidden otherwise).
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
org_id |
Organization ID. |
Réponse : organization: The organization with its teams; members (with their e-mail addresses) only for a platform_admin, a domain_admin of its domain or a member holding members.admin there (the same rule as GET /organizations/current/members): without it, the answer has no members.
PUT /api/v1/organizations/<org_id>
Update organization (platform_admin, or domain_admin of its domain).
Accès : permission de plateforme platform_admin ou domain_admin
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
org_id |
Organization ID. |
Corps de la requête
| Nom | Description |
|---|---|
name |
New organization name (a string of 1 to 120 characters once trimmed: 400 validation_error otherwise; absent or null leaves it unchanged). |
Réponse : organization: The updated organization. message: Confirmation.
PUT /api/v1/organizations/<org_id>/billing-anniversary
Update the billing anniversary date for an organization.
Accès : permission de plateforme platform_admin
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
org_id |
Organization ID. |
Corps de la requête
| Nom | Description |
|---|---|
billing_anniversary |
ISO date string or null. |
Réponse : Updated organization.
PUT /api/v1/organizations/<org_id>/capabilities
Update capabilities for an organization.
Only a platform_admin, or a domain_admin of the organization's domain, can update capabilities. Organization capabilities must be a subset of the domain's capabilities.
The known capabilities are textos (messaging), voxos (voice service), sip (SIP endpoints and softphone provisioning, /voice/endpoints* and /voice/provision), call_codes (call codes, legacy without interface: no app uses them any more) and voicemail_boxes (voicemail boxes of the endpoints and independent boxes, /voice/mailboxes). sip, call_codes and voicemail_boxes need voxos in the same list. The list replaces the previous one. Removing sip (or voxos) disables the SIP endpoints of the organization; they are enabled again when sip returns. Every route that returns an organization shows its list.
Not available to restricted API keys (a key limited to a team, or an integration's key): 403 with error forbidden.
Accès : authentifié (JWT ou clé API)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
org_id |
Organization ID. |
Corps de la requête
| Nom | Description |
|---|---|
capabilities |
List of capabilities, each one of textos, voxos, sip, call_codes, voicemail_boxes (e.g., ["textos", "voxos", "sip"]). An unknown name, a missing list, or sip, call_codes or voicemail_boxes without voxos is refused with 400 validation_error (field capabilities); a capability the domain does not have, too. |
Réponse : organization: The updated organization. message: Confirmation.
GET /api/v1/organizations/<org_id>/invitations
Get invitations for an organization.
platform_admin: any organization; domain_admin: those of their domains; a member holding members.admin or organization.settings: their current organization only.
Not available to restricted API keys (a key limited to a team, or an integration's key): 403 with error forbidden.
Accès : authentifié (JWT ou clé API); platform_admin, domain_admin of the domain of the organization, or members.admin or organization.settings in that organization when it is the current one (checked in the function: 403 forbidden otherwise).
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
org_id |
Organization ID. |
Réponse : invitations: The invitations of the organization. total: Number of invitations.
DELETE /api/v1/organizations/<org_id>/members/<user_id>
Delete a member from an organization (platform_admin, or domain_admin of its domain).
The user must be a member of that organization (a membership there, whatever their current organization; else 403). The member must be deactivated first: 409 with error user_active otherwise. 403 member_of_other_organizations when the account also belongs to organizations the caller does not administer.
Accès : permission de plateforme platform_admin ou domain_admin
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
org_id |
Organization ID. |
user_id |
User ID. |
Réponse : Success message.
PUT /api/v1/organizations/<org_id>/members/<user_id>
Update a member in an organization (platform_admin, or domain_admin of its domain).
The user must be a member of that organization (a membership there, whatever their current organization; else 403): the role changes on the membership of THIS organization (users.role follows only when it is their current one). Never one's own role or status, never a platform account (super admin, domain admin) unless the caller is a super admin (403 forbidden).
Accès : permission de plateforme platform_admin ou domain_admin
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden; refusées si le corps donne le rôle admin, domain_admin, super_admin
Paramètres de chemin
| Nom | Description |
|---|---|
org_id |
Organization ID. |
user_id |
User ID. |
Corps de la requête
| Nom | Description |
|---|---|
role |
New role for the user (optional; admin, manager or agent, another value is ignored): the member's profiles are replaced by those of the role (admin: Admin; manager: User and the organization's Manager profile; agent: User). Never on a platform_admin or domain_admin account (403). An API key never gives the admin role (403). |
is_active |
Whether the user is active (optional). |
Réponse : user: The updated member. message: Confirmation.
Errors 402 plan_limit: the role gives the messages (messages.read or messages.send) to a member who did not have them and the organization's messaging plan has no room left (as POST /api/v1/invitations): {limit (users), plan, upgrade, current}; nothing is changed. 403 member_of_other_organizations: activating or deactivating the account reaches organizations the caller does not administer.
POST /api/v1/organizations/<org_id>/members/<user_id>/impersonate
Sign in as a member of the organization, without their password ("Se connecter en tant que").
For a member holding members.admin in the organization (the Admin profile, or a profile of its own that gives it), a domain admin of its domain or a super admin, with a session (never an API key, never from a session itself opened as a member). The member must be active, and every organization they belong to must be one the caller administers (a domain admin only by a super admin, a super admin never). The answer has the shape of POST /auth/login: a new session of the member, short (no "remember me"), whose user carries impersonated_by (the caller). It does not count as the member's sign-in, it ends when the caller's account is deactivated, and it may not change the password, e-mail or phone of the account (nor ask for a verification code), delete it, create an API key or sign in as someone else in turn (403 impersonation_forbidden). Journaled in audit_logs (type impersonation_started).
Accès : authentifié (JWT ou clé API)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Limite : 20 per hour
Paramètres de chemin
| Nom | Description |
|---|---|
org_id |
Organization ID. |
user_id |
The member's user ID. |
Réponse : user: The member (as GET /auth/me), with impersonated_by {id, name, email}. access_token: Access token of the new session. refresh_token: Refresh token of the new session (12 hours without a refresh).
Errors 403 forbidden: not an administrator of the organization (members.admin), oneself, an API key or a session opened as a member. 403 member_of_other_organizations: the member also belongs to organizations the caller does not administer. 404 not_found: unknown organization, or not a member. 409 user_inactive: the account is deactivated.
GET /api/v1/organizations/current
Get current user's organization for the current domain.
If user's current organization doesn't belong to the current domain, automatically switch to the user's default organization (if it belongs to this domain), or fall back to any organization in the current domain (if available).
Header X-Organization-Fallback: none turns off the last resort above and, for a super admin or a domain admin, every implicit organization: they are no longer put in the first organization of the domain, nor kept in the one an earlier session left on their account (which is forgotten: the account has no current organization afterwards). The answer is 404 not_found and the client shows its organization picker; the administrator works in an organization by administering it (X-Admin-Organization, below). Other users are not affected.
While the header X-Admin-Organization is honored (super admin or domain admin administering an organization), the organization is the administered one: no domain check and no automatic switch, nothing is written on the account, and the response carries admin_context: true and admin_organization_id.
Not available to restricted API keys (a key limited to a team, or an integration's key): 403 with error forbidden.
Accès : authentifié (JWT ou clé API)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Réponse : organization: Organization data with member_count and team_count. admin_context: true, only while the header X-Admin-Organization is honored. admin_organization_id: The administered organization, with admin_context.
PUT /api/v1/organizations/current
Rename the current organization (permission organization.settings).
Not available to restricted API keys (a key limited to a team, or an integration's key): 403 with error forbidden.
Accès : authentifié (JWT ou clé API); Permission organization.settings in the current organization (profiles; checked in the function: 403 forbidden otherwise).
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Corps de la requête
| Nom | Description |
|---|---|
name |
New organization name (required, a string of 1 to 120 characters once trimmed: 400 validation_error otherwise). |
Réponse : organization: The updated organization. message: Confirmation.
GET /api/v1/organizations/current/credit-status
Get credit status for current organization.
Not available to restricted API keys (a key limited to a team, or an integration's key): 403 with error forbidden.
Accès : authentifié (JWT ou clé API)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Réponse : credit_balance: Credit balance of the organization. has_credits: Whether credits are available. credit_hold_count: Number of messages on credit hold.
GET /api/v1/organizations/current/members
Get members of current user's organization.
Not available to restricted API keys (a key limited to a team, or an integration's key): 403 with error forbidden.
Accès : authentifié (JWT ou clé API); Permission members.admin in the current organization (profiles; checked in the function: 403 forbidden otherwise).
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Réponse : members: The members of the organization. total: Number of members.
GET /api/v1/organizations/my
List all organizations the current user belongs to in the current domain.
Not available to restricted API keys (a key limited to a team, or an integration's key): 403 with error forbidden.
An account with the platform permission platform_admin or domain_admin also gets, without being a member, every organization it administers (platform_admin: those of the current domain, or of every domain with all_domains; domain_admin: those of their domains), with its platform role (super_admin, domain_admin) as role. Not with member_only. Use GET /organizations (q, skip, limit) to search among them without the 1000-organization cap of this list. While the header X-Admin-Organization is honored, is_current marks the administered organization and the response carries admin_context and admin_organization_id.
Accès : authentifié (JWT ou clé API)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de requête
| Nom | Description |
|---|---|
all_domains |
If "true", return organizations across all domains (not just current). |
member_only |
If "true", only the organizations where the user sees at least one team in the member view of the mobile app (by the profiles held there: with teams.all every team, otherwise only the team they are placed in, nothing without one; a platform permission adds nothing); the mobile app lists those. |
Réponse : organizations: List of organizations with user's role in each (organization role, or the platform role for an organization that is only administered), permissions (the permissions of the user there, given by their profiles; every one for an organization they administer from the platform or its domain), their domain (domain_id, domain_name, domain_host: hostname to use for that domain), created_at and is_current. total: Number of organizations listed.
POST /api/v1/organizations/switch
Switch to a different organization.
Not available to restricted API keys (a key limited to a team, or an integration's key): 403 with error forbidden.
Accès : authentifié (JWT ou clé API)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Corps de la requête
| Nom | Description |
|---|---|
organization_id |
ID of the organization to switch to. |
Réponse : user: The user, now working in the organization. organization: The organization. message: Confirmation.
Errors 400 validation_error: organization_id missing. 404 not_found: unknown organization. 403 forbidden: not a member (and not an administrator of it). 500 server_error: unexpected failure.
GET /api/v1/organizations/unread-counts
Get unread message counts per organization in the current domain.
Only the conversations of the team the user is a member of in each organization count (the same rule as the push notifications): being able to read the other teams (teams.all, platform permissions) does not make their messages a notification.
Not available to restricted API keys (a key limited to a team, or an integration's key): 403 with error forbidden.
Accès : authentifié (JWT ou clé API)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Réponse : unread_counts: Number of unread messages per organization id.
Équipes
Équipes, membres, horaires et webhook d'équipe.
GET /api/v1/teams
List teams accessible to current user in the current domain.
A member holding teams.all in the organization (profiles Admin and Manager) gets its teams; otherwise only the team they are placed in (the mobile app needs its name and organization). A restricted API key (limited to a team, or an integration's key) gets the teams it can reach only, never with their webhook secret, whatever the profiles of its owner and whatever organization its owner currently works in. webhook_secret is given team by team, to the members holding teams.provider in the team's organization (profiles Admin and Manager) and to the platform_admin and domain_admin accounts who administer it.
Accès : authentifié (JWT ou clé API)
Paramètres de requête
| Nom | Description |
|---|---|
organization_id |
Organization whose teams to list (optional; default: the caller's current organization). Except for a platform_admin, it must be an organization the caller belongs to or, for a domain admin, an organization of their domains: any other one gets 403, whatever X-Frontend-Host. With a restricted API key, it only filters the teams of the key (never 403). |
skip |
Number of teams to skip (optional, default 0; a negative value counts as 0, a value over 2147483647 is refused: 400 validation_error). |
limit |
Number of teams (optional, default: every team; max 100, a larger value is brought back to 100, 0 or less to 1). A skip or limit that is not an integer is refused (400 validation_error with field). |
Réponse : teams: The teams of the page. total: Number of teams before skip and limit. skip, limit: The values applied (limit null when absent).
POST /api/v1/teams
Create a new team in the caller's current organization.
400 validation_error for a caller without organization. An organization with a messaging plan creates no more than its max_teams: 402 plan_limit with limit (teams), plan and upgrade (the next plan, or null).
Accès : permission manage_teams (devenue teams.manage)
Corps de la requête
| Nom | Description |
|---|---|
name |
Team name (required, a string of 1 to 120 characters once trimmed: 400 validation_error otherwise). |
phone_number |
Team phone number. |
Réponse : team: The created team, with its webhook_secret. message: Confirmation.
DELETE /api/v1/teams/<team_id>
Delete a team.
Accès : accès à l'équipe (team_id); permission manage_teams (devenue teams.manage)
Paramètres de chemin
| Nom | Description |
|---|---|
team_id |
Team ID. |
Réponse : Success message.
GET /api/v1/teams/<team_id>
Get team details.
Accès : permission members.manage dans l'organisation (profils); accès à l'équipe (team_id)
Paramètres de chemin
| Nom | Description |
|---|---|
team_id |
Team ID. |
Réponse : team: The team; its webhook_secret and provider configuration only for a caller holding teams.provider in the team's organization (or administering it from the platform or its domain).
PUT /api/v1/teams/<team_id>
Update team settings.
Accès : accès à l'équipe (team_id); permission configure_hours (devenue teams.hours)
Paramètres de chemin
| Nom | Description |
|---|---|
team_id |
Team ID. |
Corps de la requête
| Nom | Description |
|---|---|
name |
Team name (optional; a string of 1 to 120 characters once trimmed: 400 validation_error otherwise). |
phone_number |
Team phone number (optional). |
business_hours |
Business hours config (optional). |
outside_hours_behavior |
Behavior setting (optional). |
auto_reply_message |
Auto-reply message (optional). |
welcome_message |
Message sent when a conversation starts (optional). |
timezone |
IANA time zone of the business hours and of the archives (optional, e.g. America/Montreal: 400 validation_error when unknown). |
message_templates |
Canned messages agents can insert, list of {title, body} (optional, replaces the list). |
Réponse : Updated team data.
PUT /api/v1/teams/<team_id>/hours
Update team business hours.
Accès : accès à l'équipe (team_id); permission configure_hours (devenue teams.hours)
Paramètres de chemin
| Nom | Description |
|---|---|
team_id |
Team ID. |
Corps de la requête
| Nom | Description |
|---|---|
business_hours |
List of {day, start, end} objects. |
outside_hours_behavior |
queue (a message received outside business hours is shown marked "queued", with the usual notifications) or reject (shown marked "rejected", without push notification); auto_reply, an older value, behaves like queue. |
auto_reply_message |
Text sent back automatically to a message received outside business hours, whatever the behavior (once a day per conversation; optional). |
timezone |
IANA time zone for business hours (e.g., America/Montreal; 400 validation_error when unknown). |
Réponse : Updated team data.
GET /api/v1/teams/<team_id>/members
List members of a team (super admins, by their platform permissions, are never listed, except to themselves).
Accès : permission members.manage dans l'organisation (profils); accès à l'équipe (team_id)
Paramètres de chemin
| Nom | Description |
|---|---|
team_id |
Team ID. |
Réponse : members: The members of the team. total: Number of members.
POST /api/v1/teams/<team_id>/members
Add a member to a team.
Accès : accès à l'équipe (team_id); permission manage_teams (devenue teams.manage)
Paramètres de chemin
| Nom | Description |
|---|---|
team_id |
Team ID. |
Corps de la requête
| Nom | Description |
|---|---|
user_id |
User ID to add to the team. |
Réponse : members: The members of the team after the change. message: Confirmation.
DELETE /api/v1/teams/<team_id>/members/<user_id>
Remove a member from a team.
Accès : accès à l'équipe (team_id); permission manage_teams (devenue teams.manage)
Paramètres de chemin
| Nom | Description |
|---|---|
team_id |
Team ID. |
user_id |
User ID to remove from the team. |
Réponse : members: The members of the team after the change. message: Confirmation.
GET /api/v1/teams/<team_id>/message-templates
List the canned messages of a team (any member of the team, agents included).
Accès : accès à l'équipe (team_id)
Paramètres de chemin
| Nom | Description |
|---|---|
team_id |
Team ID. |
Réponse : message_templates: List of {id, title, body}.
POST /api/v1/teams/<team_id>/webhook/regenerate
Regenerate team webhook secret.
Open to the managers and admins of the team's organization (permission configure_provider, like the provider settings): they already read the secret in the team list, and the previous URL stops working at once, so the provider must be updated with the new one.
Accès : accès à l'équipe (team_id); permission configure_provider (devenue teams.provider)
Paramètres de chemin
| Nom | Description |
|---|---|
team_id |
Team ID. |
Réponse : webhook_secret: The new secret (the old URL stops working). webhook_url: The new inbound webhook path, /webhook/sms/<secret> (prefix it with the API address).
GET /api/v1/teams/all-organizations
List all teams from all organizations the user belongs to.
This is used for WebSocket room joining to receive real-time updates from all organizations. While the header X-Admin-Organization is honored, only the teams of the administered organization are listed.
Accès : authentifié (JWT ou clé API)
Paramètres de requête
| Nom | Description |
|---|---|
member_only |
If "true", only the teams of the member view of the mobile app (by the profiles held in each organization: with teams.all every team, otherwise only the team they are placed in, nothing without one; a platform permission adds nothing); the mobile app asks for those. |
Réponse : team_ids: Ids of all the teams of the organization. total: Number of teams.
SMS et conversations
Envoi de SMS/MMS, conversations, archives.
GET /api/v1/conversations
List conversations for user's teams.
Conversations come from the most recent message to the oldest (last_message_at), open and closed ones (since 2026-10-14 closing a conversation is archiving it: there is no separate archived state, and the former archived conversations became closed ones). The closed conversations of a team by day of closing are also GET /api/v1/teams/<team_id>/archives, which requires the archives permission messages.archive (Admin and Manager profiles; refused to a restricted API key).
Accès : permission view_team_conversations (devenue messages.read)
Paramètres de requête
| Nom | Description |
|---|---|
team_id |
Team ID (optional, if not provided loads all user's teams). 403 forbidden for a team the user (or the API key) cannot access. |
status |
open or closed (optional, default: both). Any other value, archived included, is refused (400 validation_error, field status). |
replied |
1 = only conversations the contact wrote to at least once, 0 = only those without any inbound message yet (optional). |
q |
Free-text search (optional, 100 characters at most). Digits and phone punctuation (514 555, (514) 555-0123) are looked for in the contact's number and in the team number of the conversation; any other text, without regard to case and accents, in the contact's name and in the messages of the conversation. |
number |
Only the conversations that use this team number (optional): an E.164 number in any usual notation, or the ID of a phone number. 400 validation_error (field number) for anything else. |
unread |
1 = only the conversations with unread messages (optional, 1/0 or true/false). |
member_only |
1 = only the teams the user works in, by the profiles of each membership: with teams.all (Admin, Manager) every team of that organization, otherwise only the team they are placed in, nothing without one; a platform permission adds nothing (the web app administers). The mobile app asks for it (optional, 1/0 or true/false). With team_id, a team outside this view is refused (403 forbidden). |
skip |
Pagination offset (default 0; a negative value counts as 0, a value over 2147483647 is refused: 400 validation_error). |
limit |
Page size (default 50, max 100; a larger value is brought back to 100, 0 or less to 1). A skip or limit that is not an integer is refused (400 validation_error). |
Réponse : conversations: The page of conversations, each with its team_name and last_message ({id, body, direction, status, created_at, has_media}, null for a conversation without message). total: Number of conversations matching ALL the filters, whatever the page. skip, limit: The values applied.
DELETE /api/v1/conversations/<conversation_id>
Delete a conversation for good, with its messages, notes and timeline.
Open to the admins of the organization (permission delete_conversations: admin, domain admin, super admin); managers and agents get 403 forbidden. The role is the one held in the organization that owns the conversation, not the role in the organization the caller is currently working in. The unread counters of the team's users are announced (unread_update).
Accès : permission delete_conversations (devenue messages.delete)
Paramètres de chemin
| Nom | Description |
|---|---|
conversation_id |
Conversation ID. |
Réponse : Success message.
GET /api/v1/conversations/<conversation_id>
Get conversation details with messages.
Reading it marks it as read (conversation.updated with change "read" when it had unread messages). A closed conversation is returned like any other to whoever reaches its team: the archives permission only guards the list of GET /api/v1/teams/<team_id>/archives.
Accès : permission view_team_conversations (devenue messages.read)
Paramètres de chemin
| Nom | Description |
|---|---|
conversation_id |
Conversation ID. |
Paramètres de requête
| Nom | Description |
|---|---|
skip |
Message pagination offset (default 0; a negative value counts as 0, a value over 2147483647 is refused: 400 validation_error). |
limit |
Number of messages (default 50, max 100; a larger value is brought back to 100, 0 or less to 1). A skip or limit that is not an integer is refused (400 validation_error). |
mark_read |
0 reads the conversation without marking it read (support, administration; default 1). The conversation then keeps its unread count and nothing is announced. Another value than 1/0/true/false is refused (400 validation_error). |
Réponse : conversation: The conversation. messages: The messages of the page, newest first (skip/limit). total: Number of messages in the conversation. capabilities: What the number of the conversation can carry, a list such as ["SMS", "MMS"]. events: Every status change of the conversation in timeline order (type closed / reopened, cause inbound / agent / outbound, actor_name, via, message_id of the inbound message that reopened it, anchor_at).
PATCH /api/v1/conversations/<conversation_id>
Name the contact of a conversation, or clear the name.
Whoever may write in the conversation may rename it: the same rule as sending a message to it with POST /api/v1/sms/send (permission send_sms and access to the team, the member view of the mobile app included); an integration key whose exposed teams include the team of the conversation too. The name belongs to this conversation only (there is no shared address book: another conversation with the same number, through another team number or another team, keeps its own name). It is the name shown in the lists and searched by the q filter of GET /api/v1/conversations. An open or a closed conversation can be renamed; nothing reopens.
A real change is announced to the team (socket conversation_update with change and type "renamed" and contact_name) and delivered to the integrations as conversation.updated with change "renamed" and contact_name. The same name again changes and announces nothing.
Accès : permission send_sms (devenue messages.send)
Paramètres de chemin
| Nom | Description |
|---|---|
conversation_id |
Conversation ID. |
Corps de la requête
| Nom | Description |
|---|---|
contact_name |
The contact's name (required, may be null): trimmed, then 1 to 120 characters on one line, without control characters; null or an empty string (also once trimmed) clears the name. |
Réponse : conversation: The conversation, in the form of GET /api/v1/conversations/<id> (contact_name updated).
Errors 400 validation_error (field contact_name): body not a JSON object, contact_name missing, not a string nor null, longer than 120 characters, or carrying a line break or a control character. 403 forbidden: the team of the conversation is not accessible. 404 not_found: unknown conversation.
POST /api/v1/conversations/<conversation_id>/archive
Archive a conversation: since 2026-10-14 the same as closing it (POST .../close), kept for the clients that still call it. A closed conversation IS archived: it leaves the list of the open ones, shows in the team's closed conversations (GET /teams/<team_id>/archives) and reopens when the contact writes. Delivers conversation.updated with change "closed" and cause "agent".
Accès : permission archives (devenue messages.archive)
Paramètres de chemin
| Nom | Description |
|---|---|
conversation_id |
Conversation ID. |
Réponse : conversation: The closed conversation. message: Confirmation.
POST /api/v1/conversations/<conversation_id>/close
Close a conversation.
Delivers conversation.updated with change "closed" and cause "agent".
Accès : permission view_team_conversations (devenue messages.read)
Paramètres de chemin
| Nom | Description |
|---|---|
conversation_id |
Conversation ID. |
Corps de la requête
| Nom | Description |
|---|---|
actor_name |
Integration keys only: name of the person acting through the integration (shown in the timeline), 120 characters max. |
Réponse : conversation: The updated conversation. message: Confirmation text.
POST /api/v1/conversations/<conversation_id>/read
Mark a conversation as read, explicitly (the other devices of the user follow).
Reading it with GET /api/v1/conversations/<id> marks it too (unless mark_read=0). When the conversation had unread messages, conversation.updated with change "read" and the unread_update counters are announced; marking an already read conversation changes and announces nothing.
Accès : permission view_team_conversations (devenue messages.read)
Paramètres de chemin
| Nom | Description |
|---|---|
conversation_id |
Conversation ID. |
Réponse : conversation: The conversation (unread_count 0).
Errors 403 forbidden: the team is not accessible. 404 not_found: unknown conversation.
POST /api/v1/conversations/<conversation_id>/reopen
Reopen a closed conversation.
Delivers conversation.updated with change "reopened" and cause "agent". A conversation also reopens by itself, with the same event: cause "inbound" when the contact writes to a closed conversation (message_id then gives that message). Sending a message in a closed conversation leaves it closed (before 2026-10-14, a message sent in an archived one reopened it, cause "outbound").
Accès : permission view_team_conversations (devenue messages.read)
Paramètres de chemin
| Nom | Description |
|---|---|
conversation_id |
Conversation ID. |
Corps de la requête
| Nom | Description |
|---|---|
actor_name |
Integration keys only: name of the person acting through the integration (shown in the timeline), 120 characters max. |
Réponse : conversation: The updated conversation.
GET /api/v1/conversations/unread-count
Unread counters of the menu: the same numbers as the unread_update event.
Counts the conversations with unread messages and the unread messages of every team the user can read in the organization they work in (the administered organization in the administration context), closed conversations included.
Accès : permission view_team_conversations (devenue messages.read)
Paramètres de requête
| Nom | Description |
|---|---|
number |
Only the conversations that use this team number (optional): an E.164 number in any usual notation, or the ID of a phone number. 400 validation_error (field number) for anything else. |
member_only |
1 = only the teams of the member view of the mobile app (optional, same rule as GET /conversations). |
Réponse : unread: {conversations: number of conversations with unread messages, messages: number of unread messages}.
GET /api/v1/messages
List messages with filters.
Messages of one team, the most recent first, those of closed conversations included.
Accès : permission view_team_conversations (devenue messages.read)
Paramètres de requête
| Nom | Description |
|---|---|
team_id |
Team ID (optional when the user has a team; an integration key must give it). 403 forbidden otherwise, or for a team that is not accessible. |
direction |
Filter by direction (inbound, outbound). |
status |
Filter by status. |
skip |
Pagination offset (default 0; a negative value counts as 0, a value over 2147483647 is refused: 400 validation_error). |
limit |
Page size (default 50, max 100; a larger value is brought back to 100, 0 or less to 1). A skip or limit that is not an integer is refused (400 validation_error). |
Réponse : messages: The page of messages. skip, limit: The values applied.
DELETE /api/v1/messages/<message_id>
Delete one message for good (admins of the organization that owns the message, like deleting a conversation).
The conversation stays, even when it is left without message; its unread count is brought down to the number of inbound messages left, and the unread counters of the team's users are announced (unread_update). Files attached to the message are not removed.
Accès : permission delete_conversations (devenue messages.delete)
Paramètres de chemin
| Nom | Description |
|---|---|
message_id |
Message ID. |
Réponse : Success message.
Errors 403 forbidden: the team of the message is not accessible, or the role is not admin. 404 not_found: unknown message.
POST /api/v1/sms/send
Send an SMS or MMS message.
The message is recorded in the conversation of the team number and the contact (created if needed; a closed one stays closed: since 2026-10-14 only a message from the contact reopens it), then handed to the provider. Subscribed integrations receive it as message.sent.
Accès : permission send_sms (devenue messages.send)
Limite : 100 per hour (600 per hour pour la clé d'une intégration)
Corps de la requête
| Nom | Description |
|---|---|
to |
Recipient phone number (required). |
body |
Message text (required unless media_urls is given). |
team_id |
Team ID (optional when the user has a team, or when from_number identifies the team). An integration key has no team of its own: pass team_id or from_number (403 forbidden otherwise). |
from_number |
Sender phone number (optional, uses the number of the contact's conversation or the first team number). Without team_id, the team the number is assigned to is used. 400 number_not_in_team when it is not a number of the team. |
media_urls |
MMS attachments (optional): a list of files uploaded with POST /api/v1/upload, each given as the returned url (a string) or as an object {url, content_type}. Any other URL, or a file that no longer exists, is refused (400 validation_error). The content type recorded with the message is the uploaded file's. |
sender_name |
Integration keys only: name of the person sending through the integration, shown as the sender (default: the integration name), 120 characters max. |
external_sender |
Integration keys only: the integration's own identifier of that person, 120 characters max. |
Réponse : message: The created message. conversation: Its conversation (201).
Errors 403 number_disabled: the number that would send is switched off (reason: paused, with paused_reason monthly_fee or balance for a pause of the platform; inactive; sms_disabled), whether it was asked for with from_number or it is the only one of the team; a switched-off number is skipped when the sender is chosen by the platform. 400 validation_error: body not a JSON object, to missing or not a phone number (field to: a number in E.164 or a valid North American number, or a short code of 3 to 9 digits), neither body nor media_urls, body not a string, body longer than the limit of the domain (field body: 800 characters by default, setting settings.sms.max_body_length of the domain, 0 for no limit), media_urls not a list of uploads. 400 number_not_in_team: from_number is not (or no longer) a number of the team. 400 configuration_error: no number assigned to the team, provider not configured or not enabled, domain not found. 402 insufficient_balance: the organization's credit does not cover the message (estimated_cost and current_balance in the answer); the message is debited BEFORE the provider is asked, so requests sent at the same time cannot share one message's worth of credit (the loser gets this answer and its message is recorded as failed, error_code insufficient_balance), and a message the provider does not send is credited back. 402 account_suspended: the balance stayed negative past the grace period (E46); balance_state, release_at and balance in the answer; sending resumes when the balance is back to zero or more. 403 forbidden: team not accessible, or no team given or found. 404 not_found: team. 429: rate limit. 500 send_failed: the provider refused or failed (or, error_code billing_error, the debit itself failed and nothing was sent); error_code and message_id give the message, recorded as failed in the conversation; no message.sent is delivered, the debit is credited back, and a retry creates a new message.
GET /api/v1/teams/<team_id>/archives
The closed conversations of a team (its « archives »: since 2026-10-14 a closed conversation is archived, there is no separate archived state), most recently closed first, by the date of the closing (closed_at). The list of the conversations gives them too with status=closed.
Accès : permission archives (devenue messages.archive)
Paramètres de chemin
| Nom | Description |
|---|---|
team_id |
Team ID. |
Paramètres de requête
| Nom | Description |
|---|---|
date |
Optional date filter: closed that day (YYYY-MM-DD format, in the team's time zone). |
skip |
Pagination offset (default 0; a negative value counts as 0, a value over 2147483647 is refused: 400 validation_error). |
limit |
Page size (default 50, max 100; a larger value is brought back to 100, 0 or less to 1). A skip or limit that is not an integer is refused (400 validation_error). |
Réponse : conversations: The closed conversations (each with closed_at). skip, limit: The values applied.
GET /api/v1/teams/<team_id>/archives/dates
The days on which conversations of the team were closed (its « archives »).
Accès : permission archives (devenue messages.archive)
Paramètres de chemin
| Nom | Description |
|---|---|
team_id |
Team ID. |
Réponse : List of dates (YYYY-MM-DD format).
GET /api/v1/teams/<team_id>/phone-numbers
Get phone numbers assigned to a team.
Accès : permission send_sms (devenue messages.send)
Paramètres de chemin
| Nom | Description |
|---|---|
team_id |
Team ID. |
Réponse : phone_numbers: List of {number, label, capabilities} (active numbers of the team; capabilities = ["SMS"] or ["SMS", "MMS"], empty for a number that no SMS provider carries).
Notes de conversation
Notes internes d'une conversation (jamais envoyées au contact).
GET /api/v1/conversations/<conversation_id>/notes
List the internal notes of a conversation.
Notes are silent comments of the team: they are never sent to the contact. They come in timeline order (anchor_at): the moment they were written, or the time of the message they comment (message_id).
Accès : permission view_team_conversations (devenue messages.read)
Paramètres de chemin
| Nom | Description |
|---|---|
conversation_id |
Conversation ID. |
Paramètres de requête
| Nom | Description |
|---|---|
skip |
Number of notes to skip (optional, default 0; a negative value counts as 0, a value over 2147483647 is refused: 400 validation_error). |
limit |
Number of notes (optional, default: every note; max 100, a larger value is brought back to 100, 0 or less to 1). A skip or limit that is not an integer is refused (400 validation_error). |
Réponse : notes: Notes of the conversation, in timeline order (id, conversation_id, team_id, message_id, anchor_at, body, author_id, author_name, via, external_author, created_at, updated_at). total: Number of notes of the conversation.
POST /api/v1/conversations/<conversation_id>/notes
Add an internal note to a conversation (never sent to the contact).
Delivers conversation.note with change "created" to the integrations.
Accès : permission view_team_conversations (devenue messages.read)
Paramètres de chemin
| Nom | Description |
|---|---|
conversation_id |
Conversation ID. |
Corps de la requête
| Nom | Description |
|---|---|
body |
Note text (required, 4000 characters max after trimming spaces): 400 validation_error when missing, empty or longer. |
message_id |
Message of the conversation the note comments (optional): the note is placed right after it. 404 not_found when it is not a message of this conversation. |
author_name |
Integration keys only: name of the person writing through the integration (default: the integration name), 120 characters max. |
external_author |
Integration keys only: the integration's own identifier of that person, 120 characters max. |
Réponse : note: The created note (201), with the fields of GET .../notes.
DELETE /api/v1/conversations/<conversation_id>/notes/<note_id>
Delete an internal note (its author, or a member whose profiles in the organization of the conversation give members.manage or messages.delete: the Admin and Manager profiles; or a platform or domain administrator). A restricted API key deletes only its owner's notes.
Accès : permission view_team_conversations (devenue messages.read)
Paramètres de chemin
| Nom | Description |
|---|---|
conversation_id |
Conversation ID. |
note_id |
Note ID. |
Réponse : success: True.
PUT /api/v1/conversations/<conversation_id>/notes/<note_id>
Edit the text of an internal note (its author only).
An integration key edits the notes written through it; the integration checks external_author on its side. Delivers conversation.note with change "updated" to the integrations.
Accès : permission view_team_conversations (devenue messages.read)
Paramètres de chemin
| Nom | Description |
|---|---|
conversation_id |
Conversation ID. |
note_id |
Note ID. |
Corps de la requête
| Nom | Description |
|---|---|
body |
New note text (required, 4000 characters max after trimming spaces): 400 validation_error when missing, empty or longer. |
Réponse : note: The updated note, with the fields of GET .../notes. 403 forbidden for a note of another author, 404 not_found for an unknown or deleted note.
Numéros de téléphone
Registre des numéros et assignation aux équipes.
GET /api/v1/phone-numbers
List the phone numbers of the current organization (E09).
With an API key, only the numbers assigned to the teams the key reaches are listed (a number without a team is left out). A restricted key (limited to a team, or an integration's key) gets the numbers of its teams, whatever organization its owner currently works in.
Accès : authentifié (JWT ou clé API)
Paramètres de requête
| Nom | Description |
|---|---|
status |
active, paused, released (the trash of the organization, E11) or pending (order not yet delivered); absent = active, paused and pending. |
q |
Text searched in the label and in the digits of the number. |
skip |
Pagination offset (default 0). |
limit |
Page size (default 50, at most 100). |
Réponse : phone_numbers: The page of numbers (registry fields, plus usage, use_label_as_caller_id, state {status, enabled, sleep, released_at, expires_at}, features {voicemail, recording, sms {available, enabled}}, routing, type, area_code, city, region, order, team_name). total: Number of numbers matching the filters. skip: Offset applied. limit: Page size applied. counts: Numbers by status after the text filter: {active, paused, released, pending}.
GET /api/v1/phone-numbers/<phone_id>
Get phone number details.
With an API key, only a number assigned to a team the key reaches (403 otherwise); for a restricted key, whatever organization its owner currently works in.
Accès : authentifié (JWT ou clé API)
Paramètres de chemin
| Nom | Description |
|---|---|
phone_id |
Phone number ID. |
Réponse : phone_number: The number.
PATCH /api/v1/phone-numbers/<phone_id>
Change the attributes of a number of the organization (the fields given change).
Not available to API keys. Errors are {error, message}: 400 validation_error with field (label longer than 24 characters, usage, sms_enabled on a number without SMS or without a team to put it in), 403, 404, 409 conflict (number in the trash).
Accès : permission numbers.manage dans l'organisation (profils)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
phone_id |
Phone number ID. |
Corps de la requête
| Nom | Description |
|---|---|
label |
Name of the number, at most 24 characters, no control character or line break (empty = the number itself). |
usage |
business or personal. |
sms_enabled |
true puts the number in the default team of the organization (when it has none) and switches SMS on; false takes it out of its team. |
Réponse : phone_number: The number after the change (same object as GET /phone-numbers/<id>).
PUT /api/v1/phone-numbers/<phone_id>
Switch a phone number on or off (is_active), for the platform only.
An inactive number is not charged the monthly fees, so only the platform permissions (platform_admin, domain_admin, acting for the organization with the X-Admin-Organization header) may write is_active; any other member, even with numbers.manage, is refused (403 forbidden) and uses PATCH /phone-numbers/<id> (sms_enabled, label, usage) or releases the number instead. The number must belong to the current organization (403 forbidden otherwise). A request without is_active changes nothing.
Accès : permission numbers.manage dans l'organisation (profils)
Paramètres de chemin
| Nom | Description |
|---|---|
phone_id |
Phone number ID. |
Corps de la requête
| Nom | Description |
|---|---|
is_active |
Whether number is active, a boolean (400 validation_error, field is_active, otherwise). |
Réponse : phone_number: The updated number. message: Confirmation.
POST /api/v1/phone-numbers/<phone_id>/assign
Assign phone number to a team.
Accès : permission numbers.manage dans l'organisation (profils)
Paramètres de chemin
| Nom | Description |
|---|---|
phone_id |
Phone number ID. |
Corps de la requête
| Nom | Description |
|---|---|
team_id |
Team ID to assign (null to unassign). |
Réponse : phone_number: The number after the change. message: Confirmation (assigned, or unassigned when team_id is null).
GET /api/v1/phone-numbers/<phone_id>/prices
Prices of a number of the organization: installation, monthly fee, options, forwarding per minute.
Amounts come from the pricing plan of the organization (rates did_setup, did_monthly, feature_, voice_minute_forward), the number's own prices winning for the installation and the monthly fee. Only exposes prices: the monthly billing is separate. Not available to API keys.
Accès : authentifié (JWT ou clé API)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
phone_id |
Phone number ID. |
Réponse : prices: {currency, setup, monthly, features {voicemail, recording, sms, flow}, per_minute {forward}}.
GET /api/v1/phone-numbers/<phone_id>/provider
Get SMS provider info for a phone number (read-only: the domain administrator manages the provider).
Accès : permission numbers.manage dans l'organisation (profils)
Paramètres de chemin
| Nom | Description |
|---|---|
phone_id |
Phone number ID. |
Réponse : provider: The provider name (the configuration is managed by the domain administrator). message: A reminder that the domain administrator manages it.
POST /api/v1/phone-numbers/<phone_id>/release
Release a number: it goes to the trash of the organization (E11).
The number leaves the organization (no team, no billing, no calls) but stays RESERVED for it until state.expires_at (the domain's trash_days, 10 days by default; 0 = given back to the domain pool at once), and can be restored until then. Its routing is switched off, not deleted. Not available to API keys. Errors are {error, message}: 403, 404, 409 conflict (already in the trash, access number, order not delivered).
Accès : permission numbers.manage dans l'organisation (profils)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
phone_id |
Phone number ID. |
Réponse : phone_number: The number, with state.status released, state.released_at and state.expires_at.
POST /api/v1/phone-numbers/<phone_id>/restore
Take a number back out of the trash (E11).
The number returns to the organization with its label, usage, voice settings and routing, and to its team (the default team when that team is gone). Nothing is charged here. Not available to API keys. Errors are {error, message}: 402 insufficient_balance (with balance and required: the balance does not cover the monthly fee of the number), 403, 404 (not in the trash of this organization), 409 expired (the reservation has ended) or conflict.
Accès : permission numbers.manage dans l'organisation (profils)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
phone_id |
Phone number ID. |
Réponse : phone_number: The restored number.
GET /api/v1/phone-numbers/unassigned
List unassigned phone numbers.
Not available to restricted API keys (a key limited to a team, or an integration's key): 403 with error forbidden.
Accès : authentifié (JWT ou clé API)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Réponse : phone_numbers: The numbers of the organization that no team uses. total: Number of numbers.
Achat de numéros
Recherche de numéros disponibles, prix et acquisition (permission numbers.buy).
POST /api/v1/phone-numbers/acquire
Buy a number: installation fee and first month are debited from the credits.
Either a number from the search results, or criteria (the first match is bought). The number goes to the default team of the organization with SMS on. Two transactions (installation, first month) appear in the history. A number ordered from a provider that is not delivered at once comes back with 202 and pending true: it is delivered later (or refunded and removed if the provider fails). Not available to API keys. Errors are {error, message}: 400 validation_error / country_unsupported, 402 insufficient_balance (with balance and required, nothing is charged), 409 number_unavailable, 502/503 provider errors.
Accès : permission numbers.buy dans l'organisation (profils)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Corps de la requête
| Nom | Description |
|---|---|
number |
Number from the search results (E.164); or |
country |
Country (required without number). |
type |
local or tollfree. |
area_code |
Area code. |
city |
City. |
state |
Province or state. |
Réponse : phone_number: The number bought (same object as GET /phone-numbers/<id>). balance: Credit balance after the debit. pending: True while the order awaits the provider's delivery.
GET /api/v1/phone-numbers/available
Search the numbers that can be bought, with their prices for the organization.
Sources: the domain pool first, then (when the domain uses a number provider) its API if the pool has nothing that matches. Not available to API keys. Errors are {error, message}: 400 validation_error with field, 502/503 provider_error / provider_unavailable.
Accès : permission numbers.buy dans l'organisation (profils)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de requête
| Nom | Description |
|---|---|
country |
ISO 3166-1 alpha-2 country, required (CA, US...). |
type |
local or tollfree. |
state |
Province or state. |
city |
City (part of the name). |
area_code |
Area code (3 digits). |
skip |
Offset (default 0). |
limit |
Page size (default 25, at most 25). |
Réponse : results: List of {number, type, country, state, city, area_code, setup_fee, price_per_month, currency, capabilities}. count: Number of numbers matching (all pages).
GET /api/v1/phone-numbers/available/countries
Countries and number types the domain sells (pool and number provider merged).
Not available to API keys. Errors are {error, message}: 502/503 when the number provider fails and the pool has nothing.
Accès : permission numbers.buy dans l'organisation (profils)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Réponse : countries: List of {code, name, types} where types are local and/or tollfree.
GET /api/v1/phone-numbers/available/regions
Provinces and cities in which numbers can be bought (free numbers of the pool and the number provider), to fill the state and city of GET /available.
A number provider that sells by region (the platform's today) answers only a search that names a province, a city or an area code. Not available to API keys. Errors are {error, message}: 502/503 when the number provider fails and the pool has nothing.
Accès : permission numbers.buy dans l'organisation (profils)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Réponse : regions: [{code (province or state), cities: [name, ...]}], sorted.
Portage de numéros
Porter ses numéros d'un autre transporteur : vérification, demande, suivi, correction après un refus, annulation (permission numbers.buy, quand le domaine le permet).
POST /api/v1/phone-numbers/port/check
Check whether numbers can be ported in, before a request.
Not available to API keys. Errors are {error, message}: 403 port_not_enabled, 502/503 when the provider fails.
Accès : permission numbers.buy dans l'organisation (profils)
Corps de la requête
| Nom | Description |
|---|---|
numbers |
1 to 50 Canadian numbers (required). |
Réponse : results: [{number, portable, reason (already_here: the number is already on the platform, in_progress: another request has it, not_portable), carrier (current carrier), rate_center}].
GET /api/v1/phone-numbers/port/config
Whether the organization may port numbers in, the earliest date it may ask and what a number costs.
Not available to API keys.
Accès : permission numbers.buy dans l'organisation (profils)
Réponse : enabled: The domain lets its organizations port their numbers in. earliest_due_date: The first date a request may ask (YYYY-MM-DD; the carriers need 5 business days). provinces: Provinces of the service address. max_numbers: Numbers in one request at most. fees: {port_fee, monthly, currency}: what one number costs, debited when its port completes (null when not enabled). balance: Credit balance of the organization.
GET /api/v1/port-requests
The port requests of the organization, newest first.
Not available to API keys.
Accès : permission numbers.buy dans l'organisation (profils)
Paramètres de requête
| Nom | Description |
|---|---|
state |
open (every request not completed nor cancelled) or a state (submitted, carrier_pending, confirmed, rejected, cancel_requested, cancelled, completed). |
skip |
Offset (default 0). |
limit |
Page size (default 50, at most 100). |
Réponse : port_requests: List of requests (as GET /port-requests/<id>, without events). total: Requests matching. skip, limit: The page.
POST /api/v1/port-requests
Ask to port numbers in: they move to the organization when the current carrier lets them go.
The request goes to the carriers at once. Nothing is debited now: the fee of the port and the first month of each number are debited when it arrives; the balance must cover them when asking. Follow it with GET /port-requests/<id> (the event port_update tells each change in real time; an e-mail is sent when it is confirmed, refused, completed or cancelled). Not available to API keys. Errors are {error, message}: 400 validation_error with field, 402 insufficient_balance (with balance and required), 403 port_not_enabled, 409 number_not_portable (with numbers: the results of the check that failed), 422 port_refused (the provider refused the request), 502/503.
Accès : permission numbers.buy dans l'organisation (profils)
Corps de la requête
| Nom | Description |
|---|---|
numbers |
1 to 50 Canadian numbers, held by the same holder (required). |
full_name |
Name (person or company) on the account at the current carrier (required). |
street_number |
Civic number of the service address (required). |
street_name |
Street of the service address (required). |
floor |
Floor. |
suite |
Suite. |
city |
City (required). |
province |
Province code: AB, BC, MB, NB, NL, NS, NT, NU, ON, PE, QC, SK or YT (required). |
postal_code |
Canadian postal code (required). |
account_number |
Account number at the current carrier (required). |
current_carrier |
Name of the current carrier. |
due_date |
Wanted date, YYYY-MM-DD, a weekday from earliest_due_date of GET /phone-numbers/port/config (required). |
comments |
Note for the carriers (up to 500 characters). |
Réponse : port_request: The request (as GET /port-requests/<id>).
GET /api/v1/port-requests/<request_id>
A port request and its history.
Not available to API keys.
Accès : permission numbers.buy dans l'organisation (profils)
Paramètres de chemin
| Nom | Description |
|---|---|
request_id |
Port request ID. |
Réponse : port_request: {id, state (submitted, carrier_pending: at the current carrier, confirmed: date set, rejected: to correct and send again, cancel_requested, cancelled, completed), scope (organization or pool), organization_id, organization_name, domain_id, numbers [{number, state (pending, carrier_pending, confirmed, rejected, cancelled, ported), carrier, rate_center, due_date (confirmed date), rejection [{code, message, remarks, hint}], phone_id (once ported), ported_at}], holder {full_name, street_number, street_name, floor, suite, city, province, postal_code, account_number, current_carrier}, due_date, confirmed_date, comments, fees {port_fee, monthly, currency}, rejection_hint (holder_mismatch, not_portable, conflicting_order, due_date or other), attention {at, number, message} (the request changed at the carrier), cancel_request {at, reason, refused_at, refused_reason}, attempts, submit_uncertain, created_at, updated_at, completed_at, events [{kind (request or provider), event, number, due_date, at, rejection, remarks, reason}]}.
POST /api/v1/port-requests/<request_id>/cancel
Cancel a port request that is not completed.
When the provider cannot cancel by itself, the request becomes cancel_requested: the platform asks the provider and closes it (or tells why it could not: the port goes on). Not available to API keys. Errors are {error, message}: 409 conflict (completed, cancelled or already asked).
Accès : permission numbers.buy dans l'organisation (profils)
Paramètres de chemin
| Nom | Description |
|---|---|
request_id |
Port request ID. |
Corps de la requête
| Nom | Description |
|---|---|
reason |
Why (up to 300 characters). |
Réponse : port_request: The request (as GET /port-requests/<id>).
POST /api/v1/port-requests/<request_id>/resubmit
Send a refused request again, corrected. The fields given change; the others are kept.
Not available to API keys. Errors are {error, message}: 400 validation_error, 409 conflict (the request is not refused), 422 port_refused, 502/503.
Accès : permission numbers.buy dans l'organisation (profils)
Paramètres de chemin
| Nom | Description |
|---|---|
request_id |
Port request ID. |
Corps de la requête
| Nom | Description |
|---|---|
full_name |
As POST /port-requests. |
street_number |
As POST /port-requests. |
street_name |
As POST /port-requests. |
floor |
As POST /port-requests. |
suite |
As POST /port-requests. |
city |
As POST /port-requests. |
province |
As POST /port-requests. |
postal_code |
As POST /port-requests. |
account_number |
As POST /port-requests. |
current_carrier |
As POST /port-requests. |
due_date |
As POST /port-requests. |
comments |
As POST /port-requests. |
Réponse : port_request: The request (as GET /port-requests/<id>).
Portage de numéros (domaine)
Les demandes de portage des organisations du domaine, et le portage de numéros vers la banque du domaine (admin de domaine).
GET /api/v1/domains/<domain_id>/port-config
Whether the domain can port numbers into its pool, from when, at what cost.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : enabled: A number provider of the domain ports numbers in. organizations_enabled: The organizations may port their own numbers (port_in_enabled of PUT /domains/<id>/did-provider). earliest_due_date, provinces, max_numbers: As GET /phone-numbers/port/config. fees: {port_fee, monthly, currency}: the provider's cost of one number ported into the pool.
GET /api/v1/domains/<domain_id>/port-requests
Every port request of the domain: its organizations' and its own (into the pool), newest first.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Paramètres de requête
| Nom | Description |
|---|---|
state |
open or a state (as GET /port-requests). |
scope |
organization or pool. |
organization_id |
Only this organization. |
skip |
Offset (default 0). |
limit |
Page size (default 50, at most 100). |
Réponse : port_requests: As GET /port-requests. total, skip, limit: The page.
POST /api/v1/domains/<domain_id>/port-requests
Port numbers into the pool of the domain (they arrive free, to sell or assign). Journalled.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
numbers |
As POST /port-requests. |
full_name |
As POST /port-requests. |
street_number |
As POST /port-requests. |
street_name |
As POST /port-requests. |
floor |
As POST /port-requests. |
suite |
As POST /port-requests. |
city |
As POST /port-requests. |
province |
As POST /port-requests. |
postal_code |
As POST /port-requests. |
account_number |
As POST /port-requests. |
current_carrier |
As POST /port-requests. |
due_date |
As POST /port-requests. |
comments |
As POST /port-requests. |
Réponse : port_request: The request (as GET /port-requests/<id>).
Errors 400 validation_error. 404 port_unavailable (no number provider of the domain ports numbers in). 409 number_not_portable. 422 port_refused. 502/503.
GET /api/v1/domains/<domain_id>/port-requests/<request_id>
A port request of the domain and its history (as GET /port-requests/<id>).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
request_id |
Port request ID. |
Réponse : port_request: As GET /port-requests/<id>.
POST /api/v1/domains/<domain_id>/port-requests/<request_id>/cancel
Cancel a port request of the domain (as POST /port-requests/<id>/cancel).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
request_id |
Port request ID. |
Corps de la requête
| Nom | Description |
|---|---|
reason |
Why (up to 300 characters). |
Réponse : port_request: As GET /port-requests/<id>.
POST /api/v1/domains/<domain_id>/port-requests/<request_id>/resubmit
Send a refused request of the domain again, corrected (as POST /port-requests/<id>/resubmit).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
request_id |
Port request ID. |
Corps de la requête
| Nom | Description |
|---|---|
full_name |
As POST /port-requests. |
street_number |
As POST /port-requests. |
street_name |
As POST /port-requests. |
floor |
As POST /port-requests. |
suite |
As POST /port-requests. |
city |
As POST /port-requests. |
province |
As POST /port-requests. |
postal_code |
As POST /port-requests. |
account_number |
As POST /port-requests. |
current_carrier |
As POST /port-requests. |
due_date |
As POST /port-requests. |
comments |
As POST /port-requests. |
Réponse : port_request: As GET /port-requests/<id>.
POST /api/v1/domains/<domain_id>/port-requests/check
Check whether numbers can be ported in (as POST /phone-numbers/port/check).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
numbers |
1 to 50 Canadian numbers (required). |
Réponse : results: As POST /phone-numbers/port/check.
Pool et fournisseur de numéros
Numéros que le domaine vend (ajout unitaire ou par lot, ou achat au fournisseur de numéros), d'où viennent les numéros achetés par les organisations, corbeille (admin de domaine).
GET /api/v1/domains/<domain_id>/did-provider
Where the numbers the organizations buy come from, the number providers the domain may use, and the trash settings.
The number providers are configured by the platform (GET /api/v1/system/number-providers); a domain admin only sees each one's id and description, never its name.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : did_provider: {type (pool, provider or hybrid), account_id (the number provider chosen; null = the first one available), countries, pricing {mode (plan or cost_plus), setup_markup_percent, monthly_markup_percent}, import_unknown_stock, port_in_enabled, webhook_secret (masked), api_configured (a number provider is available)}. providers: The number providers the domain may use: [{id, description, capabilities {prices, release, tollfree_order, regions, sms_toggle, synchronous_orders, port_in, port_cancel}}] (name only for a super admin). numbers: {trash_days, release_to_provider, default_voice_trunk_id}.
PUT /api/v1/domains/<domain_id>/did-provider
Change where the numbers the organizations buy come from (the fields given change).
Switching back to pool changes nothing for the numbers already bought. Journalled.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
type |
pool (default: the organizations buy the numbers of the pool), provider (they buy straight from the number provider), or hybrid (pool first, the provider when the pool has nothing that matches). provider and hybrid need a number provider available for the domain (400 validation_error otherwise). |
account_id |
The number provider to use (an id of providers in GET); null = the first one available. |
countries |
Countries offered. |
pricing |
{mode: plan (price list of the pricing plan) or cost_plus (provider cost plus markup), setup_markup_percent, monthly_markup_percent}. |
import_unknown_stock |
Add to the pool the numbers the provider holds for the domain and we do not know (default false). |
port_in_enabled |
The organizations may port their own numbers in, through the domain's number provider (default false; the domain admin can always port numbers into the pool). |
webhook_secret |
Secret in the delivery webhook URL /webhook/numbers/<webhook_secret>, for a provider that delivers its orders later: 16 to 128 characters among letters, digits, - and _. |
trash_days |
Days a released number stays reserved for its organization, 0 to 365 (default 10; 0 = given back at once). |
release_to_provider |
Give a number bought from the provider back to it when it returns to the pool, when the provider can take it back (default true). |
default_voice_trunk_id |
Voice trunk of the domain given to the numbers customers acquire (they then reach the voice service: voice_enabled); null leaves the voice to the domain admin, number by number. |
Réponse : did_provider: The settings after the change (as GET). providers: As GET. numbers: {trash_days, release_to_provider, default_voice_trunk_id}.
POST /api/v1/domains/<domain_id>/did-provider/sync
Follow the number provider now (the periodic task does the same).
Follows the pending orders (delivery or refund), retries the releases that failed and reconciles the stock the provider holds with the numbers we know (missing and unknown numbers are reported; unknown ones are added to the pool only when import_unknown_stock is on).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
stock |
false skips the stock reconciliation (default true). |
import_unknown |
true or false overrides import_unknown_stock for this run. |
Réponse : result: {orders {followed, delivered, failed, pending}, stuck, releases {retried, released, failed}, stock ({provider_count, our_count, missing_at_provider, unknown_at_provider, imported} or null), error ({code, message} or null)}.
POST /api/v1/domains/<domain_id>/did-provider/test
Check the connection to the number provider of the domain (one cheap read call).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : ok: Whether the provider answered and accepted the credentials. message: What happened. provider: "provider" when a number provider was tested, "pool" when the domain has none.
GET /api/v1/domains/<domain_id>/number-market/available
Numbers the number provider sells now, at its cost, to buy them for the pool.
The provider is asked live: a number shown may be taken by someone else before it is ordered. Numbers we already hold are left out. At least a province, a city or an area code is needed with a provider that sells by region (an empty list otherwise).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Paramètres de requête
| Nom | Description |
|---|---|
state |
Province (code, e.g. QC). |
city |
City (rate center), its start is enough, without case nor accents. |
area_code |
Area code (3 digits) or area code and exchange (6 digits). |
account_id |
The number provider; default: the domain's. |
limit |
Numbers returned, 1 to 25 (default 25). |
Réponse : results: [{number, type, country, state, city, area_code, setup_cost, monthly_cost, currency, capabilities}]. count: Numbers found.
Errors 404 provider_not_configured. 502/503: the provider failed.
POST /api/v1/domains/<domain_id>/number-market/order
Buy numbers from the number provider into the pool of the domain.
Each number is ordered on its own; the ones the provider delivers enter the pool (status free), bound to the provider's SMS account (SMS turned on at the provider) and to its voice trunk when the domain may use it, at the provider's cost (setup and monthly cost recorded on the number). Nothing is debited here: the domain then sells them to its organizations or assigns them. Journalled.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Limite : 30 per hour
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
numbers |
1 to 10 E.164 numbers from GET .../number-market/available (required). |
account_id |
The number provider; default: the domain's. |
Réponse : results: [{number, status: ordered (in the pool), pending (the provider delivers later), unavailable (taken meanwhile, or already ours) or failed, phone_id, message}]. ordered: How many entered the pool.
Errors 400 validation_error. 404 provider_not_configured.
GET /api/v1/domains/<domain_id>/number-market/regions
Provinces and cities in which the number provider sells numbers (to search them).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Paramètres de requête
| Nom | Description |
|---|---|
account_id |
The number provider (an id of providers in GET /domains/<id>/did-provider); default: the domain's. |
Réponse : regions: [{code (province), cities: [name, ...]}], sorted.
Errors 404 provider_not_configured: no number provider for the domain. 502/503: the provider failed.
GET /api/v1/domains/<domain_id>/number-pool
The numbers of the domain with their pool status and prices.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Paramètres de requête
| Nom | Description |
|---|---|
status |
free, assigned, trash, held, pending, returned or access; absent = all. |
country |
Only this country. |
type |
local or tollfree. |
q |
Text searched in the number, the label and the city. |
skip |
Pagination offset (default 0). |
limit |
Page size (default 50, at most 100). |
Réponse : numbers: The page (registry fields plus pool_status, source, type, area_code, city, region, country, capabilities, pricing {setup_fee, monthly_fee} (null = the pricing plan decides), organization_name, released_at, expires_at). total: Number matching the filters. skip: Offset applied. limit: Page size applied. counts: Numbers by pool status, whatever the filters.
POST /api/v1/domains/<domain_id>/number-pool
Add one number to the pool of the domain.
Errors are {error, message}: 400 validation_error with field (not an E.164 number, bad attribute), 409 conflict (the number already exists, in this domain or another).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
number |
Phone number (E.164, or North American digits). |
country |
ISO 3166-1 alpha-2 country (deduced for +1 numbers). |
type |
local or tollfree (deduced from the area code). |
area_code |
Area code (deduced for +1 numbers). |
city |
City. |
region |
Province or state. |
capabilities |
List of voice, sms (both by default). |
setup_fee |
Installation price charged to the buyer (null or absent = the pricing plan). |
monthly_fee |
Monthly price charged to the buyer (null or absent = the pricing plan). |
label |
Label of the number (the number by default). |
Réponse : phone_number: The number added (pool view).
DELETE /api/v1/domains/<domain_id>/number-pool/<phone_id>
Remove a number from the pool, as long as nobody holds it.
A number bought by an organization, held in its trash or in reserve (balance limbo) or whose order is pending cannot be removed (409 number_in_use). A number ordered from the provider is given back to it first (502/503 and nothing removed if the provider fails), unless release_at_provider is false.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
phone_id |
Phone number ID. |
Paramètres de requête
| Nom | Description |
|---|---|
release_at_provider |
false keeps the number at the provider and only removes it here (default true). |
Réponse : message: Result message.
PUT /api/v1/domains/<domain_id>/number-pool/<phone_id>
Change the attributes and prices of a number of the pool (the fields given change).
Prices apply to the next purchase; a number already bought keeps what its owner paid.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
phone_id |
Phone number ID. |
Corps de la requête
| Nom | Description |
|---|---|
country |
ISO 3166-1 alpha-2 country. |
type |
local or tollfree. |
area_code |
Area code. |
city |
City. |
region |
Province or state. |
capabilities |
List of voice, sms. |
setup_fee |
Installation price (null = the pricing plan). |
monthly_fee |
Monthly price (null = the pricing plan). |
label |
Label. |
Réponse : phone_number: The number after the change (pool view).
POST /api/v1/domains/<domain_id>/number-pool/batch
Add many numbers to the pool in one request (JSON list or CSV).
Every row is validated on its own: the valid rows are added, the others are reported (a number already known is skipped, never an error). At most 5000 rows. Send JSON {numbers: [...]} where an item is an E.164 string or an object with the single-number fields, optionally {defaults: {...}} applied to every item; or {csv: "<text>"}; or the CSV itself (Content-Type text/csv, or a multipart file). CSV columns: number, country, type, area_code, city, region (or state, province), setup_fee, monthly_fee, capabilities (voice|sms), label; comma or semicolon separated, with a header.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Paramètres de requête
| Nom | Description |
|---|---|
dry_run |
1 validates and counts without writing (CSV bodies; JSON uses the dry_run field). |
Corps de la requête
| Nom | Description |
|---|---|
numbers |
List of numbers (strings or objects). |
defaults |
Fields applied to every item (country, type, city, region, capabilities, setup_fee, monthly_fee). |
csv |
The CSV text, instead of numbers. |
dry_run |
true validates without writing. |
Réponse : created: Numbers added. skipped: List of {row, number, reason} (already_exists, duplicate_in_batch). errors: List of {row, number, field, message} for the invalid rows. total: Rows received. dry_run: Whether nothing was written. phone_numbers: The first 100 numbers added (pool view).
POST /api/v1/domains/<domain_id>/number-trash/purge
Give back to the pool, now, the numbers of the domain whose reservation has ended (the periodic task does the same).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : result: {examined, returned}.
Webhook du fournisseur de numéros
Livraison des commandes de numéros par le fournisseur (public, jeton dans l'URL).
POST /webhook/numbers/<token>
Delivery webhook of the number provider: an order was delivered or failed.
Public; the URL token is the domain's webhook_secret (settings.voice.did_provider). The body is read by the provider adapter ({order_id, status, number, message}). A delivered order puts the number in service; a failed one refunds the buyer. Unknown tokens, orders and replays answer 200 without doing anything.
Accès : public (aucune authentification)
Limite : 120 per minute
Paramètres de chemin
| Nom | Description |
|---|---|
token |
The domain's webhook_secret. |
Réponse : processed: Number of order updates applied.
Fournisseurs SMS
Fournisseurs disponibles, schémas de configuration, configuration par équipe.
GET /api/v1/providers
List available SMS providers.
Accès : authentifié (JWT ou clé API)
Paramètres de requête
| Nom | Description |
|---|---|
lang |
Language for descriptions (fr, en). |
Réponse : List of available providers with basic info.
GET /api/v1/providers/<provider_name>/guide
Get configuration guide for a provider.
Accès : permission configure_provider (devenue teams.provider)
Paramètres de chemin
| Nom | Description |
|---|---|
provider_name |
Provider type (one of GET /api/v1/providers). |
Paramètres de requête
| Nom | Description |
|---|---|
lang |
Language (fr, en). |
Réponse : Step-by-step configuration guide.
GET /api/v1/providers/<provider_name>/schema
Get configuration schema for a provider.
Accès : permission configure_provider (devenue teams.provider)
Paramètres de chemin
| Nom | Description |
|---|---|
provider_name |
Provider type (one of GET /api/v1/providers). |
Réponse : Configuration schema with required fields.
DELETE /api/v1/providers/teams/<team_id>/provider
Remove provider configuration from a team.
Accès : accès à l'équipe (team_id); permission configure_provider (devenue teams.provider)
Paramètres de chemin
| Nom | Description |
|---|---|
team_id |
Team ID. |
Réponse : Success message.
GET /api/v1/providers/teams/<team_id>/provider
Get current provider configuration for a team.
Accès : accès à l'équipe (team_id); permission configure_provider (devenue teams.provider)
Paramètres de chemin
| Nom | Description |
|---|---|
team_id |
Team ID. |
Réponse : provider: The provider name (null when none). config: The provider configuration, secrets masked. configured: Whether a provider is set.
PUT /api/v1/providers/teams/<team_id>/provider
Save provider configuration for a team.
Accès : accès à l'équipe (team_id); permission configure_provider (devenue teams.provider)
Paramètres de chemin
| Nom | Description |
|---|---|
team_id |
Team ID. |
Corps de la requête
| Nom | Description |
|---|---|
provider |
Provider type (one of GET /api/v1/providers). |
config |
Provider configuration. |
test_first |
Whether to test connection before saving (default: true). |
Réponse : team: The team, with its provider configuration. message: Confirmation.
Errors 400 validation_error: provider missing or unknown. 400 connection_error / test_failed: the test of the connection failed, nothing is saved (success false). Skip the test with test_first false.
POST /api/v1/providers/test
Test provider connection with given configuration.
Accès : permission configure_provider (devenue teams.provider)
Corps de la requête
| Nom | Description |
|---|---|
provider |
Provider type (one of GET /api/v1/providers). |
config |
Provider configuration. |
Réponse : success: Whether the connection works. message: Confirmation, or the reason of the failure.
Errors 400 validation_error: provider missing. 400 configuration_error: unknown provider or invalid configuration (success false). 400 connection_error: the provider refused the connection, with its code (success false). 500 unknown_error: unexpected failure (success false).
Invitations
Invitations d'utilisateurs dans une organisation.
GET /api/v1/invitations
List invitations for current organization.
Accès : permission members.admin dans l'organisation (profils)
Réponse : invitations: The invitations of the current organization. total: Number of invitations.
POST /api/v1/invitations
Create a new invitation.
Session (JWT) only: a request authenticated by an API key, ordinary or restricted, gets 403 (error forbidden, message "API key not allowed on this route"). An invitation is a way to sign in, and a key never hands one out. The body must be a JSON object (else 400 validation_error).
The answer is the same whether the address already has an account or not (it tells nothing about the address, and returns no profile): an invitation is created and e-mailed, valid 7 days. An address with no account follows the link to create its account (POST /auth/register). An address that already has an account is never attached to the organization by the invitation alone: the account accepts it itself, signed in, with POST /api/v1/invitations/accept (the link of its e-mail leads to the page that does it). Until then the invitation shows among the pending ones, can be resent and cancelled.
The e-mail goes through the SMTP server of the organization's domain and links to that domain's first production hostname, whatever X-Frontend-Host (the platform's server and FRONTEND_URL for an organization without domain).
An organization sends at most 50 invitations per hour (429 rate_limited, config RATELIMIT_INVITATIONS), counted for the organization, not for each sender.
An invitee who will use the messages (messages.read or messages.send among the permissions of the profiles) takes a place of the organization's messaging plan: beyond its max_users, 402 plan_limit with limit (users), plan, upgrade (the first plan with room, or null) and current (members who use the messages and pending invitations). The same check applies to every way such a member joins (acceptance of the invitation, POST /api/v1/users, a role or profiles changed), for every caller, platform and domain administrators included.
Accès : permission members.admin dans l'organisation (profils)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Corps de la requête
| Nom | Description |
|---|---|
email |
Invitee email address (a valid address, at most 254 characters; else 400 validation_error with field email). |
role |
Role for the new user (optional, default: agent; admin, manager or agent). Former way: prefer profile_ids. |
profile_ids |
Profiles the member gets (optional; ids of GET /api/v1/organizations/current/profiles, they add up); when given, the role follows them. |
team_id |
Team ID to assign (optional; a team of the organization). |
organization_id |
Organization to invite into (optional; only a platform_admin, or a domain_admin of its domain, may name an organization other than their own; a string, else 400 validation_error). |
Réponse : invitation: The invitation, with its token (shown here only). message: Confirmation. email_sent: Whether the e-mail went out (201). The answer is the same whether or not the address already belongs to an account (no profile is returned and nobody is added): an existing account accepts the invitation itself with POST /invitations/accept.
Errors 400 validation_error: email missing or invalid, role other than admin, manager or agent, profile_ids empty or naming a profile the organization cannot give (field profile_ids), team not in the organization, caller without organization, or an invitation of this organization to this address already pending. 402 plan_limit: the messaging plan has no room for one more user. 403 forbidden: organization_id of an organization the caller does not manage; or the role or the profiles give a permission the caller does not hold in the organization (field role or profile_ids; with members.manage alone, members.manage and members.admin are never given). 404 not_found: organization_id unknown. 409 conflict: the account is already a member of the organization.
DELETE /api/v1/invitations/<invitation_id>
Delete/cancel an invitation.
Accès : permission members.admin dans l'organisation (profils)
Paramètres de chemin
| Nom | Description |
|---|---|
invitation_id |
Invitation ID. |
Réponse : Success message.
POST /api/v1/invitations/<invitation_id>/resend
Resend an invitation email.
Session (JWT) only: a request authenticated by an API key, ordinary or restricted, gets 403 (error forbidden, message "API key not allowed on this route"), like the creation. The e-mail goes through the SMTP server of the organization's domain and links to that domain's first production hostname, whatever X-Frontend-Host (the platform's server and FRONTEND_URL for an organization without domain).
Accès : permission members.admin dans l'organisation (profils)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
invitation_id |
Invitation ID. |
Réponse : Success message.
Errors 404 not_found: invitation unknown. 403 forbidden: invitation of an organization the caller does not administer. 400 validation_error: already accepted. 500 configuration_error: no SMTP server for the domain. 500 email_error: the e-mail could not be sent.
POST /api/v1/invitations/accept
Accept, with an existing account, an invitation to an organization.
The account joins the organization with the profiles (else the role's profiles) and the team of the invitation: a membership is created, nothing else of the account changes (its current organization and team stay). Switch to the organization afterwards (POST /api/v1/organizations/switch). Only the owner of the invited address, signed in with that very account, can accept: the token alone, held by the inviter for one, is not enough. An invitation is used once: the link opened again by its owner, already a member of the organization, answers 200 with already_member true (and creates nothing).
Session (JWT) only: a request authenticated by an API key gets 403 (error forbidden, message "API key not allowed on this route").
The body must be a JSON object (else 400 validation_error).
Accès : authentifié (JWT ou clé API)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Corps de la requête
| Nom | Description |
|---|---|
token |
Invitation token (from the link of the e-mail; a non-empty string, else 400 validation_error with field token). |
Réponse : organization: The organization joined. role: The role the profiles amount to there (admin, manager or agent; a copy). already_member: true when the invitation had already been accepted by this account, which is a member of the organization. message: Confirmation. 404 not_found, whatever the cause (unknown, expired, used or cancelled invitation, or an invitation for another address). 402 plan_limit: the invitation gives the messages (messages.read or messages.send) and the organization's messaging plan has no room left for one more user (its place was kept while it was pending: this happens when the plan changed since); {limit (users), plan, upgrade, current}. The invitation stays usable.
GET /api/v1/invitations/verify/<token>
Verify an invitation token.
Public. Rate limited by the default limits, counted per token, whatever the X-API-Key or Authorization headers.
Accès : public (aucune authentification)
Paramètres de chemin
| Nom | Description |
|---|---|
token |
Invitation token. |
Réponse : valid: true. email: The invited address. role: The role of the invitation (domain_admin for an invitation to administer a domain). organization_name: Name of the organization (of the domain for a domain admin invitation).
Errors 404 not_found: unknown or expired token. 410 invitation_accepted: the invitation was already accepted, the account exists: sign in (email in the answer, the address the link was sent to).
Utilisateurs
Gestion des utilisateurs de l'organisation.
GET /api/v1/users
List the users of the caller's organization.
A member who holds members.manage without members.admin (the former manager) only sees the users of their own team (none without a team); with members.admin, every user of their organizations (platform_admin: every account of the platform when no team is given; with the header X-Admin-Organization, the administered organization only). Super admins are never listed as members.
Accès : permission manage_users (devenue members.manage)
Paramètres de requête
| Nom | Description |
|---|---|
team_id |
Filter by team ID (must be a team the caller can access). |
skip |
Pagination offset (default 0). |
limit |
Pagination limit (default 50, max 100). A skip or limit that is not an integer is refused (400 validation_error). |
Réponse : users: The users of the page. total: Number of users before skip and limit. skip: Offset applied. limit: Page size applied.
POST /api/v1/users
Create a user in the caller's organization.
A member with members.manage without members.admin (the former manager) only creates agents, placed in their own team; with members.admin, any organization role. The member gets the profiles of the role (admin: Admin; manager: User and the organization's Manager profile; agent: User); PUT /api/v1/organizations/current/members/<id>/profiles changes them. super_admin and domain_admin cannot be created here (platform permissions: PUT /api/v1/users/<id>/platform-permissions). 400 validation_error when the address already has an account (an invitation never reveals it: prefer POST /api/v1/invitations).
Session (JWT) only: a request authenticated by an API key, ordinary or restricted, gets 403 (error forbidden, message "API key not allowed on this route"). An account, even without a password, is a way to sign in (password reset by e-mail, OAuth), and a key never hands one out. The body must be a JSON object (else 400 validation_error).
Accès : permission manage_users (devenue members.manage)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Corps de la requête
| Nom | Description |
|---|---|
email |
User email (a valid address, at most 254 characters; stored in lower case). |
name |
User display name (1 to 100 characters, no control characters). |
password |
User password (optional if sending invite). When given it follows the rule of PUT /auth/me/password (8 to 128 characters, at least 2 of lower case, upper case, digits and symbols, not a very common password, not the e-mail address); else 400 validation_error with field password. |
role |
Organization role, translated into profiles (admin, manager or agent; default agent; one the caller may give, else 403 forbidden). |
team_id |
Team ID (must belong to the organization). |
language |
Preferred language (fr or en, default fr; else 400 validation_error with field language). |
Réponse : Created user data.
Errors 402 plan_limit: the role gives the messages (messages.read or messages.send) and the organization's messaging plan has no room left for one more user (same check as POST /api/v1/invitations, for every caller): {limit (users), plan, upgrade (the first plan with room, or null), current (members who use the messages and pending invitations)}.
DELETE /api/v1/users/<user_id>
Delete a user of the caller's organization.
Accès : permission manage_users (devenue members.manage)
Paramètres de chemin
| Nom | Description |
|---|---|
user_id |
User ID. |
Réponse : Success message.
Errors 400 validation_error: the caller cannot delete themselves. 409 user_active: deactivate the user first. 403 forbidden: user out of reach. 403 member_of_other_organizations: the account also belongs to organizations the caller does not administer.
GET /api/v1/users/<user_id>
Get user details.
Users can view themselves; a member with members.manage without members.admin (the former manager) the agents of their own team; with members.admin, the users of a shared organization; platform_admin anyone, domain_admin the users of the organizations of their domains.
Accès : authentifié (JWT ou clé API); Yourself; members.admin: the members of a shared organization; members.manage: the members of their own team there who manage nobody (neither members.manage nor members.admin); in both cases only a member whose permissions there the caller holds too (checked in the function: 403 forbidden otherwise).
Paramètres de chemin
| Nom | Description |
|---|---|
user_id |
User ID. |
Réponse : User data.
PUT /api/v1/users/<user_id>
Update a user of the caller's organization.
The role and the team belong to ONE organization: the caller's (the user must be a member of it). The other fields belong to the whole account (every organization and every domain of the user): to change them on someone else the caller must administer every organization of the user (platform_admin, a domain_admin of all their domains, a member holding members.admin in all their organizations; with members.manage alone, an agent of their own team who belongs to no other organization), else 403 member_of_other_organizations. A value identical to the stored one is no change. The user changes their own profile freely, except the password (see below).
The body must be a JSON object (else 400 validation_error).
Accès : permission manage_users (devenue members.manage)
Clés API : refusées si le corps donne le rôle admin, domain_admin, super_admin; refusées si le corps contient password
Paramètres de chemin
| Nom | Description |
|---|---|
user_id |
User ID. |
Corps de la requête
| Nom | Description |
|---|---|
name |
New name (optional; a string of 1 to 100 characters without control characters, else 400 validation_error with field name). Account-wide. |
role |
New role in the caller's organization (optional; admin, manager or agent, never above the caller, never one's own): the member's profiles are replaced by those of the role (as POST /api/v1/users). An API key never gives the admin role: 403 (error forbidden, message "API key not allowed on this route"). |
is_active |
Activate or deactivate the user (optional; a caller who administers every organization of the user, never oneself). A deactivated user can no longer sign in and their sessions stop working. Account-wide. |
team_id |
New team (optional; a team of the caller's organization, null takes the user out of any team there). Written on the membership of that organization only; the user must be a member of it. |
language |
New language (optional; fr or en, else 400 validation_error with field language). Account-wide. |
permissions |
Permissions granted to the account on top of its profiles (optional; the only grantable one is support). Only a platform_admin, or a domain_admin who administers every organization of the user, may change the grants, never their own; the permissions of the role in the list are ignored, so a form echoing the whole user is fine. Anyone else changing them gets 403 forbidden, a name that cannot be granted 400 validation_error with field permissions. permission_grants is an alias with the same rule (it is not a free field). |
first_name |
New first name (optional, rewrites name). Account-wide. |
last_name |
New last name (optional, rewrites name). Account-wide. |
timezone |
IANA time zone (optional; null clears it). Account-wide. |
password |
New password of ANOTHER account (optional): a password reset by an administrator, who must administer every organization of the user and chooses a password that follows the rule of PUT /auth/me/password (else 400 validation_error with field password). Your own password is not changed here (403 forbidden with field password): use PUT /auth/me/password, which asks for the current password. Session (JWT) only: with an API key, a body with a password gets 403 (error forbidden, message "API key not allowed on this route"), whoever the user, the key owner included. |
Réponse : Updated user data.
Errors 402 plan_limit: the new role gives the messages (messages.read or messages.send) to a member who did not have them and the organization's messaging plan has no room left (same check as POST /api/v1/invitations, for every caller; nothing is changed): {limit (users), plan, upgrade, current}. 403 member_of_other_organizations: activating or deactivating the account, or setting the password of another user, reaches organizations the caller does not administer.
DELETE /api/v1/users/<user_id>/membership
Remove a member from the caller's organization, leaving the account alone.
The user leaves the caller's organization (the current one, or the one being administered with X-Admin-Organization) and nothing else: the account stays active in its other organizations, with its profile, password and sessions. Neither the activation of the account nor the administration of its other organizations is needed (unlike PUT /users/<id> with is_active and DELETE /users/<id>, which reach the whole account). The user's SIP endpoints of this organization are detached and disabled, and the default organization they had here is forgotten. When this was their current organization they move to another one of theirs.
A user whose only organization this is cannot be removed (409 only_organization): they are deactivated, then deleted. Never oneself (400 validation_error), never a member the caller may not act on there (403: members.admin, or members.manage for a member of their team who manages nobody, and every permission the member holds there held by the caller too), never a user who is not a member (404).
Accès : permission members.admin dans l'organisation (profils)
Paramètres de chemin
| Nom | Description |
|---|---|
user_id |
User ID. |
Réponse : Success message.
PUT /api/v1/users/<user_id>/role
Change the organization role of a user of the caller's organization.
Former way: the member's profiles are replaced by those of the role (admin: Admin; manager: User and the organization's Manager profile; agent: User); prefer PUT /api/v1/organizations/current/members/<id>/profiles. Never one's own role, never a role above the caller's (403 forbidden), never on a user who is not a member of the caller's organization (403).
The body must be a JSON object (else 400 validation_error).
Accès : permission members.admin dans l'organisation (profils)
Clés API : refusées si le corps donne le rôle admin, domain_admin, super_admin
Paramètres de chemin
| Nom | Description |
|---|---|
user_id |
User ID. |
Corps de la requête
| Nom | Description |
|---|---|
role |
New role (admin, manager, agent; required, else 400 validation_error). An API key never gives the admin role: 403 (error forbidden, message "API key not allowed on this route"). |
Réponse : Updated user data.
Errors 402 plan_limit: the role gives the messages to a member who did not have them and the organization's messaging plan has no room left (as POST /api/v1/invitations): {limit (users), plan, upgrade, current}.
PUT /api/v1/users/<user_id>/team
Assign a member of the caller's organization to one of its teams.
The team belongs to ONE organization, the caller's (the current one, or the one being administered with X-Admin-Organization): the user must be a member of it (else 403) and the team one of its teams that the caller may use (else 400 validation_error). The team is written on the membership of that organization, whatever the current organization of the user: an administrator never changes the team the user has in another organization.
The body must be a JSON object (else 400 validation_error).
Accès : permission members.admin dans l'organisation (profils)
Paramètres de chemin
| Nom | Description |
|---|---|
user_id |
User ID. |
Corps de la requête
| Nom | Description |
|---|---|
team_id |
Team ID (null to remove from any team). |
Réponse : user: The updated user. message: Confirmation.
Clés API
Clés pour l'accès programmatique.
GET /api/v1/keys
List API keys of the caller, or of a user they administer.
Accès : permission generate_api_keys (devenue api_keys.manage)
Paramètres de requête
| Nom | Description |
|---|---|
user_id |
Owner of the keys (optional, defaults to the caller). |
Réponse : api_keys: The keys (id, key_prefix, name, user_id, team_id, team_ids, integration_id, created_at, last_used_at, revoked), never the secret. total: Number of keys.
POST /api/v1/keys
Create a new API key for the caller.
JWT only: a request authenticated by an API key gets 403 (error forbidden, message "API key not allowed on this route"), so a key never mints another key. A key limited to a team acts as an agent of that team, whatever the role of its creator: it cannot administer anything nor read organization-wide data (billing, organization, members).
A key that is not limited to a team has the rights of its owner (an administrator): it is only minted with the CURRENT PASSWORD of the owner (current_password: 400 validation_error when missing, 401 invalid_password when wrong), so that a stolen access token (valid one hour) does not buy a key that outlives it. An account without a password (OAuth sign-in only) has none to give. Rate limited to 10 per hour per user (429 rate_limited): the password is guessed here otherwise. The key of a super_admin or a domain_admin goes no further than the administration of the owner's current organization.
A personal key is revoked when its owner changes the password or the e-mail address, resets the password, or is deactivated (the keys of an integration are not personal).
The body must be a JSON object: anything else (no body, a body that is not JSON, a list, a string...) gets 400 validation_error.
Accès : permission generate_api_keys (devenue api_keys.manage)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Limite : 10 per hour
Corps de la requête
| Nom | Description |
|---|---|
name |
Key name/description (required, a non-empty string, else 400 validation_error). |
team_id |
Restrict the key to this team (optional; the identifier, as a string, of a team the caller can access, else 400 validation_error). |
current_password |
The password of the caller, required for a key that is not limited to a team (see above; ignored otherwise). |
expires_in_days |
Lifetime of the key in days (optional, an integer from 1 to 365, else 400 validation_error with field expires_in_days; 90 when absent). Every key made here expires: it is refused (401) after expires_at, and a new one is made to replace it. |
Réponse : api_key: The key record (same fields as in the list), without the secret. key: The secret of the key, in plain text. It is in this answer only (201): store it now. message: Confirmation. warning: Reminder that the key will not be shown again.
DELETE /api/v1/keys/<key_id>
Revoke an API key.
Accès : permission generate_api_keys (devenue api_keys.manage)
Paramètres de chemin
| Nom | Description |
|---|---|
key_id |
API key ID. |
Réponse : message: Confirmation that the key is revoked.
GET /api/v1/keys/<key_id>
Get API key details.
Accès : permission generate_api_keys (devenue api_keys.manage)
Paramètres de chemin
| Nom | Description |
|---|---|
key_id |
API key ID. |
Réponse : api_key: The key record (same fields as in the list), without the secret.
POST /api/v1/keys/<key_id>/revoke
Revoke an API key (alias of DELETE).
Accès : permission generate_api_keys (devenue api_keys.manage)
Paramètres de chemin
| Nom | Description |
|---|---|
key_id |
API key ID. |
Réponse : message: Confirmation that the key is revoked.
Widget de site web
Configuration du formulaire à insérer sur un site web (permission integrations.manage).
GET /api/v1/widgets
List the website widgets of the current organization.
Accès : permission integrations.manage dans l'organisation (profils)
Réponse : widgets: List of widgets with their embed code. total: Number of widgets.
POST /api/v1/widgets
Create a website widget for the current organization.
Accès : permission integrations.manage dans l'organisation (profils)
Corps de la requête
| Nom | Description |
|---|---|
name |
Internal name of the widget. |
team_id |
Team that receives the conversations. |
phone_number |
Team number the welcome message is sent from. |
welcome_message |
Text sent to the visitor's mobile, in the default language. |
welcome_messages |
Welcome text per language, {fr, en} (optional; an empty language falls back to the default one). |
language |
Default language, fr or en (optional, default fr); the widget follows the page language (<html lang> or data-lang) when it is fr or en. |
texts |
Displayed texts per language, {fr: {title, description, button_label, success_message}, en: {...}} (optional; a flat object applies to the default language). |
accent_color |
Hex color of the button (optional). |
text_color |
Hex color of the button text (optional, default #ffffff). |
allowed_origins |
Websites allowed to use the widget, hostnames (optional, empty = any). |
enabled |
Whether the widget accepts submissions (optional, default true). |
Réponse : widget: The created widget with its embed code.
DELETE /api/v1/widgets/<widget_id>
Delete a website widget.
Accès : permission integrations.manage dans l'organisation (profils)
Paramètres de chemin
| Nom | Description |
|---|---|
widget_id |
Widget ID. |
Réponse : Success message.
GET /api/v1/widgets/<widget_id>
Get a website widget.
Accès : permission integrations.manage dans l'organisation (profils)
Paramètres de chemin
| Nom | Description |
|---|---|
widget_id |
Widget ID. |
Réponse : widget: The widget with its embed code.
PUT /api/v1/widgets/<widget_id>
Update a website widget (same fields as creation, all optional).
Accès : permission integrations.manage dans l'organisation (profils)
Paramètres de chemin
| Nom | Description |
|---|---|
widget_id |
Widget ID. |
Réponse : widget: The updated widget. message: Confirmation.
POST /api/v1/widgets/<widget_id>/regenerate-key
Give the widget a new key: sites using the old embed code stop working.
Accès : permission integrations.manage dans l'organisation (profils)
Paramètres de chemin
| Nom | Description |
|---|---|
widget_id |
Widget ID. |
Réponse : widget: The widget with its new embed code (the old key stops working). message: Confirmation.
Intégrations
Accès d'applications tierces aux équipes d'une organisation : clé API restreinte aux équipes exposées et webhook sortant signé (permission integrations.manage).
GET /api/v1/integrations
List the integrations of the current organization.
Accès : permission integrations.manage dans l'organisation (profils)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Réponse : integrations: List of integrations (the API key itself is never returned again). total: Number of integrations. events_available: Every event a webhook can receive (what a new integration gets by default).
POST /api/v1/integrations
Create an integration for the current organization.
The API key is returned once, in api_key; it cannot be read back later (use regenerate-key to get a new one). The webhook secret is readable at any time.
The webhook URL must be https on port 443, at most 500 characters, without credentials (user:password@), and every address its host resolves to must be public (no loopback, private, link-local, reserved, multicast or carrier-grade NAT address, IPv4 or IPv6, nor an address of the platform itself). It is checked again before each delivery, which then fails with "address not allowed" if the name now points elsewhere. Redirects are not followed.
The body must be a JSON object: anything else is refused with 400 validation_error, message "The request body must be a JSON object".
Accès : permission integrations.manage dans l'organisation (profils)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Corps de la requête
| Nom | Description |
|---|---|
name |
Name of the integration (required, a string of 80 characters max), shown as the sender of the messages it sends. |
team_ids |
Teams of the organization exposed to the integration (optional, empty = every team of the organization; a team of another organization is refused with 400). |
webhook_url |
Public https URL receiving the events (optional, 500 characters max; without it the integration only uses the API). Any other URL is refused with 400 validation_error, message "webhook_url must be a public https URL". |
events |
Events to deliver (optional, default all): message.received, message.sent, message.status, conversation.updated, conversation.note. An unknown event is refused with 400. |
enabled |
Whether the key and the webhook are active (optional, default true). |
Réponse : integration: The created integration (201). api_key: The API key, shown only once.
DELETE /api/v1/integrations/<integration_id>
Delete an integration: its API key is revoked and its webhook stops.
Accès : permission integrations.manage dans l'organisation (profils)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
integration_id |
Integration ID. |
Réponse : Success message.
GET /api/v1/integrations/<integration_id>
Get an integration.
Accès : permission integrations.manage dans l'organisation (profils)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
integration_id |
Integration ID. |
Réponse : integration: The integration.
PUT /api/v1/integrations/<integration_id>
Update an integration: only the fields sent change.
A new webhook URL follows the rules of the creation (public https URL on port 443, 500 characters max); the URL already saved, sent again as is, is not checked again (so the integration can be renamed or disabled while its receiver is down). Every delivery checks the URL before the call.
The body must be a JSON object: anything else is refused with 400 validation_error, message "The request body must be a JSON object".
Accès : permission integrations.manage dans l'organisation (profils)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
integration_id |
Integration ID. |
Corps de la requête
| Nom | Description |
|---|---|
name |
New name (a string of 80 characters max), also shown as the sender of its messages (optional). |
team_ids |
New list of exposed teams (optional; replaces the list, empty = every team of the organization); the API key follows at once. |
webhook_url |
New public https URL (optional, 500 characters max); "" or null removes the webhook; the saved URL sent again unchanged is kept without a new check. A new URL resumes a paused webhook and resets its failure count and last error. Any other URL is refused with 400 validation_error, message "webhook_url must be a public https URL". |
events |
New list of events (optional; replaces the list). |
enabled |
false disables the API key and the webhook, true enables them again (optional). |
webhook_paused |
false resumes a webhook paused after repeated failures (optional; true is ignored). |
Réponse : integration: The updated integration.
GET /api/v1/integrations/<integration_id>/deliveries
Most recent webhook deliveries of an integration, newest first.
Deliveries are kept 7 days; the list holds the limit most recent ones, without pagination. The ping of /test is not listed.
Accès : permission integrations.manage dans l'organisation (profils)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
integration_id |
Integration ID. |
Paramètres de requête
| Nom | Description |
|---|---|
limit |
Number of rows (optional, integer, default 50, at most 200; values out of 1..200 are clamped). Not an integer: 400 validation_error. |
Réponse : deliveries: Event, URL, attempts, HTTP status, error ("HTTP <status>", "timeout", "connection error", "redirect not followed", "address not allowed" or "not sent"; never the content of the answer), timestamps and payload. total: Number of rows returned.
POST /api/v1/integrations/<integration_id>/regenerate-key
Replace the API key of an integration: the previous key stops working at once.
Accès : permission integrations.manage dans l'organisation (profils)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
integration_id |
Integration ID. |
Réponse : integration: The integration. api_key: The new API key in plain text, shown only once (the old key stops working). message: Confirmation.
POST /api/v1/integrations/<integration_id>/regenerate-secret
Replace the webhook signing secret of an integration.
Accès : permission integrations.manage dans l'organisation (profils)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
integration_id |
Integration ID. |
Réponse : integration: The integration with its new webhook secret. message: Confirmation.
POST /api/v1/integrations/<integration_id>/test
Send a ping event to the webhook URL and report the outcome (no retry).
The ping is signed like any event but has a reduced shape, {"id": "test", "event": "ping", "created_at", "organization_id", "integration": {"id", "name"}}, without team, conversation nor message, and its X-Textos-Delivery header is always test (do not deduplicate on it). It goes out even when the integration is disabled or its webhook paused, is not listed among the deliveries and does not count in the webhook health. The receiver should answer 2xx to ping and ignore the events it does not know.
The call follows the delivery rules: redirects are not followed, 5 s to connect, 10 s between two reads, 15 s in all; the body of the answer is neither kept nor returned. The ping waits 5 s at most for a free call slot (error "not sent" otherwise, nothing being sent). 10 tests per minute per organization (all its integrations together), then 429 rate_limited.
Accès : permission integrations.manage dans l'organisation (profils)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Limite : 10 per minute
Paramètres de chemin
| Nom | Description |
|---|---|
integration_id |
Integration ID. |
Réponse : success: Whether the endpoint answered 2xx in time. status_code: HTTP status returned by the endpoint, if any (null when no answer was received). error: null on success, else one of "HTTP <status>", "timeout", "connection error", "redirect not followed", "address not allowed" (the URL resolves to an address that is not public), "not sent" (no free call slot in time: Telma is busy, try again), "No webhook URL".
Widget de site web (public)
Endpoints appelés par le navigateur des visiteurs : script et démarrage de conversation. Sans authentification, limités en débit.
GET /widget/<key>.js
The embeddable script of a widget, with its display configuration inlined.
Never rate limited: every visitor of every customer site loads it, and in production they all arrive from the same address (a counter per address would be shared by all the sites). It is public, cached by browsers (5 minutes) and has no side effect.
Accès : public (aucune authentification)
Limite : exemptée de la limite générale par adresse
Paramètres de chemin
| Nom | Description |
|---|---|
key |
Widget key (from the embed code). |
Réponse : JavaScript that renders the form on the host page.
POST /widget/<key>/start
Start a conversation from the widget: the team's welcome text is sent to the visitor.
Rate limited per widget (10 per minute, 60 per hour) and per destination number, whatever the widget (3 per hour, 10 per day), whatever other headers the request carries. Only the welcome texts handed to the SMS provider are counted: a slot is taken on each counter, atomically, right before the text is handed over, so simultaneous starts never send more than the limits; a refused request (unknown widget, other site, honeypot hit, invalid number, misconfigured widget, text already sent moments ago) is not counted, and the slots are given back when the text could not be handed to the provider (provider not enabled, insufficient balance). Beyond, 429 with error rate_limited and retry_after (also in Retry-After); a widget or a number already at its limit gets it before any other check.
Accès : public (aucune authentification)
Limite : exemptée de la limite générale par adresse
Paramètres de chemin
| Nom | Description |
|---|---|
key |
Widget key. |
Corps de la requête
| Nom | Description |
|---|---|
phone |
The visitor's mobile number: a North American number (its 10 digits, with or without 1 or +1 in front, or "+" and the 10 digits), always counted and texted as +1 and the 10 digits; or an international number typed with "+" and another country code (8, 9 or 11 to 15 digits in all: "+" and 10 digits is read as a North American number). Anything else, a North American number whose area code or exchange starts with 0 or 1 included: 400 invalid_number. |
lang |
Language of the visitor, fr or en (optional, default: the widget's default language); picks the welcome text. |
Réponse : ok: true when the text was sent (or was sent moments ago to the same number). message: Text to show the visitor. already_sent: true when this number just received the text (a second start within a minute sends nothing).
Errors 404 not_found: widget unknown or disabled. 403 forbidden: the site is not among the allowed origins. 400 invalid_number. 429 rate_limited. 503 configuration_error: the widget has no team or number. 503 send_failed (or the code of the refusal, for example a provider not enabled or an insufficient balance): the text was not handed to the provider.
Notifications push
Jetons Firebase Cloud Messaging des applications mobiles Telma et VoXoS (une entrée par appareil).
POST /api/v1/push/register
Register the FCM token of this app installation for the current user.
Called at every login and whenever Firebase rotates the token. A token previously attached to another user is moved to the current one.
Accès : authentifié (JWT ou clé API)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Corps de la requête
| Nom | Description |
|---|---|
token |
FCM registration token. |
platform |
"ios" or "android". |
app |
"textos" (Telma app, Messages + Voice; default) or "ubinext" (Ubinext app): which notifications this installation receives (400 validation_error for any other value). |
device_id |
Stable identifier of the installation (optional). |
app_version |
App version (optional). |
voip_token |
PushKit (VoIP) token of the installation, iOS only (optional): a hexadecimal string of 64 to 200 characters (stored lower-cased), null to erase it, absent to keep the stored one. The incoming-call push of an installation that has one goes through Apple's VoIP push (the app reports the call to CallKit at once) instead of FCM. A PushKit token belongs to one installation: it is taken off any other registration that carried it. 400 validation_error (field voip_token) for another value or on android. |
Réponse : token: The registered token (masked), with has_voip_token.
GET /api/v1/push/tokens
Devices registered for push notifications by the current user.
Accès : authentifié (JWT ou clé API)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Réponse : tokens: List of the user's tokens (masked).
POST /api/v1/push/unregister
Stop notifying this app installation (logout on the device).
Accès : authentifié (JWT ou clé API)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Corps de la requête
| Nom | Description |
|---|---|
token |
FCM registration token. |
Réponse : success: True. disabled: Whether a token of the current user was disabled.
Voix (VoXoS)
Poste VoXoS (provisioning du softphone), flux d'appel, groupes de sonnerie, sons, liste noire, appels et enregistrements, codes d'appel et demandes de rappel. Réservé aux organisations dont le service voix est activé.
GET /api/v1/voice/access-numbers
Access numbers to dial to place a call with a call code (one per country, Canada first).
Legacy, without interface: the apps no longer use the call codes (an incoming call rings the softphone, outbound calls go through it); the route stays until it is removed.
Needs the call_codes capability (403 capability_required) on top of voxos; not open to API keys.
Accès : permission voice.use dans l'organisation (profils)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de requête
| Nom | Description |
|---|---|
country |
ISO 3166-1 alpha-2 country (CA, US...). Default: the country of the user's phone, else every country. |
Réponse : access_numbers: List of {country, number, label}.
GET /api/v1/voice/blacklist
Numbers, prefixes and regions the organization refuses (calls received and placed).
Accès : permission voice.blacklist dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Réponse : entries: List of {id, description, type (number|prefix|region), number (number and prefix), region_id and area_codes (region), direction (both|inbound|outbound), note, created_at}.
POST /api/v1/voice/blacklist
Add an entry to the blacklist. An entry that already exists is updated and answered with 200.
The blacklist applies to every routing of the organization's numbers (flows, direct dial, groups, forward) for entries that apply to incoming calls, to the calls placed from the SIP endpoints and with call codes (the call is hung up and recorded with the disposition BLOCKED, nothing is charged). An entry written before E28 (no direction) keeps its old reading: incoming calls only in the flows that enable the blacklist.
Accès : permission voice.blacklist dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Corps de la requête
| Nom | Description |
|---|---|
type |
number (default), prefix or region. |
number |
Phone number (10 or 11 digits, with or without +) for a number; digits of the prefix for a prefix (with + they are international digits, without it a North American prefix: 514555 is 1514555). |
region_id |
Region of GET /voice/blacklist/regions (region). |
description |
Name of the entry (24 characters at most). |
direction |
both (default), inbound or outbound. |
note |
Why (optional, 120 characters at most). |
Réponse : entry: The entry.
DELETE /api/v1/voice/blacklist/<entry_id>
Remove an entry from the blacklist.
Accès : permission voice.blacklist dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
entry_id |
Entry ID. |
Réponse : success: True.
PATCH /api/v1/voice/blacklist/<entry_id>
Rename an entry (its number, prefix or region never changes). The note and the direction can change too.
Accès : permission voice.blacklist dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
entry_id |
Entry ID. |
Corps de la requête
| Nom | Description |
|---|---|
description |
New name (24 characters at most). |
note |
New note. |
direction |
both, inbound or outbound. |
Réponse : entry: The entry.
GET /api/v1/voice/blacklist/regions
Regions that can be blocked in one entry (provinces, toll-free and premium numbers), with their area codes.
Accès : permission voice.blacklist dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Réponse : version: Version of the table (changes when a region changes). regions: List of {id, label (in the language of the request), labels {fr, en}, area_codes}.
GET /api/v1/voice/call-info
What the next call of one of the user's SIP endpoints will be like (the (i) of the app's dial pad).
Accès : membre de l'organisation; An enabled endpoint assigned to the user, in any of their organizations (404 not_found otherwise), judged in the organization of the endpoint: it needs the voice service and the sip capability there (403 voice_not_enabled, 403 capability_required with capability sip) and the user needs voice.use there (403 forbidden), whatever their current organization; not available to API keys (403 forbidden).
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de requête
| Nom | Description |
|---|---|
endpoint_id |
An endpoint assigned to the user (required; see GET /voice/provision). |
Réponse : call_info: {endpoint_id, caller_id {name, number} (what an outbound call presents: the label of the endpoint, the voice number its settings choose, else the number routed to it, else the first voice number of the organization), extension, recording (the next outbound call is recorded), recording_detail {endpoint (the endpoint's own setting), imposed_by (organization, number or null: a rule of the organization that wins on outbound calls), skip_allowed (the user may skip it for his next call), skip_next (the next outbound call is skipped)}, diagnostics {level (0 off, 1 summary, 2 detailed, 3 full), name, until}}.
PUT /api/v1/voice/call-info/recording
Record, or not, the calls one's own endpoint makes (its settings.recording, from the (i) of the app's dial pad). A rule of the organization (see recording_detail) still wins on outbound calls. Journalled.
Accès : membre de l'organisation; An enabled endpoint assigned to the user, in any of their organizations (404 not_found otherwise), judged in the organization of the endpoint: it needs the voice service and the sip capability there (403 voice_not_enabled, 403 capability_required with capability sip) and the user needs voice.use there (403 forbidden), whatever their current organization; not available to API keys (403 forbidden).
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de requête
| Nom | Description |
|---|---|
endpoint_id |
An endpoint assigned to the user (required). |
Corps de la requête
| Nom | Description |
|---|---|
enabled |
Record the calls of the endpoint (required). |
Réponse : call_info: As GET /voice/call-info.
PUT /api/v1/voice/call-info/recording/skip-next
Skip, or not, the recording the organization imposes on the next outbound call of one's own endpoint (used once by the voice server, valid 15 minutes). Journalled.
Accès : membre de l'organisation; An enabled endpoint assigned to the user, in any of their organizations (404 not_found otherwise), judged in the organization of the endpoint: it needs the voice service and the sip capability there (403 voice_not_enabled, 403 capability_required with capability sip) and the user needs voice.use there (403 forbidden), whatever their current organization; not available to API keys (403 forbidden).
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de requête
| Nom | Description |
|---|---|
endpoint_id |
An endpoint assigned to the user (required). |
Corps de la requête
| Nom | Description |
|---|---|
skip |
True to skip the next outbound call, false to take the skip back (required). |
Réponse : call_info: As GET /voice/call-info.
Errors 403 recording_skip_not_allowed: the organization does not let its users skip the recording. 409 recording_not_imposed: nothing imposes the recording of the next call (the endpoint's own setting decides).
POST /api/v1/voice/call-requests
Ask for a call code: dial the access number, then type the code, and the destination is called.
Legacy, without interface: the apps no longer use the call codes (an incoming call rings the softphone, outbound calls go through it); the route stays until it is removed.
The app shows access.tel_uri (tel:+15145550100,12345: access number, a pause, the code) to the phone dialler. The code lives 180 seconds; a new request cancels the user's pending one. The call is limited to max_duration seconds, computed from the balance and the tariff, and billed afterwards like any outbound call. Checks, in order: capability, body, verified phone (422 phone_not_verified), origin number (403 number_disabled), suspended account (402 account_suspended, E46: negative balance past the grace period), balance (402 insufficient_balance with balance), destination (422 blocked_call with reason invalid_destination, premium, blacklisted or no_rate), duration the balance allows (402), access number of the country (404 no_access_number), 60 requests per hour (429 rate_limited with retry_after).
Accès : permission voice.use dans l'organisation (profils)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Corps de la requête
| Nom | Description |
|---|---|
from_number_id |
ID of the organization's number the call comes from (the caller ID shown to the callee). |
to |
Destination number (E.164, or a North American number). |
mode |
"access_code" (the only mode). |
country |
Country of the access number (default: the country of the user's phone, else CA). |
Réponse : call_request: {id, status "pending", from {number_id, number}, to, max_duration, expires_at, created_at}. access: {number, code, ttl, tel_uri}.
DELETE /api/v1/voice/call-requests/<request_id>
Cancel a call request (the user who made it only).
Legacy, without interface: the apps no longer use the call codes (an incoming call rings the softphone, outbound calls go through it); the route stays until it is removed.
A pending request becomes cancelled and its code stops working. A request that is already over (cancelled, expired, finished) answers success without any change; a call in progress (used or connected) answers 409 conflict.
Accès : permission voice.use dans l'organisation (profils)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
request_id |
Call request ID. |
Réponse : success: True.
GET /api/v1/voice/call-requests/<request_id>
Follow a call request (the user who made it only).
Legacy, without interface: the apps no longer use the call codes (an incoming call rings the softphone, outbound calls go through it); the route stays until it is removed.
Accès : permission voice.use dans l'organisation (profils)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
request_id |
Call request ID. |
Réponse : call_request: {id, status (pending, used, connected, expired, cancelled or failed), from {number_id, number}, to, max_duration, expires_at, created_at, used_at, connected_at, ended_at, answered_seconds, failure, call_id}; the last fields appear when they exist. A pending request past its expiry reads expired.
GET /api/v1/voice/calls
Call records (CDR) of the organization, newest first by default.
Needs the voxos capability (403 voice_not_enabled otherwise). A member without voice.calls.all sees only the calls of their own SIP endpoints (made or rung); with it, every call of the organization.
Accès : membre de l'organisation; voice.use in the current organization; with endpoint_id, judged in the organization of the endpoint instead (whatever the current organization), which needs the voice service and the sip capability (403 voice_not_enabled, 403 capability_required with capability sip, 403 forbidden without voice.use there).
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de requête
| Nom | Description |
|---|---|
skip |
Pagination offset (default 0; below 0 counts as 0). |
limit |
Page size (default 50, brought back into 1-100). A skip or limit that is not an integer is refused (400 validation_error with field). |
direction |
"inbound" or "outbound" (aliases "in" and "out"; optional). |
from |
Calls started at or after this ISO 8601 date or datetime (optional; no time zone means UTC). |
to |
Calls started at or before this ISO 8601 datetime; a date alone (2026-10-31) covers that whole day (optional). |
number_id |
ID of the phone number the calls went through (optional). |
status |
"all" (default), "answered", "missed" (not answered, not blocked) or "blocked" (hung up by the blacklist). |
call_type |
direct, flow, forward, voicemail, screened, blocked or callcode (optional). |
sort |
start (default), duration, cost, caller or callee. |
order |
"desc" (default) or "asc". |
endpoint_id |
Only the calls of this SIP endpoint, which must be assigned to the user (404 not_found otherwise, whatever the user's permissions): those it placed or that rang it, in the organization of the endpoint (any of the user's organizations). The history of the app, the same on every device of the endpoint. Each call then carries mine. |
Réponse : calls: The page of calls (see GET /voice/calls/<id> for the fields). With endpoint_id, each one has mine {direction (outbound: the endpoint placed it, internal calls included; inbound: it rang), number (the other party: E.164 or short number), name (caller id name of an incoming call, label of an endpoint for a short number, else null), answered (by this endpoint, on any of its devices)}. total: Number of calls matching the filters, whatever the page. skip: Offset applied. limit: Page size applied.
GET /api/v1/voice/calls/<call_id>
One call record of the organization (without voice.calls.all: a call of the user's own SIP endpoints, 404 otherwise).
Accès : permission voice.use dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
call_id |
Call ID. |
Réponse : call: id, organization_id, phone_number_id, endpoint_id, caller and callee (the dialplan digits), from ({number, e164}) and to ({number, e164}, plus extension and label for a short number: the endpoint called or a test number), direction (an internal call is outbound), call_type (direct, flow, forward, voicemail, screened, blocked, callcode, internal), status (answered, voicemail, blocked, busy, failed: it never rang, no_answer: an outbound call nobody took, missed: an incoming call nobody took), dial_status, call_request_id, forwarded, clid, start, answer, end, duration, billsec, disposition, cost and currency (null until rated), billing (rating state, null until looked at), source_number and access_number (call-code calls), voicemail (the call ended in the voicemail), block_reason (paused, sleep, channel_limit, blacklist or account_suspended for a blocked call), recording ({state (processing, available, failed, deleted, expired), available_at, expires_at, size, duration} or null), inserted_at.
GET /api/v1/voice/diagnostics
Diagnostics reports of the organization's calls, newest first.
Needs the sip capability on the organization.
Accès : permission voice.endpoints dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de requête
| Nom | Description |
|---|---|
endpoint_id |
Only the reports of this endpoint (optional). |
skip |
Pagination offset (default 0). |
limit |
Page size (default 50, at most 100). |
Réponse : diagnostics: The page: id, endpoint_id, endpoint_name, level, direction, remote, sip_call_id, started_at, ended_at, created_at, size, app, problems (what the app found wrong, e.g. microphone_silent). total: Number of reports matching the filter. skip: Offset applied. limit: Page size applied.
POST /api/v1/voice/diagnostics
Upload the diagnostics report of a call, made by the app at its end when the diagnostics of the endpoint are on.
Only the parts of the current level of the endpoint are kept (a part of a higher level is dropped); SIP credentials (Authorization headers) are removed. Kept 30 days.
Accès : membre de l'organisation; An enabled endpoint assigned to the user, in any of their organizations (404 not_found otherwise), judged in the organization of the endpoint: it needs the voice service and the sip capability there (403 voice_not_enabled, 403 capability_required with capability sip) and the user needs voice.use there (403 forbidden), whatever their current organization; not available to API keys (403 forbidden).
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Limite : 120 per hour
Corps de la requête
| Nom | Description |
|---|---|
endpoint_id |
The endpoint of the call, assigned to the user (required). |
sip_call_id |
Call-ID of the call. |
direction |
inbound or outbound. |
remote |
The other party (number or name). |
started_at |
ISO 8601 dates of the call. |
ended_at |
ISO 8601 dates of the call. |
app |
{version, build, platform, os, device} of the app. |
summary |
Level 1 and above: one object (network, transport, srtp, codec, sip steps, rtp counters, microphone, playback, errors, problems). |
timeline |
Level 2 and above: one sample per second (at most 7200). |
sip_messages |
Level 2 and above: the SIP messages of the call as text (at most 500). |
logs |
Level 2 and above: the app's log lines of the call (at most 5000). |
rtp_trace |
Level 3: the RTP packet headers sent and received (at most 60000), never the audio. |
Réponse : diagnostic: {id, level} of the stored report.
Errors 400 validation_error: diagnostics off for the endpoint (field endpoint_id), report too large for its level (field size: 64 KB at level 1, 1 MB at level 2, 4 MB at level 3) or a malformed part.
DELETE /api/v1/voice/diagnostics/<diagnostic_id>
Delete a diagnostics report.
Accès : permission voice.endpoints dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
diagnostic_id |
Report ID. |
Réponse : success: True.
GET /api/v1/voice/diagnostics/<diagnostic_id>
One diagnostics report, whole, with what the voice server saw of the call.
Accès : permission voice.endpoints dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
diagnostic_id |
Report ID. |
Réponse : diagnostic: The fields of the list, plus summary, timeline, sip_messages, logs, rtp_trace (those of its level) and server {call (the call record matched by endpoint and time, as GET /voice/calls/<id>, or null), qos (the RTP statistics the voice server computed for each channel of the call at hang up: channel, rx/tx packets, lost, jitter, rtt)}.
GET /api/v1/voice/directory
The other endpoints of the organization of one's own endpoint, to call them by their short number (the contacts of the app). Never an SIP identifier nor a password.
Accès : membre de l'organisation; An enabled endpoint assigned to the user, in any of their organizations (404 not_found otherwise), judged in the organization of the endpoint: it needs the voice service and the sip capability there (403 voice_not_enabled, 403 capability_required with capability sip) and the user needs voice.use there (403 forbidden), whatever their current organization; not available to API keys (403 forbidden).
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de requête
| Nom | Description |
|---|---|
endpoint_id |
An endpoint assigned to the user (required); the organization is its own. |
Réponse : entries: [{id, label (the label of the endpoint, else its user's name, else its short number), extension, user_name}] of the enabled endpoints of the organization that have a short number, sorted by label, without the endpoint asking.
GET /api/v1/voice/endpoints
SIP endpoints (softphones, desk phones) of the current organization.
Needs the sip capability on the organization: 403 capability_required (with capability: sip) without it, 403 voice_not_enabled without voxos.
Accès : permission voice.endpoints dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Réponse : endpoints: [{id, name, label, organization_id, user_id, user {id, name, email}, server_id, server {id, name}, sip_server, sip_port, username, password, description, extension, enabled, max_contacts (null: the platform's), locked ({until, at, failures} while the voice server keeps it locked after wrong passwords, else null), settings (see PUT /voice/endpoints/<id>), diagnostics, created_at, updated_at}].
POST /api/v1/voice/endpoints
Create a SIP endpoint; its username and password are generated.
Needs the sip capability on the organization: 403 capability_required (with capability: sip) without it, 403 voice_not_enabled without voxos.
The SIP username (also its name) is derived from the account code of the organization (3 letters and 4 digits, given with its first endpoint, e.g. cli0001), a 0, then the short number: cli0001042 for 42; a letter is added when that name is taken (cli0001042a). 409 conflict when no account code is left for the name of the organization.
Accès : permission voice.endpoints dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Corps de la requête
| Nom | Description |
|---|---|
label |
Display name of the endpoint (e.g. "Poste de Marie"), the caller name of its calls (required): 200 characters at most, no line break or control character (400 validation_error, field label). |
user_id |
User of the organization (a member: 400 validation_error otherwise) who uses it from the mobile app (optional). |
server_id |
Voice server to place it on (optional, default: the least loaded enabled server). |
extension |
Short number (required, 400 validation_error with field extension without it): 2 to 5 digits, not starting with 0, unique in the organization (409 conflict otherwise); another endpoint dials it to call this one. Reserved: 600, 601 (tests), 112, 999 and the N11 numbers such as 911 (400 validation_error, field extension). It never changes afterwards. |
settings |
Settings of the endpoint (optional), as in PUT /voice/endpoints/<id>. |
max_contacts |
Devices registered at once on the endpoint, 1 to 10 (optional; null or absent: the platform's, 3 by default). The app on a phone or a tablet and a SIP desk phone may share the endpoint: they all ring together and the first to answer takes the call; beyond the limit, the oldest registration is replaced (400 validation_error, field max_contacts). |
Réponse : endpoint: The created endpoint with its credentials.
DELETE /api/v1/voice/endpoints/<endpoint_id>
Delete an endpoint; numbers routed to it lose their routing.
Needs the sip capability on the organization: 403 capability_required (with capability: sip) without it, 403 voice_not_enabled without voxos.
Accès : permission voice.endpoints dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
endpoint_id |
Endpoint ID. |
Réponse : success: True.
PUT /api/v1/voice/endpoints/<endpoint_id>
Rename an endpoint, assign it to a user, enable or disable it, change its settings.
Needs the sip capability on the organization: 403 capability_required (with capability: sip) without it, 403 voice_not_enabled without voxos.
Accès : permission voice.endpoints dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
endpoint_id |
Endpoint ID. |
Corps de la requête
| Nom | Description |
|---|---|
label |
Display name (optional), same rules as at the creation. |
user_id |
User of the organization, or null to unassign (optional). |
enabled |
Whether the endpoint can register (optional). |
server_id |
Move the endpoint to another voice server (optional); the app picks the new address at its next start. |
extension |
Short number (as at the creation), only for an endpoint that has none yet (created before short numbers were required); the short number of an endpoint never changes and is never removed (400 validation_error, field extension). |
max_contacts |
Devices registered at once on the endpoint, 1 to 10, or null for the platform's (optional), as at the creation. |
diagnostics |
Diagnostics of the calls of the endpoint, {level (0 off, 1 summary, 2 detailed, 3 full), hours (1 to 168, default 24: they stop by themselves)}; the app uploads a report at the end of each call (GET /voice/diagnostics). |
settings |
Settings of the endpoint (the fields given change): language (fr, en, es or null: the language of the messages it hears on its own calls), ring_seconds (5 to 120, or null: how long it rings before its no-answer handling; null = what the routing or its box says, 30 s for an internal call), dnd (do not disturb: it never rings, the call goes to its busy handling; turning it on or off here makes it without end), forward {always, busy, no_answer} (a 10-digit North American number or the short number of another endpoint of the organization; null = none; always never rings it), follow_me {enabled, numbers (1 to 3 external numbers rung together once it did not answer), ring_seconds (10 to 120)}, recording (the calls it makes are recorded), caller_id_number_id (a voice number of the organization its outbound calls present; null = the number routed to it, else the first voice number), allowed_networks (up to 20 IPv4 or IPv6 addresses or CIDR networks: its outbound calls go out only from a device in one of them; empty: anywhere; registration and incoming calls are not restricted), virtual (an extension without a phone that forwards its calls, counted as a virtual user of the phone system). The answers also carry dnd_until (ISO 8601 in UTC, or null: no end), the end of a do not disturb set by the user for their whole account (PUT /auth/me/dnd), never accepted here (400); the dnd answered is the one in force (false once dnd_until passed). 400 validation_error with field; 422 destination_not_allowed (with field, reason, country) for a forward the organization may not make. |
Réponse : endpoint: The updated endpoint (with settings).
POST /api/v1/voice/endpoints/<endpoint_id>/reset-password
Generate a new SIP password for an endpoint (phones must be reconfigured).
Needs the sip capability on the organization: 403 capability_required (with capability: sip) without it, 403 voice_not_enabled without voxos.
Accès : permission voice.endpoints dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
endpoint_id |
Endpoint ID. |
Réponse : endpoint: The endpoint with its new password.
POST /api/v1/voice/endpoints/<endpoint_id>/unlock
Unlock an endpoint the voice server locked after repeated wrong passwords (its outbound calls were refused). Journalled. Change its password first if it may have leaked.
Needs the sip capability on the organization.
Accès : permission voice.endpoints dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
endpoint_id |
Endpoint ID. |
Réponse : endpoint: The endpoint (as GET /voice/endpoints), locked null.
GET /api/v1/voice/endpoints/status
Registration state of the organization's endpoints, as seen by the voice server.
Needs the sip capability on the organization: 403 capability_required (with capability: sip) without it, 403 voice_not_enabled without voxos.
The app of the mobile app is not registered between two calls: the voice server wakes it by a push when a call comes in. reachable names the endpoints it would wake (their user has a device that receives the incoming-call push), whatever their registration state. dnd names the endpoints in do not disturb now (set by their user in the mobile app or in the endpoint's settings): they never ring, whatever their registration state.
Accès : permission voice.endpoints dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Réponse : statuses: {endpoint name: state} ("Not in use", "In use", "Unavailable"...), empty when the voice server is unreachable. reachable: {endpoint name: {devices, platforms, last_seen, app_versions}}: the endpoints whose user has a device woken by a push for an incoming call (devices: how many; platforms: ios, android; last_seen: ISO 8601, the last time the app registered that device; app_versions: versions of the app); the others are absent. dnd: {endpoint name: end}: the endpoints in do not disturb now, with its end (ISO 8601 UTC, null without end); the others are absent.
GET /api/v1/voice/flows
Voice flows of the organization (what happens to a call: prompts, menus, ringing, voicemail).
Reading with voice.use; creating, changing and deleting need voice.routing.
Accès : permission voice.use dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Réponse : flows: List of flows, by name, each with active_on ([{phone_number_id, number}]: the numbers that run it).
POST /api/v1/voice/flows
Create a voice flow. The name is unique in the organization: a name already taken gets " (1)", " (2)"...
Accès : permission voice.routing dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Corps de la requête
| Nom | Description |
|---|---|
name |
Flow name (80 characters at most). |
language |
"fr" or "en" (prompts and voicemail language). |
languages |
{fallback, switch_key, extras [{lang, fallback, switch_key, sounds {sound id: translated sound id}}]}: extra languages of the menu, the key that switches to each one and the sounds played in it (a sound without translation is played as is). |
cid_prefix |
Text prepended to the caller name on the phones (optional); 20 characters at most (a longer text is cut), a string without control characters or line breaks and without double quotes, backslashes, commas, ampersands, pipes, dollar signs, braces and backticks (400 validation_error, field cid_prefix). |
business_hours |
{enabled, timezone, schedule {mon..sun: [["09:00","17:00"]]}, closures [{start, end, repeat none|yearly|weekly, until, ranges, label}], exceptions [{date, ranges}], closed_step}. A closure is dated (the year counts): repeat "none" is one occurrence, "yearly" comes back every year, "weekly" every 7 days, until ends the repetition and ranges are the hours the line stays open on those days. |
closed |
Menu used outside the business hours, {steps, keys, retry}, same form as the open one (optional, the business_hours.closed_step is used when it is empty). |
blacklist_enabled |
Hang up on the callers of the organization's blacklist (default true). |
steps |
Ordered steps: {action: play|menu|wait|dial|groupdial|forward|goto|voicemail|hangup|back, sound_id (message played before the action), seconds, endpoint_id, group_id, flow_id, mailbox (voicemail step: the id of a voice number of the organization whose voicemail takes the message, or of a voicemail box of GET /voice/mailboxes with the voicemail_boxes capability, or empty for the number called), number, ext, screening 0|1|2, display caller|called, on_fail (a step), timeout, repeat (menu: times the prompt plays, 0 means 2, 5 at most), digit_timeout, extension_dial (menu: the caller may type the short number of an endpoint of the organization; a key of the menu wins, a prefix conflict is settled by digit_timeout; with it, on_fail is allowed on the menu: what follows when the endpoint dialled does not answer, else back to the menu), push_enabled, system_sound (play and menu, instead of sound_id: 1 to 6 standard prompts of GET /voice/system-prompts, only those names (another one: 400 validation_error with the field of the step), fr: or en: before a name fixes its language, an item {name, only: [fr|en]} is played only when the call is in one of those languages), wait ring|music (dial and groupdial: what the caller hears while ringing, absent = the setting of the number)}. |
keys |
{key: step | {description, steps [step], then menu|hangup}}: what a key does during a menu step; a key has 1 to 4 characters among 0-9, * and #. |
retry |
{attempts, step}: how many times the steps repeat, then what to do. |
Réponse : flow: The created flow.
DELETE /api/v1/voice/flows/<flow_id>
Delete a flow. Refused (409 flow_in_use with active_on) while numbers run it, unless ?force=true, which also takes the routing off those numbers. Other flows or groups that jump to it always refuse (409 conflict).
Accès : permission voice.routing dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
flow_id |
Flow ID. |
Paramètres de requête
| Nom | Description |
|---|---|
force |
true to delete a flow that numbers run (their calls are no longer routed). |
Réponse : success: True.
GET /api/v1/voice/flows/<flow_id>
One voice flow.
Accès : permission voice.use dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
flow_id |
Flow ID. |
Réponse : flow: The flow with active_on.
PUT /api/v1/voice/flows/<flow_id>
Update a voice flow (same fields as the creation, all optional; a key not given keeps its value).
Accès : permission voice.routing dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
flow_id |
Flow ID. |
Réponse : flow: The updated flow.
POST /api/v1/voice/flows/<flow_id>/assign
Set the numbers that run a flow: the list is the full set, a number left out loses its routing to this flow, a number listed is routed to it (replacing its former routing).
Accès : permission voice.routing dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
flow_id |
Flow ID. |
Corps de la requête
| Nom | Description |
|---|---|
phone_number_ids |
Voice numbers of the organization that run the flow (empty list: none). |
Réponse : flow: The flow with its new active_on.
POST /api/v1/voice/flows/<flow_id>/duplicate
Copy a flow under a new name (made unique like on creation); the copy is not assigned to any number.
Accès : permission voice.routing dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
flow_id |
Flow ID. |
Corps de la requête
| Nom | Description |
|---|---|
name |
Name of the copy. |
Réponse : flow: The copy.
GET /api/v1/voice/groups
Call groups of the organization.
Reading with voice.use; creating, changing and deleting need voice.routing.
Accès : permission voice.use dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Réponse : groups: List of groups, each with stages (lists of members), members, fallback and used_by ({flows, numbers}).
POST /api/v1/voice/groups
Create a call group: stages rung one after the other, the members of a stage ring together.
Accès : permission voice.routing dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Corps de la requête
| Nom | Description |
|---|---|
name |
Group name. |
stages |
Stages, each a list of members {type "endpoint", endpoint_id} or {type "external", number (North American), ext (digits sent once the call is answered)}. A stage with an extension only holds external numbers sharing it. At most 10 members per stage and 20 in all. |
endpoint_ids |
Endpoints of the group (former shape, used when stages is absent). |
strategy |
"ringall" (all at once, default) or "sequential" (one after the other); former shape, with endpoint_ids. |
timeout |
Ring time of each stage in seconds, 5 to 120 (default 20). |
push_enabled |
Wake the mobile apps by push (default true). |
ringing |
"generated" (the caller hears the ringing tone of the platform, default) or "real" (the caller hears the ringing of the called phones). |
fallback |
Step run when nobody answers: play, forward, voicemail, hangup, dial or goto (optional). |
Réponse : group: The created group.
DELETE /api/v1/voice/groups/<group_id>
Delete a call group; refused (409 conflict with used_by) while a flow or a number routing uses it.
Accès : permission voice.routing dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
group_id |
Group ID. |
Réponse : success: True.
PUT /api/v1/voice/groups/<group_id>
Update a call group (same fields as the creation, all optional).
Accès : permission voice.routing dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
group_id |
Group ID. |
Réponse : group: The updated group.
GET /api/v1/voice/moh
Music on hold of the organization: the free library of the platform, its own tracks, the playlists it may choose, and what it plays. Held calls always hear the chosen playlist; ringing plays it instead of the ringback tone while phones ring (groups, flows, forwards).
Accès : permission voice.use dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Réponse : library: Tracks of the free library [{id, name, duration_s, collection, author, author_url, license {name, url}, source_url}]. tracks: Tracks of the organization [{id, name, duration_s, size, rights_confirmed, used_by [{type class, id, name}]}]. classes: Playlists it may choose [{id, name, name_en (English name of a playlist of the platform, null for its own), platform, platform_default, sound_ids, order random|alpha}]: those of the platform, then its own. settings: Choice of the organization {class_id (null = the default of the platform), ringing}. numbers: Its voice numbers [{id, number, label, class_id (null = the organization's), ringing (null = the organization's)}]. limits: Limits of a track {max_per_account, min_duration_s, max_duration_s, max_bytes, formats}.
POST /api/v1/voice/moh/classes
Create a playlist of the organization (20 at most) from tracks of the library and of its own.
Accès : permission voice.routing dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Corps de la requête
| Nom | Description |
|---|---|
name |
1 to 60 characters (required). |
sound_ids |
Tracks, 1 to 50, in this order (required). |
order |
random (default) or alpha (the order of the names). |
Réponse : class: The playlist.
DELETE /api/v1/voice/moh/classes/<class_id>
Delete a playlist of the organization; the organization and the numbers that chose it play the default music.
Accès : permission voice.routing dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
class_id |
Playlist ID. |
Réponse : success: True.
PUT /api/v1/voice/moh/classes/<class_id>
Change a playlist of the organization (those of the platform cannot change: 404).
Accès : permission voice.routing dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
class_id |
Playlist ID. |
Corps de la requête
| Nom | Description |
|---|---|
name |
1 to 60 characters (optional). |
sound_ids |
Tracks, 1 to 50 (optional). |
order |
random or alpha (optional). |
Réponse : class: The playlist.
PUT /api/v1/voice/moh/numbers/<phone_id>
What one voice number plays, over the choice of the organization.
Accès : permission voice.routing dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
phone_id |
Phone number ID. |
Corps de la requête
| Nom | Description |
|---|---|
class_id |
Playlist ID, or null = the organization's (optional). |
ringing |
true, false, or null = the organization's (optional). |
Réponse : number: {id, number, label, class_id, ringing}.
PUT /api/v1/voice/moh/settings
What the organization plays: a playlist (its own or one of the platform; null = the default of the platform) and whether the music replaces the ringback tone while phones ring.
Accès : permission voice.routing dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Corps de la requête
| Nom | Description |
|---|---|
class_id |
Playlist ID, or null (optional). |
ringing |
true: the music instead of the ringback tone; false: the ringback tone (optional). |
Réponse : settings: {class_id, ringing}.
POST /api/v1/voice/moh/tracks
Upload a track of music on hold (multipart/form-data). The organization must confirm it holds the rights to broadcast it on the phone (who and when are kept). Limits: 30 tracks, wav/mp3/m4a/ogg, 20 MB, 5 seconds to 15 minutes; refused uploads answer 400 with a code (format_invalid, empty_file, file_too_large, audio_unreadable, duration_too_short, duration_too_long, account_limit).
Accès : permission voice.routing dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Corps de la requête
| Nom | Description |
|---|---|
file |
The audio file (required). |
name |
Name of the track (default: the file name). |
rights_confirmed |
true, the confirmation of the broadcast rights (required). |
Réponse : track: The created track.
DELETE /api/v1/voice/moh/tracks/<sound_id>
Delete a track of the organization; its playlists lose it (a playlist left empty plays the default music).
Accès : permission voice.routing dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
sound_id |
Track ID. |
Réponse : success: True.
PUT /api/v1/voice/moh/tracks/<sound_id>
Rename a track of the organization.
Accès : permission voice.routing dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
sound_id |
Track ID. |
Corps de la requête
| Nom | Description |
|---|---|
name |
New name (required). |
Réponse : track: The track.
GET /api/v1/voice/moh/tracks/<sound_id>/file
The audio of a track, to listen to it in the portal: one of the library or one of the organization.
Accès : permission voice.use dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
sound_id |
Track ID. |
Réponse : The file (audio/*).
GET /api/v1/voice/my-endpoints
The endpoints of the current user in the current organization, with their settings (to set one's own do not disturb, forwards and follow me).
Needs the sip capability on the organization. Not available to API keys.
Accès : permission voice.use dans l'organisation (profils); Only the endpoints assigned to the user.
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Réponse : endpoints: [{id, label, extension, enabled, settings (as GET /voice/endpoints)}].
PUT /api/v1/voice/my-endpoints/<endpoint_id>/settings
Set one's own endpoint: do not disturb, forwards, follow me and the recording of the calls it makes (the other settings are an administrator's). A change of the recording is journalled, as PUT /voice/call-info/recording; a rule of the organization still wins on outbound calls.
Needs the sip capability on the organization. Not available to API keys.
Accès : permission voice.use dans l'organisation (profils)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
endpoint_id |
An endpoint of the current user in the current organization (404 not_found otherwise). |
Corps de la requête
| Nom | Description |
|---|---|
dnd |
As the settings of PUT /voice/endpoints/<id>. Another setting: 400 validation_error; a forward the organization may not make: 422 destination_not_allowed. |
forward |
As the settings of PUT /voice/endpoints/<id>. Another setting: 400 validation_error; a forward the organization may not make: 422 destination_not_allowed. |
follow_me |
As the settings of PUT /voice/endpoints/<id>. Another setting: 400 validation_error; a forward the organization may not make: 422 destination_not_allowed. |
recording |
As the settings of PUT /voice/endpoints/<id>. Another setting: 400 validation_error; a forward the organization may not make: 422 destination_not_allowed. |
Réponse : settings: The settings of the endpoint after the change.
GET /api/v1/voice/numbers
Phone numbers of the organization with voice enabled, and how each one is routed.
A number gets its voice from the platform (a trunk set by the platform administrator for a number of a platform account) or from the domain administrator (a number of the domain's own accounts, on its own trunks); the organization decides where its calls go (permission voice.routing, PUT /voice/numbers/<id>/routing).
Accès : permission voice.use dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de requête
| Nom | Description |
|---|---|
status |
active, paused, released (the trash) or pending; default: everything but the trash. |
q |
Text matched in the name and in the digits of the number. |
skip |
Pagination offset (default 0). |
limit |
Page size (default 50, at most 100). |
with_stats |
1 adds stats ({calls, missed, duration, cost}, see GET /voice/stats) to each number, over the calls of the period (optional; 0 or absent: no figures); without voice.calls.all, only the calls of the user's own endpoints. |
from |
With with_stats, only the calls started at or after this ISO 8601 date or datetime (optional). |
to |
With with_stats, only the calls started at or before this ISO 8601 datetime; a date alone covers the whole day (optional). |
direction |
With with_stats, only the calls of this direction, inbound or outbound (optional). |
Réponse : numbers: The page of phone numbers, each with state {status, enabled, sleep, released_at, expires_at}, features {voicemail, recording, sms}, routing (null when calls are refused), server_id and, with with_stats, stats. total: Number of numbers matching the filters. counts: {active, paused, released, pending} after the text filter. skip: Offset applied. limit: Page size applied.
PUT /api/v1/voice/numbers/<phone_id>/routing
Route the incoming calls of a number.
Accès : permission voice.routing dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
phone_id |
Phone number ID (voice-enabled, in the organization). |
Corps de la requête
| Nom | Description |
|---|---|
action |
"dial" (ring an endpoint), "dial-then-playback" (ring, then play a message), "playback", "voicemail", "flow" (run a voice flow), "groupdial" (ring a group), "forward" (forward to an external number), "custom" (sets the AGI variables of variables, then runs next_action; reserved to the platform and domain administrators, 403 forbidden for a member of the organization), or null to refuse calls. |
endpoint_id |
Endpoint to ring (dial actions). |
flow_id |
Voice flow to run (flow). |
group_id |
Ring group (groupdial). |
forward_number |
External number to forward the calls to (forward), 10-digit North American; billed as an outbound call. 422 destination_not_allowed (with reason and country) when the organization's forward countries or the premium-rate block refuse it (E47). |
timeout |
Ring time in seconds (default 30). |
push_enabled |
Wake the mobile app by push before ringing (default true). |
sound_id |
Sound of the organization to play (playback, dial-then-playback): uploaded or recorded with POST /voice/sounds; it wins over sound. 400 validation_error (field sound_id) for a sound of another organization. |
sound |
Name of a sound file of the voice server to play instead (playback, dial-then-playback), such as "hello-world" or "custom/welcome": 1 to 64 letters, digits, "_", ".", "/" or "-". 400 validation_error (field sound) for anything else, a line break included. |
mailbox |
Voicemail box (voicemail): the id of a voice number of the organization or of a voicemail box (GET /voice/mailboxes); empty: the box of the number. 400 validation_error (field mailbox) for anything else. |
variables |
Object of AGI variables, names [A-Za-z_][A-Za-z0-9_]* and plain string or number values (custom). |
next_action |
AGI action run after the variables, default hangup (custom). |
Réponse : number: The phone number with its new routing.
GET /api/v1/voice/provision
SIP account of the current user for the Voice tab of the Telma mobile app.
Called by the app after login and when the organization changes. The voice accounts of a user are the enabled SIP endpoints assigned to them in every organization that has the voice service and the sip capability, whatever the current organization and whether or not they are a member of it (the assignment by a member who has voice.endpoints there is the grant; no permission of the current organization is needed): the Voice tab shows as soon as there is one. The provisioned one is the one asked with endpoint_id, otherwise the first of the current organization, otherwise the first one (sorted by organization then name); endpoints lists them all so that the app can offer the choice. The app registers its push token with POST /api/v1/push/register (app: "textos") so that incoming calls wake it up.
Without any voice account, the error tells why for the current organization: 403 voice_not_enabled without voxos, 403 capability_required (with capability: sip) without sip, 404 no_endpoint otherwise.
Accès : authentifié (JWT ou clé API)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de requête
| Nom | Description |
|---|---|
endpoint_id |
Endpoint to provision among the user's (optional); an unknown or disabled one falls back to the default. |
Réponse : sip: {server, port, transport, srtp, username, password, display_name} of the user's SIP account; the server is the one the endpoint is placed on, transport (udp, tcp or tls) one of its active listeners; srtp is "sdes" when the transport is tls (the app offers SRTP with keys in the SDP, AES_CM_128_HMAC_SHA1_80, and applies it when the answer accepts it), null otherwise. endpoint: {id, name, username, extension, label (the label of the endpoint, else the user's name, else its name: what the app shows instead of the SIP account), organization {id, name}} of the provisioned voice endpoint. endpoints (array): Every voice account of the user, {id, name, username, extension, label, organization {id, name}}, sorted by organization then name. call_info: What the next call of the provisioned endpoint will be like, as GET /voice/call-info (caller ID, recording, diagnostics level). organization: {id, name} of the organization of the provisioned endpoint. push: {app} the value to register the push token with.
Errors 404 no_endpoint: no enabled SIP endpoint is assigned to the user in an organization with the voice service.
GET /api/v1/voice/sounds
Audio prompts of the organization, with the places that use them.
Reading with voice.use; uploading, renaming and deleting need voice.routing.
Accès : permission voice.use dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de requête
| Nom | Description |
|---|---|
kind |
ivr (messages of the flows), greeting (voicemail greetings) or system (messages the platform plays; the organization's own and, as inherited, the ones of its domain) (optional). |
Réponse : sounds: List of sounds {id, name, slug, kind, filename, extension, size, duration_s, used_by [{type flow|group|number, id, name}], attached_to, key and language (system)}. limits: The limits of the domain {max_per_account, min_duration_s, max_duration_s, max_bytes, formats}.
POST /api/v1/voice/sounds
Upload an audio prompt (multipart/form-data). The limits of the domain are checked here: count, formats, size and the duration measured from the file (defaults: 20 sounds, wav/mp3/m4a/ogg/webm, 5 MB, 1 to 90 seconds). Refused uploads answer 400 with a code (format_invalid, empty_file, file_too_large, audio_unreadable, duration_too_short, duration_too_long, account_limit).
A system sound replaces the one of the organization with the same key and language.
Accès : permission voice.routing dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Corps de la requête
| Nom | Description |
|---|---|
file |
The audio file. |
name |
Name of the sound (default: the file name). |
kind |
ivr (default), greeting or system. |
key |
System sound only: filtering (announcement before a filtered call), name_prompt (asks the caller's name), invalid_key (wrong key in a menu), voicemail_greeting (default voicemail message), paused (number paused or asleep) or recording (legal announcement of a recorded call). |
language |
System sound only: fr, en or es. |
Réponse : sound: The created sound, with duration_s.
DELETE /api/v1/voice/sounds/<sound_id>
Delete a sound. Refused (409 conflict with used_by) while a flow, a group or the routing of a number (its message, playback or dial-then-playback) uses it; the check comes first, nothing is touched when it fails. The numbers that play it as their voicemail greeting lose it (voicemail.sound_id becomes null).
Accès : permission voice.routing dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
sound_id |
Sound ID. |
Réponse : success: True.
PUT /api/v1/voice/sounds/<sound_id>
Rename a sound.
Accès : permission voice.routing dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
sound_id |
Sound ID. |
Corps de la requête
| Nom | Description |
|---|---|
name |
New name. |
Réponse : sound: The updated sound.
PUT /api/v1/voice/sounds/<sound_id>/attach
Attach a voicemail greeting to numbers (the full set; a number has one greeting, so it leaves the others).
The greeting becomes the voicemail.sound_id of each number (the same fact as PATCH /voice/numbers/<id>), and the numbers that leave the set lose it.
Accès : permission voice.routing dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
sound_id |
Sound ID (kind greeting). |
Corps de la requête
| Nom | Description |
|---|---|
phone_number_ids |
Voice numbers of the organization that play this greeting (empty list: none). |
Réponse : sound: The sound with attached_to and used_by.
GET /api/v1/voice/sounds/<sound_id>/file
The uploaded audio file, to listen to it in the portal (the system sounds of the domain too).
Readable with voice.use, like the list of sounds.
Accès : permission voice.use dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
sound_id |
Sound ID. |
Réponse : The file (audio/*).
GET /api/v1/voice/sounds/tts
What text-to-speech can do for the sounds (voices of the platform's Piper service).
Accès : permission voice.routing dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Réponse : available (boolean): Whether text-to-speech is offered (false: the portal hides it). languages: Languages it speaks (fr, en, es; only those whose voice is installed). max_length: Longest text accepted, in characters.
POST /api/v1/voice/sounds/tts
Create a sound from a text (text-to-speech, Piper voices). The sound counts like an uploaded one (same limits of the domain: count, size, duration) and keeps its text (tts).
Accès : permission voice.routing dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Corps de la requête
| Nom | Description |
|---|---|
text |
Text to speak (required, 1000 characters at most). |
language |
fr, en or es (required; one of languages of GET /voice/sounds/tts). |
name |
Name of the sound (default: the start of the text). |
kind |
ivr (default) or greeting. |
Réponse : sound: The created sound, with duration_s and tts {text, language}.
Errors 400 validation_error: text missing or too long (field text), unknown language (field language), kind (field kind). 400 with code as POST /voice/sounds (duration_too_long, account_limit...). 503 tts_unavailable: the text-to-speech service is off or failed.
GET /api/v1/voice/stats
Call statistics of the organization: totals and one row per number (dashboard, numbers list).
Needs the voxos capability (403 voice_not_enabled otherwise). Aggregated on the call records (CDR) of the organization, blocked calls included in calls. answered is the calls answered, missed those neither answered nor blocked by the blacklist, duration the billable seconds of the answered calls, cost the sum of the cost of the rated calls (a call not rated yet counts for 0). Without voice.calls.all, only the calls of the user's own SIP endpoints.
Accès : permission voice.use dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de requête
| Nom | Description |
|---|---|
from |
Calls started at or after this ISO 8601 date or datetime (optional; no time zone means UTC). |
to |
Calls started at or before this ISO 8601 datetime; a date alone (2026-10-31) covers that whole day (optional). |
number_id |
Only the calls of this phone number (optional). |
direction |
"inbound" or "outbound" (aliases "in" and "out"; optional). |
call_type |
direct, flow, forward, voicemail, screened, blocked or callcode (optional). |
Réponse : stats: totals ({calls, answered, missed, duration, cost}), by_number (list of {phone_number_id, number, calls, missed, duration, cost}, the busiest number first; calls without number are listed with null), currency (CAD unless the domain sets another one for voice billing).
GET /api/v1/voice/system-prompts
Standard prompts of the voice server that a flow can play (play and menu steps, system_sound).
Installed in French and English on every voice server. A step plays each one in the language of the call (the flow's, or the one the caller switched to), or in a fixed language with fr: or en: before the name ("en:for-english-press" in a French menu), or only in some languages of the call ({name, only: ["fr"]}); up to 6 in a row. GET /voice/system-prompts/<name>/audio plays one.
Accès : permission voice.use dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Réponse : prompts: [{name, group (greeting, menu, keys, digits, waiting, info, voicemail, errors, end), text {fr, en}}]. languages: Languages they exist in. max: Most prompts in one step.
GET /api/v1/voice/system-prompts/<path:name>/audio
Listen to a standard prompt of GET /voice/system-prompts (the IVR editor plays it before choosing it).
Accès : permission voice.use dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
name |
Name of a prompt of the catalog (without fr: or en:), e.g. if-u-know-ext-dial or digits/9. |
Paramètres de requête
| Nom | Description |
|---|---|
language |
fr (default) or en. |
Réponse : The prompt as a WAV file (audio/wav, 8 kHz mono), kept an hour in the browser.
Errors 404 not_found: not a prompt of the catalog, or no voice server gave it. 400 validation_error: unknown language (field language).
Fonctions voix des numéros
Pause, mode veille, messagerie vocale, enregistrement des appels et limites par numéro. Capacité voxos.
GET /api/v1/voice/calls/<call_id>/recording
The recording of a call (MP3). Permission voice.recordings only (the Admin profile): a recording is a private conversation.
Answers 200 with the audio; 202 {status: "processing", retry_after} while the file is being prepared (try again after retry_after seconds); 410 recording_expired once the retention is over or after a deletion; 404 not_found for an unknown call, a call that was not recorded and a recording that was lost.
Accès : permission voice.recordings dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
call_id |
Call ID. |
Paramètres de requête
| Nom | Description |
|---|---|
download |
1 to get it as an attachment (default: inline). |
Réponse : The MP3 (audio/mpeg).
GET /api/v1/voice/limits
The limits that apply to the organization's voice service.
Set by the domain administrator (PUT /domains/<id>/organizations/<organization_id>/voice-limits, PUT /domains/<id>/voice-limits); with the phone system of a subscription, max_channels follows its lines.
Accès : permission voice.use dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Réponse : limits: {max_channels (simultaneous calls, a forward counts two; null: no limit), source (subscription: the lines of the phone system, organization, formula, domain or none), forward_countries (ISO codes a forward or an outbound call may go to), allowed_prefixes (extra destinations allowed), premium_blocked, max_call_seconds, max_call_cost, spend_limit, spend_window_minutes, suspend_on_cap, international_hours, loop_max_attempts, loop_window_seconds, unknown_rate_per_minute}. outbound_suspension: Why the outbound calls of the organization are suspended {reason (spend, call_cost, manual, platform: the switch of the whole platform), detail, at}; null when they are not. Only a domain or platform administrator lifts it (POST /domains/<id>/organizations/<organization_id>/voice-outbound/resume; the platform switch: PUT /system/voice-security).
GET /api/v1/voice/mailboxes
The voicemail boxes of the endpoints and the standalone boxes the user listens to: the box of their own endpoints and the standalone boxes they are a member of (all of them with voice.routing).
The boxes of the numbers are the voice settings of each number (GET /voice/numbers, PATCH /voice/numbers/<id>).
Accès : permission voice.use dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Réponse : enabled (boolean): Whether the organization has the voicemail_boxes capability (no box otherwise). mailboxes: [{id, kind (endpoint or standalone), name, enabled, endpoint_id, endpoint {id, label, user_id}, greeting_sound_id, language, emails, delay, member_ids, unread}].
POST /api/v1/voice/mailboxes
Create the box of an endpoint or a standalone box (capability voicemail_boxes).
Accès : permission voice.routing dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Corps de la requête
| Nom | Description |
|---|---|
kind |
endpoint (the box of an endpoint: it takes the message when the routing rings that endpoint and nobody answers) or standalone (a box chosen in a call flow) (required). |
endpoint_id |
The endpoint (kind endpoint; one box per endpoint). |
name |
1 to 60 characters (standalone: required; endpoint: default the endpoint's label). |
enabled |
Default true. |
greeting_sound_id |
A sound of the organization played before the beep (empty: the system greeting). |
language |
fr, en or es (default fr). |
emails |
Up to 10 addresses that receive each message (MP3 attached). |
delay |
Seconds the endpoint rings before the box answers, 1 to 240 (kind endpoint, default 20). |
member_ids |
Users who listen to it (standalone; the members who have voice.routing always do). |
Réponse : mailbox: The box.
Errors 403 capability_required: the organization does not have voicemail_boxes.
DELETE /api/v1/voice/mailboxes/<mailbox_id>
Delete a box. Its messages are kept (only the members who have voice.routing see them afterwards); a call flow that named it says that nobody is available.
Accès : permission voice.routing dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
mailbox_id |
Box ID. |
Réponse : success: True.
PUT /api/v1/voice/mailboxes/<mailbox_id>
Change a box (the fields given; see POST, kind and endpoint_id cannot change).
Accès : permission voice.routing dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
mailbox_id |
Box ID. |
Réponse : mailbox: The box.
PATCH /api/v1/voice/numbers/<phone_id>
Change the voice functions of a number: pause, sleep mode, voicemail, call recording, name as caller ID.
The fields given are changed (voicemail and recording field by field). A paused number (enabled: false) plays a message and hangs up; a number in sleep mode goes to the voicemail when it is on, else plays the same message, and sends no SMS push. Pausing a number takes it out of sleep mode. Sleep mode is refused (409 function_not_allowed) on a paused number and on a number routed to a call flow. Switching the voicemail off removes a routing that points at it. A number in the trash answers 409 conflict.
Accès : permission voice.routing dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
phone_id |
Phone number ID (voice-enabled, in the organization). |
Corps de la requête
| Nom | Description |
|---|---|
enabled |
false pauses the number (calls get a message), true resumes it. A pause of the balance lifecycle (paused_reason: balance) is not changed by this. |
sleep |
true puts the number in sleep mode, false takes it out. |
use_label_as_caller_id |
Present the number's name as the caller name on outgoing calls (needs a name). |
voicemail |
{enabled, delay (ring seconds before the voicemail, 1 to 240), emails (at most 10 addresses that receive the message as an MP3), sound_id (own greeting, a sound of the organization, null for the system message), language (fr, en or es)}. |
recording |
{enabled (record the calls of this number), announce (play the legal announcement; null follows the domain)}. |
Réponse : number: The number with state {enabled, sleep}, features {voicemail, recording} and routing.
POST /api/v1/voice/numbers/sleep
Put several numbers in sleep mode, or take them out, in one request (all or none).
Accès : permission voice.routing dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Corps de la requête
| Nom | Description |
|---|---|
number_ids |
IDs of the numbers (voice-enabled, in the organization), 1 to 100. |
sleep |
true to put them in sleep mode, false to take them out. |
Réponse : numbers: The numbers after the change. 409 function_not_allowed (with number_id) when one of them is paused or routed to a call flow; none is changed then.
GET /api/v1/voice/rating/max-duration
The longest call to a destination a credit pays for, at the organization's retail price (increments, minimum and connection fee included), capped by the domain's longest call.
Accès : permission voice.use dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de requête
| Nom | Description |
|---|---|
destination |
The number called (required). |
credit |
The amount to spend (default: the organization's balance). |
Réponse : seconds: The longest call (0: the credit does not pay for a call). credit: The credit used. rate: The rate applied, or null when no price covers the destination (then seconds is 0 and reason no_rate). reason: null, no_rate or insufficient_balance.
GET /api/v1/voice/rating/quote
What a call to a destination is billed to the organization (its retail price), from the cost of the trunk its outbound rules choose and the margins. The platform and domain administrators also see the wholesale price; the platform administrator the cost.
Accès : permission voice.use dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de requête
| Nom | Description |
|---|---|
destination |
The number called (required). |
seconds |
Length of the call (default 60). |
Réponse : quote: {destination, prefix, seconds, retail {per_minute, amount, billed_seconds, rules, mode} or null (the domain bills with its own price list: see rate)}, or null when no cost covers the destination. rate: The rate that applies to the organization (as the domain's price list or the margin gives it): {rate_per_min, connection_fee, increment, min_seconds, currency, source} or null.
DELETE /api/v1/voice/recordings
Delete the recordings of some calls (the call records stay).
Accès : permission voice.recordings dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Corps de la requête
| Nom | Description |
|---|---|
call_ids |
IDs of the calls, 1 to 100. Calls of other organizations and calls without a recording are ignored. |
Réponse : deleted: IDs of the calls whose recording was deleted.
GET /api/v1/voice/voicemails
Voicemail messages of the organization, newest first.
Only the messages the user may hear (the rule of every /voice/voicemails route and of the push of a new message). The box of a number (mailbox_id null): the members who have voice.calls.all or voice.routing, and a member whose SIP endpoint the routing of that number rings, directly, through a ring group (a stage or its fallback) or through a call flow (a dial or groupdial step, or a goto to such a flow: a number behind an IVR counts). The box of an endpoint: the user of the endpoint, and the members who have voice.routing. A standalone box: its members (member_ids), and the members who have voice.routing. A box deleted since: the members who have voice.routing. The platform and domain administrators hear every message.
Accès : permission voice.use dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de requête
| Nom | Description |
|---|---|
number_id |
Only the messages of this number (optional). |
mailbox_id |
Only the messages of this box (an id of GET /voice/mailboxes), or "numbers" for the boxes of the numbers (optional). |
listened |
true for the listened messages, false for the new ones (optional). |
skip |
Pagination offset (default 0). |
limit |
Page size (default 50, at most 100). |
Réponse : voicemails: The page: id, phone_number_id, mailbox_id (null: the box of the number), number, caller, caller_name, duration (seconds), size, language, listened_at, created_at, expires_at. total: Number of messages matching the filters. skip: Offset applied. limit: Page size applied.
DELETE /api/v1/voice/voicemails/<voicemail_id>
Delete a voicemail message and its audio.
A member who has voice.routing deletes any message; a listener of the box of an endpoint or of a standalone box (the rule of GET /voice/voicemails) deletes its messages; the messages of the box of a number need voice.routing (403 forbidden otherwise, voice.calls.all is not enough). A message the user may not hear: 404 not_found.
Accès : permission voice.use dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
voicemail_id |
Voicemail ID. |
Réponse : success: True.
PUT /api/v1/voice/voicemails/<voicemail_id>
Mark a voicemail message listened or new.
Only a message the user may hear (the rule of GET /voice/voicemails); 404 not_found otherwise.
Accès : permission voice.use dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
voicemail_id |
Voicemail ID. |
Corps de la requête
| Nom | Description |
|---|---|
listened |
true marks it listened (the first time is kept), false marks it new. |
Réponse : voicemail: The message.
GET /api/v1/voice/voicemails/<voicemail_id>/file
The audio of a voicemail message (MP3).
Only a message the user may hear (the rule of GET /voice/voicemails); 404 not_found otherwise.
Accès : permission voice.use dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
voicemail_id |
Voicemail ID. |
Paramètres de requête
| Nom | Description |
|---|---|
download |
1 to get it as an attachment (default: inline). |
Réponse : The MP3 (audio/mpeg).
Réglages voix du domaine
Conservation des enregistrements et messages, limites de canaux et de destinations (admin de domaine).
GET /api/v1/domains/<domain_id>/organizations/<organization_id>/voice-limits
Voice limits of an organization: its own overrides and what applies after the domain's defaults.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
organization_id |
Organization ID. |
Réponse : voice_limits: The organization's overrides {max_channels, forward_countries, allowed_prefixes, max_call_seconds, max_call_cost, spend_limit, spend_window_minutes, suspend_on_cap, international_hours} (null: the domain's value). effective: What applies: {max_channels, source (subscription: the lines of the phone system, organization, formula, domain or none), forward_countries, allowed_prefixes, premium_blocked} and the fraud guards (the organization's value, else the domain's). suspension: Why the outbound calls of the organization are suspended {reason (spend, call_cost, manual, platform: the switch of the whole platform, which comes first), detail, at, by (ID of the administrator for manual, "platform" for platform)}, null when they are not.
PUT /api/v1/domains/<domain_id>/organizations/<organization_id>/voice-limits
Change the voice limits of an organization (the fields given are changed, null removes an override). Journalled.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
organization_id |
Organization ID. |
Corps de la requête
| Nom | Description |
|---|---|
max_channels |
Simultaneous calls allowed, 1 to 10000 (null: the domain's rule, or the lines of the phone system when the organization has it). A forward counts two. A value set here wins over the lines of the phone system until it is cleared. |
forward_countries |
ISO codes a forward may go to (null: the domain's list). |
allowed_prefixes |
Digit prefixes (country code first) always allowed as a destination, at most 100 (null: none). |
max_call_seconds |
As the domain's (PUT /domains/<id>/voice-limits); null: the domain's value. |
max_call_cost |
As the domain's (PUT /domains/<id>/voice-limits); null: the domain's value. |
spend_limit |
As the domain's (PUT /domains/<id>/voice-limits); null: the domain's value. |
spend_window_minutes |
As the domain's (PUT /domains/<id>/voice-limits); null: the domain's value. |
suspend_on_cap |
As the domain's (PUT /domains/<id>/voice-limits); null: the domain's value. |
international_hours |
As the domain's (PUT /domains/<id>/voice-limits); null: the domain's value. |
Réponse : voice_limits: The organization's overrides after the change. effective: What applies.
GET /api/v1/domains/<domain_id>/organizations/<organization_id>/voice-outbound
Outbound call rules of an organization: default trunk and rules on the destination (see services/voice_outbound.py).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
organization_id |
Organization ID. |
Paramètres de requête
| Nom | Description |
|---|---|
destination |
A number to test (E.164, or 10 digits for North America): test tells which trunk the rules choose. |
Réponse : voice_outbound: {default_trunk_id, rules: [{pattern, trunk_id, label}]} (rules applied in order, the first whose regular expression matches the destination in E.164, e.g. +15145551234, wins; then the default trunk; then the former order: the number's trunk, the trunk of arrival, a shared trunk). trunks: The trunks it may choose [{id, label, scope domain|platform, outbound_enabled, enabled}]; a platform trunk is named by its description (and its name for a super admin). test: With destination: {destination (E.164), trunk_id (null: the rules say nothing)}.
PUT /api/v1/domains/<domain_id>/organizations/<organization_id>/voice-outbound
Replace the outbound call rules of an organization. Journalled.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
organization_id |
Organization ID. |
Corps de la requête
| Nom | Description |
|---|---|
default_trunk_id |
Trunk for every outbound call no rule matches (null: the former order). |
rules |
Ordered [{pattern, trunk_id, label}] (50 at most): pattern = Python regular expression (1 to 100 characters) searched in the destination in E.164, e.g. "^+1(514|438)" or "^+33"; trunk_id = a trunk of trunks that allows outbound calls; label = free text (80 characters). |
Réponse : voice_outbound: The rules now in force. trunks: As GET.
Errors 400 validation_error (field default_trunk_id or rules): invalid regular expression, trunk the organization may not use or without outbound calls.
POST /api/v1/domains/<domain_id>/organizations/<organization_id>/voice-outbound/resume
Resume the outbound calls of an organization suspended by the fraud guards (spending over the window, a call cut at its maximum cost) or by an administrator. The organization itself cannot do it. Journalled with the suspension it lifts.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
organization_id |
Organization ID. |
Corps de la requête
| Nom | Description |
|---|---|
note |
What was checked (optional string, kept in the journal). |
Réponse : suspension: null.
POST /api/v1/domains/<domain_id>/organizations/<organization_id>/voice-outbound/suspend
Suspend the outbound calls of an organization (calls of its endpoints, its forwards); the calls in progress are hung up within seconds, incoming calls go on. Journalled; the administrators receive an alert.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
organization_id |
Organization ID. |
Corps de la requête
| Nom | Description |
|---|---|
note |
Why (optional string, kept up to 500 characters). |
Réponse : suspension: {reason: manual, detail {note}, at, by (ID of the administrator)}.
GET /api/v1/domains/<domain_id>/organizations/<organization_id>/voice-recording
Recording rules of the outbound calls of the endpoints of an organization (they win over the endpoint's own setting; see services/voice_recording.py).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
organization_id |
Organization ID. |
Réponse : voice_recording: {outbound_always (every outbound call of its endpoints is recorded), outbound_follows_number (the outbound calls presenting a number whose recording is on are recorded), recording_skip_allowed (a user may skip the imposed recording of his next outbound call, from the app)}.
PUT /api/v1/domains/<domain_id>/organizations/<organization_id>/voice-recording
Change the recording rules of the outbound calls of an organization (the fields given; the others are kept). Journalled.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
organization_id |
Organization ID. |
Corps de la requête
| Nom | Description |
|---|---|
outbound_always |
Record every outbound call of its endpoints. |
outbound_follows_number |
Record the outbound calls that present a number whose recording is on. |
recording_skip_allowed |
A user may skip the imposed recording of his next outbound call (PUT /voice/call-info/recording/skip-next). |
Réponse : voice_recording: The rules now in force, as GET.
GET /api/v1/domains/<domain_id>/voice-limits
Voice limits of a domain: channels, destinations and the fraud guards of outbound calls (E47). By default an organization has 10 simultaneous calls, calls to the public network (endpoints, forwards, external members of a group) go to Canada and the United States only, premium-rate numbers are refused, a call is cut after 4 hours or at 10 of cost, and 10 of cost in 5 minutes suspends the outbound calls of the organization. The voice server applies them.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : voice_limits: {default_max_channels (10 by default), channels_per_destination, min_channels, forward_countries, premium_blocked, max_call_seconds, max_call_cost, spend_limit, spend_window_minutes, suspend_on_cap, international_hours, loop_max_attempts, loop_window_seconds, unknown_rate_per_minute} (meaning and defaults: see PUT).
PUT /api/v1/domains/<domain_id>/voice-limits
Change the voice limits of a domain (the fields given are changed).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
default_max_channels |
Simultaneous calls of an organization without its own limit, 1 to 10000 (default 10; null: none). A forward counts two. |
channels_per_destination |
Formula by forward destinations: the limit is max(min_channels, distinct forward destinations x this) (null: off). |
min_channels |
Floor of the formula (default 2). |
forward_countries |
ISO 3166-1 alpha-2 codes a forward, a call of an endpoint or a call code may go to (default US and CA). |
premium_blocked |
Refuse premium-rate numbers (900, 976; default true). |
max_call_seconds |
Longest outbound call, 60 to 86400 seconds (default 14400). |
max_call_cost |
The platform's cost (CAD, from the cost table of the trunk) at which an outbound call is cut, 0.5 to 10000 (default 10). |
spend_limit |
The platform's cost (CAD) over the window that suspends the outbound calls of an organization, 0.5 to 100000 (default 10). |
spend_window_minutes |
The sliding window of spend_limit, 1 to 1440 minutes (default 5). |
suspend_on_cap |
A call cut at max_call_cost suspends the outbound calls of the organization (default true). |
international_hours |
Business hours {timezone, schedule {mon..sun: [[start, end]]}, exceptions, closures} outside which a call abroad (outside Canada and the United States) is refused; null: any time (default). |
loop_max_attempts |
The same caller to the same number more often than this in loop_window_seconds is a loop, 1 to 100 (default 3). |
loop_window_seconds |
10 to 3600 (default 60). |
unknown_rate_per_minute |
Cost assumed for a destination in Canada or the United States that no cost table covers, 0 to 10 (default 0.05); elsewhere such a call is refused. |
Réponse : voice_limits: The limits after the change.
GET /api/v1/domains/<domain_id>/voice-pricing
How the calls of the domain are priced: the platform's margin (what the domain is billed) and the domain's own (what its customers are billed).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : voice_pricing: {wholesale (the rules of the price billed to the domain: markup_percent, min_markup_per_minute, min_billed_seconds, increment_seconds, connection_fee), wholesale_override (super admin only: the rules set for this domain over the platform's), retail {mode (margin: the domain's margin over the wholesale price; rates: the domain's own price list, GET /domains/<id>/voice-rates), markup_percent, min_markup_per_minute, min_billed_seconds, increment_seconds, connection_fee}}.
PUT /api/v1/domains/<domain_id>/voice-pricing
Change the pricing of the domain's calls.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
retail |
{mode (margin or rates), markup_percent, min_markup_per_minute, min_billed_seconds, increment_seconds, connection_fee}: the domain's margin (bounds as PUT /system/voice-pricing). |
wholesale |
The platform's margin for this domain (super admin only, else 400 validation_error): the same rules; a rule set to null goes back to the platform's. |
Réponse : voice_pricing: As GET.
GET /api/v1/domains/<domain_id>/voice-rating/quote
What a call to a destination costs and is billed, for an organization of the domain (or through a trunk).
The cost and the provider's table are shown to the super admin only.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Paramètres de requête
| Nom | Description |
|---|---|
destination |
The number called (required). |
seconds |
Length of the call (default 60). |
organization_id |
The organization calling (its outbound rules choose the trunk). |
trunk_id |
A trunk the domain may use, instead. |
Réponse : quote: {destination, prefix, seconds, wholesale {per_minute, amount, billed_seconds, rules}, retail ({per_minute, amount, billed_seconds, rules, mode} or null: the domain's own price list applies), trunk_id; super admin: cost {per_minute, currency, amount, source_per_minute, source_currency}, trunk_name, table_id, table_name, description}, or null when no cost covers the destination.
GET /api/v1/domains/<domain_id>/voice-security
Voice security of a domain: its organizations whose outbound calls are suspended and their latest security events.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : platform_suspended (boolean): Every outbound call of the platform is suspended. suspended: [{organization_id, name, suspension {reason, detail, at, by}}]. events: The latest 100 security events of the domain's organizations, newest first [{id, organization_id, organization_name, type (outbound_suspended, call_cost_reached, call_duration_reached, refused_destination, refused_international_hours, refused_loop, refused_no_rate, refused_source, new_country, endpoint_locked, night_volume...), detail, at, notified (an alert was sent)}].
GET /api/v1/domains/<domain_id>/voice-settings
Voice settings of a domain: retention of recordings and voicemail messages, voicemail recording limits (E27, E31).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : voice_settings: {recording_days (days a recording is kept, 0: forever; default 30), recording_announce (play the legal announcement on recorded calls unless the number says otherwise; default false), voicemail {retention_days (default 90, 0: forever), max_seconds (longest message, default 180), silence_seconds (silence that ends a message, default 5), min_seconds (shorter messages are dropped, default 1)}, auto_softphone (every organization of the domain gets a SIP endpoint for the softphone of the mobile app and its voice numbers ring it; default false)}.
PUT /api/v1/domains/<domain_id>/voice-settings
Change the voice settings of a domain (the fields given are changed). New recordings and messages use them; those already stored keep their expiry.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
recording_days |
Days a recording is kept, 0 to 3650 (0: forever). |
recording_announce |
Play the legal announcement on recorded calls (boolean). |
voicemail |
{retention_days 0-3650, max_seconds 10-600, silence_seconds 2-30, min_seconds 0-10}. |
auto_softphone |
Automatic softphone. An organization created in the domain from then on (sign-up, OAuth sign-up, creation by an admin) receives the capabilities voxos and sip of the domain and a SIP endpoint for its owner, once they hold a membership in it (label: their name; the calls it does not answer go to the owner's verified phone when they have one); a voice number the organization receives without routing (purchase, assignment by the domain admin, port completed) rings that endpoint, with the push of the mobile app, and becomes the number its calls present. Existing organizations: the operator's catch-up command. Turning it off changes nothing already created. |
Réponse : voice_settings: The settings after the change.
Voix (interne)
Dépôt des messages vocaux et des enregistrements par les serveurs voix, jeton de service. Ni utilisateurs ni clés API.
GET /api/v1/internal/voice/ping
Check the service token (the voice server calls it at start-up).
Accès : jeton de service (Authorization: Bearer $VOICE_INTERNAL_TOKEN), ni utilisateur ni clé API
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Réponse : ok: True.
Errors 401 unauthorized: the Authorization: Bearer header is missing or is not the service token. 503 internal_api_disabled: VOICE_INTERNAL_TOKEN is not set on the server.
POST /api/v1/internal/voice/recordings
Deposit the recording of a call (service token). The call record must exist: the voice server retries on 404 call_not_found.
Accès : jeton de service (Authorization: Bearer $VOICE_INTERNAL_TOKEN), ni utilisateur ni clé API
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Corps de la requête
| Nom | Description |
|---|---|
file |
The recording, MP3 (multipart, 30 MB at most). |
call_uniqueid |
Unique id of the call on the voice server (required). |
duration |
Length in seconds (optional). |
Réponse : recording: {state, available_at, expires_at, size, duration}. 201 when stored, 200 when this call already has its recording.
Errors 401 unauthorized: the Authorization: Bearer header is missing or is not the service token. 503 internal_api_disabled: VOICE_INTERNAL_TOKEN is not set on the server.
POST /api/v1/internal/voice/voicemails
Deposit a voicemail message recorded by a voice server (service token).
Depositing the same call_uniqueid again changes nothing and answers 200 with the stored message, so the voice server can retry safely.
Accès : jeton de service (Authorization: Bearer $VOICE_INTERNAL_TOKEN), ni utilisateur ni clé API
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Corps de la requête
| Nom | Description |
|---|---|
file |
The message, MP3 (multipart, 4 MB at most). |
phone_number_id |
ID of the number whose box it is (required; an unknown or unassigned number is 404). |
caller |
Caller ID as the carrier gave it. |
caller_name |
Caller name (optional). |
duration |
Length in seconds (required). |
call_uniqueid |
Unique id of the call on the voice server (makes the deposit idempotent). |
language |
fr, en or es (optional; default: the language of the box). |
mailbox_id |
The box of an endpoint or the standalone box the message was left in (optional; it must belong to the organization of the number, else 404); absent: the box of the number. |
recorded_at |
When the message was left, ISO 8601 UTC (optional; default now). |
Réponse : voicemail: The stored message. 201 when new, 200 when this call's message was already deposited.
Errors 401 unauthorized: the Authorization: Bearer header is missing or is not the service token. 503 internal_api_disabled: VOICE_INTERNAL_TOKEN is not set on the server.
Métriques
Volumes, performance, export.
GET /api/v1/metrics/agents
Get list of agents with message counts.
Integrations (hidden service accounts) are not agents and are not listed; the messages they send still count in the team totals.
Accès : permission metrics.all ou metrics.team dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de requête
| Nom | Description |
|---|---|
team_id |
Team ID or comma-separated list of team IDs (optional; default: every team the caller can access, only the team they are placed in with metrics.team alone; a team outside them gets 403 forbidden). |
start_date |
Start date ISO format. |
end_date |
End date ISO format. |
days |
Number of days to look back (default 30). |
Réponse : agents: The agents with their message counts (integrations are not listed). period: {start, end} (ISO 8601) of the period.
GET /api/v1/metrics/export
Export metrics data as JSON.
Accès : permission view_global_metrics (devenue metrics.all)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de requête
| Nom | Description |
|---|---|
team_id |
Team ID (optional; default: every team the caller can access in the current domain; a team outside them gets 403 forbidden). |
start_date |
Start date ISO format. |
end_date |
End date ISO format. |
days |
Number of days to look back (default 30). |
Réponse : Comprehensive metrics data for export.
GET /api/v1/metrics/overview
Get overview metrics.
Accès : permission metrics.all ou metrics.team dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de requête
| Nom | Description |
|---|---|
team_id |
Team ID or comma-separated list of team IDs (optional; default: every team the caller can access in the current domain, only the team they are placed in with metrics.team alone; a team outside them gets 403 forbidden). |
user_id |
User ID or comma-separated list of user IDs (optional, filters by sent_by). |
start_date |
Start date ISO format. |
end_date |
End date ISO format. |
days |
Number of days to look back (default 30). |
Réponse : Overview metrics.
GET /api/v1/metrics/performance
Get performance metrics (response times, delivery rates).
Accès : permission metrics.all ou metrics.team dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de requête
| Nom | Description |
|---|---|
team_id |
Team ID (optional; default: every team the caller can access in the current domain, only the team they are placed in with metrics.team alone; a team outside them gets 403 forbidden). |
start_date |
Start date ISO format. |
end_date |
End date ISO format. |
days |
Number of days to look back (default 30). |
Réponse : period: {start, end} (ISO 8601) of the period. delivery_rate: Share of the messages delivered. messages_by_status: Number of messages per status. response_time: Response time statistics.
GET /api/v1/metrics/team/<team_id>
Get detailed metrics for a specific team.
by_user lists the agents only: integrations (hidden service accounts) are left out, their messages still count in the overview. With metrics.team alone (no metrics.all), only the team the caller is placed in (403 forbidden for another one).
Accès : permission metrics.all ou metrics.team dans l'organisation (profils); accès à l'équipe (team_id)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
team_id |
Team ID. |
Paramètres de requête
| Nom | Description |
|---|---|
start_date |
Start date ISO format. |
end_date |
End date ISO format. |
days |
Number of days to look back (default 30). |
Réponse : Detailed team metrics.
GET /api/v1/metrics/user/<user_id>
Get metrics for a specific user.
The messages counted are those sent in the teams the caller can access (a user who writes in several organizations shows each caller only what happened in theirs; a platform_admin sees all). One's own metrics: metrics.team is enough (counted in the team one is placed in); another user's: metrics.all and the right to manage that user (403 forbidden otherwise).
Accès : permission metrics.all ou metrics.team dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
user_id |
User ID. |
Paramètres de requête
| Nom | Description |
|---|---|
start_date |
Start date ISO format. |
end_date |
End date ISO format. |
days |
Number of days to look back (default 30). |
Réponse : User metrics.
GET /api/v1/metrics/volume
Get message volume metrics by time period.
Accès : permission metrics.all ou metrics.team dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de requête
| Nom | Description |
|---|---|
team_id |
Team ID or comma-separated list of team IDs (optional; default: every team the caller can access in the current domain, only the team they are placed in with metrics.team alone; a team outside them gets 403 forbidden). |
user_id |
User ID or comma-separated list of user IDs (optional, filters by sent_by). |
start_date |
Start date ISO format. |
end_date |
End date ISO format. |
days |
Number of days to look back (default 30). |
granularity |
hour, day, week, or month (default day). |
group_by_team |
If "true", group results by team (default false). |
group_by_user |
If "true", group results by user (default false); integrations (service accounts) get no row of their own. |
Réponse : Volume data by period.
Facturation
Crédits, utilisation, factures, plans tarifaires, opérations d'administration.
GET /api/v1/billing
Billing overview of the current organization.
Not available to restricted API keys (a key limited to a team, or an integration's key): 403 with error forbidden.
Errors are {error, message}: 400 no_organization when the user has no organization.
Accès : authentifié (JWT ou clé API)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Réponse : billing: Billing record (credit_balance, auto_recharge, payment_method without secrets, billing_email, pricing_plan_id, custom_rates, and the negative-balance lifecycle of E46: balance_state ok, negative_grace, suspended, limbo or released, with negative_since, grace_ends_at, suspended_at, limbo_at, release_at, released_at in UTC ISO 8601 with Z, null when they do not apply). pricing_plan: Pricing plan in force (rates, volume discounts). current_month_usage: Usage of the billing period by type, with total_cost and total_messages. billing_period: start and end of the current billing period. recent_transactions: The latest credit transactions.
POST /api/v1/billing/admin/add-credits
Add credits to an organization by hand (super admin only). Errors are {error, message}: 400 validation_error with field (organization_id missing, amount not above zero).
Accès : permission de plateforme platform_admin
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Corps de la requête
| Nom | Description |
|---|---|
organization_id |
Organization ID (required). |
amount |
Credits to add, above zero (required). |
description |
Label of the transaction. |
Réponse : success: true. new_balance: Balance after the credit.
POST /api/v1/billing/admin/assign-plan
Assign a pricing plan to an organization, with optional custom rates (super admin only).
Errors are {error, message}: 400 validation_error (organization_id missing), 404 not_found (plan), 500 internal_error.
Accès : permission de plateforme platform_admin
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Corps de la requête
| Nom | Description |
|---|---|
organization_id |
Organization ID (required). |
pricing_plan_id |
Plan ID (null: back to the default plan). |
custom_rates |
Rates that replace those of the plan for this organization (null: none). |
Réponse : Same response as GET /billing for the organization.
POST /api/v1/billing/admin/charge-dids
Charge monthly DID fees for all organizations (super admin only).
This should be called at the beginning of each month. Only DIDs not yet billed this month will be charged. An organization whose domain charges its monthly fees itself (settings.billing.monthly_fees, E40) is charged by that run instead.
Accès : permission de plateforme platform_admin
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Réponse : organizations_processed: Number of organizations listed in results. results: One row per organization charged something or in error: {organization_id, organization_name, charged_count, total_cost, charged_dids} or {organization_id, organization_name, error}.
POST /api/v1/billing/admin/fix-usage-dates
Give each usage record the date of its message (super admin only, maintenance).
Accès : permission de plateforme platform_admin
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Réponse : success: true. fixed_count: Number of records changed.
POST /api/v1/billing/admin/mark-billed
Mark usage records as billed (super admin only).
Accès : permission de plateforme platform_admin
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Corps de la requête
| Nom | Description |
|---|---|
organization_id |
Organization ID (required). |
record_ids |
IDs of the records to mark. |
usage_type |
Without record_ids, marks every record of this type for the current month. |
Réponse : success: true. marked_count: Number of records marked.
PUT /api/v1/billing/auto-recharge
Change the auto-recharge settings. When the balance is at or under threshold, the card is charged: mode fixed buys amount credits each time, top_up_to buys what brings the balance up to amount. Turning it on with a balance already at or under the threshold recharges at once. A failed attempt is remembered (last_failure_at): no new attempt for 24 hours unless the card or the settings change, and the members who hold billing.manage are told by e-mail, SMS, push and the real-time event auto_recharge_failed.
Errors are {error, message}: 400 validation_error with field (enabled not a boolean, threshold below zero, not a number or not among the domain's choices, amount not above zero or under the minimum or over the balance cap, mode, payment_method_id missing or not a card of the organization, amount not above threshold in top_up_to mode).
Accès : permission billing.manage dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Corps de la requête
| Nom | Description |
|---|---|
enabled |
Whether the balance is recharged automatically. |
threshold |
Balance at or under which a recharge starts (default 20, at least 0). |
amount |
Credits bought by each recharge (fixed) or balance to reach (top_up_to) (default 100, above zero). |
mode |
fixed (default; the previous behavior) or top_up_to. |
payment_method_id |
The card to charge (default: the main card). |
Réponse : auto_recharge: The settings {enabled, threshold, amount, mode, payment_method_id, last_failure_at}.
GET /api/v1/billing/balance
Current credit balance of the organization (the real-time event balance_update announces each change).
Not available to restricted API keys (a key limited to a team, or an integration's key): 403 with error forbidden.
Errors are {error, message}: 400 no_organization when the user has no organization.
Accès : authentifié (JWT ou clé API)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Réponse : credit_balance: Balance in CAD (negative after a call rated after the fact, E46). auto_recharge: Auto-recharge settings {enabled, threshold, amount, mode, payment_method_id, last_failure_at}. balance_state: ok, negative_grace, suspended, limbo or released (E46); anything but ok comes with the dates below. negative_since: When the balance became negative (UTC, ISO 8601 with Z), or null. grace_ends_at: End of the grace period, when sending and calls are suspended, or null. suspended_at: When the account was suspended, or null. limbo_at: When the numbers were put in reserve, or null. release_at: When the numbers go back to the pool of the domain, or null. released_at: When they did, or null.
POST /api/v1/billing/check-balance
Check whether the balance covers a message, without sending or charging anything.
Not available to restricted API keys (a key limited to a team, or an integration's key): 403 with error forbidden.
Errors are {error, message}: 400 no_organization when the user has no organization.
Accès : authentifié (JWT ou clé API)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Corps de la requête
| Nom | Description |
|---|---|
body |
Text of the message (segments are counted from it). |
has_media |
true for an MMS (default false). |
Réponse : has_balance: Whether the balance covers the cost. estimated_cost: Cost of the message. current_balance: Current balance. segments: Number of SMS segments.
GET /api/v1/billing/invoices
Invoices of the organization, newest first.
Not available to restricted API keys (a key limited to a team, or an integration's key): 403 with error forbidden.
Accès : authentifié (JWT ou clé API)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de requête
| Nom | Description |
|---|---|
status |
Invoice status (draft, paid...). |
skip |
Offset (default 0). |
limit |
Page size (default 50, brought back into 1-100). A skip or limit that is not an integer is refused (400 validation_error with field). |
Réponse : invoices: The page of invoices. total: Number of invoices matching the filters, whatever the page. skip: Offset applied. limit: Page size applied.
GET /api/v1/billing/invoices/<invoice_id>
One invoice of the organization.
Not available to restricted API keys (a key limited to a team, or an integration's key): 403 with error forbidden.
Errors are {error, message}: 404 not_found, 403 forbidden (invoice of another organization).
Accès : authentifié (JWT ou clé API)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
invoice_id |
Invoice ID. |
Réponse : invoice: The invoice with its lines and taxes.
GET /api/v1/billing/overview
What the organization was billed and what it will be: the last statement, the next one estimated with each line explained, and the usage of the current period with what it already cost and a straight-line projection (an estimate).
Errors are {error, message}: 400 no_organization.
Accès : permission billing.view ou billing.manage dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Réponse : currency: Currency of the amounts. balance: Credit balance. kind: subscription (a plan or the phone system, statement each month), monthly_fees (monthly fees of the numbers) or usage_only (only what is used). lines: With the phone system {included, burst, limit, manual, max_channels, price, peak {lines, at}, overage, overage_cost} (the overage of the peak is in next), else null. period: The current period {start, end, elapsed (0 to 1)}: the subscription's month (anniversary to anniversary), else the calendar month. last_statement: null, or the last monthly statement {id, number, total, date, period_start, period_end}. next: The next statement {date, lines [{code, description, detail (why: what is counted, what is included), quantity, unit_price, amount}], total}: the subscription estimated with the seats of today (as GET /billing/subscription/estimate), the monthly fees of the numbers (one number_fees line per number), or no line and date null (usage_only: no statement is issued, each use is debited when it happens; as GET /domains/<id>/billing-overview). An organization counts as subscribed only while the domain offers subscriptions (as GET /billing/subscription). usage: The current period {spent {category: amount debited}, total_spent, communications (messages and calls debited), by_subtype {sms_out, sms_in, mms_out, mms_in, call, usage_other: {amount, count}}, messages null or {used, included, beyond, cost}, minutes null or {used, included, beyond, cost}}. projection: {usage (communications projected to the end of the period), total (next statement + projected usage)}; both null in the first day of the period. previous_month: Debits of the last calendar month {start, total}.
GET /api/v1/billing/payment-config
Public configuration of the payment providers of the domain (no secret key).
Not available to restricted API keys (a key limited to a team, or an integration's key): 403 with error forbidden.
Errors are {error, message}: 400 no_organization, 404 domain_not_found.
Accès : authentifié (JWT ou clé API)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Réponse : payment_providers: {stripe: {enabled, publishable_key}, nuvei: {enabled, merchant_id, merchant_site_id, environment}}, plus fake: {enabled} on a development or test domain that uses the test provider. default_provider: Provider to offer first (stripe or nuvei).
DELETE /api/v1/billing/payment-method
Remove the payment method of the organization and turn auto-recharge off.
Accès : permission billing.manage dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Réponse : success: true.
POST /api/v1/billing/payment-method
Record the main payment method of the organization. Older name of POST /billing/payment-methods: for Nuvei the token is verified with the provider and brand, last four digits and expiry come from the provider; for Stripe (legacy) the data is kept as the client sent it.
Errors are {error, message}: 400 validation_error with field provider_payment_method_id when it is missing; for Nuvei also the errors of POST /billing/payment-methods.
Accès : permission billing.manage dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Corps de la requête
| Nom | Description |
|---|---|
provider |
stripe (default) or nuvei. |
provider_payment_method_id |
Identifier of the method at the provider (required). |
card_type |
credit (default) or debit. |
last4 |
Last four digits. |
brand |
Card brand. |
exp_month |
Expiry month, 1 to 12 (a whole number or a string of digits; 400 validation_error with field otherwise). |
exp_year |
Expiry year, four digits (same rule). |
Réponse : payment_method: {provider, last4, brand, exp_month, exp_year}.
GET /api/v1/billing/pricing-plans
The pricing plans the caller may see.
Pricing plans belong to the platform, and some are negotiated for one customer: a platform admin sees them all; a domain admin sees the plans in use in the organizations of their domains and the default plan (or, with X-Admin-Organization, the plan of that organization); any other caller sees the plan of their current organization (the default plan when it has none).
Accès : permission billing.view dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Réponse : pricing_plans: List of plans with rates, volume discounts, included credits and monthly fee.
POST /api/v1/billing/pricing-plans
Create a pricing plan (super admin only).
Errors are {error, message}: 400 validation_error with field for a missing required field.
Accès : permission de plateforme platform_admin
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Corps de la requête
| Nom | Description |
|---|---|
name |
Internal name (required). |
display_name |
Name shown to customers (required). |
rates |
Rates by usage type (required). |
volume_discounts |
Volume discount tiers. |
included_credits |
Credits included each period. |
monthly_fee |
Monthly fee. |
is_default |
Whether this is the default plan. |
Réponse : pricing_plan: The created plan.
DELETE /api/v1/billing/pricing-plans/<plan_id>
Delete a pricing plan (super admin only). Errors are {error, message}: 404 not_found, 409 conflict for the default plan.
Accès : permission de plateforme platform_admin
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
plan_id |
Pricing plan ID. |
Réponse : success: true.
PUT /api/v1/billing/pricing-plans/<plan_id>
Update a pricing plan (super admin only). Errors are {error, message}: 404 not_found.
Accès : permission de plateforme platform_admin
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
plan_id |
Pricing plan ID. |
Corps de la requête
| Nom | Description |
|---|---|
display_name |
Name shown to customers. |
rates |
Rates by usage type. |
volume_discounts |
Volume discount tiers. |
included_credits |
Credits included each period. |
monthly_fee |
Monthly fee. |
is_default |
Whether this is the default plan. |
Réponse : pricing_plan: The plan after the change.
POST /api/v1/billing/recharge
Recharge the balance: charge a card of the organization and credit the balance. The credits bought are amount; the taxes of the domain come on top of what the card is charged. A receipt (invoice) is issued and e-mailed. The balance is credited only after the provider approved the payment.
Send an Idempotency-Key header (or idempotency_key in the body): the same key never charges twice, it returns the first answer (replayed: true).
Errors are {error, message}: 400 validation_error (amount missing, not a number, more than two decimals or under the minimum, with field), 400 configuration_error (no payment method, payment provider not configured), 400 card_expired, 404 not_found (card of another organization), 409 billing_info_required (taxes apply and the billing address is missing), 409 quote_changed (with the new quote; also when the promotional code is no longer good at the moment the payment opens, quote.promo_info.error says why), 409 payment_in_progress (one payment at a time per organization, whatever the number of requests at once), 422 max_balance (with maximum), 402 payment_declined (with reason: card_declined, insufficient_funds, card_expired, cvv_declined or card_restricted), 402 authentication_required (the bank asks for a verification), 502 payment_unknown (the provider did not answer: nothing is credited and the payment is checked afterwards), 502 provider_error.
An unexpected failure answers 500 internal_error.
Accès : permission billing.manage dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Corps de la requête
| Nom | Description |
|---|---|
amount |
Credits to buy, a number above zero with at most two decimals. |
payment_method_id |
The card (default: the main card). |
promo_code |
A promotional code (optional). |
quote_total |
The total of the quote the customer saw; a different total is refused with quote_changed. It must be a finite number (400 validation_error with field quote_total otherwise). |
idempotency_key |
Same as the Idempotency-Key header. |
Réponse : success: true. new_balance: Balance after the recharge. transaction_id: Reference of the payment at the provider. transaction: The ledger row of the credit. invoice: {id, invoice_number} of the receipt. payment_id: The card debit. quote: What was charged {credits, promotion, subtotal, taxes, total, currency, credited}.
GET /api/v1/billing/subscription
The plans the domain offers and the subscription of the organization: plan, phone system, usage of the month, seats and next statement.
Errors are {error, message}: 400 no_organization.
Accès : permission billing.view ou billing.manage dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Réponse : enabled (boolean): The domain offers subscriptions. plans: The messaging plans [{key (essential, team, enterprise), name, price, included {messages, users, teams, numbers, widgets}, max_users, max_teams}]. voice: The phone system {price, included_numbers, full_user, virtual_user, minutes_per_full_user, minute_overage, warn_ratio, tollfree_inbound_per_minute, recording_per_extension, included_lines, lines_per_full_user, burst_lines, extra_line}. addons: Monthly price of the extras {number, tollfree_number, team, widget}. overage_tiers: Price of the messages beyond those included [{up_to, price}], by position in the month's volume (the last up_to is null). mms_weight: Messages an MMS counts for. subscription: null (no subscription, or the domain turned subscriptions off: then nothing of it is billed, as in GET /billing/overview), or {messaging_plan, voice, period_start, period_end, pending {messaging?, voice?} (changes at the next anniversary), usage {messages, messages_included, minutes, minutes_included}, seats {users, full_users, virtual_users, recording_extensions, numbers, tollfree_numbers, teams, widgets}, lines {included, burst, limit, manual, max_channels, price, peak {lines, at}, overage, overage_cost} (calls at once: included, allowed above, peak of the period and its overage billed on the next statement), next_statement {date, lines [{code, description, detail, quantity, unit_price, amount}], total}}.
PUT /api/v1/billing/subscription
Choose the plans of the organization. A first subscription starts today and its first month is charged at once; a dearer plan or the phone system added apply now (the difference charged pro rata of the month); a cheaper plan, a plan or the phone system dropped apply at the next anniversary. Everything is paid from the credit: the automatic recharge with a card must be on. Journaled.
Errors are {error, message}: 400 no_organization; 400 validation_error with field (field missing, unknown or inactive plan, voice not a boolean, subscriptions not offered by the domain, a first subscription with neither a plan nor the phone system); 402 auto_recharge_required (the automatic recharge is off and a plan or the phone system is chosen); 402 plan_limit {limit (users or teams), plan, upgrade (the first plan with room, or null), current (members who use the messages and pending invitations, or teams)}: the organization already has more than the chosen plan allows (checked when it is asked for, also for a cheaper plan that would only apply at the anniversary; keeping the plan held is never refused).
Accès : permission billing.manage dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Corps de la requête
| Nom | Description |
|---|---|
messaging_plan |
essential, team, enterprise, or null for none (required). |
voice |
The phone system (required). |
Réponse : As GET /billing/subscription.
GET /api/v1/billing/subscription/estimate
The monthly statement the organization would have with these plans (its seats of today).
The peak of lines of the current period beyond the included ones (in arrears, extra_line) is added when the organization already has the phone system. Errors are {error, message}: 400 no_organization, 400 validation_error with field messaging_plan (unknown plan, or a plan the domain no longer offers).
Accès : permission billing.view ou billing.manage dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de requête
| Nom | Description |
|---|---|
messaging_plan |
essential, team or enterprise (an active plan of the domain); none: without a messaging plan; absent or empty: the plan the organization holds (or the one pending for the next anniversary; none without a subscription). |
voice |
1 or true for the phone system; anything else (or absent): without it. |
Réponse : lines: [{code, description, detail, quantity, unit_price, amount}] (detail says what is counted and what is included). total: Total of the month. seats: What is counted {users, full_users, virtual_users, recording_extensions, numbers, tollfree_numbers, teams, widgets}.
GET /api/v1/billing/transactions
The operations of the balance (recharges, usage, monthly fees, refunds, gifts, adjustments), newest first.
Not available to restricted API keys (a key limited to a team, or an integration's key): 403 with error forbidden.
Errors are {error, message}: 400 validation_error with field (skip or limit not an integer; from or to not an ISO 8601 date; category, type or number_id invalid).
Accès : authentifié (JWT ou clé API)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de requête
| Nom | Description |
|---|---|
from |
ISO 8601 date or datetime, lower bound (UTC). |
to |
ISO 8601 date (the whole day) or datetime, upper bound. |
category |
all, communications, payments, recurring or admin. |
type |
all, purchase, usage, refund, adjustment or bonus. |
number_id |
Only the operations of this phone number (calls, monthly fees). |
skip |
Offset (default 0). |
limit |
Page size (default 50, brought back into 1-100). |
Réponse : transactions: The page: {id, organization_id, type, category, subtype (machine name: recharge_manual, recharge_auto, sms_out, sms_in, mms_out, mms_in, call, did_monthly, voicemail_monthly, recording_monthly, sms_monthly, flow_monthly, refund, chargeback, gift, admin_credit, admin_fix...), amount, balance_after, currency, description, phone_number_id, call_id, invoice_id, payment_method {brand, last4}, refund_id, reference_id, reference_type, created_at}. total: Number of transactions matching the filters, whatever the page. skip: Offset applied. limit: Page size applied.
GET /api/v1/billing/usage
Usage records (one per charged message), newest first.
Not available to restricted API keys (a key limited to a team, or an integration's key): 403 with error forbidden.
Errors are {error, message}: 400 validation_error with field for a skip or limit that is not an integer or a date that is not ISO 8601.
Accès : authentifié (JWT ou clé API)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de requête
| Nom | Description |
|---|---|
start_date |
ISO 8601 date or datetime, lower bound (a value without time zone is UTC). |
end_date |
ISO 8601 date or datetime, upper bound. |
type |
Usage type (sms, mms...). |
skip |
Offset (default 0). |
limit |
Page size (default 50, brought back into 1-100). |
Réponse : usage_records: The page of records. total: Number of records matching the filters, whatever the page. skip: Offset applied. limit: Page size applied.
GET /api/v1/billing/usage/detailed
Detailed usage of the billing period (from the billing anniversary) with totals.
Not available to restricted API keys (a key limited to a team, or an integration's key): 403 with error forbidden.
Accès : authentifié (JWT ou clé API)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de requête
| Nom | Description |
|---|---|
start_date |
ISO 8601 date or datetime (default: start of the billing period). 400 validation_error when it is not a date. |
end_date |
ISO 8601 date or datetime (default: now, never later). |
Réponse : detailed: Usage by type with unit prices, discounts and costs. totals: {total_cost}. period: {start, end} as dates.
GET /api/v1/billing/usage/summary
Usage totals by type for a period (the current month by default).
Not available to restricted API keys (a key limited to a team, or an integration's key): 403 with error forbidden.
Accès : authentifié (JWT ou clé API)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de requête
| Nom | Description |
|---|---|
start_date |
ISO 8601 date or datetime (default: first of the month). 400 validation_error when it is not a date. |
end_date |
ISO 8601 date or datetime (default: now). |
Réponse : summary: {type: {count, total_cost}}.
GET /api/v1/organizations/<org_id>/billing
Billing overview of any organization (super admin only); same response as GET /billing.
Accès : permission de plateforme platform_admin
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
org_id |
Organization ID. |
Réponse : billing: Billing record. pricing_plan: Pricing plan in force. current_month_usage: Usage of the billing period. billing_period: start and end of the period. recent_transactions: The latest credit transactions.
POST /api/v1/organizations/<org_id>/billing/backfill
Backfill usage records for unbilled messages and DIDs (super admin only).
Accès : permission de plateforme platform_admin
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
org_id |
Organization ID. |
Réponse : messages_processed: Messages found without usage record. usage_records_created: Usage records created. by_type: Usage records created by type (sms_outbound, sms_inbound, mms_outbound, mms_inbound). total_credits_deducted: Credit debited for those messages. dids_charged, dids_cost: Numbers charged for the month and their cost. subscription_charged, subscription_cost: Whether the monthly subscription was charged, and its cost.
This will 1. Find all messages without usage records and create them 2. Charge monthly DID fees if not already charged this month
GET /api/v1/organizations/<org_id>/billing/backfill/preview
Preview what would be backfilled for an organization (super admin only); nothing is written.
Accès : permission de plateforme platform_admin
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
org_id |
Organization ID. |
Réponse : messages: Messages without usage record, by type (sms_outbound, sms_inbound, mms_outbound, mms_inbound): {count, segments, cost}. dids: Numbers not billed this month: {standard, tollfree}, each {count, cost}. subscription: {due, cost} the monthly subscription of the plan, when due. totals: {messages_count, messages_cost, dids_count, dids_cost, subscription_cost, total_cost}.
POST /api/v1/organizations/<org_id>/billing/charge-dids
Charge monthly DID fees for a specific organization (super admin only).
Only DIDs not yet billed this month are charged; when the domain charges its monthly fees itself (settings.billing.monthly_fees, E40), its run for this organization is made instead.
Accès : permission de plateforme platform_admin
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
org_id |
Organization ID. |
Réponse : charged_count: Number of numbers charged. total_cost: Amount debited. charged_dids: The numbers charged, with their price.
POST /api/v1/organizations/<org_id>/billing/reconcile
Reconcile usage records with credit transactions (super admin only).
Accès : permission de plateforme platform_admin
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
org_id |
Organization ID. |
Réponse : usage_records_found: Usage records of messages examined. already_credited: Those that already had their credit transaction. missing_transactions: Those without one. reconciled_count: Credit transactions created. total_deducted: Credit debited. failed_insufficient_balance: Records left without a transaction for want of credit.
This will 1. Find usage records that don't have corresponding credit transactions 2. Create the missing credit transactions (deduct credits) Use this to fix cases where usage records were created without proper credit deductions.
GET /api/v1/organizations/<org_id>/billing/usage/detailed
Detailed usage of any organization (super admin only); same response as GET /billing/usage/detailed.
Accès : permission de plateforme platform_admin
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
org_id |
Organization ID. |
Paramètres de requête
| Nom | Description |
|---|---|
start_date |
ISO 8601 date or datetime (default: start of the billing period). |
end_date |
ISO 8601 date or datetime (default: now). |
Réponse : detailed: Usage by type. totals: {total_cost}. period: {start, end}.
Essai gratuit, état du compte et réglages
Essai gratuit, état du compte de l'organisation et réglages de compte d'un domaine (inscription, essai, suppression, notifications).
GET /api/v1/billing/trial
The free trial of the organization: whether it can be started, and where it stands.
The trial is off until the domain turns it on (settings.trial.enabled), and an organization has one in its life. A trial whose balance reached minimum_credit is converted to a normal account when this is read. In the administration context nothing is eligible (the trial is started by the customer).
Accès : membre de l'organisation
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Réponse : trial: eligible (can be started now), active, started_at and expires_at (UTC, null before the start), minimum_credit (the credit that turns the trial into a normal account), duration_days (of a new trial), state (active, ended, converted or null), reason (why it is not eligible, null when it is: trial_disabled, already_used, funded, has_numbers, phone_required, phone_not_verified, phone_country, phone_already_used, admin_context).
POST /api/v1/billing/trial/activate
Start the free trial: a number of the domain pool is given to the organization.
The member who asks must have a verified phone (E04): one phone number opens one trial per domain. The number goes to the organization's default team. Refused in the administration context (403 forbidden).
Accès : permission billing.manage dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Corps de la requête
| Nom | Description |
|---|---|
country |
CA or US (required; 400 country_unsupported for another one). |
type |
local or tollfree (optional). |
area_code |
Three digits (optional). |
city |
Part of the city name (optional; only for numbers the pool describes with a city). |
Réponse : 201 with phone_number (the number given) and trial (as GET /billing/trial, now active).
Errors 403 trial_restricted with reason (see GET /billing/trial), 409 number_unavailable (no free number fits), 400 validation_error.
GET /api/v1/domains/<domain_id>/account-settings
The account settings of a domain (domain admin of the domain, or super admin).
Everything here is off or neutral by default, so Textos and VoXoS domains are untouched: free sign-up (closed), free trial (off), welcome e-mail (off), the notification defaults (see GET /auth/me/notifications), the grace period of an account deletion (10 days), the default time zone of new accounts (none) and the longest SMS body (800 characters).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : account_settings: signup ({enabled, initial_credit, max_per_ip_per_day, blocked_email_domains, captcha: {provider, site_key, secret (masked), min_score}, terms_url}), trial ({enabled, duration_days, minimum_credit, initial_credit, require_verified_phone, phone_countries, cleanup_enabled, cleanup_months}), account_deletion ({grace_days}), notifications ({welcome_email, defaults}), profile ({default_timezone}), sms ({max_body_length}).
PUT /api/v1/domains/<domain_id>/account-settings
Change the account settings of a domain: the sections and fields given are changed, the others kept.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
signup |
Any of enabled (boolean), initial_credit (0 to 1000), max_per_ip_per_day (1 to 1000), blocked_email_domains (list of domain names), captcha ({provider: none, recaptcha or turnstile, site_key, secret, min_score}; the secret sent as ******** keeps the stored one), terms_url (https URL or null). |
trial |
Any of enabled, duration_days (1 to 90), minimum_credit, initial_credit (0 to 1000), require_verified_phone, phone_countries (list of ISO codes or null), cleanup_enabled, cleanup_months (1 to 36). |
account_deletion |
{grace_days: 0 to 90}. |
notifications |
{welcome_email: boolean, defaults: {email: {...}, push: {...}} as GET /auth/me/notifications}. |
profile |
{default_timezone: IANA name or null}. |
sms |
{max_body_length: 0 (no limit) to 5000}. An unknown section or field, or a wrong value, gets 400 validation_error with field. |
Réponse : account_settings: All the settings after the change, as GET.
POST /api/v1/domains/<domain_id>/trial/run
Run now the round that converts, reminds and ends the free trials of the domain (the server does the same every ten minutes; idempotent).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
dry_run |
Count what is due, write and send nothing (boolean, default false). |
Réponse : result: {examined, converted, reminders, ended, cleaned}.
GET /api/v1/organizations/current/status
The state of the account in one call: active, suspended (negative balance, E46) or trial.
Accès : membre de l'organisation
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Réponse : status: state (suspended while the balance cycle blocks the account, trial during the free trial, else active), balance, currency, low_balance (the balance is low or empty, see balance_update), trial ({active, expires_at}), capabilities (the capabilities of the organization, or of its domain when it has none of its own).
Facturation : cartes, devis et reçus
Adresse de facturation, cartes (vérifiées par le fournisseur de paiement), devis de recharge, alerte de solde bas, export de l'historique, reçus PDF, réactivation d'un numéro.
GET /api/v1/billing/info
Billing address of the organization (the empty shape when none was saved yet).
Not available to restricted API keys: 403 forbidden. Errors are {error, message}: 400 no_organization.
Accès : authentifié (JWT ou clé API)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Réponse : billing_info: {usage (personal or business), company_name, first_name, last_name, address_1, address_2, city, state, postal_code, country, confirm_not_canadian, tax_region (the province, US, INTL, or null when the address is not usable)}.
PUT /api/v1/billing/info
Save the billing address. It decides the taxes of the recharges (tax table of the domain).
Errors are {error, message}: 400 validation_error with field (usage, company_name, first_name, last_name, address_1, city, country, state, postal_code, confirm_not_canadian).
Accès : permission billing.manage dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Corps de la requête
| Nom | Description |
|---|---|
usage |
personal or business. |
company_name |
Company name (required for business). |
first_name |
First name. |
last_name |
Last name. |
address_1 |
Street address. |
address_2 |
Second line (optional). |
city |
City. |
state |
Province (Canada: AB, BC, MB, NB, NL, NS, NT, NU, ON, PE, QC, SK, YT) or US state. |
postal_code |
Canadian postal code (A1A 1A1) or US ZIP. |
country |
ISO 3166-1 alpha-2 code. |
confirm_not_canadian |
Must be true for an address outside Canada. |
Réponse : billing_info: The saved address with its tax_region.
GET /api/v1/billing/invoices/<invoice_id>/pdf
The receipt of an invoice as a PDF (seller, buyer, lines, taxes with the seller's registration numbers, card), in the language the receipt was issued in.
Not available to restricted API keys: 403 forbidden. Errors are {error, message}: 404 not_found, 403 forbidden (invoice of another organization).
Accès : authentifié (JWT ou clé API)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
invoice_id |
Invoice ID. |
Réponse : application/pdf.
PUT /api/v1/billing/low-balance
Set the low-balance alert: by e-mail, push and the real-time event low_balance when the balance falls to the threshold, at most every 7 days.
Errors are {error, message}: 400 validation_error with field (enabled not a boolean, threshold not a number or not among the domain's choices).
Accès : permission billing.manage dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Corps de la requête
| Nom | Description |
|---|---|
enabled |
Whether the alert is on. |
threshold |
Balance (CAD) at or under which the alert is sent, 0 or more. |
Réponse : low_balance: {enabled, threshold}.
POST /api/v1/billing/numbers/<phone_id>/reactivate
Bring back a number that was switched off because its monthly fees could not be paid: debits what the number itself owes for the current month and lifts the pause. The functions that were turned off (voicemail, recording, SMS) stay off; turn them back on afterwards.
Errors are {error, message}: 404 not_found (not a number of this organization), 409 conflict (the number was not paused for unpaid fees), 402 insufficient_balance (add credit first; with owed and balance).
Accès : permission billing.manage dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
phone_id |
Phone number ID. |
Réponse : reactivated: true. owed: Amount debited. balance: Balance after.
GET /api/v1/billing/payment-methods
The cards of the organization, oldest first.
Not available to restricted API keys: 403 forbidden. Errors are {error, message}: 400 no_organization.
Accès : authentifié (JWT ou clé API)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Réponse : payment_methods: [{id, provider, brand, last4, exp_month, exp_year, card_type, cardholder_name, is_default, is_expired, used_by_auto_recharge}]. No token is ever returned.
POST /api/v1/billing/payment-methods
Add a card from the token the SDK returned. The server asks the provider what the token is and stores brand, last four digits, expiry and fingerprint from the provider's answer, never from the client. The first card becomes the main one.
Errors are {error, message}: 400 card_invalid / card_expired (with field), 409 duplicate_payment_method, 422 card_unsupported (only Visa and Mastercard) / too_many_payment_methods, 502 provider_error.
Accès : permission billing.manage dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Limite : 30 per hour
Corps de la requête
| Nom | Description |
|---|---|
provider |
The provider (default: the domain's). |
user_payment_option_id |
The card token returned by the SDK (required). |
user_token_id |
The user_token_id of the session (must be the organization's). |
cardholder_name |
Name on the card. |
initial_transaction_id |
The provider's id of the verification transaction, used by the automatic recharge. |
make_default |
Make it the main card. |
Réponse : payment_method: The stored card (201).
DELETE /api/v1/billing/payment-methods/<payment_method_id>
Delete a card. If it was the main card the oldest remaining card takes over; the automatic recharge follows it, and goes off with the last card.
Errors are {error, message}: 404 not_found.
Accès : permission billing.manage dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
payment_method_id |
Card ID. |
Réponse : success: true. payment_methods: The remaining cards.
POST /api/v1/billing/payment-methods/<payment_method_id>/default
Make a card the main one.
Errors are {error, message}: 404 not_found.
Accès : permission billing.manage dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de chemin
| Nom | Description |
|---|---|
payment_method_id |
Card ID. |
Réponse : payment_methods: The cards of the organization.
POST /api/v1/billing/payment-methods/session
Open a session of the payment provider for the browser SDK. The card is collected by the SDK on the provider's side and never reaches this server; the SDK gives back a token that POST /billing/payment-methods then verifies.
Errors are {error, message}: 400 configuration_error (no payment provider), 502 provider_error / provider_unavailable.
Accès : permission billing.manage dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Limite : 30 per hour
Réponse : session: {provider, session_token, client_request_id, user_token_id (give it to the SDK as userTokenId), merchant_id, merchant_site_id, environment, expires_at}.
POST /api/v1/billing/payment-methods/tokenize
Turn a TEST card into a token. Only with the fake payment provider of development and test domains; any real provider answers 404 tokenize_unavailable: a real card number never goes through this server.
Errors are {error, message}: 400 card_invalid / card_expired (with field), 422 card_unsupported, 404 tokenize_unavailable.
Accès : permission billing.manage dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Limite : 30 per hour
Corps de la requête
| Nom | Description |
|---|---|
card_number |
A test card number (4242 4242 4242 4242 works; 4000 0000 0000 3220 asks for a verification, code 123456). |
exp_month |
Expiry month. |
exp_year |
Expiry year (four digits). |
cvv |
Security code (not kept). |
cardholder_name |
Name on the card. |
Réponse : user_payment_option_id, user_token_id, brand, last4; or verification {id, type, expires_at} when the card needs a verification.
POST /api/v1/billing/payment-methods/tokenize/verify
Complete the verification of a TEST card (fake provider only), three attempts.
Errors are {error, message}: 404 not_found (unknown or expired), 400 invalid_code, 400 verification_failed (third failure).
Accès : permission billing.manage dans l'organisation (profils)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Limite : 30 per hour
Corps de la requête
| Nom | Description |
|---|---|
verification_id |
The id returned by tokenize. |
code |
The code (123456 for the test provider). |
Réponse : user_payment_option_id, user_token_id, brand, last4.
POST /api/v1/billing/recharge/quote
What a recharge would cost, computed by the server: credits, promotion, subtotal, taxes line by line (tax table of the domain, billing address of the organization) and total. Nothing is charged.
Errors are {error, message}: 400 validation_error (amount: not a number, more than two decimals, under the minimum), 409 billing_info_required (the domain charges taxes and the address is missing or unusable), 422 max_balance (the balance would pass the cap; with maximum).
Accès : authentifié (JWT ou clé API)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Corps de la requête
| Nom | Description |
|---|---|
amount |
Credits to buy. |
promo_code |
A promotional code (optional). A refused code does not fail the quote: promo_info.error says why. |
Réponse : quote: {credits, promotion, subtotal, taxes [{code, label, rate, amount}], total, currency, minimum, maximum (null: no cap), credited (credits plus a promotional gift), promo_info ({code, operation, error, min_credit} or null), expires_at}.
GET /api/v1/billing/transactions/export
The transaction history as a CSV file (UTF-8 with a byte-order mark, opens in a spreadsheet), same filters as GET /billing/transactions, newest first, at most 20000 rows.
Errors are {error, message}: 400 validation_error with field (from, to, category, type, number_id, format), 413 too_many_rows.
Accès : authentifié (JWT ou clé API)
Clés API : refusées aux clés restreintes (limitées à une équipe, ou d'une intégration), 403 forbidden
Paramètres de requête
| Nom | Description |
|---|---|
from |
ISO 8601 date or datetime, lower bound (UTC). |
to |
ISO 8601 date (the whole day) or datetime, upper bound. |
category |
all, communications, payments, recurring or admin. |
type |
all, purchase, usage, refund, adjustment or bonus. |
number_id |
Only the operations of this phone number. |
format |
csv (the only one). |
Réponse : A text/csv attachment: date, type, category, subtype, description, amount, balance_after, currency, number, call_id, invoice_number, card.
Facturation d'un domaine
Réglages de facturation et table de taxes, fournisseur de paiement, codes promotionnels, paiements et remboursements, frais mensuels des numéros (administrateurs de domaine).
GET /api/v1/domains/<domain_id>/billing-overview
What the organizations of the domain are billed: one row each (plan, last statement, next statement estimated, debits of the month, communications, last month, balance) and the totals.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : month: The current calendar month {start, end}. organizations: [{id, name, plan, plan_name (in the language of the request), voice, lines (phone system: {included, limit, peak, at, overage, overage_cost, manual, max_channels}, else null), balance, last_statement null or {total, date}, next {date (null for usage_only), total, kind subscription|monthly_fees|usage_only}, this_month (debits since the 1st), communications (usage debited since the 1st), previous_month (debits of the last calendar month)}], sorted by name. An organization counts as subscribed only while the domain offers subscriptions. truncated (boolean): Only the first 2000 organizations are listed. totals: {this_month, communications, previous_month, next, balance}.
GET /api/v1/domains/<domain_id>/billing-settings
What the domain decides about prepaid billing (settings.billing), defaults filled in. A domain that never set them has no taxes, no balance cap and the recharge it always had.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : billing_settings: {currency (CAD), min_recharge, max_balance (null: no cap), tax_rates ({region: [{code, label, rate, number}]} or null for no taxes; regions are the Canadian province codes, US and INTL), seller ({name, address_1, address_2, city, state, postal_code, country, email, phone} printed on receipts), invoice_prefix, billing_path (page the e-mails link to), quote_ttl_minutes, receipt_email, auto_recharge_retry_hours, threshold_choices (null: any), low_balance_repeat_days, low_balance_alerts (alert on for organizations that never chose), monthly_fees ({enabled, day, hour_utc, forecast_enabled, forecast_day})}. tax_presets: Names of the tax tables tax_preset can fill in (canada).
PUT /api/v1/domains/<domain_id>/billing-settings
Change the billing settings of the domain (the fields given are changed, the others kept). Journaled.
Errors are {error, message}: 400 validation_error with field.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
min_recharge |
Smallest recharge and smallest automatic recharge, CAD (default 1). |
max_balance |
Cap of the balance after a recharge, or null for none. |
tax_preset |
canada: fill the tax table with the federal and provincial rates (TPS, TVQ, TVH); tax_numbers {tps, tvq, tvh} adds the seller's registration numbers. |
tax_numbers |
Registration numbers by tax code, used with tax_preset. |
tax_rates |
The whole tax table {region: [{code, label, rate, number}]}, or null / {} for no taxes. A line is computed on the subtotal and rounded to the cent. |
seller |
Name and address printed on receipts. |
invoice_prefix |
Receipts are numbered <prefix>-<year>-<sequence> (2 to 8 capital letters or digits); give each seller its own. |
billing_path |
Path of the page the e-mails send customers to (default /settings/billing). |
quote_ttl_minutes |
How long a quote is shown as valid (1 to 120). |
receipt_email |
Send a receipt by e-mail after a recharge. |
auto_recharge_retry_hours |
Hours without a new automatic attempt after a failure (0 to 720). |
threshold_choices |
Allowed thresholds of the automatic recharge and the low-balance alert (a list, or null for any). |
low_balance_repeat_days |
Least days between two low-balance alerts (0 to 365). |
low_balance_alerts |
Turn the low-balance alert (threshold 10) on for organizations that never chose. |
monthly_fees |
{enabled, day (1-28), hour_utc, forecast_enabled, forecast_day}: the monthly fees of the numbers (E40), off by default. |
Réponse : billing_settings: The settings after the change (as GET).
GET /api/v1/domains/<domain_id>/monthly-fees
The monthly fees charged to the organizations of the domain for a period, and the numbers switched off for unpaid fees. The fees are turned on with monthly_fees.enabled in the billing settings.
Errors are {error, message}: 400 validation_error with field (period).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Paramètres de requête
| Nom | Description |
|---|---|
period |
YYYY-MM (default the current month). |
organization_id |
Only this organization (an organization that is not one of the domain matches nothing: empty lists, never another domain's data). |
skip |
Offset (default 0). |
limit |
Page size (default 50, 1-100). |
Réponse : period: The period. charges: [{organization_id, number_id, feature (did, voicemail, recording, sms or flow), amount, source (run or acquisition), charged_at}]. total: Number of charges. switched_off: [{organization_id, number_id, features, fees, balance, at}] for the period. skip: Offset applied. limit: Page size applied.
POST /api/v1/domains/<domain_id>/monthly-fees/run
Run the monthly fees of the domain now (the server runs them every hour from the configured day and hour; idempotent: what was charged for the period is never charged twice). For each organization, number by number, the oldest first: the fees of the number and of its functions are debited together; if the balance falls short the automatic recharge is tried once; if it still does, the number is switched off (functions off, number paused) and the organization is told by e-mail and SMS. Journaled.
Errors are {error, message}: 409 conflict (monthly fees are not turned on for the domain).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
organization_id |
Only this organization. |
dry_run |
Compute and show what would be charged, write and send nothing. |
force |
Run even before the configured day and hour of the month. |
Réponse : domains, organizations: What was looked at. charged: Fees debited. total: Their amount. recharged: Catch-up recharges made. switched_off: Numbers switched off. forecasts: Warnings of the 25th sent. errors: Organizations that failed (see the logs).
GET /api/v1/domains/<domain_id>/organizations/<organization_id>/subscription
The subscription of an organization of the domain (as GET /billing/subscription).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
organization_id |
Organization ID. |
Réponse : As GET /billing/subscription.
PUT /api/v1/domains/<domain_id>/organizations/<organization_id>/subscription
Choose the plans of an organization of the domain (same rules as PUT /billing/subscription, without requiring its automatic recharge). Journaled.
Errors are {error, message}: 404 not_found (organization not in the domain); 400 validation_error with field (field missing, unknown or inactive plan, voice not a boolean, subscriptions not offered by the domain, a first subscription with neither a plan nor the phone system); 402 plan_limit {limit, plan, upgrade, current}: the organization already has more users or teams than the chosen plan allows. The domain administrator cannot force it either (as for an invitation): remove members or teams first, or raise max_users / max_teams of the plan (PUT /domains/<id>/subscription-settings).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
organization_id |
Organization ID. |
Corps de la requête
| Nom | Description |
|---|---|
messaging_plan |
essential, team, enterprise, or null for none (required). |
voice |
The phone system (required). |
Réponse : As GET /billing/subscription.
GET /api/v1/domains/<domain_id>/payment-setup
Where the payment provider stands for this domain: what is configured, the URL to give the processor for its notifications (DMN), and a checklist of what is still missing before going live. No secret is returned.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : payment_setup: {provider (the one the domain pays with, or null), enabled_providers, nuvei {enabled, environment, merchant_id, merchant_site_id, has_secret_key, notification_url}, fake_allowed, checks [{key, ok, detail?}], ready (every check but nuvei_production, tax_numbers and tax_table is ok)}.
POST /api/v1/domains/<domain_id>/payment-setup/test
Check the credentials of the domain's payment provider by opening a session with the processor (Nuvei getSessionToken). Calls the processor for real: use it after entering the sandbox credentials. Nothing is charged or stored.
Errors are {error, message}: 400 configuration_error (no provider enabled or credentials missing), 502 provider_error / provider_unavailable (the processor refused: bad merchant id, site id or secret key, or unreachable).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : ok: true. provider: The provider tested. environment: sandbox or production.
GET /api/v1/domains/<domain_id>/payments
Card payments of the organizations of the domain, newest first (the card debits, apart from the credits and receipts they produced).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Paramètres de requête
| Nom | Description |
|---|---|
organization_id |
Only this organization. |
status |
pending, approved, completing, completed, declined, failed, requires_action or unknown. |
skip |
Offset (default 0). |
limit |
Page size (default 50, 1-100). |
Réponse : payments: [{id, organization_id, kind (manual or auto), provider, status, card {brand, last4}, credits, promotion, credited, promo_code, subtotal, taxes, total, currency, provider_transaction_id, decline_code, error, invoice_id, transaction_id, refunded_total, created_at, completed_at}]. total: Number of payments matching. skip: Offset applied. limit: Page size applied.
POST /api/v1/domains/<domain_id>/payments/<payment_id>/refund
Refund a completed payment to the customer's card and take the credits back. The credits come off proportionally to the money refunded (all of them, gift included, for a full refund); the balance may go negative, which hands the account to the negative-balance lifecycle. A full refund cancels the receipt. Journaled.
Errors are {error, message}: 404 not_found, 409 conflict (not completed, already refunded, another refund in progress), 400 validation_error (amount), 502 refund_failed (the provider refused: nothing was taken back).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
payment_id |
Payment ID. |
Corps de la requête
| Nom | Description |
|---|---|
amount |
Money to give back (default: all that is left to refund). |
reason |
Note for the audit log. |
Réponse : refund: {id, payment_id, amount, credits (taken off the balance), reason, status, provider_refund_id, created_at}. payment: The payment after the refund. transaction: The ledger row of the debit.
POST /api/v1/domains/<domain_id>/payments/<payment_id>/resolve
Settle a payment whose outcome is unknown (the processor did not answer) after checking it in the processor's console: approved credits the customer, failed closes it without crediting. Journaled.
Errors are {error, message}: 404 not_found, 409 conflict (the payment is not unknown), 400 validation_error with field.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
payment_id |
Payment ID. |
Corps de la requête
| Nom | Description |
|---|---|
outcome |
approved or failed. |
provider_transaction_id |
The processor's transaction id (required for approved). |
Réponse : payment: The payment after it was settled.
POST /api/v1/domains/<domain_id>/payments/reconcile
Finish the payments a crash left half done (approved by the processor but not credited yet) and flag the ones stuck pending. The server does the same every few minutes; this runs it now.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : completed: Payments finished now. marked_unknown: Pending payments flagged unknown. errors: Payments that could not be finished (see the logs).
GET /api/v1/domains/<domain_id>/promo-codes
Promotional codes of the domain, newest first.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Paramètres de requête
| Nom | Description |
|---|---|
skip |
Offset (default 0). |
limit |
Page size (default 50, 1-100). |
Réponse : promo_codes: [{id, code, operation (gift or discount), value, min_credit, valid_from, valid_to, per_account_limit, max_uses, uses, active, description, created_at}]. total: Number of codes. skip: Offset applied. limit: Page size applied.
POST /api/v1/domains/<domain_id>/promo-codes
Create a promotional code. A gift adds value credits (a separate gift ledger row) on top of the credits bought; a discount lowers the price of the credits by value (taxes are computed after it).
Errors are {error, message}: 400 validation_error with field, 409 conflict (the code exists).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
code |
The code, 3 to 32 letters, digits, - or _ (case does not matter). |
operation |
gift or discount. |
value |
Amount in CAD. |
min_credit |
Smallest recharge it applies to (default 0). |
valid_from |
ISO 8601 start of validity (optional). |
valid_to |
ISO 8601 end of validity (optional). |
per_account_limit |
Times one organization can use it (default 1). |
max_uses |
Times it can be used in all (default unlimited). |
active |
Whether it can be used (default true). |
description |
Note for the administrators. |
Réponse : promo_code: The created code (201).
DELETE /api/v1/domains/<domain_id>/promo-codes/<promo_id>
Delete a promotional code that was never redeemed; a redeemed one is kept for the history (deactivate it with active: false).
Errors are {error, message}: 404 not_found, 409 conflict (already redeemed).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
promo_id |
Code ID. |
Réponse : success: true.
PUT /api/v1/domains/<domain_id>/promo-codes/<promo_id>
Change a promotional code (the fields given). The code itself and its operation cannot change once redeemed.
Errors are {error, message}: 404 not_found, 400 validation_error with field, 409 conflict.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
promo_id |
Code ID. |
Corps de la requête
| Nom | Description |
|---|---|
value |
Amount in CAD. |
min_credit |
Smallest recharge it applies to. |
valid_from |
ISO 8601 start of validity, or null. |
valid_to |
ISO 8601 end of validity, or null. |
per_account_limit |
Times one organization can use it. |
max_uses |
Times it can be used in all, or null. |
active |
Whether it can be used. |
description |
Note for the administrators. |
code |
New code (only while never redeemed). |
operation |
gift or discount (only while never redeemed). |
Réponse : promo_code: The code after the change.
GET /api/v1/domains/<domain_id>/subscription-settings
The subscriptions the domain offers: on or off, messaging plans, phone system, extras and price of the messages beyond those included.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : subscriptions: {enabled, plans [{key, name {fr, en}, price, included {messages, users, teams, numbers, widgets}, max_users, max_teams, active}], overage_tiers [{up_to, price}], mms_weight, voice {price, included_numbers, full_user, virtual_user, minutes_per_full_user, minute_overage, warn_ratio, tollfree_inbound_per_minute, recording_per_extension, included_lines, lines_per_full_user, burst_lines, extra_line}, addons {number, tollfree_number, team, widget}}.
PUT /api/v1/domains/<domain_id>/subscription-settings
Change the subscriptions the domain offers (the fields given are changed). Journaled.
Errors are {error, message}: 400 validation_error with field.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
enabled |
Offer the subscriptions to the organizations of the domain. |
plans |
[{key (essential, team, enterprise), price, active, max_users, max_teams, included {messages, users, teams, numbers, widgets}, name {fr, en}}] (the fields given of each plan; max_users, max_teams and included.users accept null = no limit; a name is cut at 40 characters). |
overage_tiers |
The whole list [{up_to, price}] (1 to 20 tiers), increasing up_to, the last one null. |
mms_weight |
Messages an MMS counts for (1 to 10). |
voice |
{price, included_numbers, full_user, virtual_user, minutes_per_full_user, minute_overage, warn_ratio (0.1 to 1), tollfree_inbound_per_minute, recording_per_extension, included_lines, lines_per_full_user, burst_lines (null: as many as the included lines, at least 2), extra_line} (the fields given). |
addons |
{number, tollfree_number, team, widget} (the fields given). An unknown field (at any level) is a 400 validation_error naming it. |
Réponse : subscriptions: The settings after the change.
Notifications de paiement
URL appelée par le fournisseur de paiement (Nuvei DMN) pour confirmer un paiement, un remboursement ou une rétrofacturation.
GET /webhook/billing/<provider>/<secret>
Receive a notification of the payment processor (public: the secret of the URL and the signature authenticate it).
Errors are {error, message}: 404 not_found (unknown provider or secret), 401 invalid_signature, 400 provider_unsupported.
Accès : public (aucune authentification)
Limite : exemptée de la limite générale par adresse
Paramètres de chemin
| Nom | Description |
|---|---|
provider |
nuvei. |
secret |
The webhook_secret of the provider's settings in the domain. |
Réponse : The text OK (the processor retries until it gets a 200).
Errors 500 internal_error: the notification could not be processed (the processor retries).
POST /webhook/billing/<provider>/<secret>
Receive a notification of the payment processor (public: the secret of the URL and the signature authenticate it).
Errors are {error, message}: 404 not_found (unknown provider or secret), 401 invalid_signature, 400 provider_unsupported.
Accès : public (aucune authentification)
Limite : exemptée de la limite générale par adresse
Paramètres de chemin
| Nom | Description |
|---|---|
provider |
nuvei. |
secret |
The webhook_secret of the provider's settings in the domain. |
Réponse : The text OK (the processor retries until it gets a 200).
Errors 500 internal_error: the notification could not be processed (the processor retries).
Domaines
Domaines whitelabel : paramètres, numéros, fournisseurs, admins de domaine, voix.
GET /api/v1/domains
List all domains (platform_admin), or only the domains a domain_admin administers.
Accès : permission de plateforme platform_admin ou domain_admin
Réponse : domains: The domains.
POST /api/v1/domains
Create a new domain.
Accès : permission de plateforme platform_admin
Corps de la requête
| Nom | Description |
|---|---|
name |
Domain identifier (slug), a string of 1 to 120 characters (400 validation_error otherwise). |
hostnames |
List of hostnames to match (can include port). |
display_name |
Display name for the domain, a string of 1 to 120 characters. |
Réponse : domain: The created domain. message: Confirmation.
DELETE /api/v1/domains/<domain_id>
Delete a domain.
Accès : permission de plateforme platform_admin
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : Success message.
GET /api/v1/domains/<domain_id>
Get a domain by ID.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : Domain details.
PUT /api/v1/domains/<domain_id>
Update a domain.
A body with name or hostnames is refused to any request authenticated by an API key, ordinary or restricted: 403 (error forbidden, message "API key not allowed on this route"). The hostnames give the address of the links of the domain's e-mails (password reset, invitations), so only a session (JWT) changes them. The body must be a JSON object and its fields of the documented types (else 400 validation_error).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées si le corps contient hostnames, name
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
name |
Domain identifier (slug), a string of 1 to 120 characters. |
hostnames |
List of hostnames to match (strings; a single string is accepted). |
display_name |
Display name for the domain, a string of 1 to 120 characters. |
capabilities |
List of capabilities of the domain (adding sip, call_codes or voicemail_boxes is reserved to a platform_admin: 403 forbidden for a domain_admin): textos, voxos, sip (SIP endpoints and softphone provisioning), call_codes (call codes, legacy without interface: no app uses them any more), voicemail_boxes (voicemail boxes of the endpoints and independent boxes); sip, call_codes and voicemail_boxes need voxos in the same list, else 400 validation_error (field capabilities). An organization of the domain holds a subset of this list (PUT /organizations/<id>/capabilities). |
Réponse : Updated domain.
GET /api/v1/domains/<domain_id>/admins
List all domain admins and pending invitations for a domain.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : admins: The domain admin users. pending_invitations: The invitations to become domain admin that are not accepted yet.
POST /api/v1/domains/<domain_id>/admins
Add a domain admin to a domain.
If the user exists, add them immediately. If the user doesn't exist, create an invitation.
Answers 201 in both cases. 400 validation_error: email missing, or the account is a super admin. 409 conflict: already a domain admin of this domain. 403 forbidden: the account belongs to organizations the caller does not administer (only a super admin turns such an account into a domain admin).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
email |
Email of the user to add as domain admin. |
Réponse : For an existing account: admin (the user, now domain admin), message and invited false. For an e-mail with no account: invitation (with its token), message, invited true and email_sent (whether the e-mail went out).
DELETE /api/v1/domains/<domain_id>/admins/<user_id>
Remove a domain admin from a domain.
The domain leaves the account's domain_ids; without any domain left, the account loses the platform permission domain_admin and its role becomes the one its profiles give in its current organization (agent without membership). 400 validation_error when the account is not a domain_admin.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
user_id |
User ID to remove. |
Réponse : message: Confirmation. new_role: domain_admin when the user still administers other domains, else the role held in the current organization (agent without membership).
DELETE /api/v1/domains/<domain_id>/admins/invitations/<invitation_id>
Delete a pending domain admin invitation.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
invitation_id |
Invitation ID to delete. |
Réponse : Success message.
GET /api/v1/domains/<domain_id>/negative-balance
Settings of the negative-balance lifecycle of a domain (E46).
Voice calls are debited after they took place, so a balance can become negative. The organization then goes through a grace period (notices by e-mail and SMS), is suspended (sending and calls refused, numbers paused), held in limbo (numbers kept in reserve) and released (numbers back in the pool of the domain). It runs only while voice billing is on for the domain (GET /domains/<id>/voice-billing) and enabled is not false.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : negative_balance: {enabled, grace_days, suspended_days, limbo_days, notice_days (null: the default steps), effective_notice_days, channels ({email, sms}), recharge_guard_minutes}. active: Whether the lifecycle runs for the domain now.
PUT /api/v1/domains/<domain_id>/negative-balance
Change the settings of the negative-balance lifecycle of a domain (the fields given are changed).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
enabled |
Run the lifecycle (boolean, default true; it also needs voice billing on). |
grace_days |
Days between the balance becoming negative and the suspension, 0 to 365 (default 10). |
suspended_days |
Days the account stays suspended (numbers paused, kept) before the limbo, 0 to 365 (default 0). |
limbo_days |
Days the numbers are held in reserve before they go back to the pool, 0 to 730 (default 35). |
notice_days |
Days after the balance became negative at which a notice goes out during the grace period: null for the default steps (at once, half way, 2 days and 1 day before the end), or at most 4 days below grace_days. |
channels |
{email, sms} booleans (both true by default); the SMS only goes to admins with a verified phone. |
recharge_guard_minutes |
Minutes after an automatic recharge (running, succeeded or failed) during which the organization is not moved forward, 0 to 1440 (default 30). |
Réponse : negative_balance: The settings after the change (as GET). active: Whether the lifecycle runs for the domain now.
GET /api/v1/domains/<domain_id>/negative-balance/organizations
Organizations of the domain whose balance is negative or in the middle of the lifecycle.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Paramètres de requête
| Nom | Description |
|---|---|
state |
Only this state: negative_grace, suspended, limbo or released (optional). |
skip |
Pagination offset (default 0). |
limit |
Page size (default 50, brought back into 1-100). |
Réponse : organizations: The page of {organization_id, name, credit_balance, balance_state, negative_since, grace_ends_at, suspended_at, limbo_at, release_at, released_at, numbers ({paused, held, released})}; dates are UTC (ISO 8601 with Z). total: Number of organizations matching. skip: Offset applied. limit: Page size applied.
POST /api/v1/domains/<domain_id>/negative-balance/run
Advance now the lifecycle of the domain's organizations (the periodic task does the same).
Idempotent: a notice already sent is never sent again.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
organization_id |
Only this organization (optional). |
dry_run |
Compute the transitions and the notice due, write and send nothing (boolean, default false). |
Réponse : result: {examined, transitions, notices, retried, errors, by_state}; with organization_id, {organization_id, from, to, transitions, notice, retried}.
GET /api/v1/domains/<domain_id>/organizations
List organizations in a domain.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : organizations: All the organizations of the domain. total: Number of organizations.
POST /api/v1/domains/<domain_id>/organizations/<organization_id>/credit
Add credit to, or take credit back from, an organization of the domain by hand (administrator of the domain or super admin).
A negative amount is a correction (admin_fix in the ledger): it needs a reason and may bring the balance below zero, which starts the negative-balance lifecycle like any debt. The credit is written in the ledger as an administrative operation (admin_credit, with the administrator and the reason in the audit journal). A negative balance brought back to zero or above leaves the negative-balance lifecycle like after a recharge; messages held for lack of credit are sent. Not available to API keys.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
organization_id |
Organization ID (must belong to the domain). |
Corps de la requête
| Nom | Description |
|---|---|
amount |
Credit to add (above zero) or to take back (below zero), 100000 at most in absolute value, not zero (required). |
description |
Label shown in the ledger of the organization (optional, 200 characters at most; default "Ajout manuel de crédit" or "Correction manuelle"). |
reason |
Why, kept in the audit journal only (300 characters at most; required for a correction). |
Réponse : success: true. new_balance: Balance after the operation. transaction_id: Ledger row written.
POST /api/v1/domains/<domain_id>/organizations/<organization_id>/grace-extension
Force a longer grace period for an organization whose balance is negative (journalled).
The organization goes back to negative_grace whatever its state: a suspended account is put back in service (numbers resume or return) and its grace period now ends days days from now. The reminders and the suspension notice are armed again for the new end.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
organization_id |
Organization ID. |
Corps de la requête
| Nom | Description |
|---|---|
days |
Days from now, 1 to 90 (required). |
reason |
Why, kept in the audit journal (optional text of 300 characters at most). |
Réponse : billing_state: {balance_state, negative_since, grace_ends_at, suspended_at, limbo_at, release_at, released_at, credit_balance}. 400 validation_error with field when days is out of bounds or the balance is not negative.
GET /api/v1/domains/<domain_id>/phone-numbers
List phone numbers in a domain.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : phone_numbers: The numbers of the domain (pool and organizations), each with organization_name when it belongs to an organization; provider_account_id = its SMS provider account (null: the provider settings of the domain); for a domain admin, a number bound to a platform account shows the provider "platform". total: Number of numbers.
POST /api/v1/domains/<domain_id>/phone-numbers
Create phone number(s) in domain pool (batch support).
Always answers 201 once the domain exists, even when no number was created: read created_count and errors (a number that already exists, or that cannot be stored, is listed there and the others are still created). The numbers are created in the pool of the domain, without a team. The provider configuration of a number is not set here (PUT .../phone-numbers/<phone_id>/provider).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
numbers |
List of phone numbers in E.164 format. |
number |
Single phone number (legacy, use numbers instead). |
provider |
SMS provider name (optional); it must be enabled for the domain, else 400 validation_error. |
provider_account_id |
SMS provider account of the numbers (GET /domains/<id>/sms-accounts: an account of the domain or a platform account allowed for it; 400 validation_error otherwise). It sets the provider; provider is then ignored. |
Réponse : phone_numbers: The numbers created. created_count: How many were created. errors: One string per number that was not created ("<number>: already exists", or the reason). message: Confirmation with the count.
DELETE /api/v1/domains/<domain_id>/phone-numbers/<phone_id>
Delete a phone number from domain pool.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
phone_id |
Phone number ID. |
Réponse : Success message.
PUT /api/v1/domains/<domain_id>/phone-numbers/<phone_id>
Update a phone number in domain pool.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
phone_id |
Phone number ID. |
Corps de la requête
| Nom | Description |
|---|---|
label |
Friendly label (no control character or line break: the label can become the caller name of the calls; 400 validation_error otherwise). |
provider |
SMS provider name (refused with 400 validation_error while the number is bound to an account: the account decides). |
provider_account_id |
SMS provider account of the number (an account of the domain or a platform account allowed for it, GET /domains/<id>/sms-accounts; 400 validation_error otherwise); null unbinds (back to the provider settings of the domain). |
provider_config |
Provider configuration. |
is_active |
Whether number is active, a boolean (400 validation_error otherwise). An inactive number is not charged the monthly fees and cannot send. |
voice_trunk_id |
Voice trunk of the number, a trunk of this domain or a platform trunk allowed for it (400 validation_error otherwise; null turns the voice off). Super admin only for a number of the platform (bound to a platform SMS account, or whose current or new trunk is a platform trunk): 403 forbidden for a domain admin, the platform sets it in the Providers page (PUT /system/phone-numbers/<id>/voice). |
purpose |
"access" makes the number an access number of the call codes (E23, legacy without interface; read by the voice server), null removes it. An access number must belong to the pool (no organization), have the voice enabled and a country. |
country |
ISO 3166-1 alpha-2 country of an access number (CA, US...). |
Réponse : phone_number: The updated number. message: Confirmation.
POST /api/v1/domains/<domain_id>/phone-numbers/<phone_id>/assign
Assign a phone number to an organization.
The number goes to the default team of the organization (flag is_default; the only team of an organization counts as default), created when the organization has no team yet, so that SMS work at once. An organization with several teams and none flagged default keeps the number without team: its admin assigns it (POST /phone-numbers/<id>/assign). Unassigning (organization_id null) also clears the team.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
phone_id |
Phone number ID. |
Corps de la requête
| Nom | Description |
|---|---|
organization_id |
Organization ID to assign (null to unassign). |
Réponse : phone_number: The number, with team_id set to the default team when there is one. message: Result message.
Errors 409 number_order_pending: the number was ordered from the provider and is not delivered yet.
PUT /api/v1/domains/<domain_id>/phone-numbers/<phone_id>/assigned-at
Update the organization assignment date for a phone number.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
phone_id |
Phone number ID. |
Corps de la requête
| Nom | Description |
|---|---|
assigned_at |
ISO date string or null. |
Réponse : phone_number: The updated number.
PUT /api/v1/domains/<domain_id>/phone-numbers/<phone_id>/provider
Configure SMS provider for a phone number.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
phone_id |
Phone number ID. |
Corps de la requête
| Nom | Description |
|---|---|
provider |
SMS provider name. |
config |
Provider configuration. |
test_first |
Whether to test before saving (default true). |
Réponse : phone_number: The updated number. message: Confirmation.
Errors 400 validation_error: provider missing. 400 provider_error: the connection test failed (skip it with test_first false). 404 not_found: domain or number unknown.
GET /api/v1/domains/<domain_id>/platform-voice-trunks
Which platform voice trunks a domain may use (super admin only).
Accès : permission de plateforme platform_admin
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : voice_trunks: Every platform trunk {id, name, description, provider, host, enabled, allowed (whether the domain may use it), numbers (how many numbers of the domain name it)}.
PUT /api/v1/domains/<domain_id>/platform-voice-trunks
Set which platform voice trunks a domain may use (super admin only).
Accès : permission de plateforme platform_admin
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
trunk_ids |
Ids of the platform trunks the domain may use (the whole list; [] = none). |
Réponse : voice_trunks: As GET.
Errors 400 validation_error: an id is not a platform trunk. 409 conflict: a trunk taken away is still named by numbers of the domain (trunk_id, numbers in the answer).
GET /api/v1/domains/<domain_id>/security-settings
Security settings of a domain: who must sign in with a second factor.
A second factor (authenticator app or passkey) is expected of the platform and domain admins; this setting adds the organization admins (members holding members.admin) of the domain's organizations. While the platform's MFA_ENFORCE is off (today) it is only recommended: the sign-in carries mfa_setup, nothing is refused (mfa_enforced false; the page says it will become mandatory). Once enforced (mfa_enforced true), with a grace period (MFA_GRACE_DAYS, 14 days by default) counted from the first time the account was seen needing one.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : security: mfa_organization_admins (boolean, false by default), mfa_enforced (boolean, read only: the platform imposes the second factor; false = only recommended), mfa_grace_days (the platform's grace period once enforced, read only).
PUT /api/v1/domains/<domain_id>/security-settings
Change the security settings of a domain.
Journaled (audit_logs, domain_security_changed). Never through an API key.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
mfa_organization_admins |
The organization admins of the domain must have a second factor too (required). |
Réponse : security: The settings after the change (as GET).
GET /api/v1/domains/<domain_id>/settings/branding
Get the branding (theme) of a domain.
Every key is present; null (or the historical default) when unset: each application then uses its own default. Nothing in it is secret: the same object is in GET /domains/current.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : branding: {accent_color_fr, accent_color_en, logo_url, logo_signin_url, favicon_url, theme {primary, primary_text, navbar_bg, navbar_fg, body_bg, font, radius}, support {help_url, status_url, tickets_url, updates_url}, footer_text}, see PUT /domains/<domain_id>/settings/branding.
Errors 404 not_found: domain unknown.
PUT /api/v1/domains/<domain_id>/settings/branding
Update the branding (theme) of a domain: how every application served by the domain looks.
Partial update: only the keys sent change. theme and support merge key by key; a key sent as null (or an empty string) is removed, theme or support sent as null removes all their keys. Unset = the default of each application. Every value is checked (400 validation_error with field = its path, e.g. theme.primary): colors are #rgb or #rrggbb, stored as lowercase #rrggbb; URLs are https:// (never javascript:, data: or http:), 500 characters at most; an unknown key of theme or support is refused. Each change is journaled (audit_logs, domain_branding_changed, the names of the keys sent).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
accent_color_fr |
Accent color for the French locale, #rgb or #rrggbb (null or empty: back to #003DA5). |
accent_color_en |
Accent color for the English locale, #rgb or #rrggbb (null or empty: back to #D52B1E). |
logo_url |
Logo of the domain, an https:// URL or an absolute path starting with / (null or empty: none). |
logo_signin_url |
Logo of the sign-in page, an https:// URL or an absolute path starting with / (null: removed). |
favicon_url |
Favicon, an https:// URL or an absolute path starting with / (null: removed). |
theme |
Colors and shape of the interface, merged key by key (null: all removed): primary (brand color: buttons, links, underlines), primary_text (text on primary; null: contrast computed by the client), navbar_bg, navbar_fg, body_bg (colors #rgb or #rrggbb), font (one of source-sans, system, inter, roboto), radius (corner radius in pixels, an integer from 0 to 16). |
support |
Links of the ASSISTANCE menu, https:// URLs merged key by key (null: all removed): help_url, status_url, tickets_url, updates_url. |
footer_text |
Footer, plain text of 500 characters at most (line breaks allowed, no HTML tag; null: removed). |
home |
What the web app puts first for the domain (its first page after sign-in and the top of its menu): messages, numbers or calls (null: messages). Another value gets 400 validation_error with field home. |
Réponse : branding: The whole branding after the change, as GET /domains/<domain_id>/settings/branding. message: Confirmation.
Errors 400 validation_error: a value is refused (field = its path), or the body is not a JSON object. 404 not_found: domain unknown.
PUT /api/v1/domains/<domain_id>/settings/default-payment-provider
Set default payment provider for a domain.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
provider |
Provider name (stripe, nuvei). |
Réponse : default_payment_provider: The provider now offered first. message: Confirmation.
GET /api/v1/domains/<domain_id>/settings/oauth
Get OAuth settings for a domain.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : oauth: {google, microsoft, apple}. Google and Microsoft: {enabled} only (sign-in uses the platform's credentials, the domain only turns it on). Apple: {enabled, client_id, team_id, key_id, private_key}, the private key masked as "********".
PUT /api/v1/domains/<domain_id>/settings/oauth/<provider>
Turn an OAuth provider on or off for a domain, and set the domain's Apple credentials.
Google and Microsoft sign in with the platform's credentials: the domain only turns them on (enabled), any other field of the body is ignored. Apple uses the domain's credentials and is replaced as a whole: a field left out takes its default (enabled false, empty strings); the private key is kept when it is left out, empty or sent as "********". The other providers are left alone.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
provider |
Provider name (google, microsoft, apple). |
Corps de la requête
| Nom | Description |
|---|---|
enabled |
Whether the provider is offered on the domain (default false). |
client_id |
Apple only: Services ID. |
team_id |
Apple only: team ID. |
key_id |
Apple only: key ID. |
private_key |
Apple only: private key (omitted, empty or "********" keeps the stored one). |
Réponse : google | microsoft | apple: The settings of the provider after the change, as GET /domains/<domain_id>/settings/oauth shows them, under the name of the provider in the URL. message: Confirmation.
GET /api/v1/domains/<domain_id>/settings/payment-providers
Get payment provider settings for a domain.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : payment_providers: Settings of each payment provider, secrets masked. default_payment_provider: The provider offered first.
PUT /api/v1/domains/<domain_id>/settings/payment-providers/<provider>
Update payment provider settings for a domain.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
provider |
Provider name (stripe, nuvei; fake, the offline test provider, on development and test domains only: 400 validation_error elsewhere). |
Corps de la requête
| Nom | Description |
|---|---|
enabled |
Whether the provider is enabled (a field left out keeps its stored value). |
publishable_key |
Stripe only: publishable key. |
secret_key |
Stripe and Nuvei: secret key (omitted, empty or "********" keeps the stored one). |
webhook_secret |
Stripe only: webhook secret (omitted, empty or "********" keeps the stored one). Nuvei has none to send: its notification secret is generated. |
merchant_id |
Nuvei only: merchant ID. |
merchant_site_id |
Nuvei only: merchant site ID. |
environment |
Nuvei only: "production" (default) or "sandbox"; anything else is 400 validation_error with field environment. |
Réponse : stripe | nuvei | fake: The settings of the provider after the change, under the name of the provider in the URL (secret_key and webhook_secret masked as "********"). message: Confirmation.
GET /api/v1/domains/<domain_id>/settings/smtp
Get SMTP settings for a domain.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : smtp: {host, port, username, password ("********" when set), from_email, from_name, use_tls}; an empty object when never configured.
PUT /api/v1/domains/<domain_id>/settings/smtp
Update SMTP settings for a domain.
The settings are replaced as a whole: a field left out takes its default (port 587, use_tls true, an empty string for the others, from_name = display name of the domain), it does not keep its stored value. Only the password is kept when it is left out, empty or sent as "********" (the masked value GET returns).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
host |
SMTP server hostname (in production a domain admin cannot give a private, loopback or link-local address: 400 validation_error; a host already saved is not re-checked). |
port |
SMTP port, 1 to 65535 (default 587; 400 validation_error otherwise). |
username |
SMTP username. |
password |
SMTP password (omitted, empty or "********" keeps the stored one). |
from_email |
From email address. |
from_name |
From name (default: the display name of the domain). |
use_tls |
Whether to use TLS (default true). |
Réponse : smtp: The configuration after the change (password masked as "********", empty when none). message: Confirmation.
POST /api/v1/domains/<domain_id>/settings/smtp/test
Send a test email through the SMTP settings of a domain.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
to_email |
Address the test email goes to (required). |
Réponse : success: Whether the email was sent. message: Confirmation, or the SMTP error (400).
GET /api/v1/domains/<domain_id>/sms-logs
Get SMS logs for a domain.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Paramètres de requête
| Nom | Description |
|---|---|
direction |
Filter by direction (inbound/outbound). |
account_id |
Only the entries of the numbers bound to this SMS provider account (a platform account the domain may use, or one of its own; 400 validation_error otherwise). |
provider |
Filter by provider type (super admin only; ignored for anyone else, it would name the provider of a platform account). |
start_date |
Filter by start date (ISO format). |
end_date |
Filter by end date (ISO format). |
skip |
Number of records to skip (default 0; a value that is not an integer is refused: 400 validation_error). |
limit |
Maximum records to return (default 50, max 100; a value that is not an integer is refused: 400 validation_error). |
Réponse : logs: The log entries of the page, each with provider_account_id and provider_description (the account of its number as the domain sees it: a platform account by the description the super admin wrote, never by its name; both null for a number bound to no account) and provider (reads "platform" for a domain admin when the account is the platform's). total: Number of entries before skip and limit. skip: Offset applied. limit: Page size applied.
GET /api/v1/domains/<domain_id>/sms-providers
Get SMS provider settings for a domain.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : sms_providers: Settings of each SMS provider of the domain, secrets masked.
PUT /api/v1/domains/<domain_id>/sms-providers/<provider>
Update SMS provider settings for a domain.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
provider |
Provider type (one of GET /api/v1/providers). |
Corps de la requête
| Nom | Description |
|---|---|
enabled |
Whether provider is enabled. |
api_key |
API key (providers that use one; see GET /api/v1/providers/<name>/schema). |
sms_token |
SMS token (providers that use one). |
mms_account_id |
MMS account ID (providers that handle MMS). |
webhook_secret |
Webhook secret (providers that deliver to a secret URL). |
Réponse : message: Confirmation. provider: The provider name. enabled: Whether the provider is enabled after the change.
GET /api/v1/domains/<domain_id>/voice-billing
Voice billing settings of a domain (E22).
Billing is off until a domain admin turns it on: while enabled is false no call is debited and call codes are only limited by max_call_seconds.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : voice_billing: {enabled, currency, default_rate ({rate_per_min, connection_fee, increment} or null), max_call_seconds, safety_margin_seconds, reserve}.
PUT /api/v1/domains/<domain_id>/voice-billing
Change the voice billing settings of a domain (the fields given are changed).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
enabled |
Bill the outbound voice calls of the domain's organizations (boolean). |
default_rate |
Rate for the destinations no prefix covers: {rate_per_min, connection_fee, increment} or null. |
max_call_seconds |
Longest call allowed, 60 to 86400 (default 7200). |
safety_margin_seconds |
Seconds taken off the duration the balance allows, 0 to 600 (default 10). |
reserve |
Part of the balance a call may never use (default 0). |
currency |
CAD (the only credit currency). |
Réponse : voice_billing: The settings after the change.
POST /api/v1/domains/<domain_id>/voice-billing/rate-calls
Rate and debit now the voice calls that wait for it (the periodic task does the same).
Idempotent: a call already rated is never debited again. Only the calls of the domain's organizations are looked at.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
call_id |
Rate only this call (optional). |
limit |
Most calls looked at, 1 to 1000 (default 200). |
dry_run |
Compute the outcome and write nothing (boolean, default false). |
Réponse : result: {examined, billed, free, not_billed, insufficient, failed, deferred, skipped, error, total_cost}.
GET /api/v1/domains/<domain_id>/voice-rates
Voice rates of a domain, by direction then prefix.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Paramètres de requête
| Nom | Description |
|---|---|
direction |
outbound or inbound (optional). |
prefix |
Only the rates whose prefix starts with these digits (optional). |
skip |
Pagination offset (default 0). |
limit |
Page size (default 50, brought back into 1-100). |
Réponse : rates: The page of rates {id, domain_id, prefix, direction, rate_per_min, connection_fee, increment, currency, label, enabled, created_at, updated_at}. total: Number of rates matching the filters. skip: Offset applied. limit: Page size applied.
POST /api/v1/domains/<domain_id>/voice-rates
Add a voice rate to a domain.
A rate bills the destinations whose number starts with prefix; the longest prefix wins. Example: prefix 1 (North America) 0.02 per minute, prefix 1514 (Montreal) 0.015, prefix 44 (United Kingdom) 0.05.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
prefix |
Destination prefix in digits, country code first, no + (required). |
rate_per_min |
Price of a minute (required). |
connection_fee |
Fixed price added to every billed call (default 0). |
increment |
Billing step in seconds, 1 to 3600 (default 60). |
direction |
outbound (default) or inbound (inbound calls are not billed yet). |
label |
Name of the destination (optional). |
enabled |
A disabled rate is ignored (default true). |
currency |
CAD. |
Réponse : rate: The created rate. 409 conflict when the prefix already has a rate in this direction.
DELETE /api/v1/domains/<domain_id>/voice-rates/<rate_id>
Delete a voice rate (the calls it already billed keep their cost).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
rate_id |
Rate ID. |
Réponse : success: True.
PUT /api/v1/domains/<domain_id>/voice-rates/<rate_id>
Change a voice rate (the fields given are changed).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
rate_id |
Rate ID. |
Corps de la requête
| Nom | Description |
|---|---|
prefix |
Destination prefix. |
rate_per_min |
Price of a minute. |
connection_fee |
Fixed price per billed call. |
increment |
Billing step in seconds. |
direction |
outbound or inbound. |
label |
Name of the destination. |
enabled |
Whether the rate applies. |
Réponse : rate: The rate after the change. 409 conflict when the new prefix already has a rate.
POST /api/v1/domains/<domain_id>/voice-rates/bulk
Create or replace many voice rates at once (a tariff table).
All the rates are validated first: one invalid rate refuses the whole request. A rate whose direction and prefix already exist replaces the amounts of that rate.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
rates |
List of up to 500 rates, with the fields of POST /voice-rates. |
Réponse : created: Number of rates created. updated: Number of rates replaced.
GET /api/v1/domains/<domain_id>/voice-rates/resolve
The rate that would bill a call to a destination (longest prefix, else the default rate).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Paramètres de requête
| Nom | Description |
|---|---|
destination |
Destination number (required). |
Réponse : rate: {source ("prefix" or "default"), rate_id, prefix, rate_per_min, connection_fee, increment, currency}, or null when nothing covers the destination. destination: The destination in E.164, or null when it is not a phone number.
GET /api/v1/domains/<domain_id>/voice-sound-limits
Limits applied to the audio prompts uploaded in the organizations of a domain.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : voice_sound_limits: {max_per_account (20), min_duration_s (1), max_duration_s (90), max_bytes (5 MB), formats (wav, mp3, m4a, ogg, webm)}.
PUT /api/v1/domains/<domain_id>/voice-sound-limits
Change the limits of the audio prompts of a domain (the fields given are changed; the existing sounds are kept).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
max_per_account |
Sounds an organization may keep, 1 to 500 (the system sounds do not count). |
min_duration_s |
Shortest sound in seconds, 0 to 30. |
max_duration_s |
Longest sound in seconds, 1 to 600. |
max_bytes |
Largest file, 10 KB to 12 MB. |
formats |
Accepted formats, among wav, mp3, m4a, ogg, webm. |
Réponse : voice_sound_limits: The limits after the change.
GET /api/v1/domains/<domain_id>/voice-sounds
System sounds of a domain, the default of every organization that has not recorded its own.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : sounds: List of {id, name, kind "system", key, language, duration_s, size...}. keys: The keys that exist (filtering, name_prompt, invalid_key, voicemail_greeting, paused, recording). languages: fr, en and es.
POST /api/v1/domains/<domain_id>/voice-sounds
Upload the system sound of a key and a language for the whole domain (replaces the previous one). Same checks as the sounds of an organization: formats, size, measured duration.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
file |
The audio file (multipart/form-data). |
key |
filtering, name_prompt, invalid_key, voicemail_greeting, paused or recording. |
language |
fr, en or es. |
name |
Name (default: the file name). |
Réponse : sound: The created sound.
DELETE /api/v1/domains/<domain_id>/voice-sounds/<sound_id>
Delete a system sound of the domain (the voice servers fall back on their built-in sound).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
sound_id |
Sound ID. |
Réponse : success: True.
GET /api/v1/domains/<domain_id>/voice-sounds/<sound_id>/file
The audio file of a system sound of the domain.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
sound_id |
Sound ID. |
Réponse : The file (audio/*).
GET /api/v1/domains/<domain_id>/voice-trunks
The voice trunks a domain can give its numbers.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : voice_trunks: The domain's own trunks, with their credentials (password masked) and numbers (how many numbers name each). platform_trunks: The platform trunks the super admin allowed for the domain: for a domain admin only {id, scope "platform", name null, provider null, description, enabled, outbound_enabled} (description = what the super admin wrote); a super admin also gets the name, host and provider. own_trunks_allowed (boolean): Whether the domain's admins may add their own trunks (the "own providers" permission of the domain, PUT /domains/<id>/sms-account-settings; off by default).
POST /api/v1/domains/<domain_id>/voice-trunks
Create a voice trunk of the domain (a reseller's own trunk).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
name |
Trunk name (unique), 1 to 64 characters: no control character or line break, no double quote, backslash, comma, ampersand, pipe, dollar sign, brace or backtick (the name is written into the voice server's commands); 400 validation_error with field otherwise. |
host |
SIP server hostname or IP address (400 validation_error otherwise; in production a domain admin cannot give a private, loopback or link-local address; a public provider address is fine). |
username |
SIP username (a string without control character). |
password |
SIP password. |
provider |
Name of the provider (free text, for the administrators). |
description |
Free description of the trunk (up to 200 characters). |
port |
SIP port (default: 5060). |
server_id |
Voice server that registers this trunk; default: the least loaded enabled server. |
trunk_type |
"registration" (default) or "ip_auth" (the provider identifies us by IP). |
outbound_enabled |
Calls may go out through it (default: yes for registration, no for ip_auth). |
inbound_enabled |
Calls may come in through it (default: yes; false: outbound only; not both false). |
outbound_trunk_id |
A trunk without outbound: another trunk of the same owner, open to outbound, that its outbound calls and forwards take instead (empty: none). |
max_channels |
Simultaneous calls it carries, inbound and outbound together (empty: no limit). |
allowed_ips |
ip_auth only: other IP addresses the carrier calls from (list, or one per line); the host is always accepted. |
Réponse : voice_trunk: The created trunk, with its credentials. message: Confirmation.
Errors 403 own_accounts_not_allowed: the domain may not add its own providers (PUT /domains/<id>/sms-account-settings), whoever asks; platform trunks are created under /system/voice-trunks.
DELETE /api/v1/domains/<domain_id>/voice-trunks/<trunk_id>
Delete a voice trunk of the domain.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
trunk_id |
Trunk ID. |
Réponse : Success message.
PUT /api/v1/domains/<domain_id>/voice-trunks/<trunk_id>
Update a voice trunk of the domain.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
trunk_id |
Trunk ID (a trunk of this domain; a platform trunk answers 404). |
Corps de la requête
| Nom | Description |
|---|---|
name |
Trunk name (same rules as at the creation, cannot be empty). |
description |
Free description of the trunk. |
host |
SIP server hostname or address (same rule as at creation; the host the trunk already has is not re-checked). |
username |
SIP username. |
password |
SIP password. |
port |
SIP port. |
enabled |
Whether trunk is enabled. |
server_id |
Voice server that registers this trunk. |
outbound_enabled |
Calls may go out through it (outbound calls, forwards). |
inbound_enabled |
Calls may come in through it (false: outbound only; not both false). |
outbound_trunk_id |
A trunk without outbound: another trunk of the domain, open to outbound, that its outbound calls and forwards take instead (empty: none). |
max_channels |
Simultaneous calls it carries, inbound and outbound together (empty: no limit). |
allowed_ips |
ip_auth only: other IP addresses the carrier calls from. |
static_config |
false hands a trunk still marked as defined in the voice server's static configuration to the database (true is ignored). |
Réponse : voice_trunk: The updated trunk, with its credentials. message: Confirmation.
GET /api/v1/domains/<domain_id>/voice-trunks/status
Get the state of the voice trunks, from the voice server.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : registrations: State of each own trunk of the domain, by trunk name: Registered, Rejected... for a registration trunk; Reachable, Unreachable or Unknown for an IP trunk (the voice server checks it regularly). platform_registrations: State of the platform trunks the domain may use, by trunk id (their names stay hidden).
GET /api/v1/domains/<domain_id>/webhook
Get webhook information for a domain.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : webhook_secret: The secret of the domain webhook. webhook_urls: Path of the domain-level address of each enabled SMS provider, /webhook/domain/<provider>/<secret> (still served; prefix it with the API address). The current form, /webhook/provider/<provider>/<secret>, is the one of the global provider webhooks (POST /api/v1/system/provider-webhooks).
POST /api/v1/domains/<domain_id>/webhook/regenerate
Regenerate webhook secret for a domain.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : webhook_secret: The new secret (the old URLs stop working). webhook_urls: The new paths, as in GET .../webhook. message: Confirmation.
GET /api/v1/domains/current
Get current domain info based on host header.
Public, never rate limited (a read of the domain resolved for every request anyway).
Accès : public (aucune authentification)
Limite : exemptée de la limite générale par adresse
Réponse : Public domain information.
Errors 404 domain_not_found: no domain matches X-Frontend-Host.
Système
SMTP, OAuth et webhooks fournisseurs globaux.
GET /api/v1/system/billing-overview
What every domain bills its organizations this month, summed up by domain (platform administrators).
Accès : permission de plateforme platform_admin
Réponse : month: The current calendar month {start, end}. domains: [{id, name (display name), organizations (how many), subscriptions (active subscriptions), this_month (debits since the 1st), communications (usage debited since the 1st), previous_month (debits of the last calendar month), next (next statements of the subscriptions, estimated; 0 when the domain does not offer subscriptions)}]. totals: {this_month, communications, previous_month, next}.
GET /api/v1/system/oauth
Get OAuth provider settings for current domain.
Accès : permission de plateforme platform_admin
Réponse : oauth: As GET /domains/<domain_id>/settings/oauth: Google and Microsoft {enabled}, Apple its credentials (private key masked).
Errors 404 domain_not_found: no domain matches X-Frontend-Host.
PUT /api/v1/system/oauth/<provider>
Turn an OAuth provider on or off for the current domain, and set its Apple credentials (as PUT /domains/<domain_id>/settings/oauth/<provider>).
Google and Microsoft sign in with the platform's credentials: only enabled is read. Apple is replaced as a whole, the private key kept when it is left out, empty or "********". 404 domain_not_found when no domain matches X-Frontend-Host.
Accès : permission de plateforme platform_admin
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
provider |
Provider name (google, microsoft, apple). |
Corps de la requête
| Nom | Description |
|---|---|
enabled |
Whether the provider is offered on the domain (default false). |
client_id |
Apple only: Services ID. |
team_id |
Apple only: team ID. |
key_id |
Apple only: key ID. |
private_key |
Apple only: private key (omitted, empty or "********" keeps the stored one). |
Réponse : google | microsoft | apple: The settings of the provider after the change, under the name of the provider in the URL. message: Confirmation.
GET /api/v1/system/provider-webhooks
List all provider webhooks.
Accès : permission de plateforme platform_admin
Réponse : webhooks: The global provider webhooks. total: Number of webhooks.
POST /api/v1/system/provider-webhooks
Create a new provider webhook.
Accès : permission de plateforme platform_admin
Corps de la requête
| Nom | Description |
|---|---|
provider |
Provider type (one of GET /api/v1/providers). |
display_name |
Display name. |
secret |
Webhook secret (optional, generated if not provided). |
enabled |
Whether the webhook is enabled. |
Réponse : webhook: The created webhook (with its secret). message: Confirmation.
DELETE /api/v1/system/provider-webhooks/<webhook_id>
Delete a provider webhook.
Accès : permission de plateforme platform_admin
Paramètres de chemin
| Nom | Description |
|---|---|
webhook_id |
Webhook ID. |
Réponse : Success message.
PUT /api/v1/system/provider-webhooks/<webhook_id>
Update a provider webhook.
Accès : permission de plateforme platform_admin
Paramètres de chemin
| Nom | Description |
|---|---|
webhook_id |
Webhook ID. |
Corps de la requête
| Nom | Description |
|---|---|
display_name |
Display name (optional). |
enabled |
Whether the webhook is enabled (optional). |
config |
Provider-specific configuration (optional). |
Réponse : webhook: The updated webhook. message: Confirmation.
POST /api/v1/system/provider-webhooks/<webhook_id>/regenerate
Regenerate provider webhook secret.
Accès : permission de plateforme platform_admin
Paramètres de chemin
| Nom | Description |
|---|---|
webhook_id |
Webhook ID. |
Réponse : webhook: The webhook with its new secret. message: Confirmation.
GET /api/v1/system/smtp
Get SMTP settings of the current domain (the domain of the host, X-Frontend-Host); a domain admin only for a domain they administer (403 forbidden otherwise, 404 domain_not_found without a domain).
Accès : permission de plateforme platform_admin ou domain_admin
Réponse : smtp: {host, port, username, password ("********" when set), from_email, from_name, use_tls}; the defaults when never configured.
PUT /api/v1/system/smtp
Update SMTP settings of the current domain (the domain of the host, X-Frontend-Host); a domain admin only for a domain they administer (403 forbidden otherwise, 404 domain_not_found without a domain).
The settings are replaced as a whole: a field left out takes its default (port 587, use_tls true, an empty string for the others, from_name = display name of the domain), it does not keep its stored value. Only the password is kept when it is left out, empty or sent as "********".
Accès : permission de plateforme platform_admin ou domain_admin
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Corps de la requête
| Nom | Description |
|---|---|
host |
SMTP server host. |
port |
SMTP server port (default 587). |
username |
SMTP username. |
password |
SMTP password (omitted, empty or "********" keeps the stored one). |
from_email |
From email address. |
from_name |
From name (default: the display name of the domain). |
use_tls |
Whether to use TLS (default true). |
Réponse : smtp: The configuration after the change (password masked). message: Confirmation.
POST /api/v1/system/smtp/test
Test SMTP connection of the current domain (the domain of the host, X-Frontend-Host); a domain admin only for a domain they administer (403 forbidden otherwise, 404 domain_not_found without a domain).
Accès : permission de plateforme platform_admin ou domain_admin
Corps de la requête
| Nom | Description |
|---|---|
to_email |
Email address to send test to. |
Réponse : Success or error message.
GET /api/v1/system/voice-security
Security of the voice platform: the switch that suspends every outbound call, the organizations whose outbound calls are suspended and the latest security events.
Accès : permission de plateforme platform_admin
Réponse : state: {outbound_suspended, at (last change, ISO 8601, null before any), by (ID of the account that changed it), note}. suspended: The organizations whose outbound calls are suspended, 500 at most [{organization_id, name, domain_id, suspension {reason (spend, call_cost, manual), detail, at, by}}]. events: The latest 100 security events [{id, organization_id, organization_name, type, detail, at, notified}].
PUT /api/v1/system/voice-security
Suspend or resume every outbound call of the platform (calls of the endpoints, forwards, call codes). The calls in progress are hung up within seconds; incoming calls go on. Journalled; the platform admins receive an alert.
Accès : permission de plateforme platform_admin
Corps de la requête
| Nom | Description |
|---|---|
outbound_suspended |
true suspends, false resumes (required). |
note |
Why (optional string, cut at 500 characters). |
Réponse : state: {outbound_suspended, at (ISO 8601), by (ID of the account), note}.
GET /api/v1/system/voice-servers
Voice servers the SIP endpoints and trunks are placed on.
Accès : permission de plateforme platform_admin ou domain_admin
Réponse : voice_servers: List of servers with their endpoint count.
POST /api/v1/system/voice-servers
Declare a voice server.
Accès : permission de plateforme platform_admin
Corps de la requête
| Nom | Description |
|---|---|
name |
Unique short name, the one the voice server is configured with (e.g. "voice1"). |
host |
IP address or hostname the apps and phones register on. |
port |
SIP port (default 5060). |
transport |
"udp" (default), "tcp" or "tls". |
description |
Free text (optional). |
enabled |
Whether new endpoints may be placed on it (default true). |
listeners |
{udp, tcp, tls: {enabled, port}}: what the server listens on (default UDP 5060 alone). The transport and port given to the apps must be one that is on (400 validation_error, field transport). |
tls |
{cert_file, key_file}: certificate and key of the TLS listener, paths on the voice server (default: the certificate the deployment installs there). |
external_address |
Public IP address or host name the server announces behind a NAT (empty: none). |
bind_address |
Local IPv4 address the voice listens on and sends SIP and RTP from (empty: every address). Needed with an additional (movable) IP given to providers that authenticate by address. |
local_nets |
Networks reached without the NAT (list of CIDR, e.g. ["10.0.0.0/8"]). |
api_url |
Address of the management API of the voice server, as this platform reaches it (e.g. http://10.1.1.180:8090 on the private network; http or https, a host and a port, nothing else). Empty: the voice server on the machine of the platform. Its state, its restart, the state of its endpoints and trunks and the reloads go there. |
Réponse : voice_server: The created server.
DELETE /api/v1/system/voice-servers/<server_id>
Delete a voice server that has no endpoint nor trunk left.
Accès : permission de plateforme platform_admin
Paramètres de chemin
| Nom | Description |
|---|---|
server_id |
Server ID. |
Réponse : success: True.
PUT /api/v1/system/voice-servers/<server_id>
Update a voice server. Endpoints already placed on it keep its new address at their next provisioning.
Accès : permission de plateforme platform_admin
Paramètres de chemin
| Nom | Description |
|---|---|
server_id |
Server ID. |
Corps de la requête
| Nom | Description |
|---|---|
name |
Fields to change (all optional; see POST). The voice server applies listeners, TLS, address and networks at its next start (POST .../restart). |
host |
Fields to change (all optional; see POST). The voice server applies listeners, TLS, address and networks at its next start (POST .../restart). |
port |
Fields to change (all optional; see POST). The voice server applies listeners, TLS, address and networks at its next start (POST .../restart). |
transport |
Fields to change (all optional; see POST). The voice server applies listeners, TLS, address and networks at its next start (POST .../restart). |
description |
Fields to change (all optional; see POST). The voice server applies listeners, TLS, address and networks at its next start (POST .../restart). |
enabled |
Fields to change (all optional; see POST). The voice server applies listeners, TLS, address and networks at its next start (POST .../restart). |
listeners |
Fields to change (all optional; see POST). The voice server applies listeners, TLS, address and networks at its next start (POST .../restart). |
tls |
Fields to change (all optional; see POST). The voice server applies listeners, TLS, address and networks at its next start (POST .../restart). |
external_address |
Fields to change (all optional; see POST). The voice server applies listeners, TLS, address and networks at its next start (POST .../restart). |
bind_address |
Fields to change (all optional; see POST). The voice server applies listeners, TLS, address and networks at its next start (POST .../restart). |
local_nets |
Fields to change (all optional; see POST). The voice server applies listeners, TLS, address and networks at its next start (POST .../restart). |
api_url |
Fields to change (all optional; see POST). The voice server applies listeners, TLS, address and networks at its next start (POST .../restart). |
Réponse : voice_server: The updated server.
POST /api/v1/system/voice-servers/<server_id>/restart
Write the server's SIP transports and restart it as soon as no call is up (it reads its transports at start only).
Accès : permission de plateforme platform_admin
Paramètres de chemin
| Nom | Description |
|---|---|
server_id |
Server ID. |
Réponse : status: restart_scheduled (the server restarts once its calls have ended; registered phones come back by themselves).
Errors 502 voice_server_unreachable: the voice server did not answer, or the management API reached is the one of another voice server (nothing is restarted).
GET /api/v1/system/voice-servers/<server_id>/runtime
What the voice server really runs, asked to it: version, SIP transports, audio (RTP) ports, time zone, active calls, and whether its saved settings wait for a restart.
Accès : permission de plateforme platform_admin
Paramètres de chemin
| Nom | Description |
|---|---|
server_id |
Server ID. |
Réponse : runtime: {server (its name), version, timezone, transports [{name, protocol, bind}], expected_transports, rtp {start, end}, active_calls, restart_needed, ami_connected}, or null when the voice server does not answer. reachable (boolean): Whether the voice server answered.
GET /api/v1/system/voice-settings
Settings of the voice platform, applied by every voice server within 30 seconds.
Accès : permission de plateforme platform_admin
Réponse : voice_settings: {push_wait_seconds (longest wait, after the push that wakes the app, for the app to register again: the endpoint rings as soon as it did, 0 to 15), max_contacts (devices registered at once on one endpoint, 1 to 10, default 3: the app on a phone or a tablet, a SIP desk phone; they all ring together; beyond, the oldest registration is replaced; an endpoint may set its own), inbound_trunk_check (off, warn or enforce: a call that comes in on a trunk not allowed for the number is let through, logged, or refused), callcode {max_failures, failure_window (seconds), require_verified_phone}, test_extensions (600 echo and 601 hello world for the endpoints), updated_at}.
PUT /api/v1/system/voice-settings
Change settings of the voice platform (the fields given).
Accès : permission de plateforme platform_admin
Corps de la requête
| Nom | Description |
|---|---|
push_wait_seconds |
0 to 15 (longest wait for the app woken by a push; 0 = ring at once). |
max_contacts |
1 to 10 (devices registered at once on one endpoint, unless the endpoint sets its own). |
inbound_trunk_check |
off, warn or enforce. |
callcode |
{max_failures (1 to 1000), failure_window (60 to 86400 seconds), require_verified_phone (boolean)}. |
test_extensions |
Boolean. |
Réponse : voice_settings: As GET.
Comptes fournisseurs SMS de la plateforme
Comptes chez un fournisseur SMS configurés une fois et partagés par les domaines autorisés (super admin).
GET /api/v1/system/sms-accounts
List the platform SMS provider accounts.
Accès : permission de plateforme platform_admin
Réponse : accounts: The platform accounts, secrets masked, each with numbers (how many numbers name it) and domains (the domains allowed to use it, [{id, name}]).
POST /api/v1/system/sms-accounts
Create a platform SMS provider account.
Accès : permission de plateforme platform_admin
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Corps de la requête
| Nom | Description |
|---|---|
provider |
Provider type, one of GET /api/v1/providers (required). |
name |
Name of the account (required; never shown to a domain admin). |
description |
What the domain admins of the allowed domains see of this account (up to 200 characters), in place of its name and provider. |
config |
Credentials, the fields of GET /api/v1/providers/<provider>/schema. |
webhook_secret |
Secret of the inbound webhook URL (16 to 128 letters, digits, - and _); generated when absent. |
enabled |
Whether the account sends and receives (default true). |
Réponse : account: The created account (webhook_path = path of its inbound webhook).
DELETE /api/v1/system/sms-accounts/<account_id>
Delete a platform account that no domain and no number uses.
Accès : permission de plateforme platform_admin
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
account_id |
Account ID. |
Réponse : message: Confirmation.
Errors 409 conflict: domains are allowed to use it or numbers name it (numbers, domains in the answer).
PUT /api/v1/system/sms-accounts/<account_id>
Update a platform SMS provider account.
Accès : permission de plateforme platform_admin
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
account_id |
Account ID. |
Corps de la requête
| Nom | Description |
|---|---|
name |
New name. |
description |
What the domain admins see of the account. |
config |
Credentials to change (a secret left empty or masked keeps its value). |
webhook_secret |
New secret of the inbound webhook URL (16 to 128 letters, digits, - and _); the old URL stops working at once. |
enabled |
Whether the account sends and receives; off, the numbers bound to it cannot send. |
Réponse : account: The updated account.
PUT /api/v1/system/sms-accounts/<account_id>/domains
Set the domains allowed to use a platform account (the same allowance as PUT /domains/<id>/platform-sms-accounts, seen from the account).
Accès : permission de plateforme platform_admin
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
account_id |
Account ID. |
Corps de la requête
| Nom | Description |
|---|---|
domain_ids |
Ids of the domains that may use the account (the whole list; [] = none). |
Réponse : account: The account with its domains.
Errors 400 validation_error: an id is not a domain. 409 conflict: a domain taken away still has numbers bound to the account (domain_id, numbers in the answer).
GET /api/v1/system/sms-accounts/<account_id>/numbers
The numbers of a platform account (its inventory), every domain included.
Accès : permission de plateforme platform_admin
Paramètres de chemin
| Nom | Description |
|---|---|
account_id |
Account ID. |
Réponse : numbers: [{id, number, label, domain_id, domain_name, organization_id, organization_name, is_active, removable, assigned, held, pending_order ({status, placed_at} while the provider has not delivered the number, else null)}] (removable false while the number is assigned to an organization, in the trash, held for the balance or not delivered yet).
POST /api/v1/system/sms-accounts/<account_id>/numbers
Add numbers to a platform account: they are created in the pool of a domain allowed to use the account.
Accès : permission de plateforme platform_admin
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
account_id |
Account ID. |
Corps de la requête
| Nom | Description |
|---|---|
domain_id |
Domain the numbers belong to (required; a domain allowed to use the account, else 400 validation_error). |
numbers |
Phone numbers in E.164 (up to 500; a text with one number per line is accepted). |
voice_trunk_id |
The voice trunk of the new numbers (a trunk the domain may use, else 400 validation_error); absent = no voice. |
Réponse : created: The numbers created. created_count: How many. errors: One line per number not created ("<number>: already exists" or "not a phone number"). numbers: The inventory after the change.
DELETE /api/v1/system/sms-accounts/<account_id>/numbers/<phone_id>
Remove a number from a platform account (it leaves the registry).
Accès : permission de plateforme platform_admin
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
account_id |
Account ID. |
phone_id |
Phone number ID. |
Réponse : message: Confirmation. numbers: The inventory after the change.
Errors 404 not_found: not a number of this account. 409 number_assigned, number_held or number_order_pending: the number is assigned to an organization, in the trash, held for the balance, or not delivered by the provider yet.
POST /api/v1/system/sms-accounts/<account_id>/numbers/move
Move numbers of a platform account to another domain allowed to use the account (they stay in the registry).
A number assigned to an organization is moved only with release_assigned (it is then taken back from its organization, as by an unassignment: its conversations stay with the organization's team); a number in the trash or held for the balance is never moved. A moved number loses what belonged to its old domain: a voice trunk the new domain may not use (it then arrives without voice: give it a trunk by hand; a platform trunk allowed for the new domain, or no trunk at all, keeps the voice), access number role, a previous owner's settings.
Accès : permission de plateforme platform_admin
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
account_id |
Account ID. |
Corps de la requête
| Nom | Description |
|---|---|
phone_ids |
Ids of the numbers to move (required). |
domain_id |
Destination domain (required; a domain allowed to use the account, else 400 validation_error). |
release_assigned |
Also move the numbers assigned to an organization, taking them back from it (default false). |
Réponse : moved: The numbers moved. moved_count: How many. errors: One line per number not moved. numbers: The inventory after the change.
POST /api/v1/system/sms-accounts/<account_id>/regenerate-secret
Give a platform account a new inbound webhook secret (the old URL stops working at once: change it at the provider).
Accès : permission de plateforme platform_admin
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
account_id |
Account ID. |
Réponse : account: The account with its new webhook secret.
POST /api/v1/system/sms-accounts/<account_id>/test
Test the connection of a platform account with its stored credentials.
Accès : permission de plateforme platform_admin
Paramètres de chemin
| Nom | Description |
|---|---|
account_id |
Account ID. |
Réponse : success: Whether the provider accepted the credentials. message: The provider's answer.
Comptes fournisseurs SMS d'un domaine
Comptes propres à un domaine (revendeur) et comptes de la plateforme qu'il peut utiliser ; un numéro choisit son compte (provider_account_id).
GET /api/v1/domains/<domain_id>/platform-sms-accounts
Which platform SMS provider accounts a domain may use (super admin only).
Accès : permission de plateforme platform_admin
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : accounts: Every platform account (no secrets) with allowed (whether the domain may use it) and numbers (how many numbers of the domain name it).
PUT /api/v1/domains/<domain_id>/platform-sms-accounts
Set which platform SMS provider accounts a domain may use (super admin only).
Accès : permission de plateforme platform_admin
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
account_ids |
Ids of the platform accounts the domain may use (the whole list; [] = none). |
Réponse : accounts: As GET.
Errors 400 validation_error: an id is not a platform account. 409 conflict: an account taken away is still named by numbers of the domain (account_id, numbers in the answer).
PUT /api/v1/domains/<domain_id>/sms-account-settings
What a domain may do with SMS provider accounts (super admin only).
Accès : permission de plateforme platform_admin
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
own_accounts_allowed |
Whether the domain's admins may add their own provider accounts (off by default). Turning it off keeps the accounts already there. |
Réponse : own_accounts_allowed: The value now in force.
GET /api/v1/domains/<domain_id>/sms-accounts
The SMS provider accounts a domain can bind its numbers to.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : accounts: The domain's own accounts, secrets masked, with numbers (how many numbers name each). platform_accounts: The platform accounts the super admin allowed for the domain: for a domain admin only {id, scope "platform", name null, provider null, description, enabled} (description = what the super admin wrote for them); a super admin also gets the name and provider. own_accounts_allowed (boolean): Whether the domain's admins may add their own provider accounts (set by a super admin; off by default).
POST /api/v1/domains/<domain_id>/sms-accounts
Create an SMS provider account of the domain (a reseller's own account).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
provider |
Provider type, one of GET /api/v1/providers (required). |
name |
Name of the account (required). |
config |
Credentials, the fields of GET /api/v1/providers/<provider>/schema. |
webhook_secret |
Secret of the inbound webhook URL (16 to 128 letters, digits, - and _); generated when absent. |
enabled |
Whether the account sends and receives (default true). |
Réponse : account: The created account (webhook_path = path of its inbound webhook, to configure at the provider).
Errors 403 own_accounts_not_allowed: the domain may not add its own accounts (PUT /domains/<id>/sms-account-settings), whoever asks.
DELETE /api/v1/domains/<domain_id>/sms-accounts/<account_id>
Delete an account of the domain that no number names.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
account_id |
Account ID. |
Réponse : message: Confirmation.
Errors 409 conflict: numbers name it (numbers in the answer).
PUT /api/v1/domains/<domain_id>/sms-accounts/<account_id>
Update an SMS provider account of the domain.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
account_id |
Account ID (an account of this domain; a platform account answers 404). |
Corps de la requête
| Nom | Description |
|---|---|
name |
New name. |
description |
Free description of the account. |
config |
Credentials to change (a secret left empty or masked keeps its value). |
webhook_secret |
New secret of the inbound webhook URL (16 to 128 letters, digits, - and _); the old URL stops working at once. |
enabled |
Whether the account sends and receives. |
Réponse : account: The updated account.
GET /api/v1/domains/<domain_id>/sms-accounts/<account_id>/numbers
The numbers of an account of the domain (its inventory).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
account_id |
Account ID (an account of this domain; a platform account answers 404). |
Réponse : numbers: [{id, number, label, domain_id, domain_name, organization_id, organization_name, is_active, removable}].
POST /api/v1/domains/<domain_id>/sms-accounts/<account_id>/numbers
Add numbers to an account of the domain (created in the domain's pool).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
account_id |
Account ID. |
Corps de la requête
| Nom | Description |
|---|---|
numbers |
Phone numbers in E.164 (up to 500; a text with one number per line is accepted). |
Réponse : created: The numbers created. created_count: How many. errors: One line per number not created. numbers: The inventory after the change.
DELETE /api/v1/domains/<domain_id>/sms-accounts/<account_id>/numbers/<phone_id>
Remove a number from an account of the domain (it leaves the registry).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
account_id |
Account ID. |
phone_id |
Phone number ID. |
Réponse : message: Confirmation. numbers: The inventory after the change.
Errors 404 not_found: not a number of this account. 409 number_assigned or number_held: assigned to an organization, in the trash or held.
POST /api/v1/domains/<domain_id>/sms-accounts/<account_id>/regenerate-secret
Give an account of the domain a new inbound webhook secret (the old URL stops working at once).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
account_id |
Account ID. |
Réponse : account: The account with its new webhook secret.
POST /api/v1/domains/<domain_id>/sms-accounts/<account_id>/test
Test the connection of an account of the domain with its stored credentials.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
account_id |
Account ID. |
Réponse : success: Whether the provider accepted the credentials. message: The provider's answer.
Fournisseurs de numéros de la plateforme
Comptes de la plateforme chez les fournisseurs qui vendent des numéros (super admin) ; un admin de domaine n'en voit que la description.
GET /api/v1/system/number-providers
List the number providers of the platform.
Accès : permission de plateforme platform_admin
Réponse : number_providers: [{id, provider, name, description, enabled, base_url, credentials {username, password (masked)}, sms_account_id, voice_trunk_id, costs {local_setup, local_monthly, port_in, currency}, port_in {trunks, reseller}, numbers (how many numbers were bought from it), capabilities}]. providers: The providers an account can be opened at.
POST /api/v1/system/number-providers
Add a number provider account.
Accès : permission de plateforme platform_admin
Corps de la requête
| Nom | Description |
|---|---|
provider |
The provider (one of providers in GET) (required). |
name |
Name for the super admin (required); never shown to a domain admin. |
description |
What the domain admins see of it (up to 200 characters). |
base_url |
Base URL of its API (default: the provider's). |
credentials |
{username, password}: the access to its API. |
sms_account_id |
The platform SMS account, at the same provider, the numbers bought here send through (required). The domains allowed to use that SMS account may buy from this provider. |
voice_trunk_id |
The platform voice trunk on which the provider delivers the calls (given to the numbers bought, for the domains allowed to use it). |
costs |
{local_setup, local_monthly, port_in, currency (CAD or USD)}: what the provider charges us per number bought, per month and per number ported in (its API gives no price). |
port_in |
{trunks: the provider's circuit ids where ported numbers land (a port request is refused without one), reseller: name sent with the port requests (optional)}. |
enabled |
Default true. |
Réponse : number_provider: The account (as in GET).
DELETE /api/v1/system/number-providers/<account_id>
Delete a number provider account no number was bought from (disable it otherwise).
Accès : permission de plateforme platform_admin
Paramètres de chemin
| Nom | Description |
|---|---|
account_id |
Account ID. |
Réponse : message: Confirmation.
Errors 409 conflict: numbers were bought from it (numbers in the answer).
PUT /api/v1/system/number-providers/<account_id>
Change a number provider account (the fields given change; a password empty or masked is kept).
Accès : permission de plateforme platform_admin
Paramètres de chemin
| Nom | Description |
|---|---|
account_id |
Account ID. |
Corps de la requête
| Nom | Description |
|---|---|
name |
As POST (provider cannot change). |
description |
As POST (provider cannot change). |
base_url |
As POST (provider cannot change). |
credentials |
As POST (provider cannot change). |
sms_account_id |
As POST (provider cannot change). |
voice_trunk_id |
As POST (provider cannot change). |
costs |
As POST (provider cannot change). |
port_in |
As POST (provider cannot change). |
enabled |
As POST (provider cannot change). |
Réponse : number_provider: The account.
POST /api/v1/system/number-providers/<account_id>/test
Check the connection to a number provider (one read call).
Accès : permission de plateforme platform_admin
Paramètres de chemin
| Nom | Description |
|---|---|
account_id |
Account ID. |
Réponse : ok: Whether the provider answered and accepted the credentials. message: What happened.
Fournisseurs de la plateforme
Ce que la plateforme a chez chaque fournisseur, regroupé par type : comptes SMS, trunks, comptes d'achat de numéros, numéros et leurs capacités ; trunk de provenance d'un numéro (super admin).
GET /api/v1/system/did-inventory
The inventory of a number provider account: the numbers the provider says the account holds, read now, set against the numbers of Telma.
The provider gives only the numbers and whether SMS is on: the comparison is about existence and SMS. A number of Telma is at the provider when it was bought from it, sends through one of its SMS accounts or its calls come in through one of its trunks.
Accès : permission de plateforme platform_admin
Paramètres de requête
| Nom | Description |
|---|---|
provider_id |
The number provider account (required). |
Réponse : account_id: The account. read_at: When the provider was read (ISO 8601). provider_count: How many numbers the provider lists. counts: {matched, sms_mismatch, provider_only, ignored, elsewhere, ours_only, pending, duplicates}. matched: [{number, phone_id, label, domain_id, domain_name, organization_id, organization_name, is_active, provider_sms, our_sms, sms_mismatch}]: held at the provider and in Telma at this provider. provider_only: [{number, provider_sms}]: held at the provider, unknown to Telma (to import or to ignore). ignored: [{number, provider_sms, reason, at, by}]: numbers of provider_only left aside, with the reason. elsewhere: [{number, phone_id, label, domain_id, domain_name, organization_id, organization_name, is_active, provider_sms, provider}]: held at the provider, known to Telma but bound to another provider. ours_only: [{number, phone_id, label, domain_id, domain_name, organization_id, organization_name, is_active, our_sms, bought_here, missing_since}]: in Telma at this provider, not listed by it. pending: [{number, phone_id, label, domain_id, domain_name, organization_id, organization_name, is_active, pending_order, provider_sms}]: ordered and not delivered yet (not a gap). duplicates: [{number, count, ids}]: numbers held by more than one record of Telma.
POST /api/v1/system/did-inventory/ignored
Leave aside a number the account holds and Telma does not know (a number kept at the provider on purpose, to give back...), with the reason. Reversible. Journaled.
Accès : permission de plateforme platform_admin
Corps de la requête
| Nom | Description |
|---|---|
provider_id |
The number provider account (required). |
number |
The number, in E.164 format (required). |
reason |
Why it is left aside, 1 to 200 characters (required). |
Réponse : ignored: {number, reason, at, by}.
DELETE /api/v1/system/did-inventory/ignored/<number>
Bring an ignored number back among the numbers to settle. Journaled.
Accès : permission de plateforme platform_admin
Paramètres de chemin
| Nom | Description |
|---|---|
number |
The number, in E.164 format. |
Paramètres de requête
| Nom | Description |
|---|---|
provider_id |
The number provider account (required). |
Réponse : success: True.
POST /api/v1/system/did-inventory/import
Put numbers the account holds and Telma does not know into the pool of a domain, as if bought there.
Each number is bound to the account, its SMS account and its voice trunk (when the domain may use them), active and free in the pool; SMS is turned on at the provider when it is off. Nothing is charged. Journaled.
Accès : permission de plateforme platform_admin
Corps de la requête
| Nom | Description |
|---|---|
provider_id |
The number provider account (required). |
domain_id |
The domain whose pool receives the numbers; it must be allowed to use the account (required). |
numbers |
Numbers in E.164 format, at most 100 (required). |
Réponse : results: [{number, status (imported, known: already in Telma, not_held: the provider does not list it), phone_id, message (SMS could not be turned on)}], in the order asked.
PUT /api/v1/system/phone-numbers/<phone_id>/voice
Set the voice trunk of a number: the trunk its calls come in through (and go out through when it allows it).
The trunk must be one the number's domain may use (a platform trunk allowed for it, or a trunk of the domain).
Accès : permission de plateforme platform_admin
Paramètres de chemin
| Nom | Description |
|---|---|
phone_id |
Phone number ID. |
Corps de la requête
| Nom | Description |
|---|---|
voice_trunk_id |
The trunk; null turns the voice off (required). |
Réponse : phone_number: The number (as in the domain's list).
GET /api/v1/system/providers
The providers of the platform: for each type, its SMS accounts, voice trunks and number provider accounts.
The details of each piece come from GET /system/sms-accounts, /system/voice-trunks and /system/number-providers.
Accès : permission de plateforme platform_admin
Réponse : providers: [{type (one of the types of GET /api/v1/providers, or the type a record names), label, sms_account_ids, voice_trunk_ids, number_provider_ids, numbers (how many numbers use the provider)}].
GET /api/v1/system/providers/<kind>/numbers
The numbers at a provider, with their capabilities.
A number is at the provider when it sends through one of its SMS accounts, or its calls come in through one of its trunks, or it was bought from it.
Accès : permission de plateforme platform_admin
Paramètres de chemin
| Nom | Description |
|---|---|
kind |
The provider type (as GET /system/providers). |
Réponse : numbers: [{id, number, label, domain_id, domain_name, organization_id, organization_name, is_active, removable, assigned, held, pending_order ({status, placed_at} while the provider has not delivered the number, else null), sms {account_id, account_name, account_provider, mms (the provider sends MMS), mms_configured (the account has its MMS account id)}, voice {enabled, trunk_id, trunk_name, trunk_scope (platform or domain), direction (inbound or both), missing_trunk (voice on without a trunk: the calls come in by the address of the provider's IP trunk)}}], sorted by number.
Portage de numéros (plateforme)
Toutes les demandes de portage avec la référence du fournisseur, les annulations à faire à la main, le suivi sur demande (super admin).
GET /api/v1/system/port-requests
Every port request of the platform, with the provider's account and reference.
Accès : permission de plateforme platform_admin
Paramètres de requête
| Nom | Description |
|---|---|
state |
open or a state (as GET /port-requests); cancel_requested lists the cancellations to ask the provider. |
domain_id |
Only this domain. |
skip |
Offset (default 0). |
limit |
Page size (default 50, at most 100). |
Réponse : port_requests: As GET /port-requests, plus provider_reference and account {id, name}. total, skip, limit: The page.
GET /api/v1/system/port-requests/<request_id>
A port request with the provider's events (caption, date and reference of each).
Accès : permission de plateforme platform_admin
Paramètres de chemin
| Nom | Description |
|---|---|
request_id |
Port request ID. |
Réponse : port_request: As GET /port-requests/<id>, plus provider_reference, account {id, name} and, on each event, caption, provider_at and reference.
POST /api/v1/system/port-requests/<request_id>/cancel-done
The cancellation was done at the provider (by hand): the request is closed (cancelled; completed when some numbers had already arrived).
Accès : permission de plateforme platform_admin
Paramètres de chemin
| Nom | Description |
|---|---|
request_id |
Port request ID. |
Réponse : port_request: As GET /system/port-requests/<id>.
Errors 409 conflict: no cancellation is waiting.
POST /api/v1/system/port-requests/<request_id>/cancel-refused
The provider could not cancel (too late, already moved): the request goes on, the requester is told why.
Accès : permission de plateforme platform_admin
Paramètres de chemin
| Nom | Description |
|---|---|
request_id |
Port request ID. |
Corps de la requête
| Nom | Description |
|---|---|
reason |
What the provider said (up to 300 characters). |
Réponse : port_request: As GET /system/port-requests/<id>.
Errors 409 conflict: no cancellation is waiting.
POST /api/v1/system/port-requests/follow
Read the providers' port events now and put in service the numbers that arrived (the periodic task does the same every 5 minutes).
Accès : permission de plateforme platform_admin
Réponse : result: {
Trunks voix de la plateforme
Trunks SIP configurés une fois par le super admin et partagés par les domaines autorisés ; un admin de domaine n'en voit que la description.
GET /api/v1/system/voice-cost-tables
The cost tables of the platform's providers (python -m app.cli.voice_cost_tables imports them).
Accès : permission de plateforme platform_admin
Paramètres de requête
| Nom | Description |
|---|---|
archived |
true to include the tables replaced by a newer import. |
Réponse : voice_cost_tables: [{id, name, currency, scope, effective_at, source, rows, status, replaces, created_at, trunks}] (trunks: the names of the trunks that use the table).
GET /api/v1/system/voice-cost-tables/<table_id>/lookup
The rate a cost table gives a number (longest prefix).
Accès : permission de plateforme platform_admin
Paramètres de chemin
| Nom | Description |
|---|---|
table_id |
Cost table ID. |
Paramètres de requête
| Nom | Description |
|---|---|
number |
The number, E.164 or with its international prefix (required). |
Réponse : rate: {prefix, description, rate_per_minute, connection_fee} or null when no prefix covers the number. currency: The table's currency.
GET /api/v1/system/voice-pricing
The platform's margin on the voice calls (the price billed to the domains), and the USD to CAD rate.
Accès : permission de plateforme platform_admin
Réponse : voice_pricing: {markup_percent, min_markup_per_minute, min_billed_seconds, increment_seconds, connection_fee, usd_to_cad}.
PUT /api/v1/system/voice-pricing
Change the platform's margin (the fields given).
Accès : permission de plateforme platform_admin
Corps de la requête
| Nom | Description |
|---|---|
markup_percent |
Margin on the cost, 0 to 1000 %. |
min_markup_per_minute |
The margin per minute is at least this (CAD), 0 to 100. |
min_billed_seconds |
A call that connects bills at least this, 0 to 600 seconds. |
increment_seconds |
Billed in blocks of this, 1 to 600 seconds (1: by the second; 6, 30, 60...). |
connection_fee |
Per call that connects (CAD), 0 to 100. |
usd_to_cad |
Rate applied to a cost table in USD, 0.5 to 3. |
Réponse : voice_pricing: As GET.
GET /api/v1/system/voice-trunks
List the platform voice trunks.
Accès : permission de plateforme platform_admin
Réponse : voice_trunks: The platform trunks with their credentials (password masked), numbers (how many numbers name each) and domains (the domains allowed to use it, [{id, name}]).
POST /api/v1/system/voice-trunks
Create a platform voice trunk.
Accès : permission de plateforme platform_admin
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Corps de la requête
| Nom | Description |
|---|---|
name |
Trunk name (unique), 1 to 64 characters, no character that would change a command of the voice server (see POST /domains/<id>/voice-trunks); never shown to a domain admin. |
description |
What the domain admins of the allowed domains see of this trunk (up to 200 characters), in place of its name and host. |
host |
SIP server hostname or IP address. |
username |
SIP username. |
password |
SIP password. |
provider |
Name of the provider (free text, for the administrators). |
port |
SIP port (default: 5060). |
server_id |
Voice server that registers this trunk; default: the least loaded enabled server. |
trunk_type |
"registration" (default) or "ip_auth" (the provider identifies us by IP). |
outbound_enabled |
Calls may go out through it (default: yes for registration, no for ip_auth). |
inbound_enabled |
Calls may come in through it (default: yes; false: outbound only; not both false). |
outbound_trunk_id |
A trunk without outbound: another trunk of the same owner, open to outbound, that its outbound calls and forwards take instead (empty: none). |
max_channels |
Simultaneous calls it carries, inbound and outbound together (empty: no limit). |
allowed_ips |
ip_auth only: other IP addresses the carrier calls from (list, or one per line). |
cost_table_id |
The platform cost table of the provider (GET /system/voice-cost-tables): what a call through the trunk costs us (empty: none). |
Réponse : voice_trunk: The created trunk, with its credentials and cost_table_id, cost_table_name.
DELETE /api/v1/system/voice-trunks/<trunk_id>
Delete a platform trunk that no domain and no number uses.
Accès : permission de plateforme platform_admin
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
trunk_id |
Trunk ID. |
Réponse : message: Confirmation.
Errors 409 conflict: domains are allowed to use it or numbers name it (numbers, domains in the answer).
PUT /api/v1/system/voice-trunks/<trunk_id>
Update a platform voice trunk.
Accès : permission de plateforme platform_admin
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
trunk_id |
Trunk ID. |
Corps de la requête
| Nom | Description |
|---|---|
name |
Trunk name. |
description |
What the domain admins see of the trunk. |
host |
SIP server hostname or address. |
username |
SIP username. |
password |
SIP password (empty or "********" keeps it). |
port |
SIP port. |
enabled |
Whether the trunk is enabled. |
server_id |
Voice server that registers this trunk. |
outbound_enabled |
Calls may go out through it. |
inbound_enabled |
Calls may come in through it (default: yes; false: outbound only; not both false). |
outbound_trunk_id |
A trunk without outbound: another trunk of the same owner, open to outbound, that its outbound calls and forwards take instead (empty: none). |
max_channels |
Simultaneous calls it carries (empty: no limit). |
allowed_ips |
ip_auth only: other IP addresses the carrier calls from. |
cost_table_id |
The platform cost table of the provider (empty: none). |
static_config |
false hands a trunk still marked as defined in the voice server's static configuration to the database (every new trunk is generated from the database; true is ignored). |
Réponse : voice_trunk: The updated trunk.
PUT /api/v1/system/voice-trunks/<trunk_id>/domains
Set the domains allowed to use a platform trunk (the same allowance as PUT /domains/<id>/platform-voice-trunks, seen from the trunk).
Accès : permission de plateforme platform_admin
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
trunk_id |
Trunk ID. |
Corps de la requête
| Nom | Description |
|---|---|
domain_ids |
Ids of the domains that may use the trunk (the whole list; [] = none). |
Réponse : voice_trunk: The trunk with its domains.
Errors 400 validation_error: an id is not a domain. 409 conflict: a domain taken away still has numbers on the trunk (domain_id, numbers in the answer).
GET /api/v1/system/voice-trunks/status
State of the platform trunks, from the voice server.
Accès : permission de plateforme platform_admin
Réponse : registrations: State of each platform trunk, by trunk name: a registration trunk reads Registered, Rejected, Unregistered...; an IP trunk (it does not register) Reachable or Unreachable, from the regular checks of the voice server, or Unknown before the first answer.
Profils et permissions
Catalogue des permissions, profils de l'organisation et du domaine, profils d'un membre, permissions de plateforme d'un compte.
GET /api/v1/domains/<domain_id>/profiles
The profiles a domain offers to its organizations (copies of the built-in ones the reseller adjusts).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Réponse : profiles: [{id, name, permissions, domain_id}]. builtin_profiles: The built-in profiles, to copy.
POST /api/v1/domains/<domain_id>/profiles
Create a profile of the domain, offered to all its organizations.
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
Corps de la requête
| Nom | Description |
|---|---|
name |
1 to 60 characters (required). |
permissions |
Permission names of GET /api/v1/permissions (required unless copy_of is given). |
copy_of |
A built-in profile (builtin:admin, builtin:user) or another profile of the domain to start from. |
Réponse : profile: The created profile.
DELETE /api/v1/domains/<domain_id>/profiles/<profile_id>
Delete a profile of the domain; refused while members hold it (409 profile_in_use).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
profile_id |
Profile ID. |
Réponse : success: True.
PUT /api/v1/domains/<domain_id>/profiles/<profile_id>
Rename a profile of the domain or change its permissions (every member holding it in the domain's organizations gets the change).
Accès : permission de plateforme platform_admin ou domain_admin (domain_admin : seulement ses domaines, domain_id)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
domain_id |
Domain ID. |
profile_id |
Profile ID. |
Corps de la requête
| Nom | Description |
|---|---|
name |
1 to 60 characters (optional). |
permissions |
Permission names of GET /api/v1/permissions, replacing the list (optional). |
Réponse : profile: The profile.
Errors 402 plan_limit: in one of the organizations where members hold the profile, the new permissions would give the messages (messages.read or messages.send) to more members than its messaging plan allows; nothing is changed, in any organization. The answer names them: organizations [{id, name, error (plan_limit or last_admin), limit, plan, upgrade, current}]. 409 last_admin: an organization would have nobody left with members.admin (and none goes beyond its plan); organizations as above.
PUT /api/v1/organizations/current/members/<user_id>/profiles
Give profiles to a member of the current organization (they add up: the member holds every permission of each).
The list replaces the member's profiles. The member's real-time rooms follow at once. 404 not_found when the user is not a member of the current organization; 400 validation_error (field profile_ids) for an empty list or a profile the organization cannot give.
Accès : permission members.admin dans l'organisation (profils)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
user_id |
User ID of the member. |
Corps de la requête
| Nom | Description |
|---|---|
profile_ids |
One or more ids of GET /api/v1/organizations/current/profiles (required). |
Réponse : member: {user_id, profile_ids, role (the former role it amounts to: admin, manager or agent; a copy), permissions}.
Errors 403 forbidden: the member holds a permission the caller does not hold in the organization, or the profiles give one (field profile_ids). 409 last_admin: the organization would have nobody left who holds members.admin. 402 plan_limit: the member would start using the messages (messages.read or messages.send) beyond max_users of the organization's messaging plan: {error, message, field (profile_ids), limit (users), plan, upgrade (the first plan with room, or null), current (members who use the messages and pending invitations)}, as everywhere else.
GET /api/v1/organizations/current/profiles
The profiles members of the current organization can be given: the built-in ones (Admin, User), those of its domain, its own.
404 no_organization without a current organization.
Accès : permission members.manage ou members.admin dans l'organisation (profils)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Réponse : profiles: [{id, key, name, permissions, builtin, organization_id, domain_id (profiles of the domain and of the organization), members (members of the organization who hold it)}], the built-in ones first, then those of the domain, then the organization's own.
POST /api/v1/organizations/current/profiles
Create a profile of the current organization.
404 no_organization without a current organization; 400 validation_error (field name, permissions or copy_of) for a bad name, an unknown permission or an unknown profile to copy.
Accès : permission members.admin dans l'organisation (profils)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Corps de la requête
| Nom | Description |
|---|---|
name |
1 to 60 characters (required). |
permissions |
Permission names of the organization (GET /api/v1/permissions); required unless copy_of is given. |
copy_of |
Id of a profile to start from (builtin:admin, builtin:user, one of the domain or of the organization): its permissions when permissions is absent. |
Réponse : profile: The created profile.
DELETE /api/v1/organizations/current/profiles/<profile_id>
Delete a profile of the current organization; refused while members hold it (409 profile_in_use).
Accès : permission members.admin dans l'organisation (profils)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
profile_id |
Profile ID. |
Réponse : success: True.
PUT /api/v1/organizations/current/profiles/<profile_id>
Rename a profile of the current organization or change its permissions (the built-in ones and those of the domain cannot change here: 404).
Accès : permission members.admin dans l'organisation (profils)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
profile_id |
Profile ID. |
Corps de la requête
| Nom | Description |
|---|---|
name |
1 to 60 characters (optional). |
permissions |
Permission names of GET /api/v1/permissions, replacing the list (optional). |
Réponse : profile: The profile. The members who hold it get its new permissions at once.
Errors 402 plan_limit: the new permissions give the messages (messages.read or messages.send) to members who hold the profile and did not have them, beyond max_users of the organization's messaging plan: {error, message, field (permissions), limit (users), plan, upgrade, current}, as everywhere else; nothing is changed. 409 last_admin: the organization would have nobody left with members.admin; nothing is changed.
GET /api/v1/permissions
The catalogue of permissions.
Any authenticated session; closed to API keys.
Accès : authentifié (JWT ou clé API)
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Réponse : organization: The permissions of an organization a profile can hold, in the order of the screens: [{name, description}]. platform: The permissions of the platform an account can hold: platform_admin (every domain and organization), domain_admin (the domains of the account), support. builtin_profiles: The built-in profiles of every organization, never changed: [{id, key, name, permissions}].
PUT /api/v1/users/<user_id>/platform-permissions
The platform permissions of an account (platform_admin only). The sessions of the account end (its rights changed).
Never on one's own account (403 forbidden). The stored role follows (super_admin, domain_admin, or the role of its current organization's profiles). support is the grant of PUT /api/v1/users/<id> (permissions).
Accès : permission de plateforme platform_admin
Clés API : refusées, 403 forbidden (API key not allowed on this route)
Paramètres de chemin
| Nom | Description |
|---|---|
user_id |
User ID. |
Corps de la requête
| Nom | Description |
|---|---|
permissions |
Any of platform_admin, domain_admin, support (required; [] removes them; the list replaces the current one). |
domain_ids |
The domains a domain_admin administers (optional; absent keeps them). |
Réponse : user: {id, platform_permissions (support included when granted), domain_ids}.
Fichiers
Pièces jointes MMS.
GET /api/v1/media-proxy
Proxy and convert remote media files (public, no authentication).
Serves the media of inbound MMS, which live on the SMS provider's servers: browsers load them in image, audio and video elements or open them from a link, and webhook payloads give them as proxied_url. AMR audio is converted to MP3 and 3GPP video to MP4, which browsers cannot play otherwise.
The answer is the file itself, always with X-Content-Type-Options: nosniff. Images, audio and video (image/..., audio/..., video/..., SVG excluded), PDF (application/pdf) and plain text (text/plain, with the charset the provider gives, else utf-8) keep the Content-Type of the fetched file (audio/mpeg or video/mp4 once converted), with Content-Disposition: inline. Any other file is a download: application/octet-stream with Content-Disposition: attachment; filename="media.<ext>", where <ext> is the extension of the fetched type (vcf for a vCard, bin when the type is unknown or missing).
At most 4 files are fetched and 1 converted at once: a request waits its turn, in order of arrival (15 s at most for a fetch, behind 100 others at most; 30 s for a conversion, behind 8 others), else it gets 503 busy at once. A converted file is kept in a cache named after the content fetched (SHA-256), looked up before ffmpeg runs: the same media asked under several URLs is converted once. A file fetched is also kept as is, up to 10 minutes after its fetch (100 MB and 1000 files in all, the oldest go first): the same URL asked again meanwhile, by anyone and whatever its content_type, is served from there (converted or not) without fetching it again, and a request for the same URL while it is being fetched waits for that fetch. Files served from the disk accept Range requests (206 Partial Content).
Accès : public (aucune authentification)
Limite : exemptée de la limite générale par adresse
Paramètres de requête
| Nom | Description |
|---|---|
url |
Media URL to fetch (required): http:// or https:// (https:// is assumed when the scheme is missing), on port 80 or 443 only, without credentials (nothing before an @), backslash, blank or control character; the host must be a host name (not an IP address) among the SMS providers' media hosts (MEDIA_PROXY_ALLOWED_HOSTS, and their subdomains), and all its addresses public, checked again when connecting (the connection goes to one of these checked addresses). The fragment is dropped. Redirects are not followed; 5 MB max (MEDIA_PROXY_MAX_BYTES), 90 s for the whole fetch. 400 validation_error when missing or not allowed. |
content_type |
Original content type of the media (optional), only used to ask for a conversion: audio/amr (and audio/amr-wb) converts to MP3, video/3gpp (and video/3gpp2) to MP4, as does an URL ending in .amr, .3gp or .3gpp. The file is converted only when it is AMR / 3GPP by the type the provider gives it or by its first bytes (the AMR header, the 3gp / 3g2 brand of a 3GPP file); otherwise it is served unconverted, and a failed conversion serves the original, as the type of the fetched file says. It never sets the Content-Type of the answer. |
Réponse : The media content, possibly converted. Errors: 404 not_found (no message carries this media URL: pass url exactly as in the message's media_urls[].url), 400 validation_error (url missing or not allowed, including a host that resolves to an address that is not public when the connection is made), 502 fetch_failed (the provider did not answer with the file in time, answered with a redirect, or the file is over 5 MB), 503 busy (too many fetches or conversions in progress: retry a few seconds later). Every error is JSON {error, message}.
POST /api/v1/upload
Upload a file for an MMS attachment.
Send the file as multipart/form-data, then pass the returned url in media_urls of POST /api/v1/sms/send (the only attachments it accepts).
Accès : authentifié (JWT ou clé API)
Corps de la requête
| Nom | Description |
|---|---|
file |
The file (multipart/form-data field, required): jpg, jpeg, png, gif, webp, mp4, mov, mp3, wav or m4a (by file name extension), 10 MB max. 400 validation_error when the field is missing or empty, the extension is not accepted or the file is larger. |
Réponse : url: Public URL of the file, to pass as is in media_urls of POST /api/v1/sms/send. content_type: MIME type deduced from the extension. filename: Stored file name (201).
GET /api/v1/uploads/<filename>
Serve an uploaded file.
Accès : public (aucune authentification)
Limite : exemptée de la limite générale par adresse
Paramètres de chemin
| Nom | Description |
|---|---|
filename |
Name of the uploaded file. |
Réponse : The file content.
Webhooks entrants
URL appelées par les fournisseurs SMS pour livrer les messages entrants.
GET /webhook/domain/<provider>/<secret_token>
Receive incoming SMS for a domain.
This is the secret webhook URL for domain-level SMS reception. Each provider has its own URL path. Never rate limited: the secret token of the URL authenticates the provider.
Accès : public (aucune authentification)
Limite : exemptée de la limite générale par adresse
Paramètres de chemin
| Nom | Description |
|---|---|
provider |
SMS provider type (one of GET /api/v1/providers). |
secret_token |
Domain's webhook secret token. |
Réponse : success: true. domain_id: The domain. phone_number: The number that received the message. routed_to_team: Whether a team of the organization that owns the number received it. message_id: The recorded message. duplicate: true when the provider message id was already recorded: nothing is recorded again. notes: Remarks on the routing, one string each.
Errors These routes answer providers, not integrators: their errors are {"error": "<free text>"} without message or stable code. 400 "Invalid provider" (with the name). 404 "Invalid webhook token" or "Phone number not found in domain". 401 "Invalid signature".
POST /webhook/domain/<provider>/<secret_token>
Receive incoming SMS for a domain.
This is the secret webhook URL for domain-level SMS reception. Each provider has its own URL path. Never rate limited: the secret token of the URL authenticates the provider.
Accès : public (aucune authentification)
Limite : exemptée de la limite générale par adresse
Paramètres de chemin
| Nom | Description |
|---|---|
provider |
SMS provider type (one of GET /api/v1/providers). |
secret_token |
Domain's webhook secret token. |
Réponse : success: true. domain_id: The domain. phone_number: The number that received the message. routed_to_team: Whether a team of the organization that owns the number received it. message_id: The recorded message. duplicate: true when the provider message id was already recorded: nothing is recorded again. notes: Remarks on the routing, one string each.
Errors These routes answer providers, not integrators: their errors are {"error": "<free text>"} without message or stable code. 400 "Invalid provider" (with the name). 404 "Invalid webhook token" or "Phone number not found in domain". 401 "Invalid signature".
GET /webhook/provider/<provider>/<secret_token>
Receive incoming SMS/MMS from a provider.
This endpoint looks up the provider webhook by secret token. The phone number is then used to route to the appropriate team. Never rate limited: the secret token of the URL authenticates the provider.
Accès : public (aucune authentification)
Limite : exemptée de la limite générale par adresse
Paramètres de chemin
| Nom | Description |
|---|---|
provider |
SMS provider type (one of GET /api/v1/providers). |
secret_token |
Provider webhook secret token. |
Réponse : For GET: "Ready!" response. For POST: "ok" for SMS, HTTP 200 for MMS.
Errors These routes answer providers, not integrators: their errors are {"error": "<free text>"} without message or stable code. 404 "Invalid provider or webhook token" or "Phone number not found". 400 "Missing destination phone number".
POST /webhook/provider/<provider>/<secret_token>
Receive incoming SMS/MMS from a provider.
This endpoint looks up the provider webhook by secret token. The phone number is then used to route to the appropriate team. Never rate limited: the secret token of the URL authenticates the provider.
Accès : public (aucune authentification)
Limite : exemptée de la limite générale par adresse
Paramètres de chemin
| Nom | Description |
|---|---|
provider |
SMS provider type (one of GET /api/v1/providers). |
secret_token |
Provider webhook secret token. |
Réponse : For GET: "Ready!" response. For POST: "ok" for SMS, HTTP 200 for MMS.
Errors These routes answer providers, not integrators: their errors are {"error": "<free text>"} without message or stable code. 404 "Invalid provider or webhook token" or "Phone number not found". 400 "Missing destination phone number".
GET /webhook/sms/<secret_token>
Receive incoming SMS from provider.
This is the secret webhook URL that providers call when an SMS is received. The secret_token ensures only legitimate requests are processed. Never rate limited: the secret token of the URL authenticates the provider.
A closed or archived conversation is reopened by the message, once it is recorded (like the two other inbound routes): conversation.updated with change "reopened", cause "inbound" and the message_id of that message, the conversation already counting it (last_message_at, unread_count). A duplicate delivery (same provider message id) reopens nothing.
Accès : public (aucune authentification)
Limite : exemptée de la limite générale par adresse
Paramètres de chemin
| Nom | Description |
|---|---|
secret_token |
Team's webhook secret token. |
Réponse : success: true. message_id: The recorded message (absent on a duplicate delivery). duplicate: true when the provider message id was already recorded: nothing is recorded again. within_hours: Whether the message arrived within the business hours of the team. queued: Whether it was held for the opening of the team (outside the business hours). auto_reply_sent: Whether the automatic reply was sent.
Errors These routes answer providers, not integrators: their errors are {"error": "<free text>"} without message or stable code. 404 "Invalid webhook token". 400 "No provider configured". 401 "Invalid signature".
POST /webhook/sms/<secret_token>
Receive incoming SMS from provider.
This is the secret webhook URL that providers call when an SMS is received. The secret_token ensures only legitimate requests are processed. Never rate limited: the secret token of the URL authenticates the provider.
A closed or archived conversation is reopened by the message, once it is recorded (like the two other inbound routes): conversation.updated with change "reopened", cause "inbound" and the message_id of that message, the conversation already counting it (last_message_at, unread_count). A duplicate delivery (same provider message id) reopens nothing.
Accès : public (aucune authentification)
Limite : exemptée de la limite générale par adresse
Paramètres de chemin
| Nom | Description |
|---|---|
secret_token |
Team's webhook secret token. |
Réponse : success: true. message_id: The recorded message (absent on a duplicate delivery). duplicate: true when the provider message id was already recorded: nothing is recorded again. within_hours: Whether the message arrived within the business hours of the team. queued: Whether it was held for the opening of the team (outside the business hours). auto_reply_sent: Whether the automatic reply was sent.
Errors These routes answer providers, not integrators: their errors are {"error": "<free text>"} without message or stable code. 404 "Invalid webhook token". 400 "No provider configured". 401 "Invalid signature".
POST /webhook/sms/<secret_token>/status
Update message delivery status from provider.
Never rate limited: the secret token of the URL authenticates the provider.
Accès : public (aucune authentification)
Limite : exemptée de la limite générale par adresse
Paramètres de chemin
| Nom | Description |
|---|---|
secret_token |
Team's webhook secret token. |
Réponse : Acknowledgment.
Errors These routes answer providers, not integrators: their errors are {"error": "<free text>"} without message or stable code. 404 "Invalid webhook token" or "Message not found". 400 "Missing message ID" or "Unknown provider".
Clés API : routes fermées
Cette section est générée depuis backend/app/utils/permissions.py et depuis les routes (elle n'est pas écrite à la main). Une clé API (en-tête X-API-Key) n'ouvre pas tout ce qu'ouvre une session (JWT) : les routes qui donneraient un autre moyen de se connecter, qui dépensent l'argent de l'organisation ou qui administrent le compte lui sont fermées. Le refus est un 403 {"error": "forbidden", "message": "API key not allowed on this route"}.
Fermées à toute clé API
Sur ces routes, une requête portant un en-tête X-API-Key reçoit 403 dès que l'en-tête est présent, que la clé soit ordinaire, restreinte ou même invalide :
- Second facteur et clés d'accès : les 13 opérations du groupe
- Organisations :
POST /api/v1/organizations/<org_id>/members/<user_id>/impersonate - Invitations :
POST /api/v1/invitations,POST /api/v1/invitations/<invitation_id>/resend,POST /api/v1/invitations/accept - Utilisateurs :
POST /api/v1/users - Intégrations : les 9 opérations du groupe
- Réglages voix du domaine :
PUT /api/v1/domains/<domain_id>/organizations/<organization_id>/voice-outbound - Voix (interne) : les 3 opérations du groupe
- Facturation :
POST /api/v1/billing/admin/add-credits - Essai gratuit, état du compte et réglages :
PUT /api/v1/domains/<domain_id>/account-settings - Facturation d'un domaine : les 19 opérations du groupe
- Domaines :
POST /api/v1/domains/<domain_id>/admins,DELETE /api/v1/domains/<domain_id>/admins/<user_id>,POST /api/v1/domains/<domain_id>/organizations/<organization_id>/credit,PUT /api/v1/domains/<domain_id>/platform-voice-trunks,PUT /api/v1/domains/<domain_id>/security-settings,PUT /api/v1/domains/<domain_id>/settings/default-payment-provider,PUT /api/v1/domains/<domain_id>/settings/oauth/<provider>,PUT /api/v1/domains/<domain_id>/settings/payment-providers/<provider>,PUT /api/v1/domains/<domain_id>/settings/smtp,POST /api/v1/domains/<domain_id>/settings/smtp/test,PUT /api/v1/domains/<domain_id>/sms-providers/<provider>,POST /api/v1/domains/<domain_id>/voice-trunks,DELETE /api/v1/domains/<domain_id>/voice-trunks/<trunk_id>,PUT /api/v1/domains/<domain_id>/voice-trunks/<trunk_id>,POST /api/v1/domains/<domain_id>/webhook/regenerate - Système :
PUT /api/v1/system/oauth/<provider>,PUT /api/v1/system/smtp - Comptes fournisseurs SMS de la plateforme :
POST /api/v1/system/sms-accounts,DELETE /api/v1/system/sms-accounts/<account_id>,PUT /api/v1/system/sms-accounts/<account_id>,PUT /api/v1/system/sms-accounts/<account_id>/domains,POST /api/v1/system/sms-accounts/<account_id>/numbers,DELETE /api/v1/system/sms-accounts/<account_id>/numbers/<phone_id>,POST /api/v1/system/sms-accounts/<account_id>/numbers/move,POST /api/v1/system/sms-accounts/<account_id>/regenerate-secret - Comptes fournisseurs SMS d'un domaine :
PUT /api/v1/domains/<domain_id>/platform-sms-accounts,PUT /api/v1/domains/<domain_id>/sms-account-settings,POST /api/v1/domains/<domain_id>/sms-accounts,DELETE /api/v1/domains/<domain_id>/sms-accounts/<account_id>,PUT /api/v1/domains/<domain_id>/sms-accounts/<account_id>,POST /api/v1/domains/<domain_id>/sms-accounts/<account_id>/numbers,DELETE /api/v1/domains/<domain_id>/sms-accounts/<account_id>/numbers/<phone_id>,POST /api/v1/domains/<domain_id>/sms-accounts/<account_id>/regenerate-secret - Trunks voix de la plateforme :
POST /api/v1/system/voice-trunks,DELETE /api/v1/system/voice-trunks/<trunk_id>,PUT /api/v1/system/voice-trunks/<trunk_id>,PUT /api/v1/system/voice-trunks/<trunk_id>/domains - Profils et permissions : les 11 opérations du groupe
Sur celles-ci, une clé valide, ordinaire ou restreinte, reçoit 403 ; une clé inconnue ou révoquée reçoit 401 comme partout ailleurs :
- Numéros de téléphone :
PATCH /api/v1/phone-numbers/<phone_id>,GET /api/v1/phone-numbers/<phone_id>/prices,POST /api/v1/phone-numbers/<phone_id>/release,POST /api/v1/phone-numbers/<phone_id>/restore - Achat de numéros : les 4 opérations du groupe
- Clés API :
POST /api/v1/keys - Voix (VoXoS) :
GET /api/v1/voice/access-numbers,GET /api/v1/voice/call-info,PUT /api/v1/voice/call-info/recording,PUT /api/v1/voice/call-info/recording/skip-next,POST /api/v1/voice/call-requests,DELETE /api/v1/voice/call-requests/<request_id>,GET /api/v1/voice/call-requests/<request_id>,POST /api/v1/voice/diagnostics,GET /api/v1/voice/directory,GET /api/v1/voice/my-endpoints,PUT /api/v1/voice/my-endpoints/<endpoint_id>/settings
Fermées aux clés restreintes
Une clé restreinte (limitée à une équipe, ou clé d'une intégration) agit comme un agent des équipes qu'elle atteint et n'a jamais accès aux données de toute l'organisation. Elle reçoit 403 sur tout ce qui suit ; une clé ordinaire a les droits de son créateur.
- Organisations (18 opérations)
- Notifications push (3 opérations)
- Voix (VoXoS) (63 opérations)
- Fonctions voix des numéros (15 opérations)
- Métriques (7 opérations)
- Facturation (33 opérations)
- Essai gratuit, état du compte et réglages (6 opérations)
- Facturation : cartes, devis et reçus (14 opérations)
GET /api/v1/phone-numbers/unassigned
Fermées selon le corps
Sur ces routes, une clé API reçoit 403 dès que le corps de la requête fait ce qui suit (le même appel reste ouvert à une session).
PUT /api/v1/domains/<domain_id>: refusées si le corps contienthostnames,namePUT /api/v1/organizations/<org_id>/members/<user_id>: refusées si le corps donne le rôleadmin,domain_admin,super_adminPUT /api/v1/users/<user_id>: refusées si le corps donne le rôleadmin,domain_admin,super_admin; refusées si le corps contientpasswordPUT /api/v1/users/<user_id>/role: refusées si le corps donne le rôleadmin,domain_admin,super_admin