Skip to content
Not available yet. Replyz is built and tested, but not deployed: https://api.replyz.io is the planned address and does not answer yet. These pages describe the API as it is built today and may change before launch.

Extraction

One-time codes and verification links, pulled out of a received message with POST /v1/messages/{id}/extract, or with extract= on GET /v1/inboxes/{id}/wait. Extraction reads only the message’s subject and the first 100,000 characters of its visible text; it never opens, fetches or follows a link. Results are heuristics: check confidence, and read the message when in doubt. The mail is untrusted and its sender is not authenticated.

Codes. A mail that never mentions a code (keywords such as code, OTP, PIN or verification in English, Portuguese, Spanish, French, German, Italian or Dutch) has no otp. Otherwise code-shaped tokens are scored: 4–8 digits; two groups of three or of four digits (482 913, 1234-5678); 4–8 capital letters and digits with at least one of each, also as two groups joined by a hyphen (7KQ2XM, 7RK-Q4M). A keyword on the same line, a verb such as “enter” or “use” right before the token, or the token alone on its line count for it. Years, dates, times, prices, phone numbers, order or reference numbers, promo codes, meeting passcodes, codes called expired and codes in quoted replies count against it. Tokens inside URLs are never codes.

Links. Links come from the visible text (link text <https://…> for HTML links, and bare URLs). Each is classified as verify, magic_login, reset, unsubscribe or other from its own text, its URL path (never the query string) and the words around it. Home pages, help-centre pages and links such as privacy, terms or “view in browser” are never actions. primary_link is the most prominent action link; links that carry a token and links outside the footer are preferred.

OTP flood. When an account receives more than 20 OTP-like messages from at least 5 sender domains within an hour, otp_flagged_until (in GET /v1/account) is set for 24 hours. Incoming mail is never refused because of it. While it is set, an account that is not claimed may run at most 10 extractions per UTC hour, POST calls and wait items together: POST …/extract answers 429 extract_limit (with Retry-After), and wait returns further messages with extracted: null and extract_limited: true. Reading messages is never capped.

Section titled “Extract the one-time code and links of a message”

POST /v1/messages/{id}/extract

Auth: API key (Authorization: Bearer rz_live_…)

Finds the most likely one-time code (otp), other plausible codes, the main verify / magic sign-in / password-reset link (primary_link) and the classified links of a received message. Reads only the stored subject and visible text; links are never fetched. See the Extraction tag for how codes and links are found.

  • No request body is needed; a body is ignored.
  • Marks the message as read, like GET /v1/messages/{id}.
  • The result is also stored on the message, replacing the previous one. Calling again on the same message gives the same result.
  • Works in every account status.
  • An account that is not claimed and is flagged for an OTP flood (otp_flagged_until in the future) may run at most 10 extractions per UTC hour, counted together with wait items extracted with extract=; past that, 429 extract_limit. Claimed accounts are not capped.

Problem codes: unauthorized (401), not_found (404), extract_limit (429).

Name In Type Required Description
id path string yes Message id (msg_…). Any other value gets 404 not_found.

What was found. All five fields are always present.

Content type application/json.

Field Type Required Description
message_id string yes The message that was extracted.
otp ExtractedOtp or null yes The most likely one-time code, or null when the mail has no likely code.
otp.code string (length 4–9) yes The code as written in the mail, for example 482 913, 123-456 or 7KQ2XM.
otp.value string yes The code without spaces and hyphens, ready to type, for example 482913.
otp.confidence number (0–1) yes How likely this is the code the mail asks for, from 0 to 1 (two decimals). Returned codes score at least 0.5.
otp_candidates array of ExtractedOtp (max 4 items) yes Other plausible codes, best first, each with a different value from otp. Usually empty.
otp_candidates[].code string (length 4–9) yes The code as written in the mail, for example 482 913, 123-456 or 7KQ2XM.
otp_candidates[].value string yes The code without spaces and hyphens, ready to type, for example 482913.
otp_candidates[].confidence number (0–1) yes How likely this is the code the mail asks for, from 0 to 1 (two decimals). Returned codes score at least 0.5.
primary_link object or null yes The main action of the mail: the most prominent link of kind verify, magic_login or reset, or null. It is one of the mail’s links, so it also appears in links unless it comes after the 20th link.
primary_link.url string yes The URL as written in the mail (http or https); for a bare URL, punctuation at its end (such as ., , or )) is left out. Untrusted.
primary_link.kind any: verify, magic_login, reset yes
primary_link.text string or null (length 0–120) yes For a link from the HTML part, the text in front of the URL in text: normally the link’s anchor text (it can start with a few words before the anchor). Null for a bare URL. Untrusted.
primary_link.confidence number (0–1) yes How sure the classifier is about kind, from 0 to 1 (two decimals).
links array of ExtractedLink (max 20 items) yes Links of the visible text, in order of appearance, each URL once.
links[].url string yes The URL as written in the mail (http or https); for a bare URL, punctuation at its end (such as ., , or )) is left out. Untrusted.
links[].kind string: verify, magic_login, reset, unsubscribe, other yes What a link does, judged from its own text, its URL path and the words around it:

