Accounts and sessions

Every caller that owns inboxes holds a session token. A session starts anonymous. You can turn it into an account by registering, or log it in to an existing account. An account is identified only by a random account code and protected by a password. No email address or other contact detail is ever asked for.

Account codes and recovery keys

  • The account code is 16 Crockford base32 symbols, shown as XXXX-XXXX-XXXX-XXXX. The server generates it.
  • The recovery key is 39 Crockford base32 symbols, shown in groups of five. It can log in instead of the password.
  • Both are returned once, in the registration response. The server stores only hashes of them. There is no email on file and no reset flow: if you lose the code, or lose both the password and the recovery key, the account cannot be recovered.
  • On input, codes and keys may use any case and may include or leave out the dashes. I and L are read as 1, and O as 0.
  • Passwords are 12 to 128 characters and must not be on a list of common passwords.

Responses other than registration show the code masked, as ****-****-****-XXXX.


POST/v1/session

Create an anonymous session

Creates an anonymous session and returns its token. The token appears only in this response. No credentials are needed and the body is optional.

Optional attributes

  • Name
    referral_code
    Type
    string
    Description

    A referral code to attribute to, if this session later registers an account. See Referrals.

Response

  • Name
    token
    Type
    string
    Description

    Bearer token. Store it now.

  • Name
    expires_at
    Type
    timestamp
    Description

    Session expiry. It moves forward while the session is used (30 days from last use).

  • Name
    tier
    Type
    string
    Description

    anon_session.

Request

POST
/v1/session
curl -X POST https://mail.nullnet.sh/v1/session

Response

{
  "token": "8lJ0vD2h6kq0Q8m7tX3yZr1cB5nW4eA9sUfGpKoLiHM",
  "expires_at": "2026-11-25T08:00:00Z",
  "tier": "anon_session"
}

GET/v1/session

Get the current session

Returns the session the token belongs to.

Response

  • Name
    id
    Type
    string
    Description

    Session id.

  • Name
    expires_at
    Type
    timestamp
    Description

    Session expiry.

  • Name
    tier
    Type
    string
    Description

    anon_session, free_account or premium_account.

  • Name
    has_account
    Type
    boolean
    Description

    Whether the session is logged in to an account.

Request

GET
/v1/session
curl https://mail.nullnet.sh/v1/session \
  -H "Authorization: Bearer {token}"

Response

{
  "id": "019a2f4e-6b1c-7d20-9a3e-5c8f1b2d4e6a",
  "expires_at": "2026-11-25T08:00:00Z",
  "tier": "anon_session",
  "has_account": false
}

DELETE/v1/session

End the session

Revokes the token. Inboxes owned by an anonymous session keep running until they expire, but nobody can reach them any more. Returns 204.

Request

DELETE
/v1/session
curl -X DELETE https://mail.nullnet.sh/v1/session \
  -H "Authorization: Bearer {token}"

POST/v1/accounts

Register an account

Creates an account and returns its account code and recovery key, once.

  • With an anonymous session token, that session is promoted. It becomes logged in, keeps its token (token is null in the response), and its inboxes move to the account, up to the account's live-inbox limit. adopted_inboxes says how many moved.
  • Without a token, a new logged-in session is created and its token is returned.

Returns 201. Returns 409 conflict if the session is already logged in. Registration is rate limited per caller and per client network (5 per hour for anonymous callers).

Required attributes

  • Name
    password
    Type
    string
    Description

    12–128 characters, not a common password.

Optional attributes

  • Name
    referral_code
    Type
    string
    Description

    Used only when no session token is sent. A promoted session uses the code it was created with.

Response

  • Name
    account_code
    Type
    string
    Description

    XXXX-XXXX-XXXX-XXXX. Shown only here.

  • Name
    recovery_key
    Type
    string
    Description

    Grouped recovery key. Shown only here.

  • Name
    token
    Type
    string | null
    Description

    New bearer token, or null when the presenting session was promoted (keep using that token).

  • Name
    expires_at
    Type
    timestamp
    Description

    Session expiry.

  • Name
    tier
    Type
    string
    Description

    free_account for a new account.

  • Name
    adopted_inboxes
    Type
    integer
    Description

    Inboxes moved from the anonymous session into the account.

  • Name
    warning
    Type
    string
    Description

    A reminder that the code and key cannot be recovered.

Request

POST
/v1/accounts
curl https://mail.nullnet.sh/v1/accounts \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{"password": "correct horse battery staple"}'

Response

{
  "account_code": "7KQ2-M9XH-4TPB-R3DZ",
  "recovery_key": "5H8QZ-2MV7K-XR4TA-9PB3W-6NCJE-1DFGY-0SKMQ-7HXA",
  "token": null,
  "expires_at": "2026-11-25T08:00:00Z",
  "tier": "free_account",
  "adopted_inboxes": 2,
  "warning": "Store the account code and recovery key now. There is no email on file and no reset flow: if both are lost, the account cannot be recovered."
}

