REST API

Neatbase provides a REST API that lets you read, write, and manage notes and fields in your notebooks programmatically. Use it to build integrations, automate workflows, sync with external services, or power dashboards from your Neatbase data.

The API uses a simple JSON format with field names as keys — no internal IDs to deal with. Changes made via the API sync to the Neatbase app automatically, and changes made in the app are available via the API.

Getting Started

Prerequisites

  • A Neatbase Pro subscription

Enable API Access

  1. Open your notebook in Neatbase
  2. Open the ⋯ (More) menu in the toolbar
  3. Select API...
  4. Choose Enable API

Neatbase will generate an API key and a documentation URL for your notebook.

  • API Key — A 64-character token used to authenticate all API requests. Keep it secret.
  • Docs URL — A personalized documentation page showing your notebook's actual field names, types, and curl examples.

Enabling the API changes the notebook's encryption. To answer API requests, the server needs to read your data — so the encryption key is stored on the server for as long as the API is on. See Security before enabling it on a notebook holding sensitive data.

Disable API Access

To revoke API access, open the API panel from the ⋯ (More) menu and choose Disable API. The API key is immediately invalidated — all subsequent requests will fail with a 401 error.


Authentication

All API requests require a Bearer token in the Authorization header:

curl -H "Authorization: Bearer YOUR_API_KEY" \
  https://cloud.neatbase.com/api/v1/notes

Replace YOUR_API_KEY with the key shown in your notebook's API panel.

Scenario Response
Missing or invalid API key 401 Unauthorized
API was disabled by the notebook owner 401 Unauthorized
The notebook is no longer shared 410 Gone

Base URL

All endpoints are relative to:

https://cloud.neatbase.com/api/v1

Endpoints

Get Schema

Retrieve your notebook's field definitions — useful for understanding the structure before reading or writing notes.

GET /v1/schema

Example:

curl -H "Authorization: Bearer YOUR_API_KEY" \
  https://cloud.neatbase.com/api/v1/schema

Response:

{
  "notebook_name": "Contacts",
  "fields": [
    {
      "id": "550E8400-E29B-41D4-A716-446655440000",
      "name": "Name",
      "type": "shortText",
      "order": 0
    },
    {
      "id": "6BA7B810-9DAD-11D1-80B4-00C04FD430C8",
      "name": "Email",
      "type": "email",
      "order": 1
    },
    {
      "id": "7C9E6679-7425-40DE-944B-E07FC1F90AE7",
      "name": "Amount",
      "type": "currency",
      "order": 2,
      "config": {
        "currencyCode": "USD"
      }
    },
    {
      "id": "A1B2C3D4-E5F6-7890-ABCD-EF1234567890",
      "name": "Priority",
      "type": "tags",
      "order": 3,
      "config": {
        "availableTags": "Low,Medium,High"
      }
    },
    {
      "id": "B2C3D4E5-F6A7-8901-BCDE-F12345678901",
      "name": "Total",
      "type": "formula",
      "order": 4,
      "config": {
        "formula": "{Amount} * {Quantity}"
      },
      "read_only": true
    }
  ]
}

Fields marked read_only: true (formula, image) can be read but not written. Button fields read and write differently from each other — see Button fields.


List Fields

Retrieve all field definitions for the notebook.

GET /v1/fields

Example:

curl -H "Authorization: Bearer YOUR_API_KEY" \
  https://cloud.neatbase.com/api/v1/fields

Response:

{
  "fields": [
    {
      "id": "A1B2C3D4-E5F6-7890-ABCD-EF1234567890",
      "name": "Name",
      "type": "shortText",
      "order": 0
    },
    {
      "id": "B2C3D4E5-F6A7-8901-BCDE-F12345678901",
      "name": "Amount",
      "type": "currency",
      "order": 1,
      "config": {
        "currencyCode": "USD"
      }
    }
  ]
}

Get Field

Retrieve a single field definition by ID.

GET /v1/fields/{id}

Example:

curl -H "Authorization: Bearer YOUR_API_KEY" \
  https://cloud.neatbase.com/api/v1/fields/A1B2C3D4-E5F6-7890-ABCD-EF1234567890

Returns 404 if the field doesn't exist.


List Notes

Retrieve notes with pagination, sorting, and filtering.

GET /v1/notes

Query Parameters:

