REST API

Para provides a REST API for programmatic access to your data. The API uses API key authentication and returns JSON responses.

Interactive Documentation

Visit Swagger UI for interactive API documentation where you can try requests directly.

Authentication

All API requests require an X-Api-Key header with a valid API key.

Creating an API Key

  1. Go to Preferences (from the account dropdown)
  2. Scroll to API Keys
  3. Enter a name and optional expiration date
  4. Click Generate
  5. Copy the key immediately — it is only shown once

The key format is para_ followed by 64 hex characters.

Using the Key

curl -H "X-Api-Key: para_your_key_here" https://your-domain.com/api/v1/para_items

Rate Limiting

API requests are limited to 100 requests per minute per API key. When exceeded, the API returns 429 Too Many Requests with a Retry-After header indicating when to retry.

Endpoints

List ParaItems

GET /api/v1/para_items

Returns a paginated list of ParaItems you own or have access to via collaboration.

Parameters:

Parameter Type Default Description
page integer 1 Page number (minimum 1)
per_page integer 25 Items per page (1–100)
category string Filter by category: project, area, resource, archive
trashed string false Set to true to return trashed items instead

Response headers:

Header Description
X-Total-Count Total items matching the query
X-Page Current page number
X-Per-Page Items per page
Link Pagination links (next, prev, first, last)

Example response:

[
  {
    "id": 1,
    "title": "My Project",
    "category": "project",
    "description": "A project description",
    "trashed_at": null,
    "created_at": "2026-04-22T10:00:00.000Z",
    "updated_at": "2026-04-22T15:30:00.000Z"
  }
]

Get ParaItem

GET /api/v1/para_items/:id

Returns a single ParaItem with resource counts. Accessible by owner and collaborators.

Example response:

{
  "id": 1,
  "title": "My Project",
  "category": "project",
  "description": "A project description",
  "url": "https://example.com",
  "trashed_at": null,
  "created_at": "2026-04-22T10:00:00.000Z",
  "updated_at": "2026-04-22T15:30:00.000Z",
  "tasks_count": 5,
  "notes_count": 3,
  "files_count": 2,
  "bookmarks_count": 1,
  "collaborators_count": 2
}

Create ParaItem

POST /api/v1/para_items

Creates a new ParaItem owned by the authenticated user.

Parameters:

Parameter Type Required Description
title string Yes Item title
category string Yes project, area, resource, or archive
description string No Plain text description
description_html string No HTML description (takes precedence over description)
url string No Optional URL

Response: 201 Created with ParaItemDetail body.

Update ParaItem

PATCH /api/v1/para_items/:id

Updates a ParaItem. Owner only — collaborators receive 403 Forbidden.

Parameters: Same as Create (all optional).

Response: 200 OK with ParaItemDetail body.

Trash ParaItem

DELETE /api/v1/para_items/:id

Soft-deletes (trashes) a ParaItem. Owner only. Trashed items are auto-purged after 7 days.

Response: 204 No Content

Restore ParaItem

PATCH /api/v1/para_items/:id/restore

Restores a trashed ParaItem. Owner only. Returns 404 if the item is not trashed.

Response: 200 OK with ParaItemDetail body.

Archive ParaItem

PATCH /api/v1/para_items/:id/archive

Archives a ParaItem, storing its previous category. Owner only. Returns 422 if already archived.

Response: 200 OK with ParaItemDetail body.

Unarchive ParaItem

PATCH /api/v1/para_items/:id/unarchive

Restores a ParaItem from archive to its previous category. Owner only. Returns 422 if not archived.

Response: 200 OK with ParaItemDetail body.

Error Responses

Status Meaning
401 Missing, invalid, or expired API key
404 Resource not found or not accessible
422 Validation error
429 Rate limit exceeded

Error bodies follow this format:

{
  "error": "Error message here"
}

List Journal Entries

GET /api/v1/para_items/:para_item_id/journal_entries

Returns a paginated list of journal entries for a ParaItem, ordered newest first. Includes both manual entries and system-generated entries (task created, status changed, etc.).

Parameters:

Parameter Type Default Description
para_item_id integer Required. ParaItem ID (in URL path)
page integer 1 Page number (minimum 1)
per_page integer 25 Items per page (1–100)

Example response:

