Drops

A drop is one encrypted envelope stored under a random id. These endpoints create, inspect, open, delete and extend drops, report abuse, and tell a client its current limits. All paths are relative to https://paste.nullnet.sh.

The server never sees a key. Your client builds the envelope as described in envelope format and puts the key only in the link fragment. Times are Unix seconds. Every endpoint accepts an optional X-Null-Pass header with a NULL Premium pass; without one, or with an unusable one, you get the free limits and never an error. Errors are listed on the overview.

The drop model

What the server knows about a drop.

  • Name
    id
    Type
    string
    Description

    22 base64url characters (128 random bits). The /p/<id> part of the share link.

  • Name
    created_at
    Type
    integer
    Description

    When the drop was stored.

  • Name
    expires_at
    Type
    integer
    Description

    When the drop is deleted. Never extended by reads.

  • Name
    burn
    Type
    boolean
    Description

    Burn after reading: deleted on the first open.

  • Name
    views_left
    Type
    integer | null
    Description

    Opens left before deletion. null when unlimited.

  • Name
    size_bytes
    Type
    integer
    Description

    Envelope length in bytes.


POST/api/drops

Create a drop

Uploads an envelope. The body is the raw envelope bytes; send Content-Type: application/octet-stream. No Content-Length is needed: the size cap is counted on the bytes received. The server checks only the version byte, the minimum length and, for version 2, the Argon2id parameter bounds.

The body must arrive within 15 seconds. Premium envelopes over 1 MiB + 16 KiB may take up to 10 minutes.

Query parameters

  • Name
    ttl
    Type
    integer
    Description

    Required. Lifetime in seconds, one of your ttl_presets_secs from limits. Free: 600, 3600, 86400. Premium adds 604800 and 2592000.

  • Name
    burn
    Type
    boolean
    Description

    Optional, default false. One open, then the drop is deleted.

  • Name
    views
    Type
    integer
    Description

    Optional. Opens before deletion, from 1 to your max_views (free: 10). With burn=true, only 1 is accepted. Omit for unlimited opens.

Request headers

  • Name
    X-Null-Pass
    Type
    string
    Description

    Optional premium pass. A drop created with a premium pass is owned by that account and counts against its quota.

  • Name
    X-Zkbin-Alert-To
    Type
    string
    Description

    Optional, premium. A mailbox on mail.nullnet.sh that gets an email when the drop is opened. See open alerts.

Response

201 Created with the drop id, its expiry, the options as stored, and the manage token.

  • Name
    id
    Type
    string
    Description

    The drop id.

  • Name
    expires_at
    Type
    integer
    Description

    Expiry.

  • Name
    burn
    Type
    boolean
    Description

    Whether this is a burn drop.

  • Name
    views
    Type
    integer | null
    Description

    The view limit; 1 for a burn drop, null when unlimited.

  • Name
    manage_token
    Type
    string
    Description

    43 characters. Returned only in this response; the server keeps only its SHA-256. Needed to delete or extend the drop.

Build the share link as https://paste.nullnet.sh/p/<id>#k=<key>, adding &p=1 for a password-protected drop.

Errors

400 invalid_query, 400 invalid_options, 400 invalid_envelope, 400 body_read, 400 invalid_header, 403 tier_required, 408 body_timeout, 413 too_large, 429 quota_exceeded, 429 rate_limited, 503 storage_full.

Request

POST
/api/drops
curl -X POST "https://paste.nullnet.sh/api/drops?ttl=3600&burn=true" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @envelope.bin

Response

{
  "id": "0T4iUVyd4X1YUjpQK-PZVA",
  "expires_at": 1792597824,
  "burn": true,
  "views": 1,
  "manage_token": "1sPpm5EGeOu6g0wrlzdrP3eLOy2flm3IMem0Rq0_lxY"
}

GET/api/drops/{id}/meta

Get drop metadata

Returns what the server knows about a drop. Never consumes a view and never returns ciphertext, so it is safe to call when a page loads, for example to warn that the drop burns on open.

A drop can expire or be consumed between meta and open; open is authoritative.

Response

  • Name
    created_at
    Type
    integer
    Description

    When the drop was stored.

  • Name
    expires_at
    Type
    integer
    Description

    Expiry.

  • Name
    burn
    Type
    boolean
    Description

    Whether the next open deletes it.

  • Name
    views_left
    Type
    integer | null
    Description

    Opens left; null when unlimited.

  • Name
    size_bytes
    Type
    integer
    Description

    Envelope length in bytes.

Errors

404 not_found, 429 rate_limited.

Request

GET
/api/drops/{id}/meta
curl https://paste.nullnet.sh/api/drops/0T4iUVyd4X1YUjpQK-PZVA/meta

Response

{
  "created_at": 1792597224,
  "expires_at": 1792597824,
  "burn": true,
  "views_left": 1,
  "size_bytes": 94
}

POST/api/drops/{id}/open

Open a drop

Returns the envelope and consumes one view. The body is the raw envelope (application/octet-stream). Large on-disk envelopes are streamed with a Content-Length.

The view is consumed when the server reads the drop, before delivery is confirmed: a dropped connection can lose a burn drop. For a password-protected drop, fetch the envelope once and retry wrong passwords locally; a retry costs no view.

Response headers

  • Name
    X-Zkbin-Expires-At
    Type
    integer
    Description

    Expiry, Unix seconds.

  • Name
    X-Zkbin-Views-Left
    Type
    integer
    Description

    Opens left after this one. 0 means the drop is now deleted. Absent when unlimited.

