aethexDocs

API Reference

Text messages

The messages your agents may send during a call, and the carrier that sends them. Neither half works alone: a sender with no messages sends nothing, and messages with no sender are never offered to an agent. An agent never composes text of its own. It picks a message you wrote and fills its {placeholder} blanks.

Base URL
https://api.aethexai.com/api/v1
Endpoints
12
Scopes
messaging_accounts:readmessaging_accounts:writesms_messages:readsms_messages:write
GET/messaging-accounts/providers

Which carriers this deployment can link.

List available carriers

Read this rather than hardcoding a list. Twilio is deliberately absent, because it is not linked here at all: a Twilio tenant already holds the account that answers their calls, so they designate which of their numbers texts from with PATCH /phone-numbers/{id} and sms_sender=true, and open it to inbound texts with sms_inbound=true. Posting provider: "twilio" to this endpoint is refused. Link an account here only for a carrier where the sender is a registered name rather than one of your numbers.

POST/messaging-accounts

Your own carrier credentials, verified before they are stored.

Link a carrier account

For carriers where the sender is a registered name rather than a number, which is how business messaging is sent across much of West Africa. The credential is checked against the carrier before it is saved, encrypted at rest, and never returned by any endpoint. One active account per tenant, and it takes priority over a designated Twilio number: what sends your texts must not depend on which row a query returned first. Release it to switch back. If the carrier can also receive on this account (an Africa's Talking short code), the response carries inbound_callback_url once: paste it into the carrier's dashboard. It is not shown again; POST /messaging-accounts/:id/inbound-callback issues a new one.

Request body

5
  • providerstringrequired

    One of the values from GET /messaging-accounts/providers.

  • usernamestringrequired

    Your carrier-side account name. For Africa's Talking, your username.

  • api_keystringrequired

    Your carrier API key. Write-only; no response ever contains it.

  • senderstringrequired

    Who the message appears to come from: a registered sender name like ACME, or a number. It must already be registered with the carrier; nothing here can check that, so an unregistered name surfaces as a refusal on the first send.

  • labelstringoptional

    Your own note. Display text only.

Errors

4
422Credential rejected, or the account cannot send

The carrier rejected the username and key, the account holds no credit, or it is a test-environment account on a production deployment.

Check both halves of the credential come from the same carrier account, and that it has credit. A sandbox account is refused in production on purpose: it accepts every message and delivers none, so an agent would promise a text that never arrives.

409An account is already linked

This tenant already has an active messaging account. Or another tenant has already linked this carrier account: an Africa's Talking app delivers inbound texts to one callback URL, so it belongs to one tenant.

Release the existing one first, then link the new one.

429Too many attempts

Linking is rate-limited per tenant; each attempt calls the carrier.

Wait for the window in Retry-After and try again.

503Carrier unreachable

The carrier did not answer, or the server-side encryption key is not configured.

Retry. Not knowing whether a credential is valid is a reason to retry, not to assume it is wrong.

GET/messaging-accounts

What is linked, without either half of the credential.

List linked carrier accounts

Includes released accounts, so you can see what a tenant used before. Neither the username nor the API key is returned: both are half of a working credential, and echoing one would turn a read scope into most of a write one.

Path & Query

2
  • limitintegeroptional

    How many rows to return. 1–200, defaults to 50.

  • offsetintegeroptional

    How many rows to skip before the first one returned. Defaults to 0.

GET/messaging-accounts/:id

One linked account, without its key.

Get a carrier account

Returns the account as the list does: provider, sender name, principal and status. The API key is never returned, by this route or any other.

Path & Query

1
  • idstringrequired

    The account to read.

Errors

1
404Not found

No account with this id on your tenant, or it was released.

List your accounts; a released account is gone for good, link a new one.

POST/messaging-accounts/:id/inbound-callback

Where the carrier posts texts sent to your short codes; the previous URL stops working.

Issue a new inbound callback URL

Africa's Talking does not sign its callbacks, so the URL itself proves which account a message is for: it carries a token that only your account matches. The token is stored as a digest, like an API key, so it is shown once, here and when the account is linked, and never again. Where the deployment publishes a host for carrier webhooks the field is an absolute URL; otherwise it carries the path alone and you put that host in front of it. Paste it into the Africa's Talking dashboard under SMS, Callback URLs, Incoming Messages. Calling this again revokes the previous URL immediately, which is how a leaked one is taken back. Releasing the account revokes it too.

Path & Query

1
  • idstringrequired

    The account to issue the URL for.

Errors

2
404Messaging account not found

No such account on your tenant.

Use an ID from GET /messaging-accounts.

409Account released

A released account has no credential to answer from, so it cannot receive.

Link an active account and register your short codes under it.

DELETE/messaging-accounts/:id

Stop sending through it, and scrub the stored key.

Release a carrier account

Idempotent, 204 either way. The stored API key is overwritten, not merely marked unused. Calls already in progress keep the credential they resolved before the call started; the next call falls back to a designated Twilio number if there is one, or sends nothing. Any short code answering on the account stops answering (sms_inbound off): its callback URL is dead, so the state shown is the state that holds. Relink, move the short code with PATCH /phone-numbers/:id, then grant again.

Path & Query

1
  • idstringrequired

    The account to release.

POST/sms-messages

One message your agents may send, with blanks they fill in.

Write a message

The agent chooses which message, never what it says. Put {blanks} where a value belongs and the agent supplies them at call time; the blanks are parsed from the body, never stored, so an edited body cannot disagree with what the agent is asked for. Name it in your agent's prompt to tell it when to send which. Up to 25 active messages per tenant.

Request body

3
  • namestringrequired

    How your prompt refers to it: lowercase letters, digits and underscores, starting with a letter. It cannot be renamed, because a rename would break every prompt naming it.

  • bodystringrequired

    The text, up to 480 characters, with {blanks} for the values the agent fills in. It must say something of its own: a body that is only a blank would be free text under your sender identity, which is exactly what this design prevents.

  • labelstringrequired

    What this message is for, in your own words. Required: the agent reads it to decide which message to send, so say what it does and when. Never sent to the recipient.

Errors

2
422Malformed message

The name breaks the pattern, the body is empty or over 480 characters, it has unbalanced braces, or it carries no wording of its own.

Fix the field named in the response and resubmit.

409Name already used, or too many messages

An active message already has that name, or the tenant is at 25 active messages.

Pick another name, or retire a message you no longer send.

GET/sms-messages

Every message on the tenant, with its parsed blanks.

List messages

Includes retired messages so you can see what an older prompt referred to. placeholders is derived from the body on every read, so it is always what an agent would actually be asked to supply.

Path & Query

3
  • statusstringoptional

    Return only messages with this status: active or retired.

  • limitintegeroptional

    How many rows to return. 1–200, defaults to 50.

  • offsetintegeroptional

    How many rows to skip before the first one returned. Defaults to 0.

GET/sms-messages/:id

One message template, with its placeholders.

Get a message

Returns the template as written, plus the placeholders parsed from its body — the same shape the list returns.

Path & Query

1
  • idstringrequired

    The message to read.

Errors

1
404Not found

No message with this id on your tenant, or it was deleted.

List your messages to find the right id.

PATCH/sms-messages/:id

Change the wording; the name is fixed.

Edit a message

name is deliberately not accepted. Your agent's prompt refers to a message by name, so a rename would quietly break every prompt that names it. Retire the message and write its replacement instead. Changing the body re-parses the blanks, so removing one stops the agent being asked for it.

Path & Query

1
  • idstringrequired

    The message to edit.

Request body

2
  • bodystringoptional

    The new text, same rules as writing it.

  • labelstringoptional

    The new description. Cannot be blanked — it is what the agent chooses on.

Errors

1
422Malformed message

The body breaks one of the rules, or you tried to change the name.

Fix the field named in the response, or retire and recreate to rename.

POST/sms-messages/:id/test

One real message to a number you pick, without placing a call.

Send a message now, as a test

It runs the same path a call runs: the same sender resolution, the same blank filling, the same length ceiling, the same carrier. So a message that arrives here will arrive on a call, which a separate test implementation could never promise. It really sends, and it really costs: five an hour per tenant. The response carries the text that actually went out, because seeing that is the point of a test.

Path & Query

1
  • idstringrequired

    The message to send.

Request body

3
  • tostringrequired

    Where to send it, in E.164. Your own handset, for a test.

  • valuesobjectoptional

    One value per blank in the body, up to 120 characters each.

  • agent_idstringoptional

    Resolve the sender the way a call by this agent would: the number that agent texts from, falling back to your account default. Omit it to test the default on its own. 404 if the agent is not yours.

Errors

3
409No sender

This tenant has no working sender: none designated, none linked, or the stored credential could not be read.

Designate a Twilio number or link a carrier account, then try again. A call would simply not offer to text in this state.

422Refused before or by the carrier

A blank has no value, the filled-in message is over 480 characters, or the carrier refused it: a sender it does not recognise, a number that cannot receive texts, or a recipient who opted out.

The response says which. A carrier refusal means nothing was delivered, so nothing was charged.

429Too many test sends

Five an hour per tenant. This endpoint is for checking a message.

Wait for the window in Retry-After.

DELETE/sms-messages/:id

Take it out of circulation and free its name.

Retire a message

The message stops being offered to any agent. Its name becomes available again, so a replacement can take it, which is how a rename is done.

Path & Query

1
  • idstringrequired

    The message to retire.

Errors

1
409Message is on an agent's list

An agent has this message in its sms_message_ids. Deleting it would empty that agent's selection, which reads as “every active message” — widening the list instead of narrowing it.

Remove it from the agents that name it, or retire it instead (PATCH with status: "retired"): a retired message stops being sendable and stays on the lists that name it.