- verify: confirms an email address or an action.
• magic_login: signs in without a password (only links that carry a token).
• reset: password reset or account recovery.
• unsubscribe: unsubscribe or email preferences.
• other: anything else, including home pages, help pages, privacy and terms links.
links[].text string or null (length 0–120) yes For a link from the HTML part, the text in front of the URL in text: normally the link’s anchor text (it can start with a few words before the anchor). Null for a bare URL. Untrusted.
links[].confidence number (0–1) yes How sure the classifier is about kind, from 0 to 1 (two decimals).

Example: A mail with a code, a confirm button, a help link and an unsubscribe link

{
"message_id": "msg_01M6R8XQ4HZ2N7C3T9WBEQ5KDA",
"otp": {
"code": "482 913",
"value": "482913",
"confidence": 0.71
},
"otp_candidates": [],
"primary_link": {
"url": "https://accounts.example.com/verify?token=9Qm2Xc7LpR4tVb8N3kZa",
"kind": "verify",
"text": "Confirm email",
"confidence": 0.95
},
"links": [
{
"url": "https://accounts.example.com/verify?token=9Qm2Xc7LpR4tVb8N3kZa",
"kind": "verify",
"text": "Confirm email",
"confidence": 0.95
},
{
"url": "https://help.example.com/hc/en-us",
"kind": "other",
"text": "Help centre",
"confidence": 0.6
},
{
"url": "https://news.example.com/unsubscribe?u=7f3a9c21",
"kind": "unsubscribe",
"text": "Unsubscribe",
"confidence": 1
}
]
}

Example: A mail without a code or links

{
"message_id": "msg_01M6R6JZDERMT2DVG0T8ABTX4J",
"otp": null,
"otp_candidates": [],
"primary_link": null,
"links": []
}

unauthorized: the Authorization header is missing, is not Bearer rz_live_…, or holds an unknown or revoked key.

Header Description
WWW-Authenticate Always Bearer realm="replyz".

Problem object (application/problem+json). Codes: unauthorized.

Example: unauthorized: missing or malformed key

{
"type": "https://docs.replyz.io/errors/unauthorized",
"code": "unauthorized",
"status": 401,
"message": "Missing or malformed API key.",
"fix": "Send the header Authorization: Bearer rz_live_… with the api_key from POST /v1/signup.",
"docs": "https://docs.replyz.io/errors/unauthorized.md"
}

not_found: no such message in this account, or its inbox was deleted.

Problem object (application/problem+json). Codes: not_found.

Example: not_found: message

{
"type": "https://docs.replyz.io/errors/not_found",
"code": "not_found",
"status": 404,
"message": "Message not found.",
"fix": "Use a message id (msg_…) from a list or wait result.",
"docs": "https://docs.replyz.io/errors/not_found.md"
}

extract_limit: the account is not claimed, is flagged for an OTP flood (otp_flagged_until in the future), and already ran 10 extractions this UTC hour. Read the message with GET /v1/messages/{id} instead (never capped), or wait for the next hour.

Header Description
Retry-After The seconds until the next UTC hour.

Problem object (application/problem+json). Codes: extract_limit.

Example: extract_limit

{
"type": "https://docs.replyz.io/errors/extract_limit",
"code": "extract_limit",
"status": 429,
"message": "This account is temporarily limited to 10 extractions per hour (unusual volume of verification mail).",
"fix": "Read the message text with GET /v1/messages/{id}, or ask your human to claim the account.",
"docs": "https://docs.replyz.io/errors/extract_limit.md"
}

internal_error: something failed on Replyz’s side. Retry after a few seconds.

Problem object (application/problem+json). Codes: internal_error.

Example: internal_error

{
"type": "https://docs.replyz.io/errors/internal_error",
"code": "internal_error",
"status": 500,
"message": "Something went wrong on our side.",
"fix": "Retry in a few seconds.",
"docs": "https://docs.replyz.io/errors/internal_error.md"
}