generation
The methods of pd.generation. Inputs and outputs are described in Conventions; every method can also fail as described in Errors.
Methods
pd.generation.estimate(input, init?)
Quote the credit cost of generating this article
GET /api/articles/{articleId}/estimate
Needs articles.read. Prices the article's stored generation configuration (400 when it has none) and compares the quote with the workspace's available credits. Nothing is reserved or charged: estimate.total is the most a run can cost, and the amount POST /api/articles/{articleId}/generate reserves. Research and fact checking are priced only when the article asks for them and the deployment's web_research and fact_checking features are on. What a run finally charges is the delivered part of this quote, never more.
Input
| Name | In | Type | Required | Description |
|---|---|---|---|---|
articleId |
path | string (uuid) |
yes | Id of an article. The workspace and project it belongs to are read from the article, never from the request; the caller must be a member of that workspace (and of the project, when it has members). For anyone else the answer is 404 RESOURCE_NOT_FOUND, the same as for an id that does not exist. |
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 |
|---|---|---|---|
estimate |
object |
yes | The quote for generating this article from its stored configuration. |
estimate.total |
integer |
yes | Credits the run can cost at most: the sum of breakdown. This is the amount reserved when the run starts. |
estimate.breakdown |
object |
yes | The total split by what it pays for. |
estimate.breakdown.text |
integer |
yes | Writing the article: 1 credit per started 1,000 words of wordCount. |
estimate.breakdown.images |
integer |
yes | Images: 1 credit for each of imageCount. |
estimate.breakdown.research |
integer |
yes | Web research: 1 credit when the article asks for it and the deployment's web_research feature is on, otherwise 0. |
estimate.breakdown.factCheck |
integer |
yes | Fact checking: 1 credit when the article asks for research and the deployment's fact_checking feature is on, otherwise 0. |
estimate.imageCount |
integer |
yes | Images the quote prices, cover included: the configured imageCount, or the count derived from wordCount when that is null. |
wallet |
object |
yes | The workspace's credits at this moment. |
wallet.balance |
integer |
yes | Every credit the workspace owns, held ones included. |
wallet.reserved |
integer |
yes | Credits held by runs in progress; they leave the balance when those runs settle. |
wallet.available |
integer |
yes | balance less reserved: what a new run can reserve. |
sufficient |
boolean |
yes | Whether wallet.available covers estimate.total, so that starting now would not answer 402. |
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.generation.estimate({
articleId: "3f0c5a1e-8b2d-4c6e-9a7f-1d2e3f4a5b6c",
});
console.log(result);
pd.generation.getOutline(input, init?)
The outline awaiting review
GET /api/articles/{articleId}/outline
Needs articles.read. Returns the outline stored on the article, null when it has none or the stored one no longer validates, and whether it can still be edited (editable: the article is a DRAFT holding an outline). Every run stores its outline on the article, so it can also be read after an ordinary generation; a run with reviewOutline set stops there for review. Submit the reviewed version to POST /api/articles/{articleId}/outline/approve.
Input
| Name | In | Type | Required | Description |
|---|---|---|---|---|
articleId |
path | string (uuid) |
yes | Id of an article. The workspace and project it belongs to are read from the article, never from the request; the caller must be a member of that workspace (and of the project, when it has members). For anyone else the answer is 404 RESOURCE_NOT_FOUND, the same as for an id that does not exist. |
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 |
|---|---|---|---|
outline |
object | null |
yes | The outline the article holds; null when it has none, or the stored one no longer validates. |
outline.title |
string |
yes | The article title, 1 to 300 characters. |
outline.slug |
string |
yes | The URL slug: lowercase letters and digits separated by hyphens, up to 120 characters. |
outline.metaTitle |
string |
yes | The SEO title, 1 to 70 characters. |
outline.metaDescription |
string |
yes | The SEO description, 1 to 200 characters. |
outline.sections |
object[] |
yes | The sections in reading order, 1 to 40. |
outline.sections[].id |
string |
yes | Identifier of the section inside the outline, 1 to 64 characters. |
outline.sections[].heading |
string |
yes | The section's heading, 1 to 300 characters. |
outline.sections[].level |
2 | 3 |
yes | Heading rank: 2 for a main section, 3 for a sub-section. |
outline.sections[].purpose |
string |
yes | What the section covers, 1 to 1000 characters. |
outline.sections[].keywords |
string[] |
yes | The keywords attached to the section, at most 25 of 1 to 120 characters. Empty: none. Default: []. |
outline.sections[].estimatedWords |
integer |
yes | The section's length in words, between 20 and 5000. |
editable |
boolean |
yes | true while the article is a DRAFT holding an outline, the window in which POST /api/articles/{articleId}/outline/approve is meaningful; false once writing has started. |
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.generation.getOutline({
articleId: "3f0c5a1e-8b2d-4c6e-9a7f-1d2e3f4a5b6c",
});
console.log(result);
pd.generation.approveOutline(input, init?)
Approve an outline and resume writing
POST /api/articles/{articleId}/outline/approve
Needs articles.generate. Stores the reviewed outline (or approves the stored one when the body has none), then starts the writing leg from it: writing resumes from the reviewer's version, not the generated one. Research is not repeated. Answers 400 when the article has no generation configuration or no outline, 409 when a run is already active or the article's status cannot move to QUEUED, 429 QUOTA_EXCEEDED when the plan's articles or images quota would be passed, and 402 INSUFFICIENT_CREDITS when the available credits do not cover the writing leg. The credits reserved are the full quote less research, which the first leg already spent; on completion only the delivered work is charged and the rest is released. Answers 202 with the QUEUED job (poll GET /api/articles/{articleId}/status, or listen for article.generation.completed); a repeated idempotencyKey answers 200 with the original job and reused: true, once that job has ended. Records an articles.outline_approved audit entry.
Input
| Name | In | Type | Required | Description |
|---|---|---|---|---|
articleId |
path | string (uuid) |
yes | Id of an article. The workspace and project it belongs to are read from the article, never from the request; the caller must be a member of that workspace (and of the project, when it has members). For anyone else the answer is 404 RESOURCE_NOT_FOUND, the same as for an id that does not exist. |
body |
body | object |
no | |
body.outline |
body | object |
no | The reviewed outline. It replaces the stored one as a whole, sections included, and its title, metaTitle and metaDescription become the article's. Absent: the stored outline is approved as it is, and the request answers 400 when the article has none. |
body.outline.title |
body | string |
yes | The article title, 1 to 300 characters. |
body.outline.slug |
body | string |
yes | The URL slug: lowercase letters and digits separated by hyphens, up to 120 characters. |
body.outline.metaTitle |
body | string |
yes | The SEO title, 1 to 70 characters. |
body.outline.metaDescription |
body | string |
yes | The SEO description, 1 to 200 characters. |
body.outline.sections |
body | object[] |
yes | The sections in reading order, 1 to 40. |
body.outline.sections[].id |
body | string |
yes | Identifier of the section inside the outline, 1 to 64 characters. |
body.outline.sections[].heading |
body | string |
yes | The section's heading, 1 to 300 characters. |
body.outline.sections[].level |
body | 2 | 3 |
yes | Heading rank: 2 for a main section, 3 for a sub-section. |
body.outline.sections[].purpose |
body | string |
yes | What the section covers, 1 to 1000 characters. |
body.outline.sections[].keywords |
body | string[] |
no | The keywords attached to the section, at most 25 of 1 to 120 characters. Empty: none. Default: []. |
body.outline.sections[].estimatedWords |
body | integer |
yes | The section's length in words, between 20 and 5000. |
body.idempotencyKey |
body | string |
no | Makes the request safe to repeat, 8 to 200 characters; unique across all runs. A key that already started a writing run returns it (200, reused: true) once it has ended, and answers 409 while it is still running. Absent: a fresh key is generated, so such a request always starts a new run. |
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, 202 Accepted
| Field | Type | Required | Description |
|---|---|---|---|
job |
object |
yes | The writing run, QUEUED on a 202. On a 200 replay it is the run the idempotency key started, in its current state. |
job.id |
string (uuid) |
yes | Id of the run. Cancelling and settling credits are tracked against it. |
job.articleId |
string (uuid) |
yes | Id of the article the run works on. |
job.workspaceId |
string (uuid) |
yes | Id of the workspace that owns the article. |
job.projectId |
string (uuid) |
yes | Id of the project the article belongs to. |
job.status |
"QUEUED" | "TRANSLATING" | "VALIDATING" | "RESEARCHING" | "OUTLINING" | "WRITING" | "GENERATING_IMAGE_PROMPTS" | "GENERATING_IMAGES" | "PROCESSING_MEDIA" | "FINALIZING" | "COMPLETED" | "FAILED" | "CANCELLED" |
yes | Where the run is. QUEUED: accepted, not started yet. VALIDATING, RESEARCHING, OUTLINING, WRITING, GENERATING_IMAGE_PROMPTS, GENERATING_IMAGES, PROCESSING_MEDIA, FINALIZING: the pipeline is in that step, in this order (research only when requested; the outline step is skipped when resuming an approved outline). TRANSLATING: a translation run is working. COMPLETED, FAILED and CANCELLED are final. |
job.kind |
"generation" | "translation" |
yes | generation writes an article from its configuration; translation translates an article into another language. |
job.currentStep |
"validate" | "research" | "outline" | "write" | "image-prompts" | "images" | "media-processing" | "translate" | "finalize" | null |
yes | The step the run last entered; null while it is still QUEUED. |
job.progress |
integer |
yes | Percent of the run completed, 0 to 100. It moves to the step's value on entering each step and is 100 when the run is COMPLETED; a failed or cancelled run keeps the value it reached. |
job.idempotencyKey |
string |
yes | The key the run was started with: the one the client sent, or one the server generated. Unique across all runs; a request that repeats it gets this run back. |
job.estimatedCredits |
integer |
yes | Credits quoted when the run was started, the most it can cost: the full quote for POST /api/articles/{articleId}/generate, the writing leg's share for POST /api/articles/{articleId}/outline/approve. |
job.reservedCredits |
integer |
yes | Credits held against the wallet when the run was queued, equal to estimatedCredits. The hold ends when the run settles, but this value is not reset; read consumedCredits for what was charged. |
job.consumedCredits |
integer |
yes | Credits charged for the work delivered, set when the run settles and never above reservedCredits; 0 until then, and 0 for a run that failed before the article was saved or was cancelled. |
job.errorCode |
string | null |
yes | GENERATION_FAILED or TRANSLATION_FAILED when status is FAILED; otherwise null. |
job.errorMessage |
string | null |
yes | The message of the error that failed the run; null unless status is FAILED. |
job.createdAt |
string (date-time) |
yes | When the run was accepted, ISO 8601. |
outline |
object |
yes | The outline writing resumes from: the one sent in the request, else the one the article held. |
outline.title |
string |
yes | The article title, 1 to 300 characters. |
outline.slug |
string |
yes | The URL slug: lowercase letters and digits separated by hyphens, up to 120 characters. |
outline.metaTitle |
string |
yes | The SEO title, 1 to 70 characters. |
outline.metaDescription |
string |
yes | The SEO description, 1 to 200 characters. |
outline.sections |
object[] |
yes | The sections in reading order, 1 to 40. |
outline.sections[].id |
string |
yes | Identifier of the section inside the outline, 1 to 64 characters. |
outline.sections[].heading |
string |
yes | The section's heading, 1 to 300 characters. |
outline.sections[].level |
2 | 3 |
yes | Heading rank: 2 for a main section, 3 for a sub-section. |
outline.sections[].purpose |
string |
yes | What the section covers, 1 to 1000 characters. |
outline.sections[].keywords |
string[] |
yes | The keywords attached to the section, at most 25 of 1 to 120 characters. Empty: none. Default: []. |
outline.sections[].estimatedWords |
integer |
yes | The section's length in words, between 20 and 5000. |
estimate |
object |
no | Present on a 202; absent on a 200 replay. |
estimate.total |
integer |
yes | Credits reserved for the writing leg: the full quote less research, which ran before the outline was reviewed. |
reused |
true |
no | Present, and true, only on a 200 replay: the idempotency key had already started a run. |
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.generation.approveOutline({
articleId: "3f0c5a1e-8b2d-4c6e-9a7f-1d2e3f4a5b6c",
});
console.log(result);
pd.generation.aiEdit(input, init?)
Expand, shorten, rewrite, simplify or change the tone of a passage
POST /api/articles/{articleId}/ai-edit
Needs articles.update. Runs synchronously, with no job: the edited passage comes back in the 200 and the article is not changed, so the caller decides whether to apply it. It costs no credits, but each call counts toward the plan's monthly ai_operations quota (429 QUOTA_EXCEEDED once reached). The text is written in the article's generation language, or its primaryLanguage when it has no configuration, and follows the project's default brand voice. change_tone without a tone answers 400.
Input
| Name | In | Type | Required | Description |
|---|---|---|---|---|
articleId |
path | string (uuid) |
yes | Id of an article. The workspace and project it belongs to are read from the article, never from the request; the caller must be a member of that workspace (and of the project, when it has members). For anyone else the answer is 404 RESOURCE_NOT_FOUND, the same as for an id that does not exist. |
body |
body | object |
yes | |
body.action |
body | "expand" | "shorten" | "rewrite" | "simplify" | "change_tone" |
yes | What to do with the selection. expand: develop it with more substance, about half again as long. shorten: cut it to about two thirds, keeping every claim. rewrite: new wording at about the same length. simplify: plainer words and shorter sentences. change_tone: rewrite in the requested tone. |
body.selection |
body | string |
yes | The passage to edit, 1 to 20,000 characters. It is not matched against the article's text. |
body.context |
body | string |
no | Text around the selection, up to 20,000 characters, so the edit fits where it sits. It is read for fit only and is not edited or returned. |
body.tone |
body | string |
no | The tone to rewrite in, up to 120 characters, for example warmer or more formal. Required when action is change_tone (400 otherwise); ignored by the other actions. |
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 |
|---|---|---|---|
text |
string |
yes | The edited passage, to show the user and replace the selection with if accepted. The article is not changed. |
action |
"expand" | "shorten" | "rewrite" | "simplify" | "change_tone" |
yes | The action that was applied, as sent. |
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.generation.aiEdit({
articleId: "3f0c5a1e-8b2d-4c6e-9a7f-1d2e3f4a5b6c",
body: {
action: "expand",
selection: "example",
},
});
console.log(result);
pd.generation.start(input, init?)
Start generating an article
POST /api/articles/{articleId}/generate
Needs articles.generate. Queues a run of the pipeline that researches (when asked), outlines, writes, illustrates and finalizes the article from its generation configuration, which config can set first. The body may be omitted. Credits. The quote (estimate.total, the most the run can cost) is reserved against the wallet before any provider is called; 402 INSUFFICIENT_CREDITS when the available credits are lower. When the run ends only what was delivered is charged and the rest of the hold is released: an image the provider refused, research that found no source or a fact check that checked nothing is not charged. A run that fails before the article is saved, or is cancelled, charges nothing. With reviewOutline the run stops after the outline and charges only research. consumedCredits on the job reports the charge. Refusals. 400 when no configuration exists; 409 when a run is already active for the article or its status cannot move to QUEUED (PUBLISHED, QUEUED, PROCESSING); 429 QUOTA_EXCEEDED when the plan's articles or images quota would be passed, or ai_operations for a reviewOutline run. Asynchronous and idempotent. A new run answers 202 with the QUEUED job; follow it with GET /api/articles/{articleId}/status or the SSE stream, and see article.generation.started, image.generated, credits.consumed, article.generation.completed and article.generation.failed on the webhook. The key comes from idempotencyKey, else the Idempotency-Key header; repeating it after the run has ended answers 200 with the original job and reused: true, and while it is running answers 409. On success the article becomes NEEDS_REVIEW with a new document; on failure, cancellation or an outline-only run it returns to DRAFT. Emits article.generation.started and records an articles.generation_started audit entry.
Input
| Name | In | Type | Required | Description |
|---|---|---|---|---|
articleId |
path | string (uuid) |
yes | Id of an article. The workspace and project it belongs to are read from the article, never from the request; the caller must be a member of that workspace (and of the project, when it has members). For anyone else the answer is 404 RESOURCE_NOT_FOUND, the same as for an id that does not exist. |
body |
body | object |
no | |
body.config |
body | object |
no | Sets the article's generation configuration before the run, replacing the stored one as a whole: a field left out takes its default, not its previous value. Absent: the stored configuration is used, and the request answers 400 when the article has none. |
body.config.topic |
body | string |
yes | What the article is about (1 to 500 characters). The outline step plans the article from it, and with primaryKeyword it is the query for web research and for the project's knowledge base. The article's title is not copied from it: the outline's title, written by the model, becomes the title. |
body.config.prompt |
body | string |
no | Free-form direction for the writer, up to 5000 characters (angle, points to cover, things to avoid). Sent to the outline step as 'Additional direction'; the section writers follow the outline it produces. |
body.config.primaryKeyword |
body | string |
no | The keyword the article targets, up to 200 characters. The outline step is told to use it where it reads naturally, and it joins topic in the research and knowledge-base queries. It is not copied into the article's targetKeywords. |
body.config.secondaryKeywords |
body | string[] |
no | Supporting keywords, up to 25 of 1 to 200 characters. Sent to the outline step, which spreads them over the sections' keywords. Empty by default. Default: []. |
body.config.audience |
body | string |
no | Who the article is for, up to 500 characters. Sent to the outline step, to every section writer, to the image-prompt writers and to the knowledge-base query. |
body.config.tone |
body | string |
no | The tone to write in, as free text up to 200 characters; the web app offers professional, conversational, authoritative, practical and persuasive. Sent to the outline step as 'Tone'. The section writers read no separate tone: they follow the outline and the brand voice. |
body.config.language |
body | string |
no | Language the article is written in, as a language tag such as en or pt-BR (default en). The outline, section, research and image-prompt steps use it, so the text and the images' alt text come out in it. A run does not change the article's own primaryLanguage. Default: "en". |
body.config.wordCount |
body | integer |
no | Target length of the whole article in words, 200 to 10000 (default 1200). The outline's sections are planned to add up to roughly this. It also sets the text price per started 1,000 words: 2 credits with writingQuality premium, 1 with standard (1200 costs 4 or 2), and the image count when imageCount is null. Default: 1200. |
body.config.imageStyle |
body | "photorealistic" | "editorial" | "minimalist-vector" | "3d-render" | "watercolor" | "corporate-tech" | "cinematic" | "illustration" |
no | Style of the article's images (default editorial). It applies to the cover and the inline images unless the project's imageSettings set a hero.style or inline.style, which take precedence. Values: photorealistic (natural-light documentary photograph, realistic textures, no stylisation), editorial (modern editorial illustration or photograph, clean composition, restrained palette), minimalist-vector (flat vector illustration, few shapes, solid colours, plenty of empty space), 3d-render (soft studio 3D render, smooth matte materials, simple forms), watercolor (loose watercolour painting, soft edges, muted washes), corporate-tech (clean technology visual in cool blues and greys, geometric shapes), cinematic (cinematic still, dramatic lighting, strong atmosphere, wide framing), illustration (hand-drawn illustration, confident linework, warm friendly palette). Default: "editorial". |
body.config.imageCount |
body | integer | null |
no | How many images to make, cover included, 0 to 20. 0 makes none, 1 only the cover, more add one inline image per section up to the number of outline sections. null (the default) derives it from wordCount: 2 under 1,000 words, 3 under 2,000, 4 under 3,000, otherwise 5. Each image delivered costs 1 credit, or 3 when imageQuality is hd; the quote and the reservation count the full number. Default: null. |
body.config.writingQuality |
body | "standard" | "premium" |
no | Which model writes the article (default premium). premium uses the deployment's article model and costs 2 credits per started 1,000 words; standard uses a lighter, cheaper model and costs 1 credit per started 1,000 words. Default: "premium". |
body.config.imageQuality |
body | "standard" | "hd" |
no | Quality of the article's images (default standard). standard costs 1 credit per image; hd asks the image model for its highest quality and costs 3 credits per image, for the run's images and for each regeneration of an image of the article. Default: "standard". |
body.config.brandVoiceId |
body | string (uuid) |
no | Id of the brand voice to write in. Absent, or an id that is not one of the workspace's voices, the project's default voice applies, then the workspace's default voice; with neither, no voice is applied. |
body.config.contentTemplateId |
body | string (uuid) |
no | Id of the content template to follow: the outline must contain its sections, and the last section ends on its call to action. Only built-in templates and the workspace's own count; any other id, or a deleted template, is ignored without an error. |
body.config.sourceUrl |
body | string (uri) |
no | A reference URL for the article. It is stored with the configuration; no generation step reads or fetches it. |
body.config.researchEnabled |
body | boolean |
no | Whether to research and fact-check (default false). Research looks up sources before the outline (needs the deployment's web_research feature); fact checking verifies the finished article's claims (needs fact_checking). Research adds 1 credit and fact checking 3 to the quote, each only when its feature is on, and is charged only if it delivered: research found a source, fact checking checked a claim. Default: false. |
body.config.reviewOutline |
body | boolean |
no | When true the run stops after the outline (default false): the job ends COMPLETED, the article stays DRAFT with the outline stored, and only research, if any, is charged. Read it with GET /api/articles/{articleId}/outline, edit it, then POST /api/articles/{articleId}/outline/approve to write the article. Default: false. |
body.config.faq |
body | boolean |
no | When true (default false) the article ends with a "Frequently asked questions" section of 3 to 5 questions a reader still has after the body, each answered in two or three sentences, in the article's language. It is written after the sections as one more model call, and its words come out of wordCount: the outline is planned for 250 words fewer, so the price does not change. The section is ordinary content (an H2, then an H3 and a paragraph per question) that can be edited like any other. Default: false. |
body.idempotencyKey |
body | string |
no | Makes the request safe to repeat, 8 to 200 characters; unique across all runs. A key not seen before starts a run (202). A key that already started one returns that run (200, reused: true) and reserves nothing, once it has ended; while it is still running the repeat answers 409. Absent: the Idempotency-Key header is used, and failing that a fresh key is generated, so such a request always starts a new run. |
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, 202 Accepted
| Field | Type | Required | Description |
|---|---|---|---|
job |
object |
yes | The run, QUEUED on a 202 with reservedCredits set. On a 200 replay it is the run the idempotency key started, in its current state. |
job.id |
string (uuid) |
yes | Id of the run. Cancelling and settling credits are tracked against it. |
job.articleId |
string (uuid) |
yes | Id of the article the run works on. |
job.workspaceId |
string (uuid) |
yes | Id of the workspace that owns the article. |
job.projectId |
string (uuid) |
yes | Id of the project the article belongs to. |
job.status |
"QUEUED" | "TRANSLATING" | "VALIDATING" | "RESEARCHING" | "OUTLINING" | "WRITING" | "GENERATING_IMAGE_PROMPTS" | "GENERATING_IMAGES" | "PROCESSING_MEDIA" | "FINALIZING" | "COMPLETED" | "FAILED" | "CANCELLED" |
yes | Where the run is. QUEUED: accepted, not started yet. VALIDATING, RESEARCHING, OUTLINING, WRITING, GENERATING_IMAGE_PROMPTS, GENERATING_IMAGES, PROCESSING_MEDIA, FINALIZING: the pipeline is in that step, in this order (research only when requested; the outline step is skipped when resuming an approved outline). TRANSLATING: a translation run is working. COMPLETED, FAILED and CANCELLED are final. |
job.kind |
"generation" | "translation" |
yes | generation writes an article from its configuration; translation translates an article into another language. |
job.currentStep |
"validate" | "research" | "outline" | "write" | "image-prompts" | "images" | "media-processing" | "translate" | "finalize" | null |
yes | The step the run last entered; null while it is still QUEUED. |
job.progress |
integer |
yes | Percent of the run completed, 0 to 100. It moves to the step's value on entering each step and is 100 when the run is COMPLETED; a failed or cancelled run keeps the value it reached. |
job.idempotencyKey |
string |
yes | The key the run was started with: the one the client sent, or one the server generated. Unique across all runs; a request that repeats it gets this run back. |
job.estimatedCredits |
integer |
yes | Credits quoted when the run was started, the most it can cost: the full quote for POST /api/articles/{articleId}/generate, the writing leg's share for POST /api/articles/{articleId}/outline/approve. |
job.reservedCredits |
integer |
yes | Credits held against the wallet when the run was queued, equal to estimatedCredits. The hold ends when the run settles, but this value is not reset; read consumedCredits for what was charged. |
job.consumedCredits |
integer |
yes | Credits charged for the work delivered, set when the run settles and never above reservedCredits; 0 until then, and 0 for a run that failed before the article was saved or was cancelled. |
job.errorCode |
string | null |
yes | GENERATION_FAILED or TRANSLATION_FAILED when status is FAILED; otherwise null. |
job.errorMessage |
string | null |
yes | The message of the error that failed the run; null unless status is FAILED. |
job.createdAt |
string (date-time) |
yes | When the run was accepted, ISO 8601. |
estimate |
object |
yes | The quote for the article's current configuration. On a 202 its total is what was reserved; on a replay it is computed afresh, not read back from the run. |
estimate.total |
integer |
yes | Credits the run can cost at most: the sum of breakdown. This is the amount reserved when the run starts. |
estimate.breakdown |
object |
yes | The total split by what it pays for. |
estimate.breakdown.text |
integer |
yes | Writing the article: 1 credit per started 1,000 words of wordCount. |
estimate.breakdown.images |
integer |
yes | Images: 1 credit for each of imageCount. |
estimate.breakdown.research |
integer |
yes | Web research: 1 credit when the article asks for it and the deployment's web_research feature is on, otherwise 0. |
estimate.breakdown.factCheck |
integer |
yes | Fact checking: 1 credit when the article asks for research and the deployment's fact_checking feature is on, otherwise 0. |
estimate.imageCount |
integer |
yes | Images the quote prices, cover included: the configured imageCount, or the count derived from wordCount when that is null. |
reused |
boolean |
yes | false on a 202, a new run. true on a 200: the idempotency key had already started this run, and nothing was reserved again. |
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.generation.start({
articleId: "3f0c5a1e-8b2d-4c6e-9a7f-1d2e3f4a5b6c",
});
console.log(result);
pd.generation.getStatus(input, init?)
Generation progress for an article
GET /api/articles/{articleId}/status
Needs articles.read. Returns the article's workflow status and its most recent run of any kind (generation or translation), null when none was ever started: status, currentStep and progress say where it is, and a FAILED run carries errorCode and errorMessage. Poll this, or listen to GET /api/articles/{articleId}/events, after POST /api/articles/{articleId}/generate. A run that stopped at an outline for review reads COMPLETED while the article is back to DRAFT. An image regeneration has no run to read here.
Input
| Name | In | Type | Required | Description |
|---|---|---|---|---|
articleId |
path | string (uuid) |
yes | Id of an article. The workspace and project it belongs to are read from the article, never from the request; the caller must be a member of that workspace (and of the project, when it has members). For anyone else the answer is 404 RESOURCE_NOT_FOUND, the same as for an id that does not exist. |
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 |
|---|---|---|---|
articleStatus |
"DRAFT" | "QUEUED" | "PROCESSING" | "NEEDS_REVIEW" | "CHANGES_REQUESTED" | "APPROVED" | "PUBLISHED" |
yes | The article's workflow status now: QUEUED and PROCESSING while a run owns it, then NEEDS_REVIEW after a completed generation, or DRAFT after a failed or cancelled one and after an outline-only run. |
job |
object | null |
yes | The article's most recent run of any kind; null when none was ever started. |
job.id |
string (uuid) |
yes | Id of the run. Cancelling and settling credits are tracked against it. |
job.articleId |
string (uuid) |
yes | Id of the article the run works on. |
job.workspaceId |
string (uuid) |
yes | Id of the workspace that owns the article. |
job.projectId |
string (uuid) |
yes | Id of the project the article belongs to. |
job.status |
"QUEUED" | "TRANSLATING" | "VALIDATING" | "RESEARCHING" | "OUTLINING" | "WRITING" | "GENERATING_IMAGE_PROMPTS" | "GENERATING_IMAGES" | "PROCESSING_MEDIA" | "FINALIZING" | "COMPLETED" | "FAILED" | "CANCELLED" |
yes | Where the run is. QUEUED: accepted, not started yet. VALIDATING, RESEARCHING, OUTLINING, WRITING, GENERATING_IMAGE_PROMPTS, GENERATING_IMAGES, PROCESSING_MEDIA, FINALIZING: the pipeline is in that step, in this order (research only when requested; the outline step is skipped when resuming an approved outline). TRANSLATING: a translation run is working. COMPLETED, FAILED and CANCELLED are final. |
job.kind |
"generation" | "translation" |
yes | generation writes an article from its configuration; translation translates an article into another language. |
job.currentStep |
"validate" | "research" | "outline" | "write" | "image-prompts" | "images" | "media-processing" | "translate" | "finalize" | null |
yes | The step the run last entered; null while it is still QUEUED. |
job.progress |
integer |
yes | Percent of the run completed, 0 to 100. It moves to the step's value on entering each step and is 100 when the run is COMPLETED; a failed or cancelled run keeps the value it reached. |
job.idempotencyKey |
string |
yes | The key the run was started with: the one the client sent, or one the server generated. Unique across all runs; a request that repeats it gets this run back. |
job.estimatedCredits |
integer |
yes | Credits quoted when the run was started, the most it can cost: the full quote for POST /api/articles/{articleId}/generate, the writing leg's share for POST /api/articles/{articleId}/outline/approve. |
job.reservedCredits |
integer |
yes | Credits held against the wallet when the run was queued, equal to estimatedCredits. The hold ends when the run settles, but this value is not reset; read consumedCredits for what was charged. |
job.consumedCredits |
integer |
yes | Credits charged for the work delivered, set when the run settles and never above reservedCredits; 0 until then, and 0 for a run that failed before the article was saved or was cancelled. |
job.errorCode |
string | null |
yes | GENERATION_FAILED or TRANSLATION_FAILED when status is FAILED; otherwise null. |
job.errorMessage |
string | null |
yes | The message of the error that failed the run; null unless status is FAILED. |
job.createdAt |
string (date-time) |
yes | When the run was accepted, ISO 8601. |
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.generation.getStatus({
articleId: "3f0c5a1e-8b2d-4c6e-9a7f-1d2e3f4a5b6c",
});
console.log(result);
pd.generation.cancel(input, init?)
Cancel a running generation
POST /api/articles/{articleId}/cancel
Needs articles.generate. Cancels the article's latest run; 409 when there is none or it has already ended (COMPLETED, FAILED, CANCELLED). The answer is immediate and says how: removed means the run had not started and is cancelled now (its reservation is released, the job is CANCELLED and the article returns to DRAFT); signalled means it is running and stops at its next checkpoint, which come before the outline, before each section and before each image, not during a provider call. Poll GET /api/articles/{articleId}/status for CANCELLED. A cancelled run is charged nothing, whatever it had finished. A cancel that arrives after the last image checkpoint does not stop the run, which completes and is charged normally. No webhook event is sent for a cancellation.
Input
| Name | In | Type | Required | Description |
|---|---|---|---|---|
articleId |
path | string (uuid) |
yes | Id of an article. The workspace and project it belongs to are read from the article, never from the request; the caller must be a member of that workspace (and of the project, when it has members). For anyone else the answer is 404 RESOURCE_NOT_FOUND, the same as for an id that does not exist. |
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 |
|---|---|---|---|
cancellation |
"removed" | "signalled" |
yes | removed: the job had not started and is cancelled now. signalled: it is running and stops at its next checkpoint. |
jobId |
string (uuid) |
yes | Id of the run the cancellation applies to. |
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.generation.cancel({
articleId: "3f0c5a1e-8b2d-4c6e-9a7f-1d2e3f4a5b6c",
});
console.log(result);
pd.generation.regenerateImage(input, init?)
Regenerate an image, optionally with a new prompt or style
POST /api/articles/{articleId}/regenerate-image
Needs images.regenerate. Starts replacing the picture of one of the article's images in the background (404 when assetId is not this article's). The asset keeps its id and its place in the body; the new picture is stored under a new URL, so read publicUrl again once it is done. Always answers 202 and returns no job: completion is signalled by the image.regenerated webhook event (and article.updated when the article is published), or seen by reading the image again. Credits. 1 credit (3 when the article's imageQuality is hd) is reserved at once (402 INSUFFICIENT_CREDITS when unavailable) and charged when the new image is stored; a failed regeneration releases it. The plan's images quota applies (429 QUOTA_EXCEEDED). The idempotencyKey guards against double charging: a key used before, while its earlier request is still on record, reserves nothing, starts no second regeneration and still answers 202. The prompt and style rules are on prompt and style; the shape of the image comes from the project's image settings. Records an images.regenerated audit entry when the new image is stored.
Input
| Name | In | Type | Required | Description |
|---|---|---|---|---|
articleId |
path | string (uuid) |
yes | Id of an article. The workspace and project it belongs to are read from the article, never from the request; the caller must be a member of that workspace (and of the project, when it has members). For anyone else the answer is 404 RESOURCE_NOT_FOUND, the same as for an id that does not exist. |
body |
body | object |
yes | |
body.assetId |
body | string (uuid) |
yes | Id of the image to regenerate, the cover or an inline image of this article; 404 when it belongs to another article. It keeps this id. |
body.prompt |
body | string |
no | The image prompt to use as written, 10 to 1200 characters. The project's style description and never-depict list are still appended, and the image's alt text is kept. Absent: a new prompt is written from the article's current text, and the alt text is rewritten to match. |
body.style |
body | "photorealistic" | "editorial" | "minimalist-vector" | "3d-render" | "watercolor" | "corporate-tech" | "cinematic" | "illustration" |
no | Style for this image, stored on it so later regenerations keep it. Absent: the project's style for covers or inline images, else the style the image already has. Values: photorealistic (natural-light documentary photograph, realistic textures, no stylisation), editorial (modern editorial illustration or photograph, clean composition, restrained palette), minimalist-vector (flat vector illustration, few shapes, solid colours, plenty of empty space), 3d-render (soft studio 3D render, smooth matte materials, simple forms), watercolor (loose watercolour painting, soft edges, muted washes), corporate-tech (clean technology visual in cool blues and greys, geometric shapes), cinematic (cinematic still, dramatic lighting, strong atmosphere, wide framing), illustration (hand-drawn illustration, confident linework, warm friendly palette). |
body.idempotencyKey |
body | string |
no | Makes the request safe to repeat, 8 to 200 characters. A key used before, while its earlier request is still on record, reserves nothing and starts no second regeneration, and still answers 202; use a new key for each regeneration. Absent: a fresh key is generated. |
init |
init | { signal?: AbortSignal; timeoutMs?: number } |
no | The second argument: signal aborts the call, timeoutMs overrides the client's per-attempt timeout. |
Returns 202 Accepted
| Field | Type | Required | Description |
|---|---|---|---|
reservedCredits |
integer |
yes | Credits held for the regeneration, 1. They are charged when the new image has replaced the old one and returned if the regeneration fails. |
assetId |
string (uuid) |
yes | Id of the image being regenerated. It keeps this id; its publicUrl changes when the new picture is stored. |
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.generation.regenerateImage({
articleId: "3f0c5a1e-8b2d-4c6e-9a7f-1d2e3f4a5b6c",
body: {
assetId: "3f0c5a1e-8b2d-4c6e-9a7f-1d2e3f4a5b6c",
},
});
console.log(result);
pd.generation.updateAsset(input, init?)
Edit an image's alt text, or make it the hero or an inline image
PATCH /api/articles/{articleId}/assets/{assetId}
Send altText, type, or both (neither: 400). altText is copied to every body block showing the image. type: "hero" makes an image of the article its cover and removes every body block showing it (a new version; contentHtml and contentMarkdown are re-rendered); bodyBlocksRemoved says how many. The article has one hero: while another image is the hero the answer is 409 CONFLICT, details: { reason: "hero_exists", heroAssetId }, and nothing changes; make that one inline first. type: "inline" only changes the type: no block is put back, so the image stays unused until placed. The type the image already has changes nothing. A PUBLISHED article sends article.updated when the edit changes what it shows: a promote or demote, or an alt text that appears in the rendered content (the alt text of a cover, which no block shows, sends nothing). Needs images.update.
Input
| Name | In | Type | Required | Description |
|---|---|---|---|---|
articleId |
path | string (uuid) |
yes | Id of an article. The workspace and project it belongs to are read from the article, never from the request; the caller must be a member of that workspace (and of the project, when it has members). For anyone else the answer is 404 RESOURCE_NOT_FOUND, the same as for an id that does not exist. |
assetId |
path | string (uuid) |
yes | Id of an image asset of the article articleId, as returned in its assets. An asset that belongs to another article answers 404 RESOURCE_NOT_FOUND. |
body |
body | object |
no | |
body.altText |
body | string |
no | The image's alt text (1 to 300 characters); every body block showing it follows. |
body.type |
body | "hero" | "inline" |
no | hero makes the image the article's cover and removes every body block showing it; inline makes the hero an ordinary image again, without putting a block back. |
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 |
|---|---|---|---|
asset |
object |
yes | The asset after the edit. |
asset.id |
string (uuid) |
yes | Id of the asset. |
asset.altText |
string |
yes | The asset's alternative text after the edit. |
asset.type |
"hero" | "inline" |
yes | hero: the article's cover. inline: an image of the body. |
bodyBlocksRemoved |
integer |
yes | Image blocks of the body that showed the asset and were removed because it became the hero (a new version of the document); 0 for any other edit. |
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.generation.updateAsset({
articleId: "3f0c5a1e-8b2d-4c6e-9a7f-1d2e3f4a5b6c",
assetId: "3f0c5a1e-8b2d-4c6e-9a7f-1d2e3f4a5b6c",
});
console.log(result);
pd.generation.deleteAsset(input, init?)
Delete an image
DELETE /api/articles/{articleId}/assets/{assetId}
Needs images.delete. Deletes one of the article's images, the cover or an inline one (404 when assetId is not this article's), and removes every body block that showed it, saving a new version of the document (and, for a published article, sending article.updated). The stored file is removed afterwards unless another row still uses it. Nothing is refunded: the credits the image cost were spent when it was made. Records an images.deleted audit entry.
Input
| Name | In | Type | Required | Description |
|---|---|---|---|---|
articleId |
path | string (uuid) |
yes | Id of an article. The workspace and project it belongs to are read from the article, never from the request; the caller must be a member of that workspace (and of the project, when it has members). For anyone else the answer is 404 RESOURCE_NOT_FOUND, the same as for an id that does not exist. |
assetId |
path | string (uuid) |
yes | Id of an image asset of the article articleId, as returned in its assets. An asset that belongs to another article answers 404 RESOURCE_NOT_FOUND. |
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 |
|---|---|---|---|
ok |
true |
yes | Always true: the image was deleted. |
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.generation.deleteAsset({
articleId: "3f0c5a1e-8b2d-4c6e-9a7f-1d2e3f4a5b6c",
assetId: "3f0c5a1e-8b2d-4c6e-9a7f-1d2e3f4a5b6c",
});
console.log(result);