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
- Open your notebook in Neatbase
- Tap the three-dot menu (⋯) in the toolbar
- Select API...
- Tap 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.
Note: Enabling API access on a shared notebook works whether the share is encrypted or not. If the notebook uses end-to-end encryption, the encryption key is securely transmitted to the server so it can read and write data on behalf of the API. Disabling API access removes the key from the server, restoring full end-to-end encryption.
Disable API Access
To revoke API access, open the API... sheet from the three-dot menu and tap 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.app/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 | 410 Gone |
Base URL
All endpoints are relative to:
https://cloud.neatbase.app/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.app/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 via the API.
List Fields
Retrieve all field definitions for the notebook.
GET /v1/fields
Example:
curl -H "Authorization: Bearer YOUR_API_KEY" \
https://cloud.neatbase.app/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.app/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.app/api/v1/notes?page=1&per_page=50"
Response:
{
"notes": [
{
"id": "550E8400-E29B-41D4-A716-446655440000",
"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.app/api/v1/notes?sort=created_at&order=asc"
# Sort by a field value
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://cloud.neatbase.app/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.app/api/v1/notes?filter=Name:contains:Jane"
# Multiple filters (ANDed)
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://cloud.neatbase.app/api/v1/notes?filter[]=Amount:gte:100&filter[]=Priority:contains:High"
# Combined with sorting
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://cloud.neatbase.app/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: "text" = shortText, longText, email, phone, url, reference, barcode, qrCode, color. "number" = number, currency, rating, duration, percent. "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".
Invalid filter params return a 400 error with a helpful message explaining what went wrong.
Get Note
Retrieve a single note by its ID.
GET /v1/notes/{id}
Example:
curl -H "Authorization: Bearer YOUR_API_KEY" \
https://cloud.neatbase.app/api/v1/notes/550E8400-E29B-41D4-A716-446655440000
Response:
{
"id": "550E8400-E29B-41D4-A716-446655440000",
"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.
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"]
}
}
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.app/api/v1/notes
Response (201 Created):
{
"id": "A1B2C3D4-E5F6-7890-ABCD-EF1234567890",
"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. - The note ID is generated server-side.
- All field values are validated before writing. If any validation fails, nothing is saved.
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"]
}
}
Example:
curl -X PUT \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"fields": {"Amount": 149.00}}' \
https://cloud.neatbase.app/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.app/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.
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 (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) |
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"} |
formula |
formula |
{"formula": "{Price} * {Qty}"} |
reference |
notebookId |
{"notebookId": "NOTEBOOK-UUID"} (optional) |
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.app/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, reference, checklist, image, duration, percent, color, barcode, qrCode
Layout types (sectionDivider, 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.app/api/v1/fields/FIELD-UUID
Response (200 OK):
The complete updated field is returned (same format as Create Field).
Notes:
- Including
typein the request body returns400 Bad Request— type is immutable. - Field names must be unique across all fields in the notebook.
- Returns
404if the field doesn't exist.
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.app/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 | Yes | Yes | Web URL |
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: YYYY-MM-DD |
toggle |
boolean | Yes | Yes | true or false |
tags |
array | Yes | Yes | Array of strings, e.g. ["Tag1", "Tag2"] |
checklist |
array | Yes | Yes | Array of objects: [{"text": "...", "checked": true}] |
reference |
string | Yes | Yes | UUID of a note in another notebook |
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) |
secretText |
— | No | No | Excluded from API entirely |
sectionDivider |
— | No | No | Layout element, excluded from API |
sectionDescription |
— | No | No | Layout element, excluded from API |
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, reference, barcode, qrCode, color
"Name": "Jane Doe"
"Name": null
Must be a string or null. Any other type (number, boolean, array) is rejected.
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
"Due Date": "2026-04-05"
"Due Date": null
Must be a string in YYYY-MM-DD format, or null. The format is strictly validated.
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.
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.
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
}
Rate Limits
API requests are rate-limited per API key:
| Operation | Limit |
|---|---|
| Read (GET requests) | 600 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
Rate limits reset on an hourly window.
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 |
API access has been disabled for this notebook |
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.app/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.app/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 modified via the API will appear in the app shortly.
- 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.app/api/v1/notes
Sort and Filter Notes
# Get high-value notes, sorted by amount
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://cloud.neatbase.app/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.app/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.app/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.app/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.app/api/v1/notes/YOUR_NOTE_ID
Delete a Note
curl -X DELETE \
-H "Authorization: Bearer YOUR_API_KEY" \
https://cloud.neatbase.app/api/v1/notes/YOUR_NOTE_ID
Paginate Through All Notes
# Page 1
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://cloud.neatbase.app/api/v1/notes?page=1&per_page=50"
# Page 2
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://cloud.neatbase.app/api/v1/notes?page=2&per_page=50"
Python Example
import requests
API_KEY = "YOUR_API_KEY"
BASE_URL = "https://cloud.neatbase.app/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.app/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);