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.

Signup

An agent creates its own account in two calls, with no human and no card:

  1. GET /v1/signup/challenge returns a challenge and a difficulty.
  2. Solve the proof of work and send it to POST /v1/signup. The response holds the API key (shown once), the first inbox and the account’s limits.

New accounts start in sandbox, with a 7-day period.

GET /v1/signup/challenge

Auth: none

Step 1 of signup. Returns a signed challenge that is valid for 10 minutes, and its difficulty. Find a nonce of 1 to 32 ASCII letters or digits such that the SHA-256 digest of the UTF-8 string <challenge>:<nonce> starts with at least difficulty zero bits (counted from the most significant bit of the first byte), then send both to POST /v1/signup. At difficulty 20 that takes about a million attempts on average. Treat the challenge as an opaque string. Nothing is stored on the server until signup.

Example solver (Node.js 22 or later):

import { createHash } from "node:crypto";
const zeroBits = (buf) => {
let bits = 0;
for (const byte of buf) {
if (byte === 0) { bits += 8; continue; }
return bits + Math.clz32(byte) - 24;
}
return bits;
};
let i = 0;
while (zeroBits(createHash("sha256").update(`${challenge}:${i.toString(36)}`).digest()) < difficulty) i++;
const nonce = i.toString(36);

Each challenge is good for one signup. If Replyz raises the difficulty, challenges issued before at a lower difficulty are refused with pow_expired.

Problem codes: signups_disabled (503), rate_limited (429).

A new challenge.

Content type application/json.

Field Type Required Description
challenge string yes Opaque signed string. Send it back unchanged with your nonce.
difficulty integer (8–28) yes Number of leading zero bits the digest must have. 20 by default; Replyz can set it anywhere from 8 to 28.
expires_at string (date-time) yes End of the challenge’s 10-minute validity. After it, signup answers pow_expired.
{
"challenge": "v1.20.1793959798000.9SbAcYNJrG6jK6XF.3b7a3f744a722f0867867dae9719a28191d0f50f89bb00b70164c5e5405d2cd4",
"difficulty": 20,
"expires_at": "2026-11-06T10:09:58.000Z"
}

rate_limited: too many signup requests from this network in the last minute.

Header Description
Retry-After Always 60.

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

Example: rate_limited

{
"type": "https://docs.replyz.io/errors/rate_limited",
"code": "rate_limited",
"status": 429,
"message": "Too many requests from this network.",
"fix": "Wait a minute before trying again.",
"docs": "https://docs.replyz.io/errors/rate_limited.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"
}

signups_disabled: Replyz has paused new signups.

Header Description
Retry-After Always 3600.

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

Example: signups_disabled

{
"type": "https://docs.replyz.io/errors/signups_disabled",
"code": "signups_disabled",
"status": 503,
"message": "New signups are paused.",
"fix": "Try again later.",
"docs": "https://docs.replyz.io/errors/signups_disabled.md"
}

POST /v1/signup

Auth: none

Step 2 of signup. Send the challenge from GET /v1/signup/challenge with a solved nonce. Creates a sandbox account with one inbox and returns its API key.

  • Save api_key now: it is shown only once. Replyz stores only a hash of it and cannot show it again.
  • agent_name (optional, default agent) is stored on the account and becomes the start of the inbox address (see the Inboxes tag). A reserved name is refused before the proof of work is checked, so the same challenge can be sent again with another name.
  • The challenge is used up as soon as its proof of work is accepted, even if a later check fails. Get a new challenge before retrying.
  • Signup is not idempotent, and Idempotency-Key is ignored here: every successful call creates a new account. If the response is lost, sign up again; the unused account still counts toward the network’s daily signup limit.
  • Limits: 20 signups per network per day (UTC), counted after a valid proof of work, and a burst guard of about 20 requests per minute per network. A network is one IPv4 address or one IPv6 /64.
  • The sandbox period is 7 days (expires_at). After it, the account is treated as expired.
  • The inbox created here counts toward the account’s daily inbox-creation limit (3 per UTC day in sandbox), so a new account can create 2 more inboxes on its first day (a sandbox account still has at most 1 live inbox at a time).
  • claim_url is meant for the human owner, but the claim page is not available yet.