Parameter Type Default Description
page integer 1 Page number (minimum 1)
per_page integer 100 Items per page (1–100)
sort string created_at Sort by created_at, updated_at, or a field name
order string desc Sort direction: asc or desc
filter string Filter in the format FieldName:operator:value
filter[] string[] Multiple filters (ANDed together)

Basic Example:

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://cloud.neatbase.com/api/v1/notes?page=1&per_page=50"

Response:

{
  "notes": [
    {
      "id": "550E8400-E29B-41D4-A716-446655440000",
      "app_url": "neatbase://note/00000000-0000-0000-0000-000000000000/550E8400-E29B-41D4-A716-446655440000",
      "delete_protected": false,
      "avatar_url": null,
      "fields": {
        "Name": "Jane Doe",
        "Email": "jane@example.com",
        "Amount": 42.50,
        "Priority": ["High"],
        "Total": null
      },
      "created_at": "2026-04-05 12:00:00",
      "updated_at": "2026-04-05 14:30:00"
    }
  ],
  "total": 42,
  "page": 1,
  "per_page": 50,
  "total_pages": 1
}

The updated_at field reflects when the note was last edited (not the sync timestamp). Deleted notes are excluded.

Sorting

Sort by any field name, created_at, or updated_at:

# Oldest notes first
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://cloud.neatbase.com/api/v1/notes?sort=created_at&order=asc"

# Sort by a field value
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://cloud.neatbase.com/api/v1/notes?sort=Amount&order=desc"

Notes with null values for the sort field appear last regardless of sort direction.

Filtering

Filter notes by field values using the filter parameter. The format is FieldName:operator:value.

# Single filter
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://cloud.neatbase.com/api/v1/notes?filter=Name:contains:Jane"

# Multiple filters (ANDed)
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://cloud.neatbase.com/api/v1/notes?filter[]=Amount:gte:100&filter[]=Priority:contains:High"

# Combined with sorting
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://cloud.neatbase.com/api/v1/notes?filter=Amount:gt:0&sort=Amount&order=desc"

Filter Operators:

Operator Applies To Needs Value Description
eq text, number, date, toggle Yes Equals (case-insensitive for text)
neq text, number, date Yes Not equals
contains text, tags Yes Substring match (text) or has tag (tags)
gt number, date Yes Greater than
lt number, date Yes Less than
gte number, date Yes Greater than or equal
lte number, date Yes Less than or equal
is_empty all No Field is null or empty
is_not_empty all No Field has a value

Type compatibility — which operators a field accepts depends on the category it falls into:

Category Field types
text shortText, longText, email, phone, url, reference, select, barcode, qrCode, color
number number, currency, rating, duration, percent, formula
date date
toggle toggle
tags tags

Toggle filtering: Use eq:true or eq:false (the value is a string in the URL).

Tags filtering: contains checks if the tag array includes the specified tag (case-insensitive). For example, Priority:contains:High matches notes where the Priority tags include "High".

An invalid filter returns 400 and names the problem:

{
  "error": "Unknown filter operator: \"like\". Supported: eq, neq, contains, gt, lt, gte, lte, is_empty, is_not_empty",
  "code": "invalid_request"
}

Get Note

Retrieve a single note by its ID.

GET /v1/notes/{id}

Example:

curl -H "Authorization: Bearer YOUR_API_KEY" \
  https://cloud.neatbase.com/api/v1/notes/550E8400-E29B-41D4-A716-446655440000

Response:

{
  "id": "550E8400-E29B-41D4-A716-446655440000",
  "app_url": "neatbase://note/00000000-0000-0000-0000-000000000000/550E8400-E29B-41D4-A716-446655440000",
  "delete_protected": false,
  "avatar_url": null,
  "fields": {
    "Name": "Jane Doe",
    "Email": "jane@example.com",
    "Amount": 42.50,
    "Priority": ["High"]
  },
  "created_at": "2026-04-05 12:00:00",
  "updated_at": "2026-04-05 14:30:00"
}

Returns 404 if the note doesn't exist or has been deleted.

app_url — Deep link into the Neatbase app

Every note response includes an app_url field with a neatbase://... deep link. Opening it on a device that has the Neatbase app installed (and is signed in to a participant device for this shared notebook) opens the note directly. Useful for linking back to a note from a chat message, a ticket, or your own tooling. The link works for any participant, on any of their devices.

delete_protected — Prevent deletion in the Neatbase app

Every note response also includes a top-level delete_protected boolean. When true, the Neatbase app disables note deletion for this note on every participant's device. Useful for notes that shouldn't be deleted by accident — a summary row a dashboard depends on, or a record another system references. Deleting the whole notebook still removes it.

