Documents
PDFs, emails and images in folders, linked to notes, projects, tasks and people.
On this page
- List folders
- Add a folder
- Get a folder
- Change a folder
- Delete an empty folder
- Document counts per folder
- List documents
- Upload a PDF or an image
- Import an email
- Get a document
- Change a document
- Delete a document
- Restore a document
- Download a document's file
- An email's body, as a page
- Get a document's thumbnail
- Get a document's text
- Get a document's analysis
- Analyze a document again
- Link a document
- Remove a document link
List folders
GET/folders
Folders of documents, alphabetical.
Alphabetical ignores case and accents ("apple", "Banana", "éclair"); ties by id. Filters are for browsing, so sync unfiltered.
curl "$NIFTY_URL/api/v1/folders" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Query parameters
| Name | Description |
|---|---|
parent_idULID · optional | Only this folder's subfolders. Not with root. |
roottrue · optional | true: only top-level folders. Anything else, or with parent_id, is 400. |
ordername | id | updated_at · optional | The field to sort by; ties are broken by id, so the order is stable. |
directionasc | desc · optional | asc (ascending) or desc (descending), for order. |
updated_sincedate-time · optional | Only records updated at or after this time (ISO 8601 with Z or a UTC offset), ordered by updated_at, id. |
cursorstring · optional | meta.next_cursor from the previous page. |
limitinteger · optional | Items per page, up to 100; a larger number is taken as 100. |
counttrue | false · optional | true adds meta.total_count: the size of the filtered set (with updated_since too), ignoring cursor and limit. Anything but true or false is a 400. |
Response
200 OK Errors: 400 401 429
{
"data": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"name": "Receipts",
"parent_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"color": "amber",
"icon": "wallet",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"meta": {
"next_cursor": "eyJrIjpbIjIwMjYtMTAtMDlUMDg6MzA6MDBaIl19",
"limit": 1,
"total_count": 1
}
}
Add a folder
POST/folders
Needs a name. Top level unless parent_id is sent. A blank color gets the first one no other folder uses
(colours and icons are the vocabulary's colors and picker_icons, as GET /category_choices lists them).
curl -X POST "$NIFTY_URL/api/v1/folders" \
-H "Authorization: Bearer $NIFTY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"folder":{"name":"Receipts","parent_id":null}}'
Headers
| Name | Description |
|---|---|
Idempotency-Keystring · optional | Makes a POST safe to retry for 24 hours. Needs a bearer token (400 under session auth). |
Request body
| Name | Description |
|---|---|
folderobject | |
folder.namestring · optional | Spaces are squished. A name a sibling has, ignoring case, is a 422. |
folder.parent_idULID or null · optional | The folder it's in; null for the top level. Not an id, unknown, itself or one inside it: a 422. |
folder.colorstring or null · optional | A key from the vocabulary's colors (or GET /category_choices). Blank picks the first one no other folder uses. |
folder.iconstring or null · optional | An icon from the vocabulary's picker_icons (or GET /category_choices), or null for the default folder icon. |
Response
201 Created Errors: 400 401 403 409 415 422 429
{
"data": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"name": "Receipts",
"parent_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"color": "amber",
"icon": "wallet",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
Get a folder
GET/folders/{id}
curl "$NIFTY_URL/api/v1/folders/01j9zq3k8m5x2v7c4n6b0t1r9e" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Path parameters
| Name | Description |
|---|---|
idULID | A record's ID. |
Response
200 OK Errors: 401 404 429
{
"data": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"name": "Receipts",
"parent_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"color": "amber",
"icon": "wallet",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
Change a folder
PATCH/folders/{id}
Rename, recolour or move a folder.
Only the fields sent change. Moving is changing parent_id (null: the top level); into itself or a folder
inside it is a 422 on parent_id. Documents hold only folder_id, so no document changes.
curl -X PATCH "$NIFTY_URL/api/v1/folders/01j9zq3k8m5x2v7c4n6b0t1r9e" \
-H "Authorization: Bearer $NIFTY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"folder":{"name":"Receipts","parent_id":null}}'
Path parameters
| Name | Description |
|---|---|
idULID | A record's ID. |
Request body
| Name | Description |
|---|---|
folderobject | |
folder.namestring · optional | Spaces are squished. A name a sibling has, ignoring case, is a 422. |
folder.parent_idULID or null · optional | The folder it's in; null for the top level. Not an id, unknown, itself or one inside it: a 422. |
folder.colorstring or null · optional | A key from the vocabulary's colors (or GET /category_choices). Blank picks the first one no other folder uses. |
folder.iconstring or null · optional | An icon from the vocabulary's picker_icons (or GET /category_choices), or null for the default folder icon. |
Response
200 OK Errors: 400 401 403 404 415 422 429
{
"data": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"name": "Receipts",
"parent_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"color": "amber",
"icon": "wallet",
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
Delete an empty folder
DELETE/folders/{id}
Permanent, with a folder deletion record. Only an empty folder (no subfolders, no documents) can be
deleted; otherwise it's 409 folder_not_empty. Documents deleted in the last 10 seconds that were in it lose
their folder_id (and get a new updated_at), so restoring one brings it back unfiled.
curl -X DELETE "$NIFTY_URL/api/v1/folders/01j9zq3k8m5x2v7c4n6b0t1r9e" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Path parameters
| Name | Description |
|---|---|
idULID | A record's ID. |
Response
204 Deleted Errors: 401 403 404 409 429
Document counts per folder
GET/folder_counts
How many live documents each folder holds directly, and their size.
Not a record and not paginated: there's nothing to sync, so ask again when documents change. Ordered by
folder_id, the null row (documents in no folder) first; folders with no live documents are left out, and
so is the null row when every document is in a folder. Subfolders' documents aren't counted: add them up
from parent_id. A deleted document isn't counted, even while it can be restored.
Emails' attachments aren't counted (they're inside their email), but their size is.
curl "$NIFTY_URL/api/v1/folder_counts" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Response
200 OK Errors: 401 429
{
"data": [
{
"folder_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"document_count": 1,
"byte_size": 1
}
]
}
List documents
GET/documents
Documents (PDFs and emails), alphabetical.
Live documents. Alphabetical ignores case and accents; ties by id. Filters combine (AND), except
unsorted; they're for browsing, so sync unfiltered. Search by name with GET /search?types=document.
An email's PDF attachments (email_id set) live inside it: folder_id, include_subfolders and unsorted
leave them out, as does include_attachments=false; email_id lists one email's. Unfiltered and the link
filters include them.
curl "$NIFTY_URL/api/v1/documents" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Query parameters
| Name | Description |
|---|---|
folder_idULID · optional | Only this folder's documents (not its subfolders', unless include_subfolders), without attachments. |
email_idULID · optional | Only this email's PDF attachments. Not with unsorted (400). |
include_attachmentsboolean · optional | false: leave out emails' PDF attachments (the Library home's newest). With email_id it's 400. |
include_subfoldersboolean · optional | true: with folder_id, its documents and those in every folder below it, at any depth. Without folder_id it's 400. |
unsortedtrue · optional | true: only documents with no folder and no link to a live note, project, task or person, and not an email's attachment. Not with any other filter; anything else is 400. |
note_idULID · optional | Only documents linked to this note. At most one of note_id, project_id, task_id and person_id (two is 400); an unknown or deleted one is an empty page. |
project_idULID · optional | A record's ID. |
task_idULID · optional | A record's ID. |
person_idULID · optional | A record's ID. |
ordername | created_at | updated_at | id · optional | The field to sort by; ties are broken by id, so the order is stable. |
directionasc | desc · optional | asc (ascending) or desc (descending), for order. |
updated_sincedate-time · optional | Only records updated at or after this time (ISO 8601 with Z or a UTC offset), ordered by updated_at, id. |
cursorstring · optional | meta.next_cursor from the previous page. |
limitinteger · optional | Items per page, up to 100; a larger number is taken as 100. |
counttrue | false · optional | true adds meta.total_count: the size of the filtered set (with updated_since too), ignoring cursor and limit. Anything but true or false is a 400. |
Response
200 OK Errors: 400 401 429
{
"data": [
{
"received_via_api": {
"at": "2026-10-10T09:30:00Z",
"source": "Zapier"
},
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"kind": "pdf",
"name": "Boiler warranty",
"dated_on": "2026-10-10",
"dated_on_set": true,
"folder_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"email_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"email": {
"subject": "string",
"from": {
"name": "Ada Lovelace",
"address": "[email protected]",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
},
"to": [
{
"name": "Ada Lovelace",
"address": "[email protected]",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
}
],
"cc": [
{
"name": "Ada Lovelace",
"address": "[email protected]",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
}
],
"sent_at": "2026-10-10T09:30:00Z",
"forwarded": true,
"forward_note": "string",
"forwarded_at": "2026-10-10T09:30:00Z",
"has_html": true,
"remote_images": true,
"body_url": "https://example.com",
"attachments": [
{
"filename": "string",
"content_type": "application/pdf",
"byte_size": 1,
"status": "saved",
"document_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
}
]
},
"from_email": {
"subject": "string",
"from": {
"name": "Ada Lovelace",
"address": "[email protected]",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
},
"sent_at": "2026-10-10T09:30:00Z",
"forwarded_at": "2026-10-10T09:30:00Z"
},
"note_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"project_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"task_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"person_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"links": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"note_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"project_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"task_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
}
],
"file": {
"filename": "string",
"byte_size": 1,
"content_type": "application/pdf",
"width": 1,
"height": 1,
"url": "https://example.com",
"download_url": "https://example.com",
"thumbnail_url": "https://example.com"
},
"analysis": {
"status": "reading",
"type": "payslip",
"type_label": "Payslip"
},
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"meta": {
"next_cursor": "eyJrIjpbIjIwMjYtMTAtMDlUMDg6MzA6MDBaIl19",
"limit": 1,
"total_count": 1
}
}
Upload a PDF or an image
POST/documents
A multipart/form-data body (any other type is 415) with the PDF or image in file and, optionally, the
other fields. The type is read from the bytes: anything but a PDF or a PNG, JPEG or WebP image is a 422 on
file, whatever its declared type or name (a HEIC photo says to convert it to JPEG first). Up to 50 MB.
An image (kind: image) must be at least 200 × 200 pixels' worth (40,000), no side under 64, and at most
40 megapixels. It's kept as sent; a JPEG's EXIF orientation is applied to its size, thumbnail and text. A body without a Content-Length is 411, and one over 50 MB (plus 1 MB for the other
fields) is 413, both before anything is read, even the token. With no folder and no links it's Unsorted.
Idempotency-Key compares the fields and the file's bytes, not the raw body, so a retry with new multipart
boundaries is the same request.
curl -X POST "$NIFTY_URL/api/v1/documents" \
-H "Authorization: Bearer $NIFTY_TOKEN" \
-F '[email protected]'
Headers
| Name | Description |
|---|---|
Idempotency-Keystring · optional | Makes a POST safe to retry for 24 hours. Needs a bearer token (400 under session auth). |
Request body
| Name | Description |
|---|---|
filefile | The PDF or image. Its file name (1–255 characters, no NUL) is kept as file.filename. |
namestring · optional | Spaces are squished. Blank: the file name without its extension (.pdf, .png, .jpg, .jpeg, .webp). |
folder_idULID · optional | A record's ID. |
note_ids[]ULID[] · optional | |
project_ids[]ULID[] · optional | |
task_ids[]ULID[] · optional | |
person_ids[]ULID[] · optional |
Response
201 Created Errors: 400 401 403 409 411 413 415 422 429
{
"data": {
"received_via_api": {
"at": "2026-10-10T09:30:00Z",
"source": "Zapier"
},
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"kind": "pdf",
"name": "Boiler warranty",
"dated_on": "2026-10-10",
"dated_on_set": true,
"folder_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"email_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"email": {
"subject": "string",
"from": {
"name": "Ada Lovelace",
"address": "[email protected]",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
},
"to": [
{
"name": "Ada Lovelace",
"address": "[email protected]",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
}
],
"cc": [
{
"name": "Ada Lovelace",
"address": "[email protected]",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
}
],
"sent_at": "2026-10-10T09:30:00Z",
"forwarded": true,
"forward_note": "string",
"forwarded_at": "2026-10-10T09:30:00Z",
"has_html": true,
"remote_images": true,
"body_url": "https://example.com",
"attachments": [
{
"filename": "string",
"content_type": "application/pdf",
"byte_size": 1,
"status": "saved",
"document_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
}
]
},
"from_email": {
"subject": "string",
"from": {
"name": "Ada Lovelace",
"address": "[email protected]",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
},
"sent_at": "2026-10-10T09:30:00Z",
"forwarded_at": "2026-10-10T09:30:00Z"
},
"note_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"project_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"task_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"person_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"links": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"note_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"project_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"task_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
}
],
"file": {
"filename": "string",
"byte_size": 1,
"content_type": "application/pdf",
"width": 1,
"height": 1,
"url": "https://example.com",
"download_url": "https://example.com",
"thumbnail_url": "https://example.com"
},
"analysis": {
"status": "reading",
"type": "payslip",
"type_label": "Payslip"
},
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
Import an email
POST/email_imports
Adds an email (an .eml file) as documents.
A multipart/form-data body (any other type is 415) with the email in file and, optionally, a folder and
links, as POST /documents takes them. Up to 50 MB; a body without a Content-Length is 411, and one over
50 MB (plus 1 MB for the other fields) is 413. A file that can't be read as an email is a 422 on file.
A forwarded email is imported as the original it forwards (the innermost, when forwards are nested): its
sender, recipients, date, subject, body and attachments. Its PDF attachments (read from their bytes, whatever
their declared type) become documents. Other attachments aren't kept: email.attachments lists them.
An email with PDFs and no subject, or whose body says nothing beyond greetings, stock phrases ("Please find
attached"), sign-offs, signatures and disclaimers, isn't kept as an email: its PDFs are added as ordinary
documents (email null), each with from_email. Otherwise it's an email document (kind: email) with its
PDFs inside it (email_id).
An email whose Message-ID is already in Nifty (a live email, or documents added from it) isn't imported
again: 200 with what's there and duplicate: true. It gains the links sent (the email, else each document
added from it); folder_id is ignored, so it stays where it's filed. Idempotency-Key compares the fields and the file's
bytes, as POST /documents.
curl -X POST "$NIFTY_URL/api/v1/email_imports" \
-H "Authorization: Bearer $NIFTY_TOKEN" \
-F '[email protected]'
Headers
| Name | Description |
|---|---|
Idempotency-Keystring · optional | Makes a POST safe to retry for 24 hours. Needs a bearer token (400 under session auth). |
Request body
| Name | Description |
|---|---|
filefile | The .eml file (1–255 character name, no NUL). |
folder_idULID · optional | A record's ID. |
note_ids[]ULID[] · optional | |
project_ids[]ULID[] · optional | |
task_ids[]ULID[] · optional | |
person_ids[]ULID[] · optional |
Response
200 Already imported (duplicate: true). Errors: 400 401 403 409 411 413 415 422 429
{
"data": {
"email": {
"received_via_api": {
"at": "2026-10-10T09:30:00Z",
"source": "Zapier"
},
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"kind": "pdf",
"name": "Boiler warranty",
"dated_on": "2026-10-10",
"dated_on_set": true,
"folder_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"email_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"email": {
"subject": "string",
"from": {
"name": "Ada Lovelace",
"address": "[email protected]",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
},
"to": [
{
"name": "Ada Lovelace",
"address": "[email protected]",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
}
],
"cc": [
{
"name": "Ada Lovelace",
"address": "[email protected]",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
}
],
"sent_at": "2026-10-10T09:30:00Z",
"forwarded": true,
"forward_note": "string",
"forwarded_at": "2026-10-10T09:30:00Z",
"has_html": true,
"remote_images": true,
"body_url": "https://example.com",
"attachments": [
{
"filename": "string",
"content_type": "application/pdf",
"byte_size": 1,
"status": "saved",
"document_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
}
]
},
"from_email": {
"subject": "string",
"from": {
"name": "Ada Lovelace",
"address": "[email protected]",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
},
"sent_at": "2026-10-10T09:30:00Z",
"forwarded_at": "2026-10-10T09:30:00Z"
},
"note_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"project_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"task_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"person_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"links": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"note_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"project_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"task_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
}
],
"file": {
"filename": "string",
"byte_size": 1,
"content_type": "application/pdf",
"width": 1,
"height": 1,
"url": "https://example.com",
"download_url": "https://example.com",
"thumbnail_url": "https://example.com"
},
"analysis": {
"status": "reading",
"type": "payslip",
"type_label": "Payslip"
},
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
},
"documents": [
{
"received_via_api": {
"at": "2026-10-10T09:30:00Z",
"source": "Zapier"
},
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"kind": "pdf",
"name": "Boiler warranty",
"dated_on": "2026-10-10",
"dated_on_set": true,
"folder_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"email_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"email": {
"subject": "string",
"from": {
"name": "Ada Lovelace",
"address": "[email protected]",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
},
"to": [
{
"name": "Ada Lovelace",
"address": "[email protected]",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
}
],
"cc": [
{
"name": "Ada Lovelace",
"address": "[email protected]",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
}
],
"sent_at": "2026-10-10T09:30:00Z",
"forwarded": true,
"forward_note": "string",
"forwarded_at": "2026-10-10T09:30:00Z",
"has_html": true,
"remote_images": true,
"body_url": "https://example.com",
"attachments": [
{
"filename": "string",
"content_type": "application/pdf",
"byte_size": 1,
"status": "saved",
"document_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
}
]
},
"from_email": {
"subject": "string",
"from": {
"name": "Ada Lovelace",
"address": "[email protected]",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
},
"sent_at": "2026-10-10T09:30:00Z",
"forwarded_at": "2026-10-10T09:30:00Z"
},
"note_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"project_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"task_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"person_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"links": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"note_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"project_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"task_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
}
],
"file": {
"filename": "string",
"byte_size": 1,
"content_type": "application/pdf",
"width": 1,
"height": 1,
"url": "https://example.com",
"download_url": "https://example.com",
"thumbnail_url": "https://example.com"
},
"analysis": {
"status": "reading",
"type": "payslip",
"type_label": "Payslip"
},
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
],
"duplicate": true
}
}
Get a document
GET/documents/{id}
A deleted document is a 404.
curl "$NIFTY_URL/api/v1/documents/01j9zq3k8m5x2v7c4n6b0t1r9e" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Path parameters
| Name | Description |
|---|---|
idULID | A record's ID. |
Response
200 OK Errors: 401 404 429
{
"data": {
"received_via_api": {
"at": "2026-10-10T09:30:00Z",
"source": "Zapier"
},
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"kind": "pdf",
"name": "Boiler warranty",
"dated_on": "2026-10-10",
"dated_on_set": true,
"folder_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"email_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"email": {
"subject": "string",
"from": {
"name": "Ada Lovelace",
"address": "[email protected]",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
},
"to": [
{
"name": "Ada Lovelace",
"address": "[email protected]",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
}
],
"cc": [
{
"name": "Ada Lovelace",
"address": "[email protected]",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
}
],
"sent_at": "2026-10-10T09:30:00Z",
"forwarded": true,
"forward_note": "string",
"forwarded_at": "2026-10-10T09:30:00Z",
"has_html": true,
"remote_images": true,
"body_url": "https://example.com",
"attachments": [
{
"filename": "string",
"content_type": "application/pdf",
"byte_size": 1,
"status": "saved",
"document_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
}
]
},
"from_email": {
"subject": "string",
"from": {
"name": "Ada Lovelace",
"address": "[email protected]",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
},
"sent_at": "2026-10-10T09:30:00Z",
"forwarded_at": "2026-10-10T09:30:00Z"
},
"note_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"project_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"task_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"person_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"links": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"note_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"project_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"task_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
}
],
"file": {
"filename": "string",
"byte_size": 1,
"content_type": "application/pdf",
"width": 1,
"height": 1,
"url": "https://example.com",
"download_url": "https://example.com",
"thumbnail_url": "https://example.com"
},
"analysis": {
"status": "reading",
"type": "payslip",
"type_label": "Payslip"
},
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
Change a document
PATCH/documents/{id}
Rename, date, file or link a document.
JSON. Only the fields sent change. Each id list replaces that kind's links (to live records; links to ones deleted in the last 10 seconds are kept, so undoing their delete restores them). The file can't be replaced: delete the document and upload again.
An email's attachment stays in its email's folder: a folder_id for one is a 422 (move the email, and its
attachments move with it). Moving an email moves its attachments.
curl -X PATCH "$NIFTY_URL/api/v1/documents/01j9zq3k8m5x2v7c4n6b0t1r9e" \
-H "Authorization: Bearer $NIFTY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"document":{"name":"Boiler warranty","folder_id":"01j9zq3f0a2b4c6d8e0f2g4h6j","project_ids":["01j9zq3p4q6r8s0t2v5w7x9y1z"]}}'
Path parameters
| Name | Description |
|---|---|
idULID | A record's ID. |
Request body
| Name | Description |
|---|---|
documentDocumentWrite | |
document.namestring · optional | Spaces are squished. Needn't be unique. |
document.folder_idULID or null · optional | Null: no folder. Not an id, or an unknown folder, is a 422. |
document.note_idsULID[] or null · optional | Replaces its links to notes; null or [] removes them. An unknown or deleted id, or one that isn't an id, is a 422 on the field. |
document.project_idsULID[] or null · optional | |
document.task_idsULID[] or null · optional | A task in a deleted list or project counts as deleted. |
document.person_idsULID[] or null · optional | |
document.dated_ondate or null · optional | YYYY-MM-DD: the owner's choice, which nothing else replaces. Null goes back to Nifty's choice. One that isn't a date is a 422. |
Response
200 OK Errors: 400 401 403 404 415 422 429
{
"data": {
"received_via_api": {
"at": "2026-10-10T09:30:00Z",
"source": "Zapier"
},
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"kind": "pdf",
"name": "Boiler warranty",
"dated_on": "2026-10-10",
"dated_on_set": true,
"folder_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"email_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"email": {
"subject": "string",
"from": {
"name": "Ada Lovelace",
"address": "[email protected]",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
},
"to": [
{
"name": "Ada Lovelace",
"address": "[email protected]",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
}
],
"cc": [
{
"name": "Ada Lovelace",
"address": "[email protected]",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
}
],
"sent_at": "2026-10-10T09:30:00Z",
"forwarded": true,
"forward_note": "string",
"forwarded_at": "2026-10-10T09:30:00Z",
"has_html": true,
"remote_images": true,
"body_url": "https://example.com",
"attachments": [
{
"filename": "string",
"content_type": "application/pdf",
"byte_size": 1,
"status": "saved",
"document_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
}
]
},
"from_email": {
"subject": "string",
"from": {
"name": "Ada Lovelace",
"address": "[email protected]",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
},
"sent_at": "2026-10-10T09:30:00Z",
"forwarded_at": "2026-10-10T09:30:00Z"
},
"note_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"project_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"task_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"person_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"links": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"note_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"project_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"task_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
}
],
"file": {
"filename": "string",
"byte_size": 1,
"content_type": "application/pdf",
"width": 1,
"height": 1,
"url": "https://example.com",
"download_url": "https://example.com",
"thumbnail_url": "https://example.com"
},
"analysis": {
"status": "reading",
"type": "payslip",
"type_label": "Payslip"
},
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
Delete a document
DELETE/documents/{id}
Restorable for 10 seconds (POST /documents/{id}/restore), with its folder and links; then permanent,
file and all. Gone from every endpoint at once, with a document deletion record. Deleting again is a 404.
Deleting an email deletes its attachments too (each with its deletion record), and restoring it brings them
back; an attachment can also be deleted on its own.
curl -X DELETE "$NIFTY_URL/api/v1/documents/01j9zq3k8m5x2v7c4n6b0t1r9e" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Path parameters
| Name | Description |
|---|---|
idULID | A record's ID. |
Response
204 Deleted Errors: 401 403 404 429
Restore a document
POST/documents/{id}/restore
Undo deleting a document.
Within 10 seconds of DELETE /documents/{id}: brings it back with its folder and links. Its deletion record is
removed and it gets a new updated_at. No body. Restoring one that isn't deleted changes nothing and returns
it, so a retry is safe. Once the 10 seconds have passed, or for an unknown id, it's a 404. An email comes back
with the attachments deleted with it; an attachment whose email is deleted can't come back alone (404).
curl -X POST "$NIFTY_URL/api/v1/documents/01j9zq3k8m5x2v7c4n6b0t1r9e/restore" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Path parameters
| Name | Description |
|---|---|
idULID | A record's ID. |
Headers
| Name | Description |
|---|---|
Idempotency-Keystring · optional | Makes a POST safe to retry for 24 hours. Needs a bearer token (400 under session auth). |
Response
200 Restored. The document, as GET /documents/{id} returns it. Errors: 400 401 403 404 409 415 429
{
"data": {
"received_via_api": {
"at": "2026-10-10T09:30:00Z",
"source": "Zapier"
},
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"kind": "pdf",
"name": "Boiler warranty",
"dated_on": "2026-10-10",
"dated_on_set": true,
"folder_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"email_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"email": {
"subject": "string",
"from": {
"name": "Ada Lovelace",
"address": "[email protected]",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
},
"to": [
{
"name": "Ada Lovelace",
"address": "[email protected]",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
}
],
"cc": [
{
"name": "Ada Lovelace",
"address": "[email protected]",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
}
],
"sent_at": "2026-10-10T09:30:00Z",
"forwarded": true,
"forward_note": "string",
"forwarded_at": "2026-10-10T09:30:00Z",
"has_html": true,
"remote_images": true,
"body_url": "https://example.com",
"attachments": [
{
"filename": "string",
"content_type": "application/pdf",
"byte_size": 1,
"status": "saved",
"document_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
}
]
},
"from_email": {
"subject": "string",
"from": {
"name": "Ada Lovelace",
"address": "[email protected]",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
},
"sent_at": "2026-10-10T09:30:00Z",
"forwarded_at": "2026-10-10T09:30:00Z"
},
"note_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"project_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"task_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"person_ids": [
"01j9zq3k8m5x2v7c4n6b0t1r9e"
],
"links": [
{
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"note_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"project_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"task_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
}
],
"file": {
"filename": "string",
"byte_size": 1,
"content_type": "application/pdf",
"width": 1,
"height": 1,
"url": "https://example.com",
"download_url": "https://example.com",
"thumbnail_url": "https://example.com"
},
"analysis": {
"status": "reading",
"type": "payslip",
"type_label": "Payslip"
},
"created_at": "2026-10-10T09:30:00Z",
"updated_at": "2026-10-10T09:30:00Z"
}
}
Download a document's file
GET/documents/{id}/file
The PDF or image itself (an email's .eml, message/rfc822, always as a download named after it plus .eml).
The file, with its Content-Type (file.content_type), X-Content-Type-Options: nosniff and Cache-Control: private, max-age=0 (no-store under the web session). Content-Disposition names it after the document's
current name plus its extension (.pdf, .png, .jpg, .webp). Send a single Range (e.g. bytes=0-65535) for part of it (206, with
Content-Range); several ranges get the whole file. Needs the token (or the web session) like any endpoint:
there's no public URL. The web app shows it inline in a same-origin <iframe> (X-Frame-Options: SAMEORIGIN).
curl "$NIFTY_URL/api/v1/documents/01j9zq3k8m5x2v7c4n6b0t1r9e/file" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Path parameters
| Name | Description |
|---|---|
idULID | A record's ID. |
Query parameters
| Name | Description |
|---|---|
dispositioninline | attachment · optional | attachment for a download; anything but inline or attachment is 400. |
Headers
| Name | Description |
|---|---|
Rangestring · optional |
Response
200 The whole file. Errors: 400 401 404 416 429
"@file.pdf"
An email's body, as a page
GET/documents/{id}/email_body
An email document's body as a self-contained HTML page, for a sandboxed <iframe>: its HTML cleaned (no
scripts, forms, frames or event handlers; links open in a new window), or its plain text. Served with a
Content-Security-Policy that runs nothing and loads nothing from elsewhere: remote images load only with
images=true. Not an email (or deleted): 404.
curl "$NIFTY_URL/api/v1/documents/01j9zq3k8m5x2v7c4n6b0t1r9e/email_body" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Path parameters
| Name | Description |
|---|---|
idULID | A record's ID. |
Query parameters
| Name | Description |
|---|---|
imagesboolean · optional | true: let remote images load. |
Response
200 The page. Errors: 400 401 404 429
"string"
Get a document's thumbnail
GET/documents/{id}/thumbnail
A PDF's first page as a JPEG, or an image fitted within 1024 × 768 as a WebP.
A PDF's first page is 480 pixels wide (at most 1440 tall) on white. Served with its Content-Type,
X-Content-Type-Options: nosniff and Cache-Control: private, max-age=0 (no-store under the web session).
404 when the document has none (file.thumbnail_url is null). Needs the token (or the web session).
curl "$NIFTY_URL/api/v1/documents/01j9zq3k8m5x2v7c4n6b0t1r9e/thumbnail" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Path parameters
| Name | Description |
|---|---|
idULID | A record's ID. |
Response
200 The JPEG or WebP. Errors: 401 404 429
"@file.pdf"
Get a document's text
GET/documents/{id}/text
The text Nifty read from the document, as search and summaries use it: a PDF's own text, an image's read by
OCR (or, with the Image processing AI task, by its model, with OCR's text beside it), an email's. Empty, with
read_by null, until it's read; empty when it has none. 404 for a deleted document.
curl "$NIFTY_URL/api/v1/documents/01j9zq3k8m5x2v7c4n6b0t1r9e/text" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Path parameters
| Name | Description |
|---|---|
idULID | A record's ID. |
Response
200 The text. Errors: 401 404 429
{
"data": {
"text": "string",
"truncated": true,
"read_by": "pdf",
"ocr_text": "string"
}
}
Get a document's analysis
GET/documents/{id}/analysis
This endpoint is in beta, so it can change in any release (Versions).
The PDF's own information, its text's size and the Document summaries AI task's result: a summary, the document's type, and the fields extracted for it. 404 for a deleted document.
curl "$NIFTY_URL/api/v1/documents/01j9zq3k8m5x2v7c4n6b0t1r9e/analysis" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Path parameters
| Name | Description |
|---|---|
idULID | A record's ID. |
Response
200 The analysis. Errors: 401 404 429
{
"data": {
"status": "reading",
"error": "The provider didn't answer within 5 minutes.",
"type": "string",
"type_label": "string",
"summary": "string",
"facts": [
{
"key": "net_pay",
"label": "Net pay",
"kind": "text",
"unit": "kWh",
"value": null,
"columns": [
{
"key": "string",
"label": "string",
"kind": "text",
"unit": "string"
}
]
}
],
"fields": [
{
"key": "net_pay",
"label": "Net pay",
"kind": "text",
"unit": "kWh",
"value": null,
"columns": [
{
"key": "string",
"label": "string",
"kind": "text",
"unit": "string"
}
]
}
],
"model": "string",
"analysed_at": "2026-10-10T09:30:00Z",
"pages_analysed": 1,
"image": {
"status": "pending",
"description": "A screenshot of a product registration page for a washing machine.",
"model": "string",
"error": "string"
},
"pdf": {
"title": "string",
"author": "string",
"subject": "string",
"keywords": "string",
"creator": "string",
"producer": "string",
"created_at": "2026-10-10T09:30:00Z",
"modified_at": "2026-10-10T09:30:00Z",
"page_count": 1
},
"text": {
"characters": 1,
"truncated": true
}
}
}
Analyze a document again
POST/documents/{id}/analyze
This endpoint is in beta, so it can change in any release (Versions).
Queues the Document summaries AI task for the document, replacing its analysis once done (the earlier one
stays meanwhile), and returns it as pending. 422 (details.base) when the task is off or has no model, or
the document has no text to read. 404 for a deleted document. Limited to 20 a minute per IP address.
curl -X POST "$NIFTY_URL/api/v1/documents/01j9zq3k8m5x2v7c4n6b0t1r9e/analyze" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Path parameters
| Name | Description |
|---|---|
idULID | A record's ID. |
Headers
| Name | Description |
|---|---|
Idempotency-Keystring · optional | Makes a POST safe to retry for 24 hours. Needs a bearer token (400 under session auth). |
Response
202 Queued. Errors: 401 403 404 409 422 429
{
"data": {
"status": "reading",
"error": "The provider didn't answer within 5 minutes.",
"type": "string",
"type_label": "string",
"summary": "string",
"facts": [
{
"key": "net_pay",
"label": "Net pay",
"kind": "text",
"unit": "kWh",
"value": null,
"columns": [
{
"key": "string",
"label": "string",
"kind": "text",
"unit": "string"
}
]
}
],
"fields": [
{
"key": "net_pay",
"label": "Net pay",
"kind": "text",
"unit": "kWh",
"value": null,
"columns": [
{
"key": "string",
"label": "string",
"kind": "text",
"unit": "string"
}
]
}
],
"model": "string",
"analysed_at": "2026-10-10T09:30:00Z",
"pages_analysed": 1,
"image": {
"status": "pending",
"description": "A screenshot of a product registration page for a washing machine.",
"model": "string",
"error": "string"
},
"pdf": {
"title": "string",
"author": "string",
"subject": "string",
"keywords": "string",
"creator": "string",
"producer": "string",
"created_at": "2026-10-10T09:30:00Z",
"modified_at": "2026-10-10T09:30:00Z",
"page_count": 1
},
"text": {
"characters": 1,
"truncated": true
}
}
}
Link a document
POST/document_links
Link a document to a note, project, task or person.
Exactly one target. The document gets a new updated_at and lists the link in links. An unknown or deleted
document or target (a task in a deleted list, person or project counts as deleted), no target or two, or one
already linked, is a 422 on the field. Linking a person accepts the document's pending person suggestion for
them, if any.
curl -X POST "$NIFTY_URL/api/v1/document_links" \
-H "Authorization: Bearer $NIFTY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"document_link":{"document_id":"01j9zq3d1e3f5g7h9j1k3m5n7p","note_id":"01j9zq3n6p8q0r2s4t6v8w0x2y"}}'
Headers
| Name | Description |
|---|---|
Idempotency-Keystring · optional | Makes a POST safe to retry for 24 hours. Needs a bearer token (400 under session auth). |
Request body
| Name | Description |
|---|---|
document_linkDocumentLinkWrite | document_id and exactly one of note_id, project_id, task_id and person_id. |
document_link.document_idULID | A record's ID. |
document_link.note_idULID or null · optional | A record's ID. |
document_link.project_idULID or null · optional | A record's ID. |
document_link.task_idULID or null · optional | A record's ID. |
document_link.person_idULID or null · optional | A record's ID. |
Response
201 Created Errors: 400 401 403 409 415 422 429
{
"data": {
"id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"document_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"note_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"project_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"task_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
"created_at": "2026-10-10T09:30:00Z"
}
}
Remove a document link
DELETE/document_links/{id}
Permanent; the document stays (in Unsorted if that was its last folder or link) and gets a new updated_at. An
unknown link, one whose document is deleted, or one to a deleted record (not in links; it comes back if that
record's delete is undone) is a 404.
curl -X DELETE "$NIFTY_URL/api/v1/document_links/01j9zq3k8m5x2v7c4n6b0t1r9e" \
-H "Authorization: Bearer $NIFTY_TOKEN"
Path parameters
| Name | Description |
|---|---|
idULID | A record's ID. |
Response
204 Deleted Errors: 401 403 404 429