Create an inbox
curl -X POST https://aisenseapi.com/services/v1/inbox{
"ok": true,
"inbox_id": "a85d0bee-f8f7-4be1-a1b3-8d58f3dbdfc7",
"slug": "ztjqt7n",
"address": "aisense+ztjqt7n@aisenseapi.com",
"read_url": "https://aisenseapi.com/services/v1/inbox/a85d0bee-f8f7-4be1-a1b3-8d58f3dbdfc7",
"wait_url": "https://aisenseapi.com/services/v1/inbox/a85d0bee-f8f7-4be1-a1b3-8d58f3dbdfc7/wait/25",
"expire_timestamp": 1800086400
}The call takes no arguments and answers HTTP 201. There is no account step and no API key. Hand address to whatever needs to mail you, and keep inbox_id in your own job state. It is returned here and nowhere else. No call lists inboxes, and none recovers the identifier from the address.
Two identifiers, two different powers
Every inbox has one identifier that is meant to be seen and one that is not. Getting this pair straight is the whole of using the endpoint safely, so it is worth a minute before the first call.
| Value | Where it travels | What holding it allows |
|---|---|---|
slug | Inside the address, so also in mail headers, bounce reports and the sender's logs | Sending mail to the inbox |
inbox_id | The create response only, and the read and wait URLs built from it | Reading the mail in the inbox |
The slug is seven characters drawn from a-z and 0-9. It is public by construction. The moment the address is typed into a sign-up form the slug starts appearing in mail headers, in delivery logs and in any bounce that comes back, and none of those places is under your control. Assume anyone can learn it.
What that buys them is one thing only: they can send mail to the inbox. The slug never reads it, and it never appears in a URL.
The inbox_id is a UUID and it is the only value that reads the mail. Treat it as a bearer secret. Anyone holding it reads every message in the inbox, exactly as you do, with no further check.
Guessing the address does not read the inbox. Leaking inbox_id, read_url or wait_url does. Keep those three out of logs, issue trackers, screenshots and anything a model can quote back.
A wrong inbox_id and an inbox that never existed both answer HTTP 404. Nothing answers 403. The two cases are deliberately indistinguishable, so a caller working through guesses learns nothing from the status code about which identifiers are real.
Read the mail
curl https://aisenseapi.com/services/v1/inbox/{inbox_id}{
"ok": true,
"slug": "ztjqt7n",
"address": "aisense+ztjqt7n@aisenseapi.com",
"received": 1,
"truncated": false,
"messages": [
{
"from": "noreply@example.com",
"subject": "Your verification code",
"date": "2027-01-15T08:00:00Z",
"text": "Your code is 481516. Confirm at https://example.com/confirm/abc",
"codes": [ "481516" ],
"links": [ "https://example.com/confirm/abc" ]
}
],
"created_at_timestamp": 1800000000,
"expire_timestamp": 1800086400
}The response repeats slug and address but never inbox_id. The credential is not echoed back, so a stored response body or a debug dump does not hand over read access on its own.
| Message field | Content |
|---|---|
from | The sender address |
subject | The subject line |
date | The time the service received the message |
text | The cleaned body text |
codes | Standalone 4 to 8 digit numbers found in the text |
links | Public http and https links found in the text |
date is our receive time, not the sender's Date header. That header is written by whoever sent the mail and can say anything, so it is not the value to time a workflow against.
codes and links exist so an agent does not have to write a parser for every mail template it meets. codes are read out of the same text that is returned, so the sentence around a code is there to check before acting on it. links also collects the target of an HTML link, and the cleaned text keeps the wording of such a link rather than its address, so an entry in links does not always appear in text.
Wait without a polling loop
curl https://aisenseapi.com/services/v1/inbox/{inbox_id}/wait/25The final path value accepts 0 to 25 seconds, and a value outside that range is not clamped: the route answers 404. The request returns early when a message arrives, and otherwise at the end of the window. It adds waited_seconds and wait_reason to the object above. Call it again when the message you were expecting has still not arrived.
Mail is not instant. A confirmation that a person would call immediate can spend a minute in a queue on the sending side, so plan for several consecutive waits rather than one.
A full inbox refuses mail, it does not forget
truncated is a boolean on the inbox, and it is the field to check when a message you were promised is not there. Once the inbox is at its message cap, or the new message would carry it past its total text cap, that mail is refused at delivery. Old messages are never evicted to make room, so a code that already arrived cannot disappear because a newsletter turned up after it.
The flag exists because the alternative is silence. Without it, an agent waiting for a code would see a full inbox, no code, and nothing to separate a slow sender from a refused one.
The wait form watches the flag as well as the message count, so a refusal ends the wait instead of leaving you to run out the clock on a message that will never arrive.
Create a fresh inbox when this happens. The caps are per inbox, and creation is one call.
What is kept and what is stripped
An inbox stores a cleaned reading of each message rather than the message. Six things survive: the sender address, the subject, the receive time, the cleaned text, the extracted codes and the extracted public links.
Everything else is dropped before storage. Attachments are not kept and cannot be fetched. Raw MIME, arbitrary headers, scripts and styles are removed. Links to private address ranges and to localhost are dropped from links, so the list an agent acts on carries no direct pointer into your own network. The filter reads each address as written and does not resolve host names, and text still holds whatever the sender put there.
Anyone with inbox_id reads every message. Do not point password resets for real accounts, or any personal mail, at a disposable inbox.
Limits and lifetime
| Limit | Value |
|---|---|
| Messages per inbox | 20 |
| Cleaned text per message | 64 KiB |
| Cleaned text per inbox | 256 KiB |
| New inboxes per client per UTC day | 50 |
| Active inboxes service wide | 5000 |
| Lifetime | 24 hours |
The lifetime is fixed at creation and cannot be extended. Reading the inbox does not move expire_timestamp, and neither does new mail. When the window ends the address stops accepting mail. A read answers 410 while the expired inbox is still recognised, and 404 once it has been cleared away.
The service shares the public limit of 5000 REST and MCP requests per IP per 24 hours.
MCP tools
MCP clients use create_agent_inbox and read_agent_inbox for the same two calls. create_agent_inbox takes no arguments.
| read_agent_inbox argument | Type | Default |
|---|---|---|
inbox_id | uuid, required | None |
wait_seconds | integer, 0 to 25 | 0 |
A wait_seconds of 0 reads and returns at once. A higher value holds the call open like the REST wait path, and it also returns as soon as a refused message flips truncated.
Useful flows
Verification code
Sign up with the address, wait on the inbox and read the value out of codes.
Confirmation link
Take the entry in links and follow it to activate a test account.
Delivery check
Point your own system at the address and confirm that the mail it claims to send arrives.
Throwaway signup
Give a short-lived integration test an address that costs no mailbox of yours.