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
- Go to Preferences (from the account dropdown)
- Scroll to API Keys
- Enter a name and optional expiration date
- Click Generate
- 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 ParaItemtask_id— Filter by Taskrunning—"true"returns only running entries;"false"returns only completed entriesfrom,to— ISO 8601 date/time bounds (must be combined)page,per_page— Pagination (default25, max100)
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 ParaItemfrom,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