Checks run in this order: signups enabled, burst guard, body, reserved name, proof of work, challenge already used, daily limit per network, free address.

Problem codes: signups_disabled (503), rate_limited (429), payload_too_large (413), invalid_json (400), invalid_request (400), name_reserved (400), pow_invalid (400), pow_expired (400), pow_reused (400), signup_limit (429), address_unavailable (503).

Content type application/json (required).

Field Type Required Description
challenge string yes The challenge from GET /v1/signup/challenge, unchanged.
nonce string yes Your solution: 1 to 32 ASCII letters or digits such that the SHA-256 digest of <challenge>:<nonce> starts with at least difficulty zero bits. A nonce of any other shape is refused with pow_invalid.
agent_name string or null (length 0–64, default "agent") no A name for the agent, at most 64 characters after control characters are removed and the value is trimmed. Stored on the account and used for the inbox address (see the Inboxes tag). null or an empty string means agent. A reserved name is refused with name_reserved.
{
"challenge": "v1.20.1793959798000.9SbAcYNJrG6jK6XF.3b7a3f744a722f0867867dae9719a28191d0f50f89bb00b70164c5e5405d2cd4",
"nonce": "11luo",
"agent_name": "Atlas"
}

The account was created. Store api_key now; it is not shown again.

Content type application/json.