[
  {
    "id": 42,
    "entry_type": "manual",
    "content": "Made progress on the login feature",
    "content_html": "<div class=\"trix-content\"><p>Made progress on the login feature</p></div>",
    "user_email": "user@example.com",
    "metadata": {},
    "created_at": "2026-04-22T10:00:00.000Z",
    "updated_at": "2026-04-22T10:00:00.000Z"
  }
]

Create Journal Entry

POST /api/v1/para_items/:para_item_id/journal_entries

Creates a manual journal entry. Provide either body (plain text) or body_html (HTML). If both are sent, body_html takes precedence. Collaborators are notified automatically.

Parameters:

Parameter Type Description
para_item_id integer Required. ParaItem ID (in URL path)
body string Plain text content
body_html string HTML content (takes precedence over body)

At least one of body or body_html is required.

Example request:

curl -X POST \
  -H "X-Api-Key: para_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"body": "Completed the database migration"}' \
  https://your-domain.com/api/v1/para_items/1/journal_entries

Response: 201 Created with the created journal entry JSON.

List Directories

GET /api/v1/para_items/:para_item_id/directories

Returns directories within a ParaItem. Omit parent_id to list root directories; provide it to list children of a specific directory.

Parameters:

Parameter Type Default Description
para_item_id integer Required. ParaItem ID (in URL path)
parent_id integer Filter to children of this directory. Omit for root directories

Example response:

[
  {
    "id": 1,
    "name": "Design Documents",
    "parent_id": null,
    "children_count": 2,
    "contents_count": 5,
    "created_at": "2026-04-22T10:00:00.000Z",
    "updated_at": "2026-04-22T10:00:00.000Z"
  }
]

Show Directory

GET /api/v1/para_items/:para_item_id/directories/:id

Returns a single directory with child and content counts.

Example response:

{
  "id": 1,
  "name": "Design Documents",
  "parent_id": null,
  "children_count": 2,
  "contents_count": 5,
  "created_at": "2026-04-22T10:00:00.000Z",
  "updated_at": "2026-04-22T10:00:00.000Z"
}

Create Directory

POST /api/v1/para_items/:para_item_id/directories

Creates a new directory. Nest it inside another directory by providing parent_id.

Parameters:

Parameter Type Description
para_item_id integer Required. ParaItem ID (in URL path)
name string Required. Directory name
parent_id integer Parent directory ID. Omit or null for root

Example request:

curl -X POST \
  -H "X-Api-Key: para_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"name": "Design Documents"}' \
  https://your-domain.com/api/v1/para_items/1/directories

Response: 201 Created with the created directory JSON.

Update Directory

PATCH /api/v1/para_items/:para_item_id/directories/:id

Renames a directory.

Parameters:

Parameter Type Description
name string Required. New directory name

Example request:

curl -X PATCH \
  -H "X-Api-Key: para_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"name": "Renamed Directory"}' \
  https://your-domain.com/api/v1/para_items/1/directories/1

Response: 200 OK with the updated directory JSON.

Delete Directory

DELETE /api/v1/para_items/:para_item_id/directories/:id

Deletes a directory and all its contents recursively (subdirectories, notes, files, bookmarks).

Example request:

curl -X DELETE \
  -H "X-Api-Key: para_your_key_here" \
  https://your-domain.com/api/v1/para_items/1/directories/1

Response: 204 No Content.

Move Directory

PATCH /api/v1/para_items/:para_item_id/directories/:id/move

Moves a directory to a new parent within the same ParaItem.

Parameters:

Parameter Type Description
parent_id integer Target parent directory ID. Use null to move to root

Example request:

curl -X PATCH \
  -H "X-Api-Key: para_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"parent_id": 2}' \
  https://your-domain.com/api/v1/para_items/1/directories/1/move

Response: 200 OK with the updated directory JSON.


List Notes

GET /api/v1/para_items/:para_item_id/notes

Returns notes within a ParaItem, optionally filtered by directory.

Parameters:

Parameter Type Default Description
para_item_id integer Required. ParaItem ID (in URL path)
directory_id integer Filter to notes in this directory

Example response:

[
  {
    "id": 1,
    "title": "Meeting Notes",
    "content_html": "<div class=\"trix-content\"><p>Discussion points...</p></div>",
    "directory_id": null,
    "tags": ["important", "meeting"],
    "file_urls": [],
    "created_at": "2026-04-22T10:00:00.000Z",
    "updated_at": "2026-04-22T10:00:00.000Z"
  }
]