Errors

404 not_found, 429 rate_limited, 503 unavailable.

Request

POST
/api/drops/{id}/open
curl -X POST -D - -o envelope.bin \
  https://paste.nullnet.sh/api/drops/0T4iUVyd4X1YUjpQK-PZVA/open

Response headers

HTTP/2 200
content-type: application/octet-stream
x-zkbin-expires-at: 1792597824
x-zkbin-views-left: 0

DELETE/api/drops/{id}

Delete a drop

Deletes a drop before it expires. Requires the drop's manage token as a bearer token: Authorization: Bearer <manage_token> (the scheme is case-sensitive).

Response

204 No Content when deleted.

Errors

401 missing_token (no well-formed bearer token), 403 wrong_token (another drop's token), 404 not_found, 429 rate_limited.

Request

DELETE
/api/drops/{id}
curl -X DELETE https://paste.nullnet.sh/api/drops/0T4iUVyd4X1YUjpQK-PZVA \
  -H "Authorization: Bearer 1sPpm5EGeOu6g0wrlzdrP3eLOy2flm3IMem0Rq0_lxY"

POST/api/drops/{id}/extend

Extend a drop

Sets the expiry to now + ttl. Shortening is allowed too. Requires the manage token as for delete, and accepts an optional X-Null-Pass.

ttl must be one of the applicable presets, and the new expiry must be at most created_at plus the longest of them. The applicable presets are your premium ones when your pass owns the drop (it was uploaded with a pass for the same account), and the free ones otherwise. So an anonymous drop never lives longer than 24 hours after creation, and a premium drop at most 30 days.

Query parameters

  • Name
    ttl
    Type
    integer
    Description

    Required. New lifetime from now, in seconds.

Response

  • Name
    expires_at
    Type
    integer
    Description

    The new expiry.

Errors

400 invalid_query, 400 invalid_options (not a preset, or past the maximum), 401 missing_token, 403 wrong_token, 404 not_found, 429 rate_limited.

Request

POST
/api/drops/{id}/extend
curl -X POST "https://paste.nullnet.sh/api/drops/0T4iUVyd4X1YUjpQK-PZVA/extend?ttl=86400" \
  -H "Authorization: Bearer 1sPpm5EGeOu6g0wrlzdrP3eLOy2flm3IMem0Rq0_lxY"

Response

{ "expires_at": 1792684224 }

POST/api/reports

Report a drop

Reports a drop for abuse. Anyone holding the link can. Reports are reviewed by the operator. You can also write to support@nullnet.sh.

The body is JSON (Content-Type: application/json, at most 8 KiB). Unknown members are rejected with 400 invalid_json. There is deliberately no member for the key: never send the #k= part of the link.

The answer is 202 Accepted with an empty body whether or not the drop still exists, so a report reveals nothing meta would not. Reports on one drop are aggregated and stay open for 30 days after the latest one. No client address is stored with a report.

Body

  • Name
    id
    Type
    string
    Description

    Required. The drop id from the link (/p/<id>).

  • Name
    reason
    Type
    string
    Description

    Required. One of illegal, malware, phishing, personal_data, spam, other.

  • Name
    note
    Type
    string
    Description

    Optional free text, trimmed, at most 1000 characters. No control characters other than newline and tab.

Errors

400 invalid_json, 400 invalid_report, 429 rate_limited (free: 5 per minute per IP), 503 reports_full.

Request

POST
/api/reports
curl -X POST https://paste.nullnet.sh/api/reports \
  -H "Content-Type: application/json" \
  -d '{"id":"0T4iUVyd4X1YUjpQK-PZVA","reason":"phishing","note":"fake bank login page"}'

Response

HTTP/2 202

GET/api/limits

Get limits

What the caller may do right now. Use it instead of hard-coding caps, and send your X-Null-Pass to see premium limits. Counts against the read rate limit.

Response

  • Name
    tier
    Type
    string
    Description

    free (anonymous, including any unusable pass) or premium.

  • Name
    max_envelope_bytes
    Type
    integer
    Description

    Upload cap on received envelope bytes. The plaintext may be up to this minus 16 KiB.

  • Name
    ttl_presets_secs
    Type
    integer[]
    Description

    The only ttl values create accepts.

  • Name
    max_views
    Type
    integer
    Description

    Highest views value.

  • Name
    max_files
    Type
    integer
    Description

    Entries per bundle. Enforced by the client, since the manifest is encrypted.

  • Name
    uploads_per_min
    Type
    integer
    Description

    Upload rate limit: per client IP when free, per account when premium.

  • Name
    reads_per_min
    Type
    integer
    Description

    Rate limit for meta, open, delete, extend and limits combined.

  • Name
    features
    Type
    string[]
    Description

    Premium features usable now: dashboard (the vault), open_alerts.

  • Name
    quota
    Type
    object | null
    Description

    Premium only: live_bytes, max_live_bytes, live_drops, max_live_drops for the account's live drops. null when free.

  • Name
    pass_expires_at
    Type
    integer | null
    Description

    Premium only: when the presented pass lapses. null when free.

Request

GET
/api/limits
curl https://paste.nullnet.sh/api/limits

Response

{
  "tier": "free",
  "max_envelope_bytes": 1064960,
  "ttl_presets_secs": [600, 3600, 86400],
  "max_views": 10,
  "max_files": 1,
  "uploads_per_min": 10,
  "reads_per_min": 60,
  "features": [],
  "quota": null,
  "pass_expires_at": null
}