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.
Extract the one-time code and links of a message
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
claimedand is flagged for an OTP flood (otp_flagged_untilin the future) may run at most 10 extractions per UTC hour, counted together withwaititems extracted withextract=; past that,429 extract_limit. Claimed accounts are not capped.
Problem codes: unauthorized (401), not_found (404), extract_limit (429).
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id |
path | string | yes | Message id (msg_…). Any other value gets 404 not_found. |
Responses
Section titled “Responses”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"}