Show Note

GET /api/v1/para_items/:para_item_id/notes/:id

Returns a single note with rendered HTML content, tags, and attached file URLs.

Example response:

{
  "id": 1,
  "title": "Meeting Notes",
  "content_html": "<div class=\"trix-content\"><p>Discussion points...</p></div>",
  "directory_id": null,
  "tags": ["important", "meeting"],
  "file_urls": [
    "https://your-domain.com/rails/active_storage/blobs/redirect/signed-id/attachment.pdf"
  ],
  "created_at": "2026-04-22T10:00:00.000Z",
  "updated_at": "2026-04-22T10:00:00.000Z"
}

Create Note

POST /api/v1/para_items/:para_item_id/notes

Creates a new note. Provide either content (plain text) or content_html (HTML). Attach files via multipart form data.

Parameters:

Parameter Type Description
para_item_id integer Required. ParaItem ID (in URL path)
title string Required. Note title
content string Plain text content
content_html string HTML content (takes precedence over content)
directory_id integer Directory ID. Omit or null for root
files[] file Attached files (multipart/form-data, repeatable)

Example request:

curl -X POST \
  -H "X-Api-Key: para_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"title": "Meeting Notes", "content": "Discussion points from today"}' \
  https://your-domain.com/api/v1/para_items/1/notes

Example with file attachments:

curl -X POST \
  -H "X-Api-Key: para_your_key_here" \
  -F "title=Meeting Notes" \
  -F "content=Discussion points" \
  -F "files[]=@attachment.pdf" \
  https://your-domain.com/api/v1/para_items/1/notes

Response: 201 Created with the created note JSON.

Update Note

PATCH /api/v1/para_items/:para_item_id/notes/:id

Updates a note's title, content, or attachments.

Parameters:

Parameter Type Description
title string Note title
content string Plain text content
content_html string HTML content (takes precedence over content)
files[] file Attached files (multipart/form-data, repeatable)

Example request:

curl -X PATCH \
  -H "X-Api-Key: para_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"title": "Updated Title"}' \
  https://your-domain.com/api/v1/para_items/1/notes/1

Response: 200 OK with the updated note JSON.

Delete Note

DELETE /api/v1/para_items/:para_item_id/notes/:id

Response: 204 No Content.

Move Note

PATCH /api/v1/para_items/:para_item_id/notes/:id/move

Moves a note to a different directory within the same ParaItem.

Parameters:

Parameter Type Description
directory_id integer Target directory ID. Use null to move to root

Response: 200 OK with the updated note JSON.

Transfer Note

PATCH /api/v1/para_items/:para_item_id/notes/:id/transfer

Transfers a note to a different ParaItem you have access to. Journal entries are created in both the source and destination.

Parameters:

Parameter Type Description
destination_para_item_id integer Required. Target ParaItem ID

Response: 200 OK with the updated note JSON.


List Files

GET /api/v1/para_items/:para_item_id/files

Returns files within a ParaItem, optionally filtered by directory or extension.

Parameters:

Parameter Type Default Description
para_item_id integer Required. ParaItem ID (in URL path)
directory_id integer Filter to files in this directory
extension string Filter by file extension (e.g. pdf, png)

Example response:

[
  {
    "id": 1,
    "name": "architecture.pdf",
    "directory_id": null,
    "download_url": "https://your-domain.com/rails/active_storage/blobs/redirect/signed-id/architecture.pdf",
    "byte_size": 204800,
    "content_type": "application/pdf",
    "tags": ["documentation"],
    "created_at": "2026-04-22T10:00:00.000Z",
    "updated_at": "2026-04-22T10:00:00.000Z"
  }
]

Show File

GET /api/v1/para_items/:para_item_id/files/:id

Returns a single file with its download URL, metadata, and tags.

Example response:

{
  "id": 1,
  "name": "architecture.pdf",
  "directory_id": null,
  "download_url": "https://your-domain.com/rails/active_storage/blobs/redirect/signed-id/architecture.pdf",
  "byte_size": 204800,
  "content_type": "application/pdf",
  "tags": ["documentation"],
  "created_at": "2026-04-22T10:00:00.000Z",
  "updated_at": "2026-04-22T10:00:00.000Z"
}