Set or clear it with POST (on create) or PUT (on update) — see the next two sections. The flag is API-only: there is no in-app UI to toggle it. Editing the note's field values, locking via API, duplicating, opening in detail view, etc. all still work — only the delete entry points are gated.

This only hides the delete controls in the app — it is not an access control. Anyone holding your API key can still DELETE /v1/notes/{id} directly, or clear the flag and then delete.

avatar_url — The note's avatar image

Every note response includes a top-level avatar_url: a link to the note's avatar image, or null when no image has been uploaded. Fetching it requires the same Authorization header as any other request.

null doesn't mean the app shows nothing. When a notebook has Show note avatars turned on, notes without an uploaded image fall back to the site icon for their email address or first link, and then to the note's initials. Those are rendered on the device, so there's nothing for the API to link to. See Notebooks for the full behavior.

Avatars are set and cleared with their own endpoints — see Set Note Avatar below — not through the note body.


Create Note

Create a new note with field values.

POST /v1/notes

Request Body:

{
  "fields": {
    "Name": "John Smith",
    "Email": "john@example.com",
    "Amount": 99.00,
    "Priority": ["Medium", "High"]
  },
  "delete_protected": false
}

Example:

curl -X POST \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"fields": {"Name": "John Smith", "Email": "john@example.com", "Amount": 99.00}}' \
  https://cloud.neatbase.com/api/v1/notes

Response (201 Created):

{
  "id": "A1B2C3D4-E5F6-7890-ABCD-EF1234567890",
  "app_url": "neatbase://note/00000000-0000-0000-0000-000000000000/A1B2C3D4-E5F6-7890-ABCD-EF1234567890",
  "delete_protected": false,
  "avatar_url": null,
  "fields": {
    "Name": "John Smith",
    "Email": "john@example.com",
    "Amount": 99.00,
    "Priority": ["Medium", "High"]
  },
  "created_at": "2026-04-05 15:30:00",
  "updated_at": "2026-04-05 15:30:00"
}

Behavior:

  • All fields are optional. Omitted fields are stored as null.
  • Unknown field names return 400 Bad Request.
  • Read-only fields (formula, image) are silently ignored. Button fields require an object write (see Field Types table) — booleans, strings, and other shapes return 400.
  • The note ID is generated server-side.
  • All field values are validated before writing. If any validation fails, nothing is saved.
  • delete_protected is optional and defaults to false. Set it to true to prevent the note from being deleted in the Neatbase app (see the "delete_protected" section above).

Update Note

Update specific fields on an existing note. This is a partial update — only the fields you include are changed; all other fields are preserved.

PUT /v1/notes/{id}

Request Body:

{
  "fields": {
    "Amount": 149.00,
    "Priority": ["High"]
  },
  "delete_protected": true
}

fields is optional — a request body with only delete_protected is valid (and leaves all field values untouched). Likewise, omitting delete_protected preserves the existing value.

Example:

curl -X PUT \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"fields": {"Amount": 149.00}}' \
  https://cloud.neatbase.com/api/v1/notes/550E8400-E29B-41D4-A716-446655440000

# Lock a note from in-app deletion without changing any field values
curl -X PUT \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"delete_protected": true}' \
  https://cloud.neatbase.com/api/v1/notes/550E8400-E29B-41D4-A716-446655440000

# Unlock it again
curl -X PUT \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"delete_protected": false}' \
  https://cloud.neatbase.com/api/v1/notes/550E8400-E29B-41D4-A716-446655440000

Response (200 OK):

The complete updated note is returned (same format as Get Note).

Returns 404 if the note doesn't exist.


Delete Note

Soft-delete a note. The note is marked as deleted and will no longer appear in list or get endpoints.

DELETE /v1/notes/{id}

Example:

curl -X DELETE \
  -H "Authorization: Bearer YOUR_API_KEY" \
  https://cloud.neatbase.com/api/v1/notes/550E8400-E29B-41D4-A716-446655440000

Response (200 OK):

{
  "id": "550E8400-E29B-41D4-A716-446655440000",
  "deleted": true
}

Returns 404 if the note doesn't exist or has already been deleted.


Set Note Avatar

Upload or replace a note's avatar — the circular image shown beside the note's title in the app.

POST /v1/notes/{id}/avatar

Send the image as multipart/form-data in a field named avatar. Accepts JPEG, PNG, GIF, and WebP, up to 10 MB. The type is detected from the file's contents, not its name.