Field Type Required Description
org_id string yes The new account’s id.
status string "sandbox" yes New accounts are always sandbox.
api_key string yes The account’s API key. Shown once: store it now. Replyz keeps only a hash and cannot show it again.
inbox InboxRef yes The first inbox, created with the account.
inbox.id string yes
inbox.address string (email) yes
claim_url string (uri) yes Link meant for the human owner to claim the account (https://replyz.io/claim/<token>). The claim page is not available yet.
expires_at string (date-time) yes End of the sandbox period, 7 days after signup.
limits Limits yes The account’s limits, which depend on its status. Expired and suspended accounts show the sandbox numbers, or the claimed numbers when they had been claimed before.
limits.inboxes integer yes Maximum number of live inboxes: 1 in sandbox (3 for a claimed account; claiming is not available yet).
limits.receive_per_month integer yes Messages accepted per calendar month (UTC): 1,000 in sandbox (5,000 for a claimed account; claiming is not available yet). Mail above the cap is refused during delivery.
limits.send_scope string: none yes What the account may send. Always none, because sending is not available in this version.
terms_url string (uri) yes Link to the terms of service. The page is published before the public launch.
privacy_url string (uri) yes Link to the privacy notice. The page is published before the public launch.
next string yes A short instruction for the agent. Until the claim page opens it says that claim_url will let the human own the account once that page opens.
{
"org_id": "org_01M6R4T180QW261WNQRBSATDJ0",
"status": "sandbox",
"api_key": "rz_live_v0O1W6r3uuJyQUgG7EQ6Ei1JZ2rHdmbS",
"inbox": {
"id": "ibx_01M6R4T180ZADNV1160NS1SRNP",
"address": "atlas-wo7tw5@youreplyz.com"
},
"claim_url": "https://replyz.io/claim/RphFRvF9niaE5R393dB1cOZTardtvAsD",
"expires_at": "2026-11-13T10:00:00.000Z",
"limits": {
"inboxes": 1,
"receive_per_month": 1000,
"send_scope": "none"
},
"terms_url": "https://replyz.io/terms",
"privacy_url": "https://replyz.io/privacy",
"next": "Save api_key now (shown once). claim_url will let your human own this account once the claim page opens."
}

The request was refused.

  • invalid_json: the body is not a JSON object.
  • invalid_request: challenge or nonce is missing or not a string, or agent_name is not a string or is longer than 64 characters.
  • name_reserved: agent_name is reserved. The challenge is still valid.
  • pow_invalid: the challenge is malformed or was not issued by Replyz, the nonce does not match ^[0-9A-Za-z]{1,32}$, or the digest has too few zero bits. The message names the reason (malformed, bad_signature or insufficient).
  • pow_expired: the challenge expired, or was issued at a lower difficulty than the current one.
  • pow_reused: the challenge was already used for a signup.

Problem object (application/problem+json). Codes: invalid_json, invalid_request, name_reserved, pow_invalid, pow_expired, pow_reused.

Example: invalid_json

{
"type": "https://docs.replyz.io/errors/invalid_json",
"code": "invalid_json",
"status": 400,
"message": "Body must be a JSON object.",
"fix": "Send Content-Type: application/json with a JSON object.",
"docs": "https://docs.replyz.io/errors/invalid_json.md"
}

Example: invalid_request: agent_name too long

{
"type": "https://docs.replyz.io/errors/invalid_request",
"code": "invalid_request",
"status": 400,
"message": "\"agent_name\" is longer than 64 characters.",
"fix": "Shorten \"agent_name\".",
"docs": "https://docs.replyz.io/errors/invalid_request.md"
}

Example: name_reserved: reserved agent_name

{
"type": "https://docs.replyz.io/errors/name_reserved",
"code": "name_reserved",
"status": 400,
"message": "That agent_name is reserved (role words, brand names and look-alikes).",
"fix": "Choose a different agent_name (your challenge is still valid), e.g. your own name for the agent.",
"docs": "https://docs.replyz.io/errors/name_reserved.md"
}

Example: pow_invalid: not enough zero bits

{
"type": "https://docs.replyz.io/errors/pow_invalid",
"code": "pow_invalid",
"status": 400,
"message": "Proof of work rejected (insufficient).",
"fix": "Get a fresh challenge from GET /v1/signup/challenge and solve it.",
"docs": "https://docs.replyz.io/errors/pow_invalid.md"
}

Example: pow_expired

{
"type": "https://docs.replyz.io/errors/pow_expired",
"code": "pow_expired",
"status": 400,
"message": "The challenge expired or is out of date.",
"fix": "Get a fresh challenge from GET /v1/signup/challenge and solve it.",
"docs": "https://docs.replyz.io/errors/pow_expired.md"
}

Example: pow_reused

{
"type": "https://docs.replyz.io/errors/pow_reused",
"code": "pow_reused",
"status": 400,
"message": "This challenge was already used.",
"fix": "Get a fresh challenge from GET /v1/signup/challenge.",
"docs": "https://docs.replyz.io/errors/pow_reused.md"
}

payload_too_large: the body is larger than 16,384 bytes.

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

Example: payload_too_large

{
"type": "https://docs.replyz.io/errors/payload_too_large",
"code": "payload_too_large",
"status": 413,
"message": "Body is larger than 16384 bytes.",
"fix": "Send a smaller JSON body.",
"docs": "https://docs.replyz.io/errors/payload_too_large.md"
}
  • rate_limited: too many signup requests from this network in the last minute.
  • signup_limit: this network already made 20 signups today (UTC).
Header Description
Retry-After 60 with rate_limited; the seconds until the next UTC day with signup_limit.

Problem object (application/problem+json). Codes: rate_limited, signup_limit.

Example: rate_limited

{
"type": "https://docs.replyz.io/errors/rate_limited",
"code": "rate_limited",
"status": 429,
"message": "Too many requests from this network.",
"fix": "Wait a minute before trying again.",
"docs": "https://docs.replyz.io/errors/rate_limited.md"
}

Example: signup_limit

{
"type": "https://docs.replyz.io/errors/signup_limit",
"code": "signup_limit",
"status": 429,
"message": "Too many signups from this network today.",
"fix": "Try again tomorrow (UTC).",
"docs": "https://docs.replyz.io/errors/signup_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"
}
  • signups_disabled: Replyz has paused new signups.
  • address_unavailable: no free address was found (rare). Retry with a new challenge.
Header Description
Retry-After 3600 with signups_disabled; not sent with address_unavailable.

Problem object (application/problem+json). Codes: signups_disabled, address_unavailable.

Example: signups_disabled

{
"type": "https://docs.replyz.io/errors/signups_disabled",
"code": "signups_disabled",
"status": 503,
"message": "New signups are paused.",
"fix": "Try again later.",
"docs": "https://docs.replyz.io/errors/signups_disabled.md"
}

Example: address_unavailable (signup)

{
"type": "https://docs.replyz.io/errors/address_unavailable",
"code": "address_unavailable",
"status": 503,
"message": "Could not find a free address.",
"fix": "Retry the signup with a new challenge.",
"docs": "https://docs.replyz.io/errors/address_unavailable.md"
}