Account API reference
Operations for managing an account itself: bootstrapping one programmatically, checking your send budget and usage, managing link-scanning activation, 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 records the single signup legal-assent action: agreement to the Terms of Service — which incorporate the Acceptable Use Policy and, where applicable, the Data Processing Agreement — and acknowledgement of the Privacy Policy. The field name is retained for API compatibility.- 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 records an explicit acknowledgement of the malicious-link-scanning (URL reputation) disclosure. Its absence enables link scanning by default under the signup disclosure (Privacy Policy §7a). A mismatched (stale) explicit value creates the account normally with link scanning disabled — enable it later viaPOST /v1/accounts/url-reputation. You can turn scanning off per mailbox via the mailbox scanner policy (disabled_scanners: ["url-reputation"]).
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. New accounts have link scanning enabled by default at signup, so this route mostly serves accounts that predate default-on or a disclaimer version bump. It 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 acknowledged
privacy version predates the disclosed sub-processor, the call returns
409 PRIVACY_VERSION_TOO_OLD — review and acknowledge 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.