GET /me
Current account
Base URL https://api.tempinbox.vn/api/v1. Everything is JSON;
send Accept: application/json and, where a body is required,
Content-Type: application/json.
58 operations across 14 groups, taken straight from the contract.
/meCurrent account
/meUpdate the account
| Field | Type | Rules |
|---|---|---|
locale | string | optional, max 10 |
/me/2faTwo-factor status
/me/2faDisable two-factor
Requires a current code, so a stolen session alone cannot remove the second factor.
| Field | Type | Rules |
|---|---|---|
code | string | required, max 16 |
/me/2fa/setupBegin two-factor enrolment
The secret is held server-side for a short window and is not active until `confirm` succeeds, so an abandoned enrolment cannot lock the account out.
/me/2fa/confirmActivate two-factor
| Field | Type | Rules |
|---|---|---|
code | string | required, max 16 |
/auth/registerCreate an account
| Field | Type | Rules |
|---|---|---|
email | string | required, max 255 |
password | string | required, min 8, max 200 |
locale | string | optional, max 10 |
/auth/tokenSign in
With 2FA enabled the first call answers `401 auth.totp_required`; repeat it with `code`. Invalid credentials and a wrong code are the same failure to the caller by design.
| Field | Type | Rules |
|---|---|---|
email | string | required, max 255 |
password | string | required, max 200 |
code | string | optional, max 16 |
/plansPlan and add-on catalog (prices in points)
/payment-providersPayment gateway registry (public)
Mirrors `settings['billing.providers']` so the frontend renders a switched-off gateway as a disabled button instead of hiding it or letting it be clicked until the next deploy.
/me/subscriptionCurrent subscription and effective entitlements
/me/subscriptionSubscribe or switch plan (debits points immediately)
Debit and activation happen in one transaction. Switching plans cancels the previous subscription and starts a fresh period; no proration.
| Field | Type | Rules |
|---|---|---|
planCode | string | required |
interval | string | required, one of monthly, yearly |
/me/subscriptionTurn off auto-renew (the paid period is never cut short)
/me/walletWallet balance
/me/wallet/transactionsPoints ledger (paginated)
| Parameter | In | Type |
|---|---|---|
| ||
|
/me/wallet/topupsCreate a top-up order
`sepay` is active (VND bank transfer). `bnb` is `coming_soon` and returns 400 payment.provider_unavailable. The wallet is never credited from the bank webhook directly — reconciliation matches memo + amount.
| Field | Type | Rules |
|---|---|---|
provider | string | required, one of sepay, bnb |
amount | integer | required, ≥ 1 |
currency | string | required, one of VND |
/me/wallet/topups/{orderId}Poll top-up order status
| Parameter | In | Type |
|---|---|---|
orderId | path | string |
/content/noticesNotices to show over the product
Banners and dialogs published by staff, already filtered by schedule. The HTML is sanitised on write against a fixed allowlist, so it is safe to render as-is.
/content/pagesPublished pages
Titles and keys only — enough for a footer, without shipping every page body.
/content/pages/{key}One published page
| Parameter | In | Type |
|---|---|---|
key | path | string |
/me/domainsList private domains
/me/domainsAdd a private domain
| Field | Type | Rules |
|---|---|---|
domain | string | required, max 253 |
/me/domains/{id}Remove a private domain
/me/domains/{id}/verifyRe-check the DNS records
Both the TXT proof and the MX record must resolve. A domain that verified once is re-checked periodically, so removing the MX later suspends it again.
/me/filtersList custom extraction filters
/me/filtersCreate a filter (entitlement `filters.max`)
| Field | Type | Rules |
|---|---|---|
name | string | required, max 100 |
scopeType | string | optional, one of all, domain |
domainId | string,null | optional |
fromPattern | string,null | optional |
subjectPattern | string,null | optional |
extractRegex | string | required, max 512 |
priority | integer | optional, ≥ 1, ≤ 9999 |
status | string | optional, one of active, disabled |
/me/filters/testDry-run a filter against a sample without saving
| Field | Type | Rules |
|---|---|---|
filter | extractRegex, fromPattern, subjectPattern | required |
sample | from, subject, text | required |
/me/filters/{id}Update a filter
| Field | Type | Rules |
|---|---|---|
name | string | optional, max 100 |
extractRegex | string | optional, max 512 |
fromPattern | string,null | optional |
subjectPattern | string,null | optional |
priority | integer | optional |
status | string | optional, one of active, disabled |
/me/filters/{id}Delete a filter
/integrations/sepay/webhookSePay bank-transaction callback (append-only, idempotent)
Guarded by `Authorization: Apikey <shared-secret>` plus an ingress IP allowlist. The handler only appends a `sepay_transactions` row; crediting happens in the reconciliation job.
/me/api-keysList developer keys
/me/api-keysIssue a developer key
`key` is returned exactly once — only its hash is stored, so a database leak cannot be replayed. The tier follows the account plan and cannot be chosen.
| Field | Type | Rules |
|---|---|---|
name | string | required, max 100 |
scopes | string[] | required |
allowedCidrs | string[] | optional |
expiresAt | string | optional |
/me/api-keys/{prefix}Revoke a developer key
/keys/mailboxesCreate a mailbox with a developer key
Requires scope `mailboxes:write`. The mailbox belongs to the key's owner.
| Field | Type | Rules |
|---|---|---|
domain | string | optional, max 255 |
localPart | string | optional, max 64 |
ttl | integer | optional, ≥ 60 |
/keys/mailboxes/{id}/messagesList a mailbox's messages with a developer key
Requires scope `messages:read`. A mailbox the key does not own answers 404 rather than 403, so the endpoint cannot be used to discover which mailboxes exist.
| Parameter | In | Type |
|---|---|---|
| ||
|
/keys/mailboxes/{id}/otp/latestLatest OTP for a mailbox with a developer key
Requires scope `otp:read`.
/domainsActive public domains
/me/mailboxesMailboxes this account owns
Newest first, each with a bearer token. This is the only way to find a catch-all mailbox: it was opened by an incoming message, so the owner has never seen the address.
/mailboxesCreate a mailbox
| Parameter | In | Type |
|---|---|---|
|
| Field | Type | Rules |
|---|---|---|
domain | string | optional |
localPart | string | optional, min 1, max 64, pattern ^[a-z0-9][a-z0-9._-]{0,63}$ |
ttl | integer | optional, ≥ 60 |
/mailboxes/meMailbox info + TTL
/mailboxes/meDestroy the mailbox now
/mailboxes/me/extendExtend mailbox TTL (re-issues the mailbox token)
| Field | Type | Rules |
|---|---|---|
ttl | integer | optional, ≥ 60 |
/mailboxes/me/messagesList messages (cursor paginated, optional long-poll)
| Parameter | In | Type |
|---|---|---|
| ||
| ||
|
/messages/{id}Full message (sanitized HTML)
/messages/{id}Delete a message
/messages/{id}/rawDownload the original message (.eml)
The bytes as delivered, before parsing — full `Received:` chain, `Authentication-Results`, the untouched MIME tree and the original HTML before sanitizing. Opens in any mail client. The original is the largest per-message item in redis-mail, which runs `noeviction`, so it is kept only briefly: **15 minutes** for free mailboxes and **1 hour** for paid ones. After that this answers `410 message.raw_expired` — gone, not missing. `Message.rawExpiresAt` says when, so a client can drop the button rather than offer a download that fails.
/messages/{id}/attachments/{attId}Download an attachment
Always served as `application/octet-stream` with `Content-Disposition: attachment` and `nosniff`, whatever the sender declared: the file arrived from an unauthenticated stranger, so the browser must never be allowed to render it. Attachments above the inline size limit are not retained and answer 404.
| Parameter | In | Type |
|---|---|---|
| ||
attId | path | string |
/mailboxes/me/otp/latestLatest extracted OTP
| Parameter | In | Type |
|---|---|---|
|
/sessionMailboxes opened in this session
Newest first. Expired mailboxes are omitted rather than listed as dead addresses. Each mailbox carries a freshly minted bearer token capped at its own remaining TTL — the guest key is the only credential the visitor kept, so without tokens the list would name addresses it cannot read.
/sessionOpen a guest session
Redis-only, sliding 30 minutes, no account. Lets an anonymous visitor keep several mailboxes across reloads. It carries no plan and grants no extra quota — mailbox creation stays clamped per IP, so minting a new session buys nothing.
/sessionEnd the guest session
Drops the key and the session's mailbox index. The mailboxes themselves keep their own TTL — this is signing out, not deleting mail the visitor may still hold a token for.
/healthzLiveness probe
/readyzReadiness probe (dependencies checked)
/me/webhooksList webhook endpoints
/me/webhooksRegister an endpoint (entitlement `webhooks.max`)
The URL must be https on port 443/8443 and resolve to a public address. The response is the only time the signing secret is returned.
| Field | Type | Rules |
|---|---|---|
url | string | required, max 2048 |
events | string[] | required |
domainId | string,null | optional |
/me/webhooks/{id}Update an endpoint (resuming clears the failure history)
| Field | Type | Rules |
|---|---|---|
url | string | optional |
events | string[] | optional |
status | string | optional, one of active, paused |
/me/webhooks/{id}Delete an endpoint
/me/webhooks/{id}/deliveriesRecent delivery attempts (ring buffer, last 100)
| Parameter | In | Type |
|---|---|---|
id | path | string |
|