Docs

Nibbleful stores JSON records in Collections and bills prepaid credit per accepted API request. Rejected requests are free. Dashboard browsing and exports are free.

Concepts

Organization

The billing account. Prepaid balance, team members, and API keys belong to the organization.

Collection

A JSON data store with a stable public UUID. Create one per form, inbox, or product surface.

Record

One stored JSON object. The body must be an object, not an array. Keys starting with _nibbleful_ are reserved and not stored.

Prepaid balance

There are no monthly plans. Accepted API operations debit the organization balance. A 402 means the organization does not have enough credit yet.

First request

  1. 1. Add prepaid funds. Top up from Billing. The first accepted write costs $0.001 plus transfer if the request exceeds 1 MB.
  2. 2. Create a Collection. Copy its public UUID. Leave it active — a paused collection rejects writes with 403.
  3. 3. Pick an API. Use a private nib_sk_* key from your server to read and write. Use a public nib_pk_* token only for browser write-only ingest.
  4. 4. POST a JSON object. A successful create returns 201 with the stored record. Copy X-Nibbleful-Request-Id if anything fails — it matches a row in Activity.

Private API

Server-side only. Never put a nib_sk_* key in frontend code. Keys are granted per collection. A key with no collection permissions cannot read or write — the API returns collection_not_found. Permissions cannot be edited later; revoke the key and create a new one. The plaintext secret is shown once at creation.

Endpoints

POST https://nibbleful.com/api/v1/collections/:collection_id/records
GET  https://nibbleful.com/api/v1/collections/:collection_id/records
GET  https://nibbleful.com/api/v1/collections/:collection_id/records/:record_id

Auth

Authorization: Bearer nib_sk_test_...

WRITE is required to POST. READ is required to GET. Test tokens start with nib_sk_test_; production tokens start with nib_sk_live_.

Create

curl -X POST https://nibbleful.com/api/v1/collections/COLLECTION_ID/records \
  -H "Authorization: Bearer nib_sk_..." \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","source":"website"}'

Success body

{
  "id": "01j...",
  "created_at": "2026-09-17T20:00:00.000Z",
  "data": { "email": "[email protected]", "source": "website" }
}

List

Newest first. limit defaults to 50 and maxes at 100. Pass a record id as before or after to page.

{
  "data": [ { "id": "...", "created_at": "...", "data": {} } ],
  "pagination": { "next": "01j...", "previous": null }
}

Public ingest

Write-only ingest from the browser. Enable it on the collection, then send X-Nibbleful-Token with the collection’s nib_pk_* token. The token is not a secret and is not authentication by itself. It cannot read, query, or delete data.

Endpoint

POST https://nibbleful.com/public/v1/collections/:collection_id/records

CORS allows POST and OPTIONS. Allowed request headers: Content-Type, Accept, X-Nibbleful-Token, X-Nibbleful-Turnstile-Token.

Browser example

const response = await fetch(
  "https://nibbleful.com/public/v1/collections/COLLECTION_ID/records",
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Nibbleful-Token": "nib_pk_test_..."
    },
    body: JSON.stringify({
      email: "[email protected]"
    })
  }
);

Public ingest checks run in this order: collection exists, ingest enabled, token valid, not paused, payload size, JSON object, origin, rate limit, Turnstile, honeypot, duplicate, daily spend, then balance. A later failure can still happen after an earlier check has already recorded state — see duplicate protection below.

Collection states

State API behavior
Active Reads and writes are accepted when auth, scope, and balance allow it.
Paused Private reads/writes and public ingest return 403 collection_paused. Records stay. Resume from Settings.
Archived / deleted Returns 404 collection_not_found. Stored payloads are erased. Billing history is kept.

Public ingest protections

These apply only to /public/v1. Rejected requests are free. Configure them on the collection Settings tab.

Control Default What it does
Allowed origins Empty If set, the request Origin must match exactly. Origin is not authentication and can be spoofed outside a browser.
Turnstile Off Requires a Cloudflare Turnstile widget token in X-Nibbleful-Turnstile-Token.
Honeypot Off If a client sends a non-empty _nibbleful_hp field, the request is rejected. Keep that field empty in real forms.
Duplicate protection On Fingerprints collection + JSON body + client IP for 60 seconds and returns 409 duplicate on repeats. The fingerprint is claimed when the check runs, even if a later check such as balance fails. Wait, change the body, or turn this off to retry the same payload.
Rate limits Always on 10 requests per IP per collection per 60 seconds, and 60 requests per collection per 60 seconds.
Daily spend limit Optional Caps public ingest charges for the collection. Private API writes are not affected.