Upload File

POST /api/v1/para_items/:para_item_id/files

Uploads a file using multipart form data.

Parameters:

Parameter Type Description
para_item_id integer Required. ParaItem ID (in URL path)
file file Required. The file to upload (multipart/form-data)
name string Display name. Defaults to the uploaded filename
directory_id integer Directory ID. Omit or null for root

Example request:

curl -X POST \
  -H "X-Api-Key: para_your_key_here" \
  -F "file=@architecture.pdf" \
  -F "directory_id=1" \
  https://your-domain.com/api/v1/para_items/1/files

Response: 201 Created with the created file JSON.

Update File

PATCH /api/v1/para_items/:para_item_id/files/:id

Renames a file.

Parameters:

Parameter Type Description
name string New display name

Response: 200 OK with the updated file JSON.

Delete File

DELETE /api/v1/para_items/:para_item_id/files/:id

Response: 204 No Content.

Move File

PATCH /api/v1/para_items/:para_item_id/files/:id/move

Moves a file to a different directory within the same ParaItem.

Parameters:

Parameter Type Description
directory_id integer Target directory ID. Use null to move to root

Response: 200 OK with the updated file JSON.

Transfer File

PATCH /api/v1/para_items/:para_item_id/files/:id/transfer

Transfers a file to a different ParaItem you have access to.

Parameters:

Parameter Type Description
destination_para_item_id integer Required. Target ParaItem ID

Response: 200 OK with the updated file JSON.


List Bookmarks

GET /api/v1/para_items/:para_item_id/bookmarks

Returns bookmarks within a ParaItem, optionally filtered by directory.

Parameters:

Parameter Type Default Description
para_item_id integer Required. ParaItem ID (in URL path)
directory_id integer Filter to bookmarks in this directory

Example response:

[
  {
    "id": 1,
    "url": "https://example.com/article",
    "title": "Interesting Article",
    "description": "An article about software architecture",
    "favicon_url": "https://example.com/favicon.ico",
    "directory_id": null,
    "fetch_status": "completed",
    "comments_html": "<div class=\"trix-content\"><p>Great read</p></div>",
    "singlefile_archive_status": "completed",
    "readability_archive_status": "completed",
    "video_archive_status": "not_applicable",
    "created_at": "2026-04-22T10:00:00.000Z",
    "updated_at": "2026-04-22T10:00:00.000Z"
  }
]

Show Bookmark

GET /api/v1/para_items/:para_item_id/bookmarks/:id

Returns a single bookmark with comments, fetch status, and archive statuses.

Example response:

{
  "id": 1,
  "url": "https://example.com/article",
  "title": "Interesting Article",
  "description": "An article about software architecture",
  "favicon_url": "https://example.com/favicon.ico",
  "directory_id": null,
  "fetch_status": "completed",
  "comments_html": "<div class=\"trix-content\"><p>Great read</p></div>",
  "singlefile_archive_status": "completed",
  "readability_archive_status": "completed",
  "video_archive_status": "not_applicable",
  "created_at": "2026-04-22T10:00:00.000Z",
  "updated_at": "2026-04-22T10:00:00.000Z"
}

Create Bookmark

POST /api/v1/para_items/:para_item_id/bookmarks

Creates a new bookmark. Metadata is fetched automatically and all applicable archive jobs are enqueued.

Parameters:

Parameter Type Description
para_item_id integer Required. ParaItem ID (in URL path)
url string Required. The URL to bookmark
title string Display title. Auto-fetched from URL if omitted
comments string Plain text comments
comments_html string HTML comments (takes precedence over comments)
directory_id integer Directory ID. Omit or null for root

Example request:

curl -X POST \
  -H "X-Api-Key: para_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/article", "comments": "Worth reading"}' \
  https://your-domain.com/api/v1/para_items/1/bookmarks

Response: 201 Created with the created bookmark JSON.

Update Bookmark

PATCH /api/v1/para_items/:para_item_id/bookmarks/:id

Updates a bookmark's URL, title, or comments.

Parameters:

Parameter Type Description
url string The bookmark URL
title string Display title
comments string Plain text comments
comments_html string HTML comments (takes precedence over comments)

Response: 200 OK with the updated bookmark JSON.

Delete Bookmark

DELETE /api/v1/para_items/:para_item_id/bookmarks/:id

