PrismDraft SDK

translations

The methods of pd.translations. Inputs and outputs are described in Conventions; every method can also fail as described in Errors.

Methods

pd.translations.estimate(input, init?)

Estimate a translation

GET /api/articles/{articleId}/translations/estimate

What translating the article would cost, from its current content, next to the workspace's credit wallet and whether the wallet covers it. Needs articles.read. The language is refused (400 INVALID_REQUEST) when the translation would be: not one of the project's languages, or the source's own. Read-only: nothing is reserved or charged until POST /api/articles/{articleId}/translations.

Input

Name In Type Required Description
articleId path string (uuid) yes Id of the source article: the version that is translated from.
language query string yes Language tag the article would be translated into. Checked as POST /api/articles/{articleId}/translations checks it: it must be one of the project's languages and different from the source article's language (INVALID_REQUEST otherwise). The price depends on the source article's length only.
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 What translating the article costs, from its current content.
estimate.words integer yes Words counted in the source article's current content.
estimate.total integer yes Credits the translation costs: one per started 1,000 words of the source. The same amount whatever the target language; reserved when the translation starts.
wallet object yes The workspace's credit wallet now, to compare with estimate.total.
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.

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.translations.estimate({
  articleId: "3f0c5a1e-8b2d-4c6e-9a7f-1d2e3f4a5b6c",
  language: "example",
});
console.log(result);

pd.translations.create(input, init?)

Translate an article into another of its project's languages

POST /api/articles/{articleId}/translations

Creates the draft version in language, in the source's translation group and starting with the source's categories, and queues a job that translates the content and copies the images. Needs both articles.create and articles.generate. Answers 202 with the QUEUED version and its job; the credits (see GET /api/articles/{articleId}/translations/estimate) are reserved before any model is called, and 402 INSUFFICIENT_CREDITS leaves nothing behind. Repeating an idempotencyKey answers 200 with the original job and reused: true, whatever has happened to it since: job and article are as they are now, so a translation that failed since is returned with its failed job and its draft, not started again, and estimate is null. 409 when the group already has a version in that language (an empty draft left by a failed translation is reused instead) or the source has a generation running; INVALID_REQUEST when the source has no content, the language is the source's own or not one of the project's; 429 QUOTA_EXCEEDED when the copied images would exceed the storage quota. A new version emits an article.created webhook, and an audit entry is written.

Input

Name In Type Required Description
articleId path string (uuid) yes Id of the source article: the version that is translated from.
body body object yes
body.language body string yes Language tag to translate into. Must be one of the project's languages and different from the source article's language (INVALID_REQUEST otherwise).
body.idempotencyKey body string no Client-chosen key, 8 to 200 characters, that makes the request safe to repeat: a second request with the same key answers 200 with the original job instead of starting another. Taken from the Idempotency-Key header when absent here. Without either key a repeat is not recognised as one, but it still cannot start a second translation into a language the group already has: it answers 409 while that version is queued, running or finished. Only when the first translation failed or was cancelled, which leaves an empty draft, does a repeat start a new run, on that draft.
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
article object yes The version in the target language, a draft in the source's translation group. status is QUEUED on a new request; on a replay it is the version as it is now. Its content is empty until the queued job writes it.
article.id string (uuid) yes Id of the article.
article.workspaceId string (uuid) yes Id of the workspace that owns the article.
article.projectId string (uuid) yes Id of the project the article belongs to.
article.title string yes The article's title, printed above its body.
article.slug string yes The article's address segment, unique among the project's articles of the same language. Renaming an article that has been published keeps its old slug as a redirect.
article.status "DRAFT" | "QUEUED" | "PROCESSING" | "NEEDS_REVIEW" | "CHANGES_REQUESTED" | "APPROVED" | "PUBLISHED" yes Where the article is in its life. DRAFT: being written or edited, and what an unpublished article goes back to. QUEUED and PROCESSING: owned by an AI job (waiting, then running); nothing but the job moves it out. NEEDS_REVIEW: submitted, waiting for a decision. CHANGES_REQUESTED: sent back by a reviewer (reject or request changes). APPROVED: approved and ready to publish. PUBLISHED: published. Only the workflow routes change it, and a move the state machine does not allow answers 409.
article.documentJson object yes The canonical article body as typed blocks. contentHtml and contentMarkdown are rendered from it on every save, so edit this and never them.
article.documentJson.version 1 yes Version of the document format. Always 1.
article.documentJson.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.
article.documentJson.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.
article.documentJson.blocks[]<heading>.type "heading" yes Block kind: always heading.
article.documentJson.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.
article.documentJson.blocks[]<heading>.text string yes The heading's words.
article.documentJson.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.
article.documentJson.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.
article.documentJson.blocks[]<paragraph>.type "paragraph" yes Block kind: always paragraph.
article.documentJson.blocks[]<paragraph>.text string yes The paragraph's words.
article.documentJson.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.
article.documentJson.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.
article.documentJson.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).
article.documentJson.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.
article.documentJson.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.
article.documentJson.blocks[]<list>.type "list" yes Block kind: always list.
article.documentJson.blocks[]<list>.ordered boolean yes true: a numbered list. false: a bulleted list.
article.documentJson.blocks[]<list>.items string[] yes The top-level items, as text. A reader that ignores children still sees every top-level item.
article.documentJson.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.
article.documentJson.blocks[]<list>.children[].ordered boolean yes true: a numbered sub-list. false: a bulleted one. Independent of the parent list.
article.documentJson.blocks[]<list>.children[].items string[] yes The sub-list's items, at least one, as text.
article.documentJson.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 article.documentJson.blocks[]<list>.children[]: see above.
article.documentJson.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.
article.documentJson.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.
article.documentJson.blocks[]<quote>.type "quote" yes Block kind: always quote.
article.documentJson.blocks[]<quote>.text string yes The quoted words.
article.documentJson.blocks[]<quote>.attribution string no Who said it, always plain text. Absent: the quote is unattributed.
article.documentJson.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.
article.documentJson.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.
article.documentJson.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).
article.documentJson.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.
article.documentJson.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.
article.documentJson.blocks[]<image>.type "image" yes Block kind: always image.
article.documentJson.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.
article.documentJson.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.
article.documentJson.blocks[]<image>.caption string no Plain-text caption printed under the image. Absent: no caption.
article.documentJson.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.
article.documentJson.blocks[]<code>.type "code" yes Block kind: always code.
article.documentJson.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.
article.documentJson.blocks[]<code>.text string yes The code, verbatim: never interpreted as Markdown and never translated.
article.documentJson.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.
article.documentJson.blocks[]<table>.type "table" yes Block kind: always table.
article.documentJson.blocks[]<table>.header string[] yes The header row: one cell of text per column, at least one.
article.documentJson.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.
article.documentJson.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.
article.documentJson.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.
article.documentJson.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.
article.documentJson.blocks[]<divider>.type "divider" yes Block kind: always divider.
article.contentHtml string | null yes The body rendered as HTML from documentJson, with image blocks resolved to their public URLs. Read-only: re-rendered on every document save and again on publish. null before the first render.
article.contentMarkdown string | null yes The body rendered as Markdown from documentJson, with image blocks resolved to their public URLs. Read-only: re-rendered like contentHtml. null before the first render. On GET /api/articles/{articleId}, stripLead=true returns it without the lead.
article.metaTitle string | null yes The SEO title for search results and link previews; null when none is set.
article.metaDescription string | null yes The SEO description for search results and link previews; null when none is set.
article.targetKeywords string[] yes The keywords the article is written to rank for, in the order given.
article.secondaryKeywords string[] yes Supporting keywords to work into the article; empty when none.
article.primaryLanguage string yes Language tag the article is written in, e.g. en or pt-BR; one of the project's languages.
article.translationGroupId string (uuid) yes Shared by every language version of one article, so the versions can be found together. An article with no translations is alone in its group.
article.generationConfig object | null yes The settings an AI generation was started with (topic, tone, length, image style, ...), kept so the article can be rerun. null for an article created without them.
article.generationConfig.topic 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.
article.generationConfig.prompt 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.
article.generationConfig.primaryKeyword 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.
article.generationConfig.secondaryKeywords string[] yes 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: [].
article.generationConfig.audience 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.
article.generationConfig.tone 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.
article.generationConfig.language string yes 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".
article.generationConfig.wordCount integer yes 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.
article.generationConfig.imageStyle "photorealistic" | "editorial" | "minimalist-vector" | "3d-render" | "watercolor" | "corporate-tech" | "cinematic" | "illustration" yes 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".
article.generationConfig.imageCount integer | null yes 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.
article.generationConfig.writingQuality "standard" | "premium" yes 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".
article.generationConfig.imageQuality "standard" | "hd" yes 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".
article.generationConfig.brandVoiceId 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.
article.generationConfig.contentTemplateId 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.
article.generationConfig.sourceUrl string (uri) no A reference URL for the article. It is stored with the configuration; no generation step reads or fetches it.
article.generationConfig.researchEnabled boolean yes 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.
article.generationConfig.reviewOutline boolean yes 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.
article.generationConfig.faq boolean yes 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.
article.outline object | null yes The outline the AI pipeline planned the article from, kept so a writing step can be retried (and, with reviewOutline, the one a person approved). null for an article that was not generated.
article.outline.title string yes The article title, 1 to 300 characters.
article.outline.slug string yes The URL slug: lowercase letters and digits separated by hyphens, up to 120 characters.
article.outline.metaTitle string yes The SEO title, 1 to 70 characters.
article.outline.metaDescription string yes The SEO description, 1 to 200 characters.
article.outline.sections object[] yes The sections in reading order, 1 to 40.
article.outline.sections[].id string yes Identifier of the section inside the outline, 1 to 64 characters.
article.outline.sections[].heading string yes The section's heading, 1 to 300 characters.
article.outline.sections[].level 2 | 3 yes Heading rank: 2 for a main section, 3 for a sub-section.
article.outline.sections[].purpose string yes What the section covers, 1 to 1000 characters.
article.outline.sections[].keywords string[] yes The keywords attached to the section, at most 25 of 1 to 120 characters. Empty: none. Default: [].
article.outline.sections[].estimatedWords integer yes The section's length in words, between 20 and 5000.
article.createdBy string | null yes Id of the user who created the article (for an article made with an API key, the key's creator). null when no user is attached or that user has been deleted.
article.publishedAt string (date-time) | null yes The article's publication date: when it was published, or the date given at creation or publish for an imported article. null until then; unpublishing keeps it.
article.briefId string (uuid) | null yes Id of the stored content brief the article was written from; null when none.
article.publishedUrl string | null yes The address the publishing integration reported for the live article, which links from other articles point to. null until a publish reports one, and for a webhook target, which never does.
article.wordCount integer | null yes Words in documentJson, stored with every document write; null for an article not counted since the count was added, until the backfill reaches it.
article.createdAt string (date-time) yes When the article was created.
article.updatedAt string (date-time) yes When the article last changed (a save, a status move, a re-render). It is what expectedUpdatedAt is compared with on PATCH /api/articles/{articleId}.
job object yes The translation job (kind translation) that writes the version in the background. On a replay, the job the idempotency key started first.
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 | null yes What this request reserved: the words counted and the credits. Null on a replay (reused true), which reserves nothing.
estimate.words integer yes Words counted in the source article's current content.
estimate.total integer yes Credits the translation costs: one per started 1,000 words of the source. The same amount whatever the target language; reserved when the translation starts.
reused boolean yes True when the idempotency key had already started this job: the answer is then the original job, with status 200 instead of 202.

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.translations.create({
  articleId: "3f0c5a1e-8b2d-4c6e-9a7f-1d2e3f4a5b6c",
  body: {
    language: "example",
  },
});
console.log(result);