API keys

API keys let a server or script use a premium account without a session token. A key acts for its account on the inbox, message and paste-pass endpoints, with its own rate limits. This page also covers paste passes, which let a NULL account carry its plan to paste.nullnet.sh.

Using a key

Send the key in the X-API-Key header:

Request with an API key

curl https://mail.nullnet.sh/v1/inboxes \
  -H "X-API-Key: grk_Xk3vQ9aB_s8Jd0Lm2Pq4Rt6Vx8Zb1Cd3Ef5Gh7Ij9Kl0Mn2Op4Qr"
  • A key is grk_, an 8-character prefix, _, and a 43-character secret (56 characters in all). The grk_<prefix> part is shown in listings so you can tell keys apart.
  • The full key is shown once, in the create response. The server stores only its SHA-256 hash.
  • A key can call: inboxes (/v1/inboxes…), messages (/v1/messages…, /v1/inboxes/:id/messages…, export) and paste passes. Inboxes created with a key belong to the account.
  • A key cannot call the session, account, billing, referral, webhook, custom-domain or API-key endpoints, and cannot open the WebSocket. Those return 401 unauthorized.
  • Requests with a key of a premium account use the premium_api_key limits, counted per key.
  • If a request carries both Authorization and X-API-Key, the bearer token is used.

Scopes

  • Name
    read
    Description

    GET and HEAD requests.

  • Name
    write
    Description

    Every other method: creating and deleting inboxes, marking messages read, deleting messages, minting paste passes.

A request outside the key's scopes returns 403 forbidden. A key without write can read mail but not change anything.


The API key model

  • Name
    id
    Type
    string
    Description

    Key id.

  • Name
    prefix
    Type
    string
    Description

    grk_ and the 8-character prefix, for display.

  • Name
    label
    Type
    string | null
    Description

    Your label.

  • Name
    scopes
    Type
    array
    Description

    read, write, or both.

  • Name
    created_at
    Type
    timestamp
    Description

    Creation time.

  • Name
    last_used_at
    Type
    timestamp | null
    Description

    Last use, to the minute.


POST/v1/api-keys

Create a key

Creates a key. Needs a logged-in session of a premium account (up to 10 live keys). The body is optional. Returns 201.

  • 400 bad_request: scopes is not a non-empty subset of read and write.
  • 402 payment_required: you are at your key limit. Revoke one first.
  • 403 tier_required: your tier has no API keys.

Optional attributes

  • Name
    label
    Type
    string
    Description

    Free text, up to 64 characters.

  • Name
    scopes
    Type
    array
    Description

    Subset of read, write. Both when omitted.

Response

  • Name
    key
    Type
    string
    Description

    The full key. Store it now.

  • Name
    api_key
    Type
    object
    Description

    The key's metadata.

Request

POST
/v1/api-keys
curl https://mail.nullnet.sh/v1/api-keys \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{"label": "ci signup tests", "scopes": ["read", "write"]}'

Response

{
  "key": "grk_Xk3vQ9aB_s8Jd0Lm2Pq4Rt6Vx8Zb1Cd3Ef5Gh7Ij9Kl0Mn2Op4Qr",
  "api_key": {
    "id": "019a2f80-6d7e-7f80-91a2-b3c4d5e6f708",
    "prefix": "grk_Xk3vQ9aB",
    "label": "ci signup tests",
    "scopes": ["read", "write"],
    "created_at": "2026-10-26T08:00:00Z",
    "last_used_at": null
  }
}

GET/v1/api-keys

List keys

Returns the account's live keys as an array. The secret is never returned.

Request

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

Response

[
  {
    "id": "019a2f80-6d7e-7f80-91a2-b3c4d5e6f708",
    "prefix": "grk_Xk3vQ9aB",
    "label": "ci signup tests",
    "scopes": ["read", "write"],
    "created_at": "2026-10-26T08:00:00Z",
    "last_used_at": "2026-10-26T08:12:00Z"
  }
]

DELETE/v1/api-keys/:id

Revoke a key

Revokes the key. It stops working at once. Returns 204, or 404 if there is no such live key.

Request

DELETE
/v1/api-keys/:id
curl -X DELETE https://mail.nullnet.sh/v1/api-keys/019a2f80-6d7e-7f80-91a2-b3c4d5e6f708 \
  -H "Authorization: Bearer {token}"

Paste passes

A paste pass is a short-lived signed token that tells NULL paste (paste.nullnet.sh) what a logged-in NULL account may do there. The mail API signs it with Ed25519. The paste service checks it offline against a pinned public key and applies the limits it carries. A pass grants nothing on the mail API itself. How the paste service uses it is described in Paste premium.

  • The token has the form np1.<payload>.<signature> (unpadded base64url) and is sent to the paste API in the X-Null-Pass header.
  • A pass is valid for one hour. It cannot be revoked; the short lifetime is the bound. Mint a new one when it expires.
  • The pass carries tier (free or premium), a per-account pseudonym sub that does not reveal the account, and the paste limits of your tier: max_size_bytes, max_ttl_secs, max_files, live_storage_bytes, uploads_per_min and a list of enabled features (dashboard, open_alerts, file_requests).
  • A lapsed premium subscription simply yields a free pass.

The current paste limits per tier:

LimitFree accountPremium
Largest paste or upload1 MiB100 MiB
Longest expiry24 hours30 days
Files per link120
Live storage per accountnone2 GiB
Uploads per minute1060
Featuresnonedashboard, open_alerts, file_requests

POST/v1/passes/paste

Mint a paste pass

Mints a pass for the calling account. Empty body. Authenticate with a logged-in session token, or with an API key that has the write scope.

  • 401 unauthorized: no credentials, an anonymous session, or a bad token.
  • 403 tier_required: your tier has no paste limits.
  • 403 forbidden: an API key without the write scope.
  • 429 rate_limited: the normal per-tier request limit.
  • 503 unavailable: passes are not configured on the server.

Response

  • Name
    pass
    Type
    string
    Description

    np1.…. Send it to the paste API as X-Null-Pass.

  • Name
    expires_at
    Type
    integer
    Description

    Pass expiry, Unix seconds.

Request

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

Response

{
  "pass": "np1.eyJ2IjoxLCJraWQiOiI3YTVmYmIzYWVmM2MwN2ZiIiwidGllciI6InByZW1pdW0i….tN_-Kw1IgUQR67BY6m4Z…",
  "expires_at": 1793005200
}

GET/v1/passes/public-key

Get the pass public key

The Ed25519 public key that verifies passes. No authentication. Returns 503 unavailable when passes are not configured.

Response

  • Name
    v
    Type
    integer
    Description

    Pass format version, 1.

  • Name
    alg
    Type
    string
    Description

    Always Ed25519.

  • Name
    kid
    Type
    string
    Description

    Key id: the first 8 bytes of SHA-256 of the public key, as 16 hex characters. Every pass names the kid it was signed with.

  • Name
    public_key
    Type
    string
    Description

    The raw 32-byte public key, unpadded base64url.

Request

GET
/v1/passes/public-key
curl https://mail.nullnet.sh/v1/passes/public-key

Response

{
  "v": 1,
  "alg": "Ed25519",
  "kid": "7a5fbb3aef3c07fb",
  "public_key": "q3q7Bv1_-GqCx8BX5dqJspYFpEVAzcR_3_2lBA3W68Y"
}