POST/v1/auth/login

Log in

Logs in with the account code and exactly one secret: the password or the recovery key.

If you send an anonymous session token, that session is promoted like at registration: it keeps its token and its inboxes move to the account. Otherwise a new token is returned.

An unknown account code, a wrong password and a wrong recovery key all return the same 401 invalid_credentials, in the same time. Attempts are limited to 10 per hour per caller and 10 per hour per account code.

Required attributes

  • Name
    account_code
    Type
    string
    Description

    The account code, in any accepted spelling.

Exactly one of

  • Name
    password
    Type
    string
    Description

    The password.

  • Name
    recovery_key
    Type
    string
    Description

    The recovery key, instead of the password.

Response

  • Name
    token
    Type
    string
    Description

    Bearer token: the one you sent if an anonymous session was promoted, otherwise a new one.

  • Name
    expires_at
    Type
    timestamp
    Description

    Session expiry.

  • Name
    tier
    Type
    string
    Description

    free_account or premium_account.

  • Name
    account_code
    Type
    string
    Description

    Masked account code.

  • Name
    adopted_inboxes
    Type
    integer
    Description

    Inboxes moved from the anonymous session into the account.

Request

POST
/v1/auth/login
curl https://mail.nullnet.sh/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"account_code": "7kq2m9xh4tpbr3dz", "password": "correct horse battery staple"}'

Response

{
  "token": "q3Zt9WbV1xYc0kE7rN2mH5uJ8fA4sDgPoLiK6aMnBvC",
  "expires_at": "2026-11-25T08:00:00Z",
  "tier": "free_account",
  "account_code": "****-****-****-R3DZ",
  "adopted_inboxes": 0
}

POST/v1/auth/logout

Log out

Revokes the current session. Returns 204. The account's inboxes are not affected.

Request

POST
/v1/auth/logout
curl -X POST https://mail.nullnet.sh/v1/auth/logout \
  -H "Authorization: Bearer {token}"

POST/v1/auth/logout-all

Log out everywhere

Revokes every session of the account, including this one. Takes effect at once. Returns 204. Needs a logged-in session.

Request

POST
/v1/auth/logout-all
curl -X POST https://mail.nullnet.sh/v1/auth/logout-all \
  -H "Authorization: Bearer {token}"

GET/v1/account

Get the account

Returns the logged-in account and the limits that apply to it. Use limits to show quotas in a UI instead of hard-coding them.

Response

  • Name
    account_code
    Type
    string
    Description

    Masked code, ****-****-****-XXXX.

  • Name
    tier
    Type
    string
    Description

    free_account or premium_account.

  • Name
    premium
    Type
    boolean
    Description

    Whether a paid plan is active.

  • Name
    created_at
    Type
    timestamp
    Description

    Registration time.

  • Name
    inboxes
    Type
    integer
    Description

    Live inboxes owned by the account.

  • Name
    limits
    Type
    object
    Description

    The resolved limits for this account. Field names match the limits table (concurrent_inboxes, inbox_retention_secs, messages_per_inbox, storage_bytes_per_inbox, max_message_bytes, search_per_min, ws_connections, webhooks, custom_domains, api_keys, and more). An optional quota is null when the feature is not offered. limits.paste holds the limits signed into paste passes.

Request

GET
/v1/account
curl https://mail.nullnet.sh/v1/account \
  -H "Authorization: Bearer {token}"

Response

{
  "account_code": "****-****-****-R3DZ",
  "tier": "free_account",
  "premium": false,
  "created_at": "2026-10-26T08:00:00Z",
  "inboxes": 2,
  "limits": {
    "api_requests_per_min": 120,
    "api_burst": 40,
    "inbox_create_per_hour": 30,
    "concurrent_inboxes": 5,
    "inbox_retention_secs": 86400,
    "messages_per_inbox": 200,
    "storage_bytes_per_inbox": 104857600,
    "max_message_bytes": 10485760,
    "search_per_min": 10,
    "ws_connections": 3,
    "ws_subscriptions_per_conn": 5,
    "webhooks": null,
    "custom_domains": null,
    "api_keys": null,
    "paste": {
      "max_size_bytes": 1048576,
      "max_ttl_secs": 86400,
      "max_files": 1,
      "live_storage_bytes": null,
      "uploads_per_min": 10,
      "dashboard": false,
      "open_alerts": false,
      "file_requests": false
    }
    // ...
  }
}

PATCH/v1/account

Change credentials

