PrismDraft SDK

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);