API
Workjournal publishes an OpenAPI 3.1 specification covering a curated
16-operation subset of the REST API. It powers the public
Workjournal Custom GPT in ChatGPT and is available to any
HTTP client that speaks standard OAuth 2.0.
OpenAPI 3.1 spec https://api.workjournal.pro/openapi.json public, no auth required
Need the full ~35-tool catalog? See the MCP server.
Authentication
Workjournal does not issue managed long-term API keys or static bearer tokens.
Every client authenticates via OAuth with PKCE; the access tokens it receives
are short-lived (issued and refreshed by Supabase). Revoking access means signing
out — not rotating a leaked secret.
| Authorization URL | https://auth.workjournal.pro/auth/v1/oauth/authorize |
| Token URL | https://auth.workjournal.pro/auth/v1/oauth/token |
| Scopes | email |
| Grant types | authorization_code, refresh_token |
| PKCE | Required (S256) |
| Discovery | https://auth.workjournal.pro/.well-known/oauth-authorization-server |
API reference
Base URL: https://api.workjournal.pro/v1. Every endpoint takes
Authorization: Bearer <access_token>.
Workspaces
listWorkspaces
GET /workspaces
Return every workspace the authenticated user owns. Most users have exactly one (their personal workspace). Use the `slug` field on each row when calling other operations that take `workspaceSlug`.
Responses
200 Array of workspace objects.
| Field | Type | Description |
data | object[] | |
data[].id | string | |
data[].slug | string | URL-safe workspace identifier. |
data[].name | string | |
data[].owner_id | string | |
data[].tier_code | string | Pricing tier code: free, plus, pro, admin. |
data[].created_at | string | |
- 401 Missing or invalid bearer token. The user must reauthenticate.
Journals
listJournals
GET /workspaces/{workspaceSlug}/journals
Return every journal in the workspace the caller can see (as owner or contributor). For journals shared from other workspaces use listSharedJournals.
Parameters
| Name | In | Type | Required | Description |
workspaceSlug | path | string pattern: `^[a-z0-9][a-z0-9-]{0,29}$` | yes | URL-safe workspace identifier. Find it via listWorkspaces (the `slug` field on each row). For most users this is their personal workspace. |
Responses
200 Paginated list of journal objects.
| Field | Type | Description |
data | object[] | |
data[].id | string | |
data[].slug | string | URL-safe journal identifier, unique within the workspace. |
data[].name | string | |
data[].workspace_id | string | |
data[].description | string | null | |
data[].is_public | boolean | |
data[].public_slug | string | null | |
data[].created_at | string | |
data[].contributor | object | The caller's contributor row on this journal. |
data[].prompt | object | Active writing prompt for this journal. Returned on getJournal only. |
total | integer | |
offset | integer | |
limit | integer | |
- 401 Missing or invalid bearer token. The user must reauthenticate.
- 403 The user is authenticated but not authorised for this resource (e.g. not a contributor on the journal).
- 404 The requested resource does not exist or is not visible to the caller.
createJournal
POST /workspaces/{workspaceSlug}/journals
Create a new journal in the workspace. The slug is derived from the name (kebab-case) unless explicitly provided. The caller becomes the owner.
Parameters
| Name | In | Type | Required | Description |
workspaceSlug | path | string pattern: `^[a-z0-9][a-z0-9-]{0,29}$` | yes | URL-safe workspace identifier. Find it via listWorkspaces (the `slug` field on each row). For most users this is their personal workspace. |
Request body
Journal creation payload.
| Field | Type | Required | Description |
name | string length 1–20 | yes | |
description | string | no | |
slug | string pattern: `^[a-z0-9][a-z0-9-]{0,29}$` | no | Optional. Derived from name if omitted. |
Responses
201 The created journal.
| Field | Type | Description |
id | string | |
slug | string | URL-safe journal identifier, unique within the workspace. |
name | string | |
workspace_id | string | |
description | string | null | |
is_public | boolean | |
public_slug | string | null | |
created_at | string | |
contributor | object | The caller's contributor row on this journal. |
contributor.role | "owner" | "contributor" | |
contributor.created_at | string | |
prompt | object | Active writing prompt for this journal. Returned on getJournal only. |
prompt.source | "default" | "custom" | Whether this is the system default prompt or a custom one assigned to the journal. |
prompt.name | string | null | |
prompt.slug | string | null | |
prompt.body | string | Markdown prompt body. Treat as the writing instruction for entries in this journal. |
- 401 Missing or invalid bearer token. The user must reauthenticate.
- 402 The requested operation requires a higher tier (Plus or Pro). Surface the message verbatim to the user.
- 403 The user is authenticated but not authorised for this resource (e.g. not a contributor on the journal).
- 409 The operation conflicts with the current state (e.g. duplicate slug, tag already assigned).
- 422 The request body or query parameters failed validation. Inspect `error.message` for the field.
getJournal
GET /workspaces/{workspaceSlug}/journals/{journalSlug}
Return a single journal including its active writing prompt. Read `prompt.body` and apply it as the writing instruction when calling createEntry — it dictates structure, voice, and what sections to include.
Parameters
| Name | In | Type | Required | Description |
workspaceSlug | path | string pattern: `^[a-z0-9][a-z0-9-]{0,29}$` | yes | URL-safe workspace identifier. Find it via listWorkspaces (the `slug` field on each row). For most users this is their personal workspace. |
journalSlug | path | string pattern: `^[a-z0-9][a-z0-9-]{0,29}$` | yes | URL-safe journal identifier within the workspace. Find it via listJournals (the `slug` field on each row). |
Responses
200 Journal object including the resolved active prompt.
| Field | Type | Description |
id | string | |
slug | string | URL-safe journal identifier, unique within the workspace. |
name | string | |
workspace_id | string | |
description | string | null | |
is_public | boolean | |
public_slug | string | null | |
created_at | string | |
contributor | object | The caller's contributor row on this journal. |
contributor.role | "owner" | "contributor" | |
contributor.created_at | string | |
prompt | object | Active writing prompt for this journal. Returned on getJournal only. |
prompt.source | "default" | "custom" | Whether this is the system default prompt or a custom one assigned to the journal. |
prompt.name | string | null | |
prompt.slug | string | null | |
prompt.body | string | Markdown prompt body. Treat as the writing instruction for entries in this journal. |
- 401 Missing or invalid bearer token. The user must reauthenticate.
- 403 The user is authenticated but not authorised for this resource (e.g. not a contributor on the journal).
- 404 The requested resource does not exist or is not visible to the caller.
listSharedJournals
GET /shared-with-me/journals
Return journals the user has been invited into across all workspaces. Each row carries its real `workspace.slug` — use that as `workspaceSlug` for follow-up calls; the slug `shared-with-me` is not a real workspace.
Responses
200 Array of journals shared with the user, with workspace and owner context.
| Field | Type | Description |
data | object[] | |
data[].journal | object | |
data[].workspace | object | |
data[].owner | object | |
data[].role | "owner" | "contributor" | |
data[].created_at | string | |
- 401 Missing or invalid bearer token. The user must reauthenticate.
exportJournal
GET /workspaces/{workspaceSlug}/journals/{journalSlug}/export
Return every entry in the journal as a single payload. JSON includes metadata; md/csv are flat dumps suitable for sharing or backup. Capped at 10000 entries with a `truncated` flag.
Parameters
| Name | In | Type | Required | Description |
workspaceSlug | path | string pattern: `^[a-z0-9][a-z0-9-]{0,29}$` | yes | URL-safe workspace identifier. Find it via listWorkspaces (the `slug` field on each row). For most users this is their personal workspace. |
journalSlug | path | string pattern: `^[a-z0-9][a-z0-9-]{0,29}$` | yes | URL-safe journal identifier within the workspace. Find it via listJournals (the `slug` field on each row). |
format | query | "json" | "md" | "csv" | no | Output format. `json` (default) returns a structured object with the journal + entries array. `md` and `csv` return file payloads with Content-Disposition: attachment. |
Responses
200 Export payload. JSON returns the structured object below; md and csv return file payloads with Content-Disposition: attachment.
| Field | Type | Description |
journal | object | |
journal.id | string | |
journal.slug | string | URL-safe journal identifier, unique within the workspace. |
journal.name | string | |
journal.workspace_id | string | |
journal.description | string | null | |
journal.is_public | boolean | |
journal.public_slug | string | null | |
journal.created_at | string | |
journal.contributor | object | The caller's contributor row on this journal. |
journal.prompt | object | Active writing prompt for this journal. Returned on getJournal only. |
entries | object[] | |
entries[].id | string | |
entries[].journal_id | string | |
entries[].index | integer | Per-journal monotonic entry number. The canonical identifier for getEntry / updateEntry / deleteEntry. |
entries[].title | string | |
entries[].summary | string | |
entries[].body | string | Full markdown body. Only present on getEntry, listEntries with include=body, and createEntry/updateEntry responses. |
entries[].created_at | string | |
entries[].created_by | string | null | |
entries[].client | string | null | Free-form label of the client that wrote the entry (e.g. "chatgpt", "claude-code", "cli"). |
entries[].tags | string[] | Tag names attached to this entry. Each must be assigned to the journal. |
truncated | boolean | |
total_entries | integer | |
- 401 Missing or invalid bearer token. The user must reauthenticate.
- 403 The user is authenticated but not authorised for this resource (e.g. not a contributor on the journal).
- 404 The requested resource does not exist or is not visible to the caller.
Entries
listEntries
GET /workspaces/{workspaceSlug}/journals/{journalSlug}/entries
Return entries in this journal, newest first. Default is slim rows (title + summary); pass include=body for full markdown. Filter by tag with one or more ?tag= params (AND semantics). Use limit to bound the response.
Parameters
| Name | In | Type | Required | Description |
workspaceSlug | path | string pattern: `^[a-z0-9][a-z0-9-]{0,29}$` | yes | URL-safe workspace identifier. Find it via listWorkspaces (the `slug` field on each row). For most users this is their personal workspace. |
journalSlug | path | string pattern: `^[a-z0-9][a-z0-9-]{0,29}$` | yes | URL-safe journal identifier within the workspace. Find it via listJournals (the `slug` field on each row). |
limit | query | integer min 1, max 100 | no | Max entries to return. Default 50, max 100. For "last N" queries pass the N here. |
offset | query | integer min 0 | no | Pagination offset. Combined with limit for paging. |
include | query | string | no | Comma-separated extras. Currently only `body` is honoured; pass it to include the full markdown body on each entry. |
tag | query | string[] | no | Tag name to filter on. Repeat for AND semantics (all listed tags must be present). Comma-separated values also work: ?tag=a,b is equivalent to ?tag=a&tag=b. |
Responses
200 Paginated list of entries.
| Field | Type | Description |
data | object[] | |
data[].id | string | |
data[].journal_id | string | |
data[].index | integer | Per-journal monotonic entry number. The canonical identifier for getEntry / updateEntry / deleteEntry. |
data[].title | string | |
data[].summary | string | |
data[].body | string | Full markdown body. Only present on getEntry, listEntries with include=body, and createEntry/updateEntry responses. |
data[].created_at | string | |
data[].created_by | string | null | |
data[].client | string | null | Free-form label of the client that wrote the entry (e.g. "chatgpt", "claude-code", "cli"). |
data[].tags | string[] | Tag names attached to this entry. Each must be assigned to the journal. |
total | integer | |
offset | integer | |
limit | integer | |
- 401 Missing or invalid bearer token. The user must reauthenticate.
- 403 The user is authenticated but not authorised for this resource (e.g. not a contributor on the journal).
- 404 The requested resource does not exist or is not visible to the caller.
createEntry no-confirm
POST /workspaces/{workspaceSlug}/journals/{journalSlug}/entries
The primary write op. Title is a short outcome line (≤80 chars). Summary is 1-3 sentences a future reader can scan (≤500 chars). Body is full markdown. Tags must already be assigned to this journal (Plus+).
Parameters
| Name | In | Type | Required | Description |
workspaceSlug | path | string pattern: `^[a-z0-9][a-z0-9-]{0,29}$` | yes | URL-safe workspace identifier. Find it via listWorkspaces (the `slug` field on each row). For most users this is their personal workspace. |
journalSlug | path | string pattern: `^[a-z0-9][a-z0-9-]{0,29}$` | yes | URL-safe journal identifier within the workspace. Find it via listJournals (the `slug` field on each row). |
Request body
Entry payload. Follow the journal's prompt.body for structure and voice — fetch it via getJournal.
| Field | Type | Required | Description |
title | string length 1–80 | yes | |
summary | string length 1–500 | yes | |
body | string length 1–∞ | yes | Markdown body. No length cap. |
client | string | no | Optional client label. Defaults to the OAuth client name (chatgpt for Custom GPT calls). |
tags | string[] | no | Tag names. Each must be currently assigned to the journal. Plus+ tier only. |
Responses
201 The created entry, including its assigned `index`.
| Field | Type | Description |
id | string | |
journal_id | string | |
index | integer | Per-journal monotonic entry number. The canonical identifier for getEntry / updateEntry / deleteEntry. |
title | string | |
summary | string | |
body | string | Full markdown body. Only present on getEntry, listEntries with include=body, and createEntry/updateEntry responses. |
created_at | string | |
created_by | string | null | |
client | string | null | Free-form label of the client that wrote the entry (e.g. "chatgpt", "claude-code", "cli"). |
tags | string[] | Tag names attached to this entry. Each must be assigned to the journal. |
- 401 Missing or invalid bearer token. The user must reauthenticate.
- 402 The requested operation requires a higher tier (Plus or Pro). Surface the message verbatim to the user.
- 403 The user is authenticated but not authorised for this resource (e.g. not a contributor on the journal).
- 404 The requested resource does not exist or is not visible to the caller.
- 422 The request body or query parameters failed validation. Inspect `error.message` for the field.
getEntry
GET /workspaces/{workspaceSlug}/journals/{journalSlug}/entries/{index}
Return the full entry (title + summary + markdown body + tags + metadata) for the given per-journal index.
Parameters
| Name | In | Type | Required | Description |
workspaceSlug | path | string pattern: `^[a-z0-9][a-z0-9-]{0,29}$` | yes | URL-safe workspace identifier. Find it via listWorkspaces (the `slug` field on each row). For most users this is their personal workspace. |
journalSlug | path | string pattern: `^[a-z0-9][a-z0-9-]{0,29}$` | yes | URL-safe journal identifier within the workspace. Find it via listJournals (the `slug` field on each row). |
index | path | integer min 1 | yes | Per-journal monotonic entry number, as returned by listEntries or createEntry. Not a UUID. |
Responses
200 The entry object.
| Field | Type | Description |
id | string | |
journal_id | string | |
index | integer | Per-journal monotonic entry number. The canonical identifier for getEntry / updateEntry / deleteEntry. |
title | string | |
summary | string | |
body | string | Full markdown body. Only present on getEntry, listEntries with include=body, and createEntry/updateEntry responses. |
created_at | string | |
created_by | string | null | |
client | string | null | Free-form label of the client that wrote the entry (e.g. "chatgpt", "claude-code", "cli"). |
tags | string[] | Tag names attached to this entry. Each must be assigned to the journal. |
- 401 Missing or invalid bearer token. The user must reauthenticate.
- 403 The user is authenticated but not authorised for this resource (e.g. not a contributor on the journal).
- 404 The requested resource does not exist or is not visible to the caller.
updateEntry
PATCH /workspaces/{workspaceSlug}/journals/{journalSlug}/entries/{index}
Partial update — only the supplied fields change. Sending tags replaces the full tag set on the entry; pass an empty array to clear. Confirms with the user before firing (consequential).
Parameters
| Name | In | Type | Required | Description |
workspaceSlug | path | string pattern: `^[a-z0-9][a-z0-9-]{0,29}$` | yes | URL-safe workspace identifier. Find it via listWorkspaces (the `slug` field on each row). For most users this is their personal workspace. |
journalSlug | path | string pattern: `^[a-z0-9][a-z0-9-]{0,29}$` | yes | URL-safe journal identifier within the workspace. Find it via listJournals (the `slug` field on each row). |
index | path | integer min 1 | yes | Per-journal monotonic entry number, as returned by listEntries or createEntry. Not a UUID. |
Request body
Partial entry payload. Any omitted field is left unchanged.
| Field | Type | Required | Description |
title | string length 1–80 | no | |
summary | string length 1–500 | no | |
body | string length 1–∞ | no | |
tags | string[] | no | Replaces the entry's tag set. Pass [] to clear all tags. |
Responses
200 The updated entry.
| Field | Type | Description |
id | string | |
journal_id | string | |
index | integer | Per-journal monotonic entry number. The canonical identifier for getEntry / updateEntry / deleteEntry. |
title | string | |
summary | string | |
body | string | Full markdown body. Only present on getEntry, listEntries with include=body, and createEntry/updateEntry responses. |
created_at | string | |
created_by | string | null | |
client | string | null | Free-form label of the client that wrote the entry (e.g. "chatgpt", "claude-code", "cli"). |
tags | string[] | Tag names attached to this entry. Each must be assigned to the journal. |
- 401 Missing or invalid bearer token. The user must reauthenticate.
- 402 The requested operation requires a higher tier (Plus or Pro). Surface the message verbatim to the user.
- 403 The user is authenticated but not authorised for this resource (e.g. not a contributor on the journal).
- 404 The requested resource does not exist or is not visible to the caller.
- 422 The request body or query parameters failed validation. Inspect `error.message` for the field.
deleteEntry
DELETE /workspaces/{workspaceSlug}/journals/{journalSlug}/entries/{index}
Permanently remove the entry. The per-journal index is not reused. Confirms with the user before firing (consequential).
Parameters
| Name | In | Type | Required | Description |
workspaceSlug | path | string pattern: `^[a-z0-9][a-z0-9-]{0,29}$` | yes | URL-safe workspace identifier. Find it via listWorkspaces (the `slug` field on each row). For most users this is their personal workspace. |
journalSlug | path | string pattern: `^[a-z0-9][a-z0-9-]{0,29}$` | yes | URL-safe journal identifier within the workspace. Find it via listJournals (the `slug` field on each row). |
index | path | integer min 1 | yes | Per-journal monotonic entry number, as returned by listEntries or createEntry. Not a UUID. |
Responses
- 401 Missing or invalid bearer token. The user must reauthenticate.
- 403 The user is authenticated but not authorised for this resource (e.g. not a contributor on the journal).
- 404 The requested resource does not exist or is not visible to the caller.
searchEntries
GET /workspaces/{workspaceSlug}/journals/{journalSlug}/entries/search
Search across title, summary, and body of every entry in this journal. Match is full-text and ranked. For "find anything about X" questions, prefer this over listEntries with manual filtering.
Parameters
| Name | In | Type | Required | Description |
workspaceSlug | path | string pattern: `^[a-z0-9][a-z0-9-]{0,29}$` | yes | URL-safe workspace identifier. Find it via listWorkspaces (the `slug` field on each row). For most users this is their personal workspace. |
journalSlug | path | string pattern: `^[a-z0-9][a-z0-9-]{0,29}$` | yes | URL-safe journal identifier within the workspace. Find it via listJournals (the `slug` field on each row). |
q | query | string length 1–∞ | yes | Search query. Non-empty. Multi-word queries match any token; use longer phrases for more specific results. |
limit | query | integer min 1, max 100 | no | Max results. Default 50, max 100. |
offset | query | integer min 0 | no | Pagination offset. |
Responses
200 Matching entries, newest match first.
| Field | Type | Description |
data | object[] | |
data[].id | string | |
data[].journal_id | string | |
data[].index | integer | Per-journal monotonic entry number. The canonical identifier for getEntry / updateEntry / deleteEntry. |
data[].title | string | |
data[].summary | string | |
data[].body | string | Full markdown body. Only present on getEntry, listEntries with include=body, and createEntry/updateEntry responses. |
data[].created_at | string | |
data[].created_by | string | null | |
data[].client | string | null | Free-form label of the client that wrote the entry (e.g. "chatgpt", "claude-code", "cli"). |
data[].tags | string[] | Tag names attached to this entry. Each must be assigned to the journal. |
total | integer | |
offset | integer | |
limit | integer | |
- 401 Missing or invalid bearer token. The user must reauthenticate.
- 403 The user is authenticated but not authorised for this resource (e.g. not a contributor on the journal).
- 404 The requested resource does not exist or is not visible to the caller.
- 422 The request body or query parameters failed validation. Inspect `error.message` for the field.
ragSearch no-confirm
POST /workspaces/{workspaceSlug}/journals/{journalSlug}/rag-search
Rank journal chunks by cosine similarity to a query embedding. Use when the caller asks about a concept rather than a keyword. Pro+ tier; Free/Plus get 402.
Parameters
| Name | In | Type | Required | Description |
workspaceSlug | path | string pattern: `^[a-z0-9][a-z0-9-]{0,29}$` | yes | URL-safe workspace identifier. Find it via listWorkspaces (the `slug` field on each row). For most users this is their personal workspace. |
journalSlug | path | string pattern: `^[a-z0-9][a-z0-9-]{0,29}$` | yes | URL-safe journal identifier within the workspace. Find it via listJournals (the `slug` field on each row). |
Request body
Query embedding payload. limit defaults to 10, capped at 50.
| Field | Type | Required | Description |
query | string length 1–2000 | yes | |
limit | integer min 1, max 50 | no | |
Responses
200 Ranked chunk results.
| Field | Type | Description |
data | object[] | |
data[].entry_id | string | |
data[].entry_index | integer | |
data[].title | string | |
data[].chunk_index | integer | |
data[].chunk_text | string | Raw text of the matching chunk. |
data[].similarity | number | Cosine similarity in [-1, 1]. Higher = closer match. |
data[].created_at | string | |
query | string | |
limit | integer | |
- 401 Missing or invalid bearer token. The user must reauthenticate.
- 402 The requested operation requires a higher tier (Plus or Pro). Surface the message verbatim to the user.
- 403 The user is authenticated but not authorised for this resource (e.g. not a contributor on the journal).
- 404 The requested resource does not exist or is not visible to the caller.
- 422 The request body or query parameters failed validation. Inspect `error.message` for the field.
listTags
GET /workspaces/{workspaceSlug}/tags
Return the workspace tag registry. Pass `journal_slug` to also include an `assigned` boolean on each row indicating whether the tag is usable on entries in that journal. Free workspaces have zero tags.
Parameters
| Name | In | Type | Required | Description |
workspaceSlug | path | string pattern: `^[a-z0-9][a-z0-9-]{0,29}$` | yes | URL-safe workspace identifier. Find it via listWorkspaces (the `slug` field on each row). For most users this is their personal workspace. |
journal_slug | query | string | no | Optional journal slug. When set, each tag row carries an `assigned` boolean. |
Responses
200 Workspace tag registry, optionally annotated with per-journal `assigned` status.
| Field | Type | Description |
data | object[] | |
data[].id | string | |
data[].workspace_id | string | |
data[].name | string | Kebab-case tag name (canonical, lowercased). |
data[].description | string | null | |
data[].created_by | string | |
data[].created_at | string | |
data[].assigned | boolean | Only present on listTags when ?journal_slug= is set. True if the tag is currently usable on entries in that journal. |
- 401 Missing or invalid bearer token. The user must reauthenticate.
- 403 The user is authenticated but not authorised for this resource (e.g. not a contributor on the journal).
- 404 The requested resource does not exist or is not visible to the caller.
createTag
POST /workspaces/{workspaceSlug}/tags
Create a workspace-level tag. Name is kebab-case, max 50 chars. After creation, assign the tag to one or more journals via assignTag before it can be attached to entries. Plus+ tier only.
Parameters
| Name | In | Type | Required | Description |
workspaceSlug | path | string pattern: `^[a-z0-9][a-z0-9-]{0,29}$` | yes | URL-safe workspace identifier. Find it via listWorkspaces (the `slug` field on each row). For most users this is their personal workspace. |
Request body
Tag definition.
| Field | Type | Required | Description |
name | string pattern: `^[a-z0-9][a-z0-9-]{0,49}$`, length 0–50 | yes | |
description | string length 0–200 | no | |
Responses
201 The created tag.
| Field | Type | Description |
id | string | |
workspace_id | string | |
name | string | Kebab-case tag name (canonical, lowercased). |
description | string | null | |
created_by | string | |
created_at | string | |
assigned | boolean | Only present on listTags when ?journal_slug= is set. True if the tag is currently usable on entries in that journal. |
- 401 Missing or invalid bearer token. The user must reauthenticate.
- 402 The requested operation requires a higher tier (Plus or Pro). Surface the message verbatim to the user.
- 403 The user is authenticated but not authorised for this resource (e.g. not a contributor on the journal).
- 409 The operation conflicts with the current state (e.g. duplicate slug, tag already assigned).
- 422 The request body or query parameters failed validation. Inspect `error.message` for the field.
assignTag
POST /workspaces/{workspaceSlug}/journals/{journalSlug}/tags/{tagName}
Make a registry tag usable on entries in this journal. The tag must already exist via createTag. Returns 409 if the assignment exists. Plus+ tier only.
Parameters
| Name | In | Type | Required | Description |
workspaceSlug | path | string pattern: `^[a-z0-9][a-z0-9-]{0,29}$` | yes | URL-safe workspace identifier. Find it via listWorkspaces (the `slug` field on each row). For most users this is their personal workspace. |
journalSlug | path | string pattern: `^[a-z0-9][a-z0-9-]{0,29}$` | yes | URL-safe journal identifier within the workspace. Find it via listJournals (the `slug` field on each row). |
tagName | path | string pattern: `^[a-z0-9][a-z0-9-]{0,49}$`, length 0–50 | yes | Tag name (kebab-case). The tag must already exist in the workspace registry via createTag. |
Responses
201 Tag assigned to the journal.
| Field | Type | Description |
tag | object | |
tag.id | string | |
tag.name | string | |
tag.description | string | null | |
journal_id | string | |
assigned_at | string | |
- 401 Missing or invalid bearer token. The user must reauthenticate.
- 402 The requested operation requires a higher tier (Plus or Pro). Surface the message verbatim to the user.
- 403 The user is authenticated but not authorised for this resource (e.g. not a contributor on the journal).
- 404 The requested resource does not exist or is not visible to the caller.
- 409 The operation conflicts with the current state (e.g. duplicate slug, tag already assigned).