Response: 204 No Content.

Move Bookmark

PATCH /api/v1/para_items/:para_item_id/bookmarks/:id/move

Moves a bookmark to a different directory within the same ParaItem.

Parameters:

Parameter Type Description
directory_id integer Target directory ID. Use null to move to root

Response: 200 OK with the updated bookmark JSON.

Transfer Bookmark

PATCH /api/v1/para_items/:para_item_id/bookmarks/:id/transfer

Transfers a bookmark to a different ParaItem you have access to.

Parameters:

Parameter Type Description
destination_para_item_id integer Required. Target ParaItem ID

Response: 200 OK with the updated bookmark JSON.


Time Tracking

List Time Entries

GET /api/v1/time_entries

Returns a paginated list of time entries accessible to the authenticated user
(own entries with no para_item_id, plus any entries on accessible ParaItems),
ordered by started_at descending.

Query parameters:

  • para_item_id — Filter by ParaItem
  • task_id — Filter by Task
  • running"true" returns only running entries; "false" returns only completed entries
  • from, to — ISO 8601 date/time bounds (must be combined)
  • page, per_page — Pagination (default 25, max 100)

Response: 200 OK with array of TimeEntry objects and pagination headers (X-Total-Count, X-Page, X-Per-Page, Link).

Get Time Entry

GET /api/v1/time_entries/:id

Response: 200 OK with TimeEntry JSON, or 404 if not found / not accessible.

Create Time Entry

POST /api/v1/time_entries

Body:

{
  "description": "Designing the dashboard",
  "started_at": "2026-04-29T09:00:00Z",
  "stopped_at": "2026-04-29T10:30:00Z",
  "para_item_id": 42,
  "task_id": 7,
  "notes": "Wireframes done"
}

description and started_at are required. Omit stopped_at to create a running entry.

Response: 201 Created with TimeEntry JSON, or 422 for validation errors / inaccessible para_item_id.

Start Time Entry

POST /api/v1/time_entries/start

Convenience endpoint that creates a running entry with started_at set to "now".
Only one running entry per user is permitted.

Body: description (required), para_item_id, task_id, notes (all optional).

Response: 201 Created with TimeEntry JSON, or 422 if another entry is already running.

Update Time Entry

PATCH /api/v1/time_entries/:id

Updates a time entry. Only the entry's owner may update it — collaborators on the same para_item receive 403.

Body: Any subset of description, started_at, stopped_at, para_item_id, task_id, notes.

Response: 200 OK with TimeEntry JSON.

Stop Time Entry

PATCH /api/v1/time_entries/:id/stop

Sets stopped_at to the current time on a running entry. Idempotent: already-stopped entries are returned unchanged. Owner only.

Response: 200 OK with TimeEntry JSON.

Delete Time Entry

DELETE /api/v1/time_entries/:id

Permanently destroys a time entry. Owner only.

Response: 204 No Content

Get Current Running Entry

GET /api/v1/time_entries/current

Returns the currently-running time entry for the authenticated user, or null if no entry is running.

Response: 200 OK with TimeEntry JSON or null.

Time Reports

GET /api/v1/time_entries/reports

Aggregates completed time entries (with stopped_at set) into daily totals and per-project totals.

Query parameters:

  • para_item_id — Filter by ParaItem
  • from, to — ISO 8601 date bounds (default: last 30 days)

Response:

{
  "from": "2026-04-01",
  "to": "2026-04-30",
  "total_seconds": 123456,
  "daily": [
    { "date": "2026-04-01", "seconds": 3600 }
  ],
  "by_project": [
    { "para_item_id": 42, "title": "Project X", "seconds": 7200 },
    { "para_item_id": null, "title": null, "seconds": 600 }
  ]
}

List Time Entries for a ParaItem

GET /api/v1/para_items/:para_item_id/time_entries

Same as GET /api/v1/time_entries, scoped to the given ParaItem.

Create Time Entry for a ParaItem

POST /api/v1/para_items/:para_item_id/time_entries

Same as POST /api/v1/time_entries but para_item_id is taken from the URL path.


Key Management

  • Each user can have up to 5 API keys
  • Keys can have an optional expiration date
  • Delete unused keys from Preferences at any time
  • Keys are stored as SHA-256 hashes — Para cannot recover a lost key