PrismDraft SDK

billing

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

Methods

pd.billing.listCreditPackages(init?)

The credit packs on sale

GET /api/credit-packages

Returns the active credit packs in display order. Needs a signed-in session or API key but no workspace permission. A pack without a Stripe price is still listed, with purchasable false: do not offer a checkout for it. Price and credit amount are server-side values; a checkout takes only the pack id.

Input

Name In Type Required Description
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
packages object[] yes The active packs, in display order.
packages[].id string (uuid) yes Id of the pack; send it as creditPackageId to POST /api/workspaces/{workspaceId}/checkout.
packages[].name string yes Display name of the pack, unique among packs.
packages[].description string yes Short text describing the pack; an empty string when there is none.
packages[].creditAmount integer yes Whole credits the pack adds to the wallet once Stripe confirms the payment.
packages[].priceCents integer yes Price in the smallest unit of currency (cents for usd or eur). Display only: Stripe charges the amount of its own Price.
packages[].currency string yes Lower-case ISO 4217 code, e.g. usd.
packages[].purchasable boolean yes False when the pack has no Stripe price or billing is not configured.

Errors 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.billing.listCreditPackages();
console.log(result);

pd.billing.getCredits(input, init?)

Wallet balance, reserved and available

GET /api/workspaces/{workspaceId}/credits

Requires credits.read. available is balance less reserved. planBalance is the part of the balance the plan granted this period: it is spent first and expires at renewal, at planPeriodEnd (null unless the subscription is active or trialing). reservations lists the 50 most recent holds of any status, newest first.

Input

Name In Type Required Description
workspaceId path string (uuid) yes Id of the workspace the request works in. The caller must be a member of it, or hold an API key issued by it. For anyone else, an unknown id and an id they do not belong to both answer 404 RESOURCE_NOT_FOUND, so membership cannot be probed.
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
wallet object yes The workspace's credit wallet.
wallet.balance integer yes Every credit the workspace owns, including those earmarked by active reservations.
wallet.reserved integer yes The part of balance earmarked by active reservations for running work.
wallet.available integer yes Credits that can be spent now: balance less reserved.
wallet.planBalance integer yes The part of balance the plan granted this period: spent first, and expired at the next renewal. Never more than balance.
wallet.planPeriodEnd string (date-time) | null yes When the plan credits expire (the end of the current subscription period); null without an active or trialing subscription, or when the period end is unknown.
reservations object[] yes The 50 most recent reservations of any status, newest first.
reservations[].id string (uuid) yes Id of the reservation.
reservations[].workspaceId string (uuid) yes Id of the workspace whose wallet holds the credits.
reservations[].walletId string (uuid) yes Id of the workspace's credit wallet.
reservations[].articleId string (uuid) | null yes Id of the article the work is for; null when the work has no article or the article was deleted.
reservations[].jobId string (uuid) | null yes Id of the generation job that holds the credits; null when no job is involved or it was deleted.
reservations[].operation string yes What the reservation covers, e.g. article.generate.
reservations[].idempotencyKey string | null yes Key that makes a retried request reuse this reservation instead of holding credits twice; null when none was given.
reservations[].amount integer yes Whole credits earmarked when the reservation was made.
reservations[].capturedAmount integer yes Credits actually charged when the reservation was captured, never more than amount; 0 until then.
reservations[].status "active" | "captured" | "released" | "expired" yes active: the credits are still earmarked. captured: the work finished and its actual cost was charged. released: the hold was dropped without a charge, e.g. the job failed first. expired: the hold outlived expiresAt without being settled and was lifted by the periodic sweep.
reservations[].expiresAt string (date-time) yes When an unsettled reservation is lifted automatically; one hour after creation unless the work asked for another time to live.
reservations[].createdAt string (date-time) yes When the credits were earmarked.
reservations[].updatedAt string (date-time) yes When the reservation last changed status.

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.billing.getCredits({
  workspaceId: "3f0c5a1e-8b2d-4c6e-9a7f-1d2e3f4a5b6c",
});
console.log(result);

pd.billing.adjustCredits(input, init?)

Adjust a wallet by hand

