blog
The methods of pd.blog. Inputs and outputs are described in Conventions; every method can also fail as described in Errors.
Methods
pd.blog.listPosts(input, init?)
The blog's published posts in one language
GET /api/blog/posts
Public: needs no session. Returns the published posts of the site's own blog project in one language, newest first. Drafts, articles of any other workspace and everything from the editing side (keywords, outlines, generation settings, authors, storage paths) are never exposed. The answer carries Cache-Control: public, max-age=60, so an unpublished post can stay visible for up to a minute.
Input
| Name | In | Type | Required | Description |
|---|---|---|---|---|
locale |
query | "en" | "fr" |
yes | Language of the posts to list: en or fr. Posts written in a regional variant such as en-GB are listed under their base language. |
limit |
query | integer |
no | Most posts to return. From 1 to 50, default 20. Default: 20. |
offset |
query | integer |
no | Posts to skip, newest first, for paging with limit. Default 0. Default: 0. |
category |
query | string |
no | Only posts in this category, given as its slug in locale, the slug of an entry from GET /api/blog/categories. A slug that matches no category returns an empty list. Omit for every category. |
init |
init | { signal?: AbortSignal; timeoutMs?: number } |
no | The second argument: signal aborts the call, timeoutMs overrides the client's per-attempt timeout. |
Returns 200 OK
| Field | Type | Required | Description |
|---|---|---|---|
posts |
object[] |
yes | Published posts in the requested language, newest first. |
posts[].slug |
string |
yes | The post's address segment, unique within its language; use it in GET /api/blog/posts/{slug}. |
posts[].title |
string |
yes | The post's title, used as its heading. |
posts[].seoTitle |
string |
yes | Title for the browser tab and search results (the SEO meta title, else title); the heading uses title. |
posts[].description |
string |
yes | Summary for listings and search results: the SEO meta description, else the post's first paragraph cut to 200 characters. |
posts[].locale |
"en" | "fr" |
yes | Language the post is written in. |
posts[].publishedAt |
string (date-time) |
yes | When the post was published; posts are listed newest first by this date. |
posts[].updatedAt |
string (date-time) |
yes | When the post was last changed. |
posts[].readingMinutes |
integer |
yes | Estimated reading time in minutes, at 220 words a minute and never below 1. |
posts[].cover |
object | null |
yes | The hero image, else the first image of the body; null when the post has none. |
posts[].cover.url |
string |
yes | Public URL of the image file. |
posts[].cover.alt |
string |
yes | Alternative text for the image; an empty string when none was written. |
posts[].categories |
object[] |
yes | The categories the post carries, sorted by name. |
posts[].categories[].name |
string |
yes | Category name in the language of the post or list; falls back to the category's primary-language name when it has none there. |
posts[].categories[].slug |
string |
yes | Category slug in that language; pass it as category to GET /api/blog/posts to list the category's posts. |
posts[].translations |
object[] |
yes | The other published versions of the post in the blog's languages, at their slugs. |
posts[].translations[].locale |
"en" | "fr" |
yes | Language of the other version. With slug, pass it as language to GET /api/blog/posts/{slug} to read that version. |
posts[].translations[].slug |
string |
yes | Slug of the other version in its own language. |
Errors 400 invalid input, 401 missing or invalid key, 403 the key's roles lack the permission, 404 not found, 429 rate limited or out of quota. See Errors.
Example
import { PrismDraft } from "@prism-draft/sdk";
const pd = new PrismDraft({ apiKey: process.env.PRISMDRAFT_API_KEY! });
const result = await pd.blog.listPosts({
locale: "en",
});
console.log(result);
pd.blog.listCategories(input, init?)
The categories that have published posts, in one language
GET /api/blog/categories
Public: needs no session. Returns the categories of the site's blog that carry at least one published post in the language, sorted by name, each with the number of such posts. A category with no name in the language appears under its primary-language name. Cached for up to a minute (Cache-Control: public, max-age=60).
Input
| Name | In | Type | Required | Description |
|---|---|---|---|---|
locale |
query | "en" | "fr" |
yes | Language to list categories for: en or fr. Only categories with a published post in that language appear, named in that language. |
init |
init | { signal?: AbortSignal; timeoutMs?: number } |
no | The second argument: signal aborts the call, timeoutMs overrides the client's per-attempt timeout. |
Returns 200 OK
| Field | Type | Required | Description |
|---|---|---|---|
categories |
object[] |
yes | Categories with at least one published post in the language, sorted by name. |
categories[].name |
string |
yes | Category name in the language of the post or list; falls back to the category's primary-language name when it has none there. |
categories[].slug |
string |
yes | Category slug in that language; pass it as category to GET /api/blog/posts to list the category's posts. |
categories[].count |
integer |
yes | Published posts in the language that carry it. |
Errors 400 invalid input, 401 missing or invalid key, 403 the key's roles lack the permission, 404 not found, 429 rate limited or out of quota. See Errors.
Example
import { PrismDraft } from "@prism-draft/sdk";
const pd = new PrismDraft({ apiKey: process.env.PRISMDRAFT_API_KEY! });
const result = await pd.blog.listCategories({
locale: "en",
});
console.log(result);
pd.blog.getPost(input, init?)
One published post
GET /api/blog/posts/{slug}
Public: needs no session. Returns one published post of the site's blog with its body, images and the blog pages of the posts it links to. Answers 404 when no published post matches. A slug that was the old address of a renamed post resolves to that post and the returned slug is the current one, so a caller can redirect. Without language, the earliest published post with that slug wins. Cached for up to a minute (Cache-Control: public, max-age=60).
Input
| Name | In | Type | Required | Description |
|---|---|---|---|---|
slug |
path | string |
yes | Slug of the post, the slug of a listed post. Slugs are unique per language, so two language versions can share one: add language to pick a version. |
language |
query | "en" | "fr" |
no | The post's language. A slug is unique per language, so two language versions can share one; with language the post (or the old address) in that language is answered. Without it, the earliest published post with that slug, in any language. |
init |
init | { signal?: AbortSignal; timeoutMs?: number } |
no | The second argument: signal aborts the call, timeoutMs overrides the client's per-attempt timeout. |
Returns 200 OK
| Field | Type | Required | Description |
|---|---|---|---|
post |
object |
yes | The published post. When the requested slug is an old address of a renamed post, this is the renamed post and its slug is the current one. |
post.slug |
string |
yes | The post's address segment, unique within its language; use it in GET /api/blog/posts/{slug}. |
post.title |
string |
yes | The post's title, used as its heading. |
post.seoTitle |
string |
yes | Title for the browser tab and search results (the SEO meta title, else title); the heading uses title. |
post.description |
string |
yes | Summary for listings and search results: the SEO meta description, else the post's first paragraph cut to 200 characters. |
post.locale |
"en" | "fr" |
yes | Language the post is written in. |
post.publishedAt |
string (date-time) |
yes | When the post was published; posts are listed newest first by this date. |
post.updatedAt |
string (date-time) |
yes | When the post was last changed. |
post.readingMinutes |
integer |
yes | Estimated reading time in minutes, at 220 words a minute and never below 1. |
post.cover |
object | null |
yes | The hero image, else the first image of the body; null when the post has none. |
post.cover.url |
string |
yes | Public URL of the image file. |
post.cover.alt |
string |
yes | Alternative text for the image; an empty string when none was written. |
post.categories |
object[] |
yes | The categories the post carries, sorted by name. |
post.categories[].name |
string |
yes | Category name in the language of the post or list; falls back to the category's primary-language name when it has none there. |
post.categories[].slug |
string |
yes | Category slug in that language; pass it as category to GET /api/blog/posts to list the category's posts. |
post.translations |
object[] |
yes | The other published versions of the post in the blog's languages, at their slugs. |
post.translations[].locale |
"en" | "fr" |
yes | Language of the other version. With slug, pass it as language to GET /api/blog/posts/{slug} to read that version. |
post.translations[].slug |
string |
yes | Slug of the other version in its own language. |
post.hero |
object | null |
yes | The post's newest hero image, kept out of the body; null when it has none. Unlike cover, it never falls back to a body image. |
post.hero.url |
string |
yes | Public URL of the image file. |
post.hero.alt |
string |
yes | Alternative text for the image; an empty string when none was written. |
post.heroAssetId |
string (uuid) | null |
yes | The cover asset's id; null when it has none. |
post.document |
object |
yes | The post's body in the article document format: an ordered list of typed blocks. |
post.document.version |
1 |
yes | Version of the document format. Always 1. |
post.document.blocks |
(object<heading> | object<paragraph> | object<list> | object<quote> | object<image> | object<code> | object<table> | object<divider>)[] |
yes | The article body, top to bottom. Each block's type says which of heading, paragraph, list, quote, image, code, table and divider it is and which other fields it carries. An empty array is an empty article. |
post.document.blocks[]<heading>.id |
string |
yes | Identifier of the block inside its document, 1 to 64 characters. A client picks it; keep it stable across saves, since a comment's targetId and a publish warning's blockId name a block by it. |
post.document.blocks[]<heading>.type |
"heading" |
yes | Block kind: always heading. |
post.document.blocks[]<heading>.level |
1 | 2 | 3 | 4 | 5 | 6 |
yes | Heading rank, 1 (<h1>) to 6 (<h6>). The article's title is its own field and is printed above the body, so generated bodies carry no level-1 heading; one at the very start is part of the lead that stripLead removes. |
post.document.blocks[]<heading>.text |
string |
yes | The heading's words. |
post.document.blocks[]<heading>.format |
"markdown" |
no | Absent: the heading is plain text, shown exactly as written. "markdown": inline Markdown limited to bold, italic, inline code, links (http, https, mailto and relative only), autolinks, escaped characters and character references. |
post.document.blocks[]<paragraph>.id |
string |
yes | Identifier of the block inside its document, 1 to 64 characters. A client picks it; keep it stable across saves, since a comment's targetId and a publish warning's blockId name a block by it. |
post.document.blocks[]<paragraph>.type |
"paragraph" |
yes | Block kind: always paragraph. |
post.document.blocks[]<paragraph>.text |
string |
yes | The paragraph's words. |
post.document.blocks[]<paragraph>.links |
object[] |
no | Links from words of text to other articles, at most 20. Only the first link to each article in the whole document is kept (the rest are dropped on save). Absent: none. Ignored when format is "markdown": write the links in the text instead. |
post.document.blocks[]<paragraph>.links[].text |
string |
yes | The words of the block's text that become the link, 1 to 200 characters. Matched literally, first occurrence only; a link whose words no longer occur in the block is dropped on the next save. |
post.document.blocks[]<paragraph>.links[].articleId |
string (uuid) |
yes | Id of the article the words link to. It is resolved when the article is rendered: the words stay plain text until that article is PUBLISHED in the same project and has an address (its blog page, or the URL its publishing integration reported). |
post.document.blocks[]<paragraph>.format |
"markdown" |
no | Absent: the paragraph is plain text, shown exactly as written. "markdown": inline Markdown limited to bold, italic, inline code, links (http, https, mailto and relative only), autolinks, escaped characters and character references. |
post.document.blocks[]<list>.id |
string |
yes | Identifier of the block inside its document, 1 to 64 characters. A client picks it; keep it stable across saves, since a comment's targetId and a publish warning's blockId name a block by it. |
post.document.blocks[]<list>.type |
"list" |
yes | Block kind: always list. |
post.document.blocks[]<list>.ordered |
boolean |
yes | true: a numbered list. false: a bulleted list. |
post.document.blocks[]<list>.items |
string[] |
yes | The top-level items, as text. A reader that ignores children still sees every top-level item. |
post.document.blocks[]<list>.children |
(object | null)[] |
no | Optional, one entry per item of items (an entry count that differs from items is refused): the level 2 sub-list under that item, or null for an item with none. Lists nest to four levels. |
post.document.blocks[]<list>.children[].ordered |
boolean |
yes | true: a numbered sub-list. false: a bulleted one. Independent of the parent list. |
post.document.blocks[]<list>.children[].items |
string[] |
yes | The sub-list's items, at least one, as text. |
post.document.blocks[]<list>.children[].children |
(object | null)[] |
no | Optional, one entry per item of this sub-list (an entry count that differs from items is refused): the level 3 sub-list under that item, or null for an item with none. Same shape as post.document.blocks[]<list>.children[]: see above. |
post.document.blocks[]<list>.format |
"markdown" |
no | Absent: every item of the list, at every level, is plain text, shown exactly as written. "markdown": inline Markdown limited to bold, italic, inline code, links (http, https, mailto and relative only), autolinks, escaped characters and character references. |
post.document.blocks[]<quote>.id |
string |
yes | Identifier of the block inside its document, 1 to 64 characters. A client picks it; keep it stable across saves, since a comment's targetId and a publish warning's blockId name a block by it. |
post.document.blocks[]<quote>.type |
"quote" |
yes | Block kind: always quote. |
post.document.blocks[]<quote>.text |
string |
yes | The quoted words. |
post.document.blocks[]<quote>.attribution |
string |
no | Who said it, always plain text. Absent: the quote is unattributed. |
post.document.blocks[]<quote>.links |
object[] |
no | Links from words of text to other articles, at most 20. Only the first link to each article in the whole document is kept (the rest are dropped on save). Absent: none. Ignored when format is "markdown": write the links in the text instead. |
post.document.blocks[]<quote>.links[].text |
string |
yes | The words of the block's text that become the link, 1 to 200 characters. Matched literally, first occurrence only; a link whose words no longer occur in the block is dropped on the next save. |
post.document.blocks[]<quote>.links[].articleId |
string (uuid) |
yes | Id of the article the words link to. It is resolved when the article is rendered: the words stay plain text until that article is PUBLISHED in the same project and has an address (its blog page, or the URL its publishing integration reported). |
post.document.blocks[]<quote>.format |
"markdown" |
no | Absent: the quoted text is plain text, shown exactly as written. "markdown": inline Markdown limited to bold, italic, inline code, links (http, https, mailto and relative only), autolinks, escaped characters and character references. |
post.document.blocks[]<image>.id |
string |
yes | Identifier of the block inside its document, 1 to 64 characters. A client picks it; keep it stable across saves, since a comment's targetId and a publish warning's blockId name a block by it. |
post.document.blocks[]<image>.type |
"image" |
yes | Block kind: always image. |
post.document.blocks[]<image>.assetId |
string (uuid) |
yes | Id of the article's asset the block shows (assets[].id of GET /api/articles/{articleId}). The asset's address is resolved when the article is rendered; a block whose asset is missing renders as nothing. The article's hero asset is its cover and cannot be added to the body. |
post.document.blocks[]<image>.altText |
string |
yes | Alternative text for the image. When empty, the asset's own alternative text is used when the article is rendered. |
post.document.blocks[]<image>.caption |
string |
no | Plain-text caption printed under the image. Absent: no caption. |
post.document.blocks[]<code>.id |
string |
yes | Identifier of the block inside its document, 1 to 64 characters. A client picks it; keep it stable across saves, since a comment's targetId and a publish warning's blockId name a block by it. |
post.document.blocks[]<code>.type |
"code" |
yes | Block kind: always code. |
post.document.blocks[]<code>.language |
string |
no | The language of the code (the fence's info string), at most 40 characters, e.g. ts. Renderers keep only the characters A-Z a-z 0-9 _ + # . -. Absent: unspecified. |
post.document.blocks[]<code>.text |
string |
yes | The code, verbatim: never interpreted as Markdown and never translated. |
post.document.blocks[]<table>.id |
string |
yes | Identifier of the block inside its document, 1 to 64 characters. A client picks it; keep it stable across saves, since a comment's targetId and a publish warning's blockId name a block by it. |
post.document.blocks[]<table>.type |
"table" |
yes | Block kind: always table. |
post.document.blocks[]<table>.header |
string[] |
yes | The header row: one cell of text per column, at least one. |
post.document.blocks[]<table>.rows |
string[][] |
yes | The body rows, each an array of cell text with exactly as many cells as header (otherwise refused). May be empty. |
post.document.blocks[]<table>.align |
("left" | "center" | "right" | null)[] |
no | One alignment per column, so as many entries as header has cells (otherwise refused); null leaves a column to the browser's default. Absent: all default. |
post.document.blocks[]<table>.format |
"markdown" |
no | Absent: every cell, header included, is plain text, shown exactly as written. "markdown": inline Markdown limited to bold, italic, inline code, links (http, https, mailto and relative only), autolinks, escaped characters and character references. |
post.document.blocks[]<divider>.id |
string |
yes | Identifier of the block inside its document, 1 to 64 characters. A client picks it; keep it stable across saves, since a comment's targetId and a publish warning's blockId name a block by it. |
post.document.blocks[]<divider>.type |
"divider" |
yes | Block kind: always divider. |
post.images |
Record<string, object> |
yes | The images the document's blocks refer to, by asset id. |
post.images.* |
object |
no | The value under each key. |
post.images.*.url |
string |
yes | Public URL of the image file. |
post.images.*.alt |
string |
yes | Alternative text for the image; an empty string when none was written. |
post.links |
Record<string, string> |
yes | Where each post the document links to can be read, by article id. Only published posts of the blog appear. |
Errors 400 invalid input, 401 missing or invalid key, 403 the key's roles lack the permission, 404 not found, 429 rate limited or out of quota. See Errors.
Example
import { PrismDraft } from "@prism-draft/sdk";
const pd = new PrismDraft({ apiKey: process.env.PRISMDRAFT_API_KEY! });
const result = await pd.blog.getPost({
slug: "example",
});
console.log(result);
pd.blog.getPostEdit(input, init?)
Where the caller can edit a published post
GET /api/blog/posts/{slug}/edit
Not public: needs a session or API key. Returns the workspace and article ids behind a published post, so a signed-in member can open it in the editor; the public post carries neither. The workspace is read from the article, never from the request, and the caller needs articles.update on it. Answers 404 when no published post has the slug (an old, redirected slug does not match) or when the caller is not a member of the blog's workspace, and 403 when a member lacks the permission. Not cached.
Input
| Name | In | Type | Required | Description |
|---|---|---|---|---|
slug |
path | string |
yes | Slug of the post, the slug of a listed post. Slugs are unique per language, so two language versions can share one: add language to pick a version. |
language |
query | "en" | "fr" |
no | The post's language. A slug is unique per language, so two language versions can share one; with language the post (or the old address) in that language is answered. Without it, the earliest published post with that slug, in any language. |
init |
init | { signal?: AbortSignal; timeoutMs?: number } |
no | The second argument: signal aborts the call, timeoutMs overrides the client's per-attempt timeout. |
Returns 200 OK
| Field | Type | Required | Description |
|---|---|---|---|
workspaceId |
string (uuid) |
yes | Id of the workspace that holds the post's article, to open it in the editor. |
articleId |
string (uuid) |
yes | Id of the article behind the post. |
Errors 400 invalid input, 401 missing or invalid key, 403 the key's roles lack the permission, 404 not found, 429 rate limited or out of quota. See Errors.
Example
import { PrismDraft } from "@prism-draft/sdk";
const pd = new PrismDraft({ apiKey: process.env.PRISMDRAFT_API_KEY! });
const result = await pd.blog.getPostEdit({
slug: "example",
});
console.log(result);