Account API reference
Operations for managing an account itself: bootstrapping one programmatically, checking your send budget and usage, turning on optional scanning, exporting your data, and deleting the account. Dashboard-only account settings (billing checkout, inbox counts) are managed from the web dashboard and are not part of this API surface.
For the machine-readable contract, see the generated OpenAPI spec at
/docs/openapi.json. Error codes and their remedies are
catalogued once at /agents/errors — this page names the codes
each operation can return and links there instead of restating remediation.
Auth is either a Bearer API key (admin or agent role) or a dashboard
session cookie; where an operation is admin-only, an agent-role key is
rejected with 403 INSUFFICIENT_SCOPE. Each operation below carries a
Classification tag drawn from read-only, mutating, and secret-revealing.
POST /v1/accounts/signup
Create a new account with an API key and no password — the programmatic/CLI signup path. Every new account must verify both its email address and a mobile phone number before protected product routes unlock.
Auth: none (public endpoint). Classification: mutating,
secret-revealing.
Request:
{
"email": "[email protected]",
"phone_number": "+13125550123",
"accept_terms": true,
"invite_code": "...",
"cli_signup_code": "...",
"accept_web_risk_version": "v2"
}phone_numberis required and must include its country code. Common spaces, parentheses, periods, and hyphens are normalized before validation and storage in E.164 form. ReplyLayer sends a 6-digit SMS one-time password through Telnyx Verify v2; signup responses expose only the masked destination.accept_termsmust betrue; it acknowledges the Terms of Service, Privacy Policy, Acceptable Use Policy, and DPA.- Depending on how signup is gated for your deployment, the request must carry
either an
invite_codeor a single-usecli_signup_code(issued to you from the dashboard by an existing customer). A missing code returns403 CLI_SIGNUP_CODE_REQUIRED; an invalid, expired, or already-consumed code returns403 CLI_SIGNUP_CODE_INVALID. accept_web_risk_versionis optional and acknowledges the malicious-link- scanning (URL reputation) disclosure. Its absence creates the account normally with link scanning left disabled — you can enable it later viaPOST /v1/accounts/url-reputation. Mismatched values are treated the same as absence.
Response (201):
{
"account_id": "uuid",
"api_key": "rly_live_<public_id>.<secret>",
"verification_required": true,
"email_verification_required": true,
"phone_verification_required": true,
"sms_delivery_status": "sent",
"phone_number_masked": "•••• 0123",
"message": "Check your email for a 6-digit verification code."
}The api_key is shown in plaintext only on this response — store it
immediately. It can call the verification/recovery endpoints, but protected
product routes return 403 EMAIL_NOT_VERIFIED while email is pending and then
403 PHONE_NOT_VERIFIED while phone verification is pending. sms_delivery_status
is sent, pending, or not_required; pending means account creation succeeded
but the caller must retry the SMS with
POST /v1/auth/resend-phone-verification. See
/docs/authentication for both verification steps.
Errors you'll care about: 403 CLI_SIGNUP_CODE_REQUIRED /
403 CLI_SIGNUP_CODE_INVALID (signup gate), and 429 SIGNUP_RATE_LIMITED — a
per-IP limiter caps this endpoint at 10 requests / 60s and returns a
Retry-After header plus details.retry_after, distinct from the generic
RATE_LIMITED.
GET /v1/accounts
Minimal identity ("whoami") for the authenticated caller.
Auth: any account-scoped key (admin or agent) or session.
Classification: read-only.
Response (200):
{ "account_id": "uuid", "email": "[email protected]", "status": "active", "tier": "sandbox" }Useful for confirming which account a key belongs to — for example, echoing the
account's own email into the confirm_email field required by account deletion,
without persisting the email locally.
GET /v1/accounts/quota
Send-budget preflight — the effective daily send limit and how much of it remains today. This is the endpoint an agent should call before a send.
Auth: admin, agent, or session (all receive a 200 — unlike
GET /v1/accounts/usage, which is admin-only).
Classification: read-only.
Response (200):
{
"today": { "count": 5, "limit": 200, "day": "2026-05-30" },
"sends_remaining": 195,
"reset_at": "2026-05-31T00:00:00.000Z",
"scope": "agent",
"bound_mailbox_ids": ["uuid1", "uuid2"]
}today.limitis the effective daily limit — it accounts for trust level and may be lower than the raw tier cap. A sandbox account reports15here. This is the same value the send-time budget gate enforces, sosends_remaining = max(0, limit - count)matches what a send will actually allow. See /docs/limits for the full tier and sandbox table.reset_atis an ISO-8601 UTC instant at midnight UTC of the next UTC day, when the budget resets.scopedisambiguates the two empty-bound_mailbox_idscases:"admin"(an unrestricted key/session —[]means all mailboxes) vs"agent"(a scoped key — an empty list means the key has no bound mailboxes and cannot send). Key offscope, notbound_mailbox_ids.length, to tell "all" from "none".- An optional
warmupobject appears only while a new paid account is inside its shared-domain new-sender warm-up window; when present,today.limitreflects the warm-up cap so the preflight never advertises headroom a send would refuse.
When the budget is exhausted, send endpoints return 429 RATE_LIMITED with
enriched details: { daily_limit, sends_remaining: 0, reset_at } — the same
numbers reported here. See /agents/send-gates.
GET /v1/accounts/usage
Full account usage: send budget, 30-day history, mailbox count, deliverability rates, health status bands, and trust-promotion progress.
Auth: admin or session only (agent keys → 403 INSUFFICIENT_SCOPE).
Classification: read-only.
Response (200):
{
"today": { "count": 5, "limit": 100, "day": "2026-04-08" },
"history": [{ "day": "2026-04-07", "count": 12 }],
"mailbox_count": 2,
"mailbox_limit": 5,
"rates": { "bounce_7d": 0.012, "complaint_7d": 0.0003, "delivery_7d": 0.987 },
"health": {
"bounce": {
"value": 0.012,
"status": "healthy",
"basis_count": 84,
"window": "7d",
"summary": "Bounce rate is comfortably within acceptable range.",
"action_hint": null
}
},
"trust": {
"level": 1,
"next_promotion": {
"target_level": 2,
"eligible": false,
"requirements": {
"min_age_days": { "required": 7, "current": 3, "met": false },
"min_emails": { "required": 20, "current": 12, "met": false },
"max_bounce_rate": { "required": 0.02, "current": 0.012, "met": true },
"max_complaint_rate": { "required": 0.0005, "current": 0.0003, "met": true }
}
}
}
}ratesare cohort-based, windowed on provider-accepted sends.healthgives server-derived display bands:statusis one ofhealthy,watch,at_risk, orinsufficient_data. Enforcement thresholds are not exposed.next_promotionisnullat the lowest trust level (which requires payment to advance) and at the maximum level.
POST /v1/accounts/url-reputation
Enable malicious link scanning (URL reputation) for the account, recording acknowledgement of the current disclosure. This turns on an account-wide scanning data flow, so it is an admin action.
Auth: admin Bearer or session only (agent keys → 403 INSUFFICIENT_SCOPE,
before any work). Classification: mutating.
Request:
{ "accept_web_risk_version": "v2" }accept_web_risk_version is required and must equal the current disclosure
version. Read that version from the account-info endpoint
(GET /v1/auth/me), which reports a url_reputation
block (active, accepted_version, current_version, privacy_ok). A stale
value returns 400 DISCLAIMER_VERSION_MISMATCH; if the account's accepted privacy
version predates the disclosed sub-processor, the call returns
409 PRIVACY_VERSION_TOO_OLD — re-accept the current Privacy Policy first.
Response (200):
{
"url_reputation": {
"active": true,
"accepted_version": "v2",
"current_version": "v2",
"privacy_ok": true
},
"disclosure": { "notice": "<disclosure text>", "advisory_url": "<advisory link>" }
}The disclosure object echoes the notice text and an advisory link for the
underlying reputation-data provider named in the Privacy Policy.
GET /v1/accounts/export
Data portability export (GDPR Art. 20): all account data as structured JSON.
Auth: admin or session only (agent keys → 403 INSUFFICIENT_SCOPE).
Classification: read-only.
Response (200):
{
"account": { "id": "uuid", "email": "...", "phone_number": "+13125550123", "phone_verification_required_at": "...", "phone_verified_at": "...", "tier": "sandbox", "status": "active", "data_region": "us" },
"mailboxes": [],
"messages": { "items": [], "total": 89, "truncated": false },
"message_events": { "items": [], "truncated": false },
"recipients": [],
"domains": [],
"api_keys": [{ "id": "uuid", "prefix": "rly_live_<public_id>.****", "status": "active" }],
"send_budgets": [],
"trust_history": [],
"suppressed_addresses": [],
"exported_at": "2026-04-09T00:00:00.000Z"
}Messages and events are capped at 10,000 items, with truncated: true when more
exist. API keys are masked — the key hash is never exposed. Each successful export
writes one audit entry with counts only (no PII).
DELETE /v1/accounts · DELETE /v1/accounts/:id
Soft-delete the account with a 30-day grace period. Both forms have identical
semantics; the :id variant exists for REST-idiomatic callers (the :id must
equal the caller's own account id).
Auth: admin Bearer or session (agent keys → 403 INSUFFICIENT_SCOPE).
Classification: mutating (destructive).
Two layers of confirmation:
- Intent gate (all paths, Bearer included). The body must include
confirm_emailequal to the account's own email (case-insensitive). Missing →400 DELETE_CONFIRMATION_REQUIRED; wrong email →403 DELETE_CONFIRMATION_MISMATCH. - Auth factor. A Bearer admin key skips step-up (possession of the key is the
confirmation). A dashboard session runs a method-aware step-up ladder: for an
SMS-based MFA account, an SMS re-auth code — mint a challenge with
POST /v1/auth/reauth/sms/send(action_scope: "account_delete"), then passreauth_challenge+mfa_code; for a TOTP account, atotp_code; else the accountpassword; else — for an SSO-only account with none —400 DELETE_REQUIRES_TOTP(withdetails.required_method: "totp_or_sms").mfa_codeis the method-blind field for the SMS code;totp_codeis still accepted as its alias. Exception: an account that never completed signup phone verification (and has no step-up factor) deletes withconfirm_emailalone, since MFA enrollment is blocked until the phone is verified and such an account cannot hold resources.
Request (Bearer path):
{ "confirm_email": "[email protected]" }Request (session path — TOTP account):
{ "confirm_email": "[email protected]", "password": "..." }Request (session path — SMS-MFA account):
{ "confirm_email": "[email protected]", "reauth_challenge": "uuid", "mfa_code": "123456" }Response (200):
{
"status": "deleted",
"message": "Account scheduled for deletion. You have 30 days to contact support to reinstate."
}Hard purge runs automatically after 30 days: it removes database rows, stored message contents, and any custom-domain DNS configuration; an anonymized audit trail is retained and a confirmation email is sent before purge.
Errors you'll care about:
409 LEGAL_HOLD_ACTIVE— the account is under a customer legal hold; lift it first.503 BILLING_TEARDOWN_RETRY— billing teardown failed transiently; the account staysactiveand the request can be retried.- On the
:idform only:400 VALIDATION_ERRORwhen:idis not a UUID, and403 INSUFFICIENT_SCOPEwhen:iddoes not match the caller's account id.