Very high-resolution images are rejected even when they're under the size limit — an avatar is displayed at a small size, so resize before uploading if you hit that. Animated images aren't supported; send a still frame.

Example:

curl -X POST \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "avatar=@/path/to/photo.jpg" \
  https://cloud.neatbase.com/api/v1/notes/550E8400-E29B-41D4-A716-446655440000/avatar

Response (200 OK):

{
  "id": "550E8400-E29B-41D4-A716-446655440000",
  "avatar_url": "https://cloud.neatbase.com/api/v1/images/share_AbC123_9f8e7d6c5b4a3210.jpg"
}

The image is cropped to a square from the center and resized to 256×256 — the same treatment the app applies — so send a square image if the framing matters. The result reaches every participant's device on their next sync.

Uploading replaces any existing avatar. Returns 400 if the field is missing, the file isn't a supported image, or it exceeds 10 MB.


Delete Note Avatar

Remove a note's avatar.

DELETE /v1/notes/{id}/avatar

Example:

curl -X DELETE \
  -H "Authorization: Bearer YOUR_API_KEY" \
  https://cloud.neatbase.com/api/v1/notes/550E8400-E29B-41D4-A716-446655440000/avatar

Response (200 OK):

{
  "id": "550E8400-E29B-41D4-A716-446655440000",
  "avatar_url": null
}

The removal propagates to every participant. The app then falls back to the note's site icon or initials, as described under avatar_url in Get Note.

Avatars only appear when the notebook has Show note avatars enabled in its settings — uploading one while that's off stores the image without displaying it.


Field Management

Manage your notebook's field schema programmatically — add, rename, reorder, or remove fields without opening the app.

Important: A field's type cannot be changed after creation. To change the type, delete the field and create a new one.

Create Field

POST /v1/fields

Request Body:

{
  "name": "Amount",
  "type": "currency",
  "order": 3,
  "config": {
    "currencyCode": "USD"
  }
}
Parameter Required Description
name Yes Field name (max 40 characters, must be unique within the notebook)
type Yes One of the valid field types (see table below)
order No Position in the field list (defaults to end)
showInList No Whether the field's value shows as a subtitle line under the note title in the app's note list. Defaults to true.
config Depends Required for types that need configuration

Types that require config:

Type Config Key Example
currency currencyCode {"currencyCode": "USD"}
tags availableTags {"availableTags": "Low,Medium,High"}
select availableOptions {"availableOptions": "Option A,Option B,Option C"}
formula formula {"formula": "{Price} * {Qty}"}
reference notebookIds {"notebookIds": "NOTEBOOK-UUID,OTHER-UUID"} (optional; comma-separated. The legacy single notebookId is still read and written for compatibility)
reference notebookShareIds App-managed — comma-separated share IDs, index-aligned with notebookIds, written when a target notebook is shared so other participants can resolve it. If you change notebookIds, omit this key and let the app rebuild it; the server drops it whenever its slot count doesn't match notebookIds, since a misaligned echo would make targets resolve to the wrong notebook.
reference, url allowMultiple {"allowMultiple": "true"} (optional — makes the field hold a LIST of values; see Multi-value fields)

Example:

curl -X POST \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Amount", "type": "currency", "config": {"currencyCode": "USD"}}' \
  https://cloud.neatbase.com/api/v1/fields

Response (201 Created):

{
  "id": "A1B2C3D4-E5F6-7890-ABCD-EF1234567890",
  "name": "Amount",
  "type": "currency",
  "order": 3,
  "config": {
    "currencyCode": "USD"
  }
}

Valid types for creation: shortText, longText, email, phone, url, number, currency, rating, formula, date, toggle, tags, select, reference, checklist, image, comments, color, duration, percent, barcode, qrCode, button

Layout types (sectionDivider, sectionHeading, sectionDescription) and secretText cannot be created via the API.


Update Field

Update a field's name, order, or config. This is a partial update — only the properties you include are changed.

PUT /v1/fields/{id}

Request Body (all optional, at least one required):

{
  "name": "New Name",
  "order": 5,
  "config": {
    "currencyCode": "EUR"
  }
}

Example:

curl -X PUT \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Price", "order": 2}' \
  https://cloud.neatbase.com/api/v1/fields/FIELD-UUID

Response (200 OK):

showInList is accepted here too. Omitting it preserves the field's current value, so a partial update that only changes the name or config can't flip the field's visibility by accident.