Pricing

  • Accepted API requests cost $0.001.
  • The first 1 MB of transfer (bytes in + bytes out) is included per accepted request. Each started additional MB is $0.001.
  • Storage is $1/GB-month based on retained byte-time, billed monthly per collection, rounded down to $0.001.
  • Rejected requests, dashboard browsing, and exports are not billed as API usage.
  • Top-ups are $5–$100. Optional auto top-up charges a saved card when balance is low; the credit appears after Stripe confirms.

Errors

Failures return JSON. Read error.code, not only the HTTP status — several protections share 403.

{
  "error": {
    "code": "collection_paused",
    "message": "This collection is paused."
  }
}
HTTP Code API What to do
401 missing_api_key

The API key is missing.

Private Send Authorization: Bearer nib_sk_... on private API requests.
401 invalid_api_key

The supplied API key is invalid.

Private The secret does not match any key. Secrets are shown only once at creation.
401 revoked_api_key

The supplied API key has been revoked.

Private Create a new key. Revoked keys cannot be restored.
404 collection_not_found

The collection was not found or this API key does not have access to it.

Both The collection UUID is wrong, the collection is archived, or this private key is not granted access. The API does not distinguish those cases.
403 insufficient_scope

The API key does not have permission for this operation.

Private The key exists but lacks READ or WRITE on this collection. Revoke it and create a new key with the right permissions.
403 collection_paused

This collection is paused.

Both Resume the collection on its Settings tab. Pause rejects private reads/writes and public ingest. Existing records stay in place.
400 invalid_payload

The request body must be a JSON object.

Both The body must be a JSON object, for example {"email":"[email protected]"}. Arrays, empty bodies, and invalid JSON are rejected.
413 payload_too_large

The request payload exceeds the 25 MB limit.

Both Request bodies are capped at 25 MB.
404 record_not_found

The record was not found.

Private The record UUID is wrong, or the record was deleted.
402 insufficient_balance

The organization does not have enough prepaid balance.

Both Top up prepaid credit before retrying. Rejected requests are free. Public ingest may attempt auto top-up if it is enabled, but the credit lands only after Stripe confirms, so this request still returns 402.
403 public_ingest_disabled

Public ingest is not enabled for this collection.

Public Enable public ingest on the collection. A public token is required.
401 invalid_public_token

The supplied public ingest token is invalid.

Public Use the current nib_pk_* token in X-Nibbleful-Token. Rotating the token invalidates the previous one.
403 origin_rejected

The request origin is not allowed.

Public The Origin header is not on the collection allowlist. Leave the allowlist empty to skip origin checks.
429 rate_limited

Too many requests. Try again later.

Public Public ingest allows 10 requests per IP per collection per 60 seconds, and 60 requests per collection per 60 seconds. Retry-After is set on 429 responses.
403 turnstile_missing

A Turnstile token is required.

Public This collection requires Cloudflare Turnstile. Send the widget token in X-Nibbleful-Turnstile-Token.
403 turnstile_failed

The request could not be verified.

Public The Turnstile token was missing, invalid, or could not be verified. Get a fresh widget token and retry.
403 honeypot

The request was rejected.

Public A reserved honeypot field was filled in. Keep _nibbleful_hp empty in real browser forms.
409 duplicate

This request was already received.

Public The same JSON body was already seen from this IP for this collection in the last 60 seconds. The fingerprint is claimed when that check runs, even if a later check such as insufficient balance rejects the request. Wait, change the payload, or disable duplicate protection.
403 daily_spend_limit_reached

This collection has reached its daily spend limit.

Public This collection's public ingest daily spend cap was reached. Private nib_sk_* writes are not affected.
500 internal_error

An internal error occurred.

Both The request was not accepted. Check Activity for the request id in X-Nibbleful-Request-Id.

Debugging

  • Every API response includes X-Nibbleful-Request-Id. Open Activity and search that id.
  • Activity shows outcome, rejection reason, and public ingest protection results (token, origin, Turnstile, honeypot, duplicate, spend cap).
  • If the browser shows 403, check whether the collection is paused before assuming the public token is wrong.
  • If you see 402 then 409 on the same body, add funds and wait 60 seconds or change the payload. Duplicate protection already claimed the first attempt.
  • Dashboard record lists and CSV/JSON exports do not consume API credit.