Changes the password, issues a new recovery key, or both. The current password is always required. A wrong one returns 401 invalid_credentials.

A password change logs out every other session of the account. A new recovery key replaces the old one, which stops working, and is shown only in this response.

Required attributes

  • Name
    current_password
    Type
    string
    Description

    The current password.

Optional attributes (at least one)

  • Name
    new_password
    Type
    string
    Description

    The new password (12–128 characters, not a common password).

  • Name
    rotate_recovery_key
    Type
    boolean
    Description

    Issue a new recovery key. Default false.

Response

  • Name
    password_changed
    Type
    boolean
    Description

    Whether the password changed.

  • Name
    recovery_key
    Type
    string | null
    Description

    The new recovery key, shown once, or null.

  • Name
    warning
    Type
    string | null
    Description

    Loss warning when a key was issued.

Request

PATCH
/v1/account
curl -X PATCH https://mail.nullnet.sh/v1/account \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{"current_password": "correct horse battery staple", "rotate_recovery_key": true}'

Response

{
  "password_changed": false,
  "recovery_key": "9DXM4-QW2KT-7HPAR-3BZN8-5CFJE-0GYVS-1MKTQ-6RXA",
  "warning": "Store the account code and recovery key now. There is no email on file and no reset flow: if both are lost, the account cannot be recovered."
}

DELETE/v1/account

Delete the account

Deletes the account at once. There is no grace period. Every inbox is deleted (its mail is purged shortly after), every session and API key is revoked, custom domains are removed, and both credentials are destroyed. Returns 204.

Required attributes

  • Name
    password
    Type
    string
    Description

    The current password.

Request

DELETE
/v1/account
curl -X DELETE https://mail.nullnet.sh/v1/account \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{"password": "correct horse battery staple"}'

Referrals

Every account has a public referral code: 4–16 lowercase letters and digits, safe to share. Generated codes are 8 characters. The share link is https://mail.nullnet.sh/r/<code>.

A referral is attributed once, when the referred account is created, and only for an active code of another live account. Pass the code as referral_code to POST /v1/session or POST /v1/accounts. A bad or unknown code never fails the signup; it is ignored.

The referrer earns 5 % of the referred account's crypto deposits as credit, for 12 months after the signup. A reward is paid only if the referrer has already deposited at least $5 themselves; a referee deposit credited before that earns nothing. One referred account can earn a referrer at most $50 in total. Rewards appear in the ledger as referral_reward. Referral stats show totals only, never a list of referred accounts.


GET/v1/referrals

Get referral stats

Returns your code, share link and totals. Needs a logged-in session.

Response

  • Name
    code
    Type
    string | null
    Description

    Active referral code.

  • Name
    share_url
    Type
    string | null
    Description

    https://mail.nullnet.sh/r/<code>.

  • Name
    signups
    Type
    integer
    Description

    Accounts attributed to you.

  • Name
    deposited_referees
    Type
    integer
    Description

    How many of those have deposited.

  • Name
    earned
    Type
    string
    Description

    Reward credit paid so far, in USD.

  • Name
    pending
    Type
    string
    Description

    Reward credit awaiting release, in USD.

  • Name
    reward_bps
    Type
    integer | null
    Description

    Your reward rate in basis points of referee deposits (500 = 5 %).

  • Name
    window_months
    Type
    integer
    Description

    How long after signup a referee's deposits earn rewards.

Request

GET
/v1/referrals
curl https://mail.nullnet.sh/v1/referrals \
  -H "Authorization: Bearer {token}"

Response

{
  "code": "k3v9qz2m",
  "share_url": "https://mail.nullnet.sh/r/k3v9qz2m",
  "signups": 4,
  "deposited_referees": 1,
  "earned": "0.500000000000000000",
  "pending": "0.000000000000000000",
  "reward_bps": 500,
  "window_months": 12
}

POST/v1/referrals/rotate

Rotate the referral code

Replaces your code with a new random one. The old code stops attributing at once. Rate limited per tier. Returns the same object as GET /v1/referrals.

Request

POST
/v1/referrals/rotate
curl -X POST https://mail.nullnet.sh/v1/referrals/rotate \
  -H "Authorization: Bearer {token}"

PUT/v1/referrals/code

Choose a vanity code

Sets a code of your choice. Premium accounts only; other tiers get 403 tier_required. The code is case-insensitive, 4–16 letters and digits, and must not be a reserved word such as admin or support. Returns 409 conflict if the code is taken. The old code stops attributing. Returns the same object as GET /v1/referrals.

Required attributes

  • Name
    code
    Type
    string
    Description

    The code you want.

Request

PUT
/v1/referrals/code
curl -X PUT https://mail.nullnet.sh/v1/referrals/code \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{"code": "alice"}'