The complete updated field is returned (same format as Create Field).

Notes:

  • Including type in the request body returns 400 Bad Request — type is immutable.
  • Field names must be unique across all fields in the notebook.
  • Returns 404 if the field doesn't exist.

Rename Tags

Rename one or more tags on a tags field — updating the field's tag list and rewriting the tag on every note that carries it.

PUT /v1/fields/{id}

Use rename_tags instead of rewriting config.availableTags. A config rewrite can't tell a rename apart from a delete-and-add, so the old tag would stay behind on your notes; rename_tags states the intent, and the server migrates everything in one step.

Request Body:

{
  "rename_tags": {
    "Old Name": "New Name"
  }
}

Example:

curl -X PUT \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"rename_tags": {"Urgent": "Critical"}}' \
  https://cloud.neatbase.com/api/v1/fields/FIELD-UUID

Response (200 OK): the updated field, plus migrated_values — how many notes had the tag rewritten:

{
  "id": "FIELD-UUID",
  "name": "Tags",
  "type": "tags",
  "order": 3,
  "config": { "availableTags": "Critical,Blocked" },
  "migrated_values": 7
}

Notes:

  • Only valid on tags fields.
  • Old names are matched against the configured tags case-insensitively; tag colors are preserved.
  • New names can't contain , or :, and can't collide with another existing tag (renaming onto an existing tag — a merge — isn't supported).
  • Can't be combined with config in the same request. name and order are fine.
  • Safe to retry: re-sending a rename that already applied is a no-op.
  • Devices pick up the change on their next sync, like any other API edit.

Rename Select Options

The same operation exists for select fields as rename_options — it updates the field's option list and rewrites the option on every note that holds it, with the same rules and the same migrated_values response. The one difference: a select option may contain : (only , is reserved).

curl -X PUT \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"rename_options": {"In Progress": "Doing"}}' \
  https://cloud.neatbase.com/api/v1/fields/FIELD-UUID

Delete Field

Soft-delete a field and all its values across every note in the notebook.

DELETE /v1/fields/{id}

Example:

curl -X DELETE \
  -H "Authorization: Bearer YOUR_API_KEY" \
  https://cloud.neatbase.com/api/v1/fields/FIELD-UUID

Response (200 OK):

{
  "id": "A1B2C3D4-E5F6-7890-ABCD-EF1234567890",
  "deleted": true
}

Returns 404 if the field doesn't exist or has already been deleted.

Warning: Deleting a field permanently removes all data stored in that field across all notes. This cannot be undone via the API.


Field Types

The API uses the same field types as the Neatbase app. Each type has a specific JSON representation:

Type JSON Type Read Write Notes
shortText string Yes Yes Single-line text
longText string Yes Yes Multi-line text
email string Yes Yes Email address
phone string Yes Yes Phone number
url string, or array when multiple Yes Yes Web URL. See Multi-value fields when the field allows several.
number number Yes Yes Numeric value
currency number Yes Yes Monetary amount (see schema for currency code)
rating integer Yes Yes Integer from 1 to 5
date string Yes Yes Format depends on field config. Date-only fields: YYYY-MM-DD. Fields with "Include time" enabled: ISO 8601 datetime in UTC (e.g., 2026-05-17T15:00:00Z). See "Date Fields" under Validation Rules below for accepted write formats.
toggle boolean Yes Yes true or false
tags array Yes Yes Array of strings, e.g. ["Tag1", "Tag2"]
select string Yes Yes One of the field's configured options (or null/"" to clear). Strict validation.
checklist array Yes Yes Array of objects: [{"id": "...", "text": "...", "checked": true}]
comments array Yes Yes Array of {id, date, author, content} objects. Supports {"append": "text"} and {"delete": "id"} shortcuts.
reference string, or array when multiple Yes Yes UUID of a note in one of the referenced notebooks (or null/"" to clear). Must be UUID-shaped, else 400. Existence is not verified — see Reference Fields under Validation Rules.
duration number Yes Yes Total seconds (e.g., 5400 = 1h 30m)
percent number Yes Yes Number from 0 to 100
color string Yes Yes Hex color (e.g., #FF5733)
barcode string Yes Yes Barcode string
qrCode string Yes Yes QR code string
image string Yes No Returns an image URL (read-only)
formula null Yes No Always null — computed on device (read-only)
button string (read) / object (write) Yes Yes A resolved URL when read, an object when written — see Button fields
secretText No No Excluded from API entirely
sectionDivider No No Layout element, excluded from API
sectionHeading No No Layout element, excluded from API
sectionDescription No No Layout element, excluded from API

Button fields

Button fields behave differently on read and write, so they're worth their own note.

Reading returns the resolved URL as a string — the per-note override if one is set, otherwise the field's default template — with {NOTE_ID} and {NOTEBOOK_ID} filled in.

You get null instead when any of these is true:

  • the button is disabled on that note,
  • the template is empty, or
  • the template is exactly #.

That last one is a deliberate placeholder: set a field's default URL to # and every note's button stays inert until you give it a real one. It wins over the enabled flag.

{NOTEBOOK_ID} resolves to the share ID — the short identifier in your notebook's documentation URL (/d/{shareId}) and share links — not the notebook UUID you'd see inside the app. The app substitutes its own local UUID there, so the same template resolves differently depending on which side fills it in.

Writing takes an object. Both keys are optional, and a partial write merges with whatever is already set:

{
  "fields": {
    "Open Invoice": { "url": "https://billing.example.com/{NOTE_ID}", "enabled": true }
  }
}

Sending null for the field clears both the URL override and the enabled flag, returning the note to the field's default.

To change the default URL for every note at once, update the field itself with PUT /v1/fields/{id} — that's a field setting, not a per-note value.

Validation Rules

When creating or updating notes, field values are validated based on their type. Invalid values return a 400 Bad Request error.

Text Fields

Types: shortText, longText, email, phone, url, barcode, qrCode, color

"Name": "Jane Doe"
"Name": null

Must be a string or null. Any other type (number, boolean, array) is rejected.

Multi-value fields

Types: reference, url

Reference and URL fields can be switched, per field, to hold a list of values instead of one. The field's schema entry then carries "multiple": true:

{ "name": "Links", "type": "url", "multiple": true }

Check that flag rather than guessing from a note's data — the shape follows the field's configuration, not what happens to be stored:

  • Reading, a multiple field is always an array, even when it holds zero or one value. Fields without the flag still return a plain string or null, exactly as before.
  • Writing, a multiple field accepts either shape — a bare string for one value, or an array for several — so existing code keeps working when the flag gets turned on.
  • Writing an array to a field that is NOT multiple returns 400. The first element isn't silently kept.
"Links": ["https://example.com/pricing", "https://example.com/docs"]
"Links": "https://example.com/pricing"
"Links": null

Send null or [] to clear the field. Array elements must be strings (URL) or UUID-shaped strings (Reference). Duplicate Reference UUIDs in one array are collapsed to the first occurrence.

To turn the flag on for a field, set allowMultiple in its config:

curl -X PUT https://cloud.neatbase.com/api/v1/fields/FIELD-UUID \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"config": {"allowMultiple": "true"}}'

Note: filter= and sort= operate on the stored value as text, so eq will not match a single value inside a multi-value field. Use contains there.

Reference Fields

Types: reference

"Client": "A1B2C3D4-E5F6-7890-ABCD-EF1234567890"
"Client": ["A1B2C3D4-E5F6-7890-ABCD-EF1234567890", "B1B2C3D4-E5F6-7890-ABCD-EF1234567890"]
"Client": null

Must be a UUID-shaped string, or null/"" to clear. Anything else returns 400. Lowercase UUIDs are accepted and normalized to uppercase. A field with allowMultiple accepts an array of UUIDs — see Multi-value fields.

Existence is not verified. A reference points at a note in a different notebook — which is a different share, or one that was never uploaded to the server at all. The API has no access to that notebook's notes, so it cannot confirm the UUID resolves to anything. A syntactically valid UUID that points nowhere is stored as-is and displays in the app as "Unknown note".

To get a valid UUID, call GET /v1/notes against the referenced notebook's own API and use the id of the note you want to link to. That notebook needs its API enabled separately.

A Reference field can target more than one notebook (notebookIds in its config). The stored value is just a note UUID either way — the app resolves it against whichever of the field's target notebooks contains it, and, failing that, against any notebook on the device: note IDs are globally unique, so a valid link renders even if the target list is out of date.

Cleared references read back as null, not "".

Number Fields

Types: number, currency, duration, percent

"Amount": 42.50
"Amount": -10
"Amount": null

Must be a numeric value (integer or float) or null.

Rating

"Stars": 4
"Stars": null

Must be an integer from 1 to 5, or null. Floats like 3.5 are rejected — use whole numbers only.

Date

Date fields come in two flavors depending on whether the field has the "Include time" option enabled in the app's field editor.

Date-only fields (default):

"Due Date": "2026-04-05"
"Due Date": null

Must be a string in YYYY-MM-DD format, or null. Read responses return the same YYYY-MM-DD shape.

Fields with "Include time" enabled:

"Meeting": "2026-04-05T15:00:00Z"
"Meeting": "2026-04-05T15:00:00-07:00"
"Meeting": "2026-04-05 15:00:00"
"Meeting": "2026-04-05"
"Meeting": null

Accepts ISO 8601 datetimes (with Z for UTC, an explicit offset like -07:00, or fractional seconds), the legacy yyyy-MM-dd HH:mm:ss format (UTC assumed if no zone), or a bare YYYY-MM-DD (stored at midnight UTC). Read responses always return ISO 8601 UTC: "2026-04-05T15:00:00Z".

You can tell which flavor a field uses by checking config.includesTime === "true" in the field schema (GET /v1/fields).

Toggle

"Active": true
"Active": false
"Active": null

Must be a JSON boolean (true or false) or null. String values like "true" are rejected.

Tags

"Categories": ["Design", "Marketing"]
"Categories": []
"Categories": null

Must be an array of strings, an empty array, or null. Each element must be a string. Objects and non-string elements are rejected.

Select

"Status": "Active"
"Status": null

Must be a string matching one of the field's configured availableOptions, or null/"" to clear. Submitting an unknown value returns 400 with the list of valid options.

Checklist

"Tasks": [
  {"text": "Design mockup", "checked": true},
  {"text": "Review copy", "checked": false}
]
"Tasks": null

Must be an array of checklist items or null. Each item should have text (string) and checked (boolean) keys.

Comments

"Activity Log": [
  {
    "id": "A1B2C3D4-...",
    "date": "2026-04-05 12:00:00.000",
    "author": "Alice",
    "content": "Called the customer"
  }
]

Three accepted shapes:

  1. Full array — replace the entire list. Each entry needs content (max 2000 chars). Missing id/date/author are filled in server-side (UUID, now, "API").
  2. Append shortcut{"append": "text"} or {"append": "text", "author": "Bob"}. Adds a single entry without reading the existing list.
  3. Delete shortcut{"delete": "<comment-id>"}. Removes a single entry by id; no-op if the id doesn't match.

Sending null or [] clears all entries. Date format is yyyy-MM-dd HH:mm:ss.SSS UTC.

Duration

"Prep Time": 5400
"Prep Time": null

Must be a numeric value representing total seconds, or null. For example, 5400 = 1 hour 30 minutes.

Percent

"Progress": 75
"Progress": null

Must be a numeric value from 0 to 100, or null.

Color

"Brand Color": "#FF5733"
"Brand Color": null

Must be a hex color string (e.g., #FF5733), or null.

Barcode / QR Code

"Barcode": "012345678905"
"QR Code": "https://example.com"

Must be a string or null. Any text value is accepted.


Pagination

List endpoints return paginated results:

GET /v1/notes?page=2&per_page=25
Parameter Default Range Description
page 1 1+ Page number
per_page 100 1–100 Results per page

Response metadata:

{
  "notes": [...],
  "total": 150,
  "page": 2,
  "per_page": 25,
  "total_pages": 6
}

Security

The API key is a password. Anyone holding it can read, change, and delete every note in that notebook. Keep it in an environment variable or a secrets manager — not in client-side code, a public repository, or a URL.

Enabling the API turns off end-to-end encryption for that notebook. Normally Neatbase encrypts a shared notebook on your device and the server only ever stores unreadable ciphertext. The API can't work that way — the server has to read your data to answer a request — so turning the API on stores the encryption key on the server. Turning it off deletes that key and restores end-to-end encryption. Notebooks without the API enabled are unaffected.

To rotate a key, disable the API and enable it again. That invalidates the old key immediately and issues a new one; anything still using the old key starts getting 401.

There is no read-only key. One key, full access. If you need a lower-privilege integration, put your own service in front of the API rather than handing the key out.

Rate Limits

API requests are rate-limited per API key:

Operation Limit
Read (GET requests) 240 per hour
Write (POST, PUT, DELETE) 120 per hour

When a limit is exceeded, the API returns:

{
  "error": "Rate limit exceeded. Try again later.",
  "code": "rate_limit"
}

HTTP Status: 429 Too Many Requests

Each limit uses a fixed one-hour window that starts with your first request. When the window closes, the counter resets.


Error Handling

All errors follow a consistent format:

{
  "error": "Human-readable error message",
  "code": "machine_readable_code"
}

Error Reference

HTTP Status Code Description
400 invalid_request Malformed request body or missing fields object
400 invalid_request Field validation failed (message includes field name and reason)
400 invalid_field Unknown field name (not in the notebook's schema)
401 unauthorized Missing or invalid API key
404 not_found Note or resource not found
410 gone The notebook is no longer shared, so its API is gone
429 rate_limit Rate limit exceeded
500 server_error Internal error (e.g., database transaction failed)

Images

Image fields are read-only in the API. When a note has an image field with data, the API returns a URL:

{
  "fields": {
    "Photo": "https://cloud.neatbase.com/api/v1/images/share_abc123_def456.jpg"
  }
}

To download the image, make a GET request to the URL with your API key:

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://cloud.neatbase.com/api/v1/images/share_abc123_def456.jpg" \
  -o photo.jpg

Images that haven't been set return null.

Image fields cannot be written via the API. Use the Neatbase app to add or update images.


Sync Behavior

The Neatbase app syncs with the server automatically while it's open. This means:

  • API → App: Notes and fields created or changed through the API show up in the app within about a minute, or immediately when the app comes back to the foreground.
  • App → API: Notes and fields created or modified in the app will be available via the API after the next sync cycle.
  • Deletions: Soft-deleted notes and fields (via API or app) are removed from both sides after sync.

The sync is bidirectional and automatic — no additional setup required.


Examples

List All Notes

curl -H "Authorization: Bearer YOUR_API_KEY" \
  https://cloud.neatbase.com/api/v1/notes

Sort and Filter Notes

# Get high-value notes, sorted by amount
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://cloud.neatbase.com/api/v1/notes?filter=Amount:gte:1000&sort=Amount&order=desc"

# Find notes with a specific tag
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://cloud.neatbase.com/api/v1/notes?filter=Priority:contains:High"

# Notes with upcoming due dates, sorted by most recently edited
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://cloud.neatbase.com/api/v1/notes?filter=Due%20Date:gte:2026-03-29&sort=updated_at&order=desc"

Create a Note

curl -X POST \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "fields": {
      "Name": "Acme Corp",
      "Email": "hello@acme.com",
      "Amount": 5000,
      "Priority": ["High"],
      "Active": true
    }
  }' \
  https://cloud.neatbase.com/api/v1/notes

Update Specific Fields

Only include the fields you want to change:

curl -X PUT \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"fields": {"Amount": 7500, "Priority": ["High", "Urgent"]}}' \
  https://cloud.neatbase.com/api/v1/notes/YOUR_NOTE_ID

Delete a Note

curl -X DELETE \
  -H "Authorization: Bearer YOUR_API_KEY" \
  https://cloud.neatbase.com/api/v1/notes/YOUR_NOTE_ID

Paginate Through All Notes

# Page 1
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://cloud.neatbase.com/api/v1/notes?page=1&per_page=50"

# Page 2
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://cloud.neatbase.com/api/v1/notes?page=2&per_page=50"

Python Example

import requests

API_KEY = "YOUR_API_KEY"
BASE_URL = "https://cloud.neatbase.com/api/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}

# List notes
response = requests.get(f"{BASE_URL}/notes", headers=HEADERS)
notes = response.json()["notes"]

for note in notes:
    print(note["fields"]["Name"], "-", note["fields"].get("Email"))

# Create a note
new_note = requests.post(
    f"{BASE_URL}/notes",
    headers={**HEADERS, "Content-Type": "application/json"},
    json={"fields": {"Name": "New Contact", "Email": "new@example.com"}}
)
print("Created:", new_note.json()["id"])

JavaScript Example

const API_KEY = "YOUR_API_KEY";
const BASE_URL = "https://cloud.neatbase.com/api/v1";

// List notes
const response = await fetch(`${BASE_URL}/notes`, {
  headers: { "Authorization": `Bearer ${API_KEY}` }
});
const { notes } = await response.json();

// Create a note
const created = await fetch(`${BASE_URL}/notes`, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${API_KEY}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    fields: { "Name": "New Contact", "Email": "new@example.com" }
  })
});
console.log("Created:", (await created.json()).id);

Join our mailing list

Get the latest news and feature updates.

Unsubscribe anytime. Privacy.