POST /api/workspaces/{workspaceId}/credits/adjust

Requires credits.adjust, which only the Owner role holds by default. A positive amount adds credits and a negative one removes them; either way a manual_adjustment row is appended to the ledger and the balance is never edited in place. A deduction is checked against the available credits (not the total balance) and answers 409 if it is larger. It also lowers planBalance when the balance falls below it. Records a credits.adjusted audit entry with the amount, the reason and the balance after. Returns the wallet as it stands afterwards.

Input

Name In Type Required Description
workspaceId path string (uuid) yes Id of the workspace the request works in. The caller must be a member of it, or hold an API key issued by it. For anyone else, an unknown id and an id they do not belong to both answer 404 RESOURCE_NOT_FOUND, so membership cannot be probed.
body body object yes
body.amount body integer yes Whole credits to add (positive) or remove (negative). 0 is refused. A deduction larger than the available credits is refused with 409.
body.reason body string yes Why the balance is changed, 3 to 500 characters. Kept in the ledger row's metadata.reason and in the audit entry.
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
wallet object yes The wallet as it stands after the adjustment.
wallet.id string (uuid) yes Id of the wallet; a workspace has exactly one.
wallet.workspaceId string (uuid) yes Id of the workspace the wallet belongs to.
wallet.balance integer yes Every credit the workspace owns after the change, including those reserved.
wallet.reservedBalance integer yes The part of balance earmarked by active reservations.
wallet.planBalance integer yes The part of balance granted by the plan this period (spent first, expires at renewal); lowered when a deduction takes the balance below it.
wallet.available integer yes Credits that can be spent now: balance less reservedBalance.

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.billing.adjustCredits({
  workspaceId: "3f0c5a1e-8b2d-4c6e-9a7f-1d2e3f4a5b6c",
  body: {
    amount: 1,
    reason: "example",
  },
});
console.log(result);

pd.billing.listPlans(init?)

The plans on offer

GET /api/plans

Public: needs no session. Returns the active plans in display order, the free plan included, with each plan's monthly credit grant, limits, monthly price and yearly price (yearlyPriceCents, two months free). It also returns the credit packs on sale, the credits a new account is granted at sign-up, and what an all-defaults article costs, so a pricing page can turn credits into a number of articles. A plan with purchasable false cannot be subscribed to here.

Input

Name In Type Required Description
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
plans object[] yes The active plans, in display order, the free plan included.
plans[].id string (uuid) yes Id of the plan; send it as planId to POST /api/workspaces/{workspaceId}/subscription/checkout.
plans[].key string yes Stable identifier of the plan, e.g. free; the plan without a subscription has the key free.
plans[].name string yes Display name of the plan.
plans[].description string yes Short text describing the plan; an empty string when there is none.
plans[].monthlyCredits integer yes Whole credits granted to the wallet with each paid subscription invoice. They are plan credits: spent first, and they expire at the next renewal.
plans[].maxMembers integer yes Most members the workspace may have on this plan; enforced as the users quota.
plans[].maxProjects integer yes Most projects the workspace may have on this plan; enforced as the projects quota.
plans[].monthlyAiOperations integer yes AI operations the workspace may run per calendar month on this plan; enforced as the ai_operations quota.
plans[].priceCents integer yes Price per month in the smallest unit of currency (cents); 0 for the free plan.
plans[].yearlyPriceCents integer yes Price per year when billed yearly, in the smallest unit of currency (cents): ten months of priceCents, so two months free. A yearly subscription grants twelve months of monthlyCredits at once, which expire at the yearly renewal. 0 for the free plan.
plans[].currency string yes Lower-case ISO 4217 code, e.g. eur.
plans[].purchasable boolean yes False for the free plan, a plan without a Stripe price, or when billing is off.
plans[].yearlyPurchasable boolean yes False for the free plan, a plan whose yearly Stripe price is not synced, or when billing is off; send interval: "year" to the checkout only when true.
creditPackages object[] yes The top-up packs on sale, the same list as GET /api/credit-packages.
creditPackages[].id string (uuid) yes Id of the pack; send it as creditPackageId to POST /api/workspaces/{workspaceId}/checkout.
creditPackages[].name string yes Display name of the pack, unique among packs.
creditPackages[].description string yes Short text describing the pack; an empty string when there is none.
creditPackages[].creditAmount integer yes Whole credits the pack adds to the wallet once Stripe confirms the payment.
creditPackages[].priceCents integer yes Price in the smallest unit of currency (cents for usd or eur). Display only: Stripe charges the amount of its own Price.
creditPackages[].currency string yes Lower-case ISO 4217 code, e.g. usd.
creditPackages[].purchasable boolean yes False when the pack has no Stripe price or billing is not configured.
signupGrantCredits integer yes Credits a new account receives once at sign-up (a deployment setting).
typicalArticle object yes The all-defaults article, so credits can be shown as a number of articles.
typicalArticle.wordCount integer yes Target length, in words, of an article with every setting at its default.
typicalArticle.imageCount integer yes Images an all-defaults article of that length includes.
typicalArticle.textCredits integer yes Credits for the text: 2 per started 1,000 words with premium writing, the default.
typicalArticle.imageCredits integer yes Credits for the images: 1 per image.
typicalArticle.credits integer yes What an all-defaults article costs in credits, research left out: textCredits plus imageCredits.

Errors 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.billing.listPlans();
console.log(result);

pd.billing.getSubscription(input, init?)

The plan in force, the subscription, and its invoices

GET /api/workspaces/{workspaceId}/subscription

Requires billing.read. Stripe is the authority on the subscription: this answer mirrors what Stripe last reported, and the mirror changes only when a verified Stripe webhook arrives. plan is the plan whose terms apply, which is the free plan when the workspace never subscribed or its subscription status does not carry the plan. subscription is null if the workspace never subscribed. invoices lists the last 24 paid subscription invoices.

Input

Name In Type Required Description
workspaceId path string (uuid) yes Id of the workspace the request works in. The caller must be a member of it, or hold an API key issued by it. For anyone else, an unknown id and an id they do not belong to both answer 404 RESOURCE_NOT_FOUND, so membership cannot be probed.
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
plan object yes The plan whose terms apply now: the subscription's plan while its status carries it (active, trialing, past_due), otherwise the free plan.
plan.id string (uuid) yes Id of the plan; send it as planId to POST /api/workspaces/{workspaceId}/subscription/checkout.
plan.key string yes Stable identifier of the plan, e.g. free; the plan without a subscription has the key free.
plan.name string yes Display name of the plan.
plan.description string yes Short text describing the plan; an empty string when there is none.
plan.monthlyCredits integer yes Whole credits granted to the wallet with each paid subscription invoice. They are plan credits: spent first, and they expire at the next renewal.
plan.maxMembers integer yes Most members the workspace may have on this plan; enforced as the users quota.
plan.maxProjects integer yes Most projects the workspace may have on this plan; enforced as the projects quota.
plan.monthlyAiOperations integer yes AI operations the workspace may run per calendar month on this plan; enforced as the ai_operations quota.
plan.priceCents integer yes Price per month in the smallest unit of currency (cents); 0 for the free plan.
plan.yearlyPriceCents integer yes Price per year when billed yearly, in the smallest unit of currency (cents): ten months of priceCents, so two months free. A yearly subscription grants twelve months of monthlyCredits at once, which expire at the yearly renewal. 0 for the free plan.
plan.currency string yes Lower-case ISO 4217 code, e.g. eur.
plan.purchasable boolean yes False for the free plan, a plan without a Stripe price, or when billing is off.
plan.yearlyPurchasable boolean yes False for the free plan, a plan whose yearly Stripe price is not synced, or when billing is off; send interval: "year" to the checkout only when true.
subscription object | null yes The subscription as Stripe last described it; null if the workspace never subscribed.
subscription.planKey string yes Key of the plan the subscription is for; differs from plan.key while the subscription does not carry its plan (e.g. canceled).
subscription.status "incomplete" | "incomplete_expired" | "trialing" | "active" | "past_due" | "canceled" | "unpaid" | "paused" yes Stripe's own subscription status, mirrored as Stripe last reported it. The workspace keeps the plan's terms under active, trialing and past_due (Stripe is still retrying the card); under any other status it falls back to the free plan.
subscription.currentPeriodEnd string (date-time) | null yes End of the paid period Stripe reported; the next renewal unless cancelAtPeriodEnd. Null when Stripe gave none.
subscription.cancelAtPeriodEnd boolean yes True when the subscription was set to end at currentPeriodEnd instead of renewing.
subscription.billingInterval "month" | "year" yes How often Stripe bills the subscription: month or year. A yearly subscription's plan credits were granted for the whole year and expire at currentPeriodEnd.
invoices object[] yes Paid subscription invoices, newest first, at most 24.
invoices[].id string (uuid) yes Id of the invoice record in this API (not Stripe's invoice id).
invoices[].billingReason string | null yes Stripe's billing_reason for the invoice, e.g. subscription_create or subscription_cycle; null when Stripe gave none.
invoices[].amountPaidCents integer yes Amount Stripe collected, in the smallest unit of currency (cents).
invoices[].currency string yes Lower-case ISO 4217 code of the amount paid, e.g. eur.
invoices[].creditsGranted integer yes Plan credits this invoice added to the wallet; each paid invoice grants once.
invoices[].paidAt string (date-time) yes When Stripe recorded the payment.
invoices[].plan object | null yes The plan the invoice was for; null when it could not be matched to a plan.
invoices[].plan.key string yes Stable identifier of the plan the invoice was for.
invoices[].plan.name string yes Display name of that plan.

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.billing.getSubscription({
  workspaceId: "3f0c5a1e-8b2d-4c6e-9a7f-1d2e3f4a5b6c",
});
console.log(result);

pd.billing.startSubscriptionCheckout(input, init?)

Start a Stripe checkout for a plan

POST /api/workspaces/{workspaceId}/subscription/checkout

Requires credits.purchase. Opens a Stripe Checkout session in subscription mode and returns its URL with 201; the customer pays on Stripe's page. Nothing changes here until Stripe's webhook arrives: the subscription and the plan credits come from the paid invoice, never from the browser returning to successUrl. Only for a workspace without a live subscription; plan changes and cancellation go through POST /api/workspaces/{workspaceId}/subscription/portal. Answers 404 for an unknown or inactive plan; 400 for the free plan or a plan with no Stripe price for the requested interval; 409 when the workspace already has a subscription (any status except canceled or incomplete_expired) or an earlier checkout was just paid; 402 when billing is not configured. Opening a new checkout expires the workspace's earlier unpaid subscription checkout. A buyer who was on the waitlist and received the launch email gets 50% off the first invoice of a monthly plan automatically (once, within 30 days of that email); a Stripe failure while confirming it answers 402 instead of charging full price.

Input

Name In Type Required Description
workspaceId path string (uuid) yes Id of the workspace the request works in. The caller must be a member of it, or hold an API key issued by it. For anyone else, an unknown id and an id they do not belong to both answer 404 RESOURCE_NOT_FOUND, so membership cannot be probed.
body body object yes
body.planId body string (uuid) yes Id of the plan to subscribe to, from GET /api/plans. The free plan cannot be bought. The Stripe Price comes from the plan, not from the request.
body.interval body "month" | "year" no How often to bill: month (default) or year, which costs the plan's yearlyPriceCents and grants twelve months of plan credits at once. year needs the plan's yearlyPurchasable to be true. Default: "month".
body.successUrl body string (uri) no Where Stripe sends the customer after subscribing. Defaults to PrismDraft's billing page with ?checkout=success. Arriving there changes nothing; the subscription and its credits arrive by Stripe webhook.
body.cancelUrl body string (uri) no Where Stripe sends the customer if they leave the checkout. Defaults to PrismDraft's billing page with ?checkout=cancelled.
init init { signal?: AbortSignal; timeoutMs?: number } no The second argument: signal aborts the call, timeoutMs overrides the client's per-attempt timeout.

Returns 201 Created

Field Type Required Description
checkout object yes The Stripe Checkout session that was just opened.
checkout.checkoutUrl string (uri) yes Stripe-hosted Checkout page to redirect the customer to. Opening it does not add credits; only Stripe's confirmation does.
checkout.stripeSessionId string yes Id of the Stripe Checkout session (cs_…).
checkout.creditAmount integer yes Credits the purchase grants once paid; 0 for a subscription, whose credits come with each paid invoice.

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.billing.startSubscriptionCheckout({
  workspaceId: "3f0c5a1e-8b2d-4c6e-9a7f-1d2e3f4a5b6c",
  body: {
    planId: "3f0c5a1e-8b2d-4c6e-9a7f-1d2e3f4a5b6c",
  },
});
console.log(result);

pd.billing.openPortal(input, init?)

Open the Stripe billing portal

POST /api/workspaces/{workspaceId}/subscription/portal

Requires credits.purchase. Opens a Stripe billing portal session and returns its URL, where the customer changes plan, cancels or updates the card. Any change made there reaches this API by Stripe webhook, not through this call. The request body is optional. Answers 402 when billing is not configured.

Input

Name In Type Required Description
workspaceId path string (uuid) yes Id of the workspace the request works in. The caller must be a member of it, or hold an API key issued by it. For anyone else, an unknown id and an id they do not belong to both answer 404 RESOURCE_NOT_FOUND, so membership cannot be probed.
body body object no
body.returnUrl body string (uri) no Where the portal's back link returns the customer. Defaults to PrismDraft's billing page.
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
portal object yes The Stripe billing portal session that was just opened.
portal.url string (uri) yes URL of the Stripe billing portal session; redirect the customer to 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.billing.openPortal({
  workspaceId: "3f0c5a1e-8b2d-4c6e-9a7f-1d2e3f4a5b6c",
});
console.log(result);

pd.billing.startCheckout(input, init?)

Start a Stripe checkout for a credit pack

POST /api/workspaces/{workspaceId}/checkout

Requires credits.purchase. Opens a one-off Stripe Checkout session for the pack and returns its URL with 201, and records the checkout as pending (visible in GET /api/workspaces/{workspaceId}/payments). Credits are added only when Stripe's verified webhook confirms the payment: that appends a purchase ledger row and emits the credits.purchased webhook event, and arriving at successUrl adds nothing. Answers 404 for an unknown or inactive pack, 400 for a pack with no Stripe price, and 402 when billing is not configured.

Input

Name In Type Required Description
workspaceId path string (uuid) yes Id of the workspace the request works in. The caller must be a member of it, or hold an API key issued by it. For anyone else, an unknown id and an id they do not belong to both answer 404 RESOURCE_NOT_FOUND, so membership cannot be probed.
body body object yes
body.creditPackageId body string (uuid) yes Id of the credit pack to buy, from GET /api/credit-packages. The price and the credits come from the pack, not from the request.
body.successUrl body string (uri) no Where Stripe sends the customer after paying. Defaults to PrismDraft's billing page with ?checkout=success. Arriving there adds no credits; only Stripe's confirmation does.
body.cancelUrl body string (uri) no Where Stripe sends the customer if they leave the checkout. Defaults to PrismDraft's billing page with ?checkout=cancelled.
init init { signal?: AbortSignal; timeoutMs?: number } no The second argument: signal aborts the call, timeoutMs overrides the client's per-attempt timeout.

Returns 201 Created

Field Type Required Description
checkout object yes The Stripe Checkout session that was just opened.
checkout.checkoutUrl string (uri) yes Stripe-hosted Checkout page to redirect the customer to. Opening it does not add credits; only Stripe's confirmation does.
checkout.stripeSessionId string yes Id of the Stripe Checkout session (cs_…).
checkout.creditAmount integer yes Credits the purchase grants once paid; 0 for a subscription, whose credits come with each paid invoice.

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.billing.startCheckout({
  workspaceId: "3f0c5a1e-8b2d-4c6e-9a7f-1d2e3f4a5b6c",
  body: {
    creditPackageId: "3f0c5a1e-8b2d-4c6e-9a7f-1d2e3f4a5b6c",
  },
});
console.log(result);