Documentation menu

api.char-gen.com/partner/v1

CharGen API

Build D&D content generation into your own product: images, video, audio, 3D, and structured entities, backed by a prepaid USD wallet.

The CharGen API lets your product create the same content CharGen users make on char-gen.com: images, video, audio, 3D models, and structured D&D entities such as NPCs, settlements, dungeons, and full characters. It is a small REST API. One endpoint creates a generation, one polls it, and a handful more cover your account, the model catalog, and billing.

The base URL for every call is:

text
https://api.char-gen.com

Requests and responses are JSON. Errors use application/problem+json (RFC 9457).

Authentication#

Every endpoint except the public pricing list requires an API key in the Authorization header:

text
Authorization: Bearer live_<key id>_<secret>

Keys are minted in the developer portal. The full key is shown exactly once, when you mint it. After that, the portal only shows the key id and the last 4 characters, so copy the key into your secret manager at mint time.

A few rules for handling keys:

  • Keys start with live_ or test_. The prefix is a label for your own bookkeeping. Both kinds authenticate against the same API and bill the same wallet.
  • Keys are secrets. Keep them server-side. Never ship them in client code, mobile apps, or public repositories.
  • Each key carries scopes: generate:image, generate:video, generate:audio, generate:model3d, generate:entity, read, and billing. A request without the required scope returns 403 with an empty body.
  • If a key leaks, revoke it in the portal and mint a new one. Revocation takes effect immediately.

A missing or invalid key returns 401 with an empty body.

Your first generation#

Renders (images, video, audio, 3D models) go to POST /partner/v1/generations; structured entities (NPCs, dungeons, pantheons and 15 more) go to POST /partner/v1/entities. Both take the same body shape, two fields: type and input. The Idempotency-Key header is required on both (more on it below).

bash
export CHARGEN_API_KEY="live_..."
 
curl -s -X POST "https://api.char-gen.com/partner/v1/generations" \
  -H "Authorization: Bearer $CHARGEN_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "image",
    "input": {
      "model": "GPT Image 2",
      "prompt": "a dwarf battle cook in a cluttered tavern kitchen, oil painting",
      "size": "1024x1024"
    }
  }'

The response is 202 Accepted with a Location header pointing at the status endpoint:

json
{ "id": "0b7a9f6e-3c2d-4e8a-9c1f-2d5b8a7e4f01", "status": "queued", "priceUsd": 0.12 }

priceUsd is the amount this generation drew from your wallet. The value above is an example; the pricing table is the live source.

Then poll:

bash
curl -s "https://api.char-gen.com/partner/v1/generations/0b7a9f6e-3c2d-4e8a-9c1f-2d5b8a7e4f01" \
  -H "Authorization: Bearer $CHARGEN_API_KEY"
json
{
  "id": "0b7a9f6e-3c2d-4e8a-9c1f-2d5b8a7e4f01",
  "status": "succeeded",
  "assets": [
    {
      "url": "https://...",
      "webpUrl": "https://...",
      "thumbnailUrl": "https://...",
      "videoUrl": null
    }
  ]
}

Idempotency#

The Idempotency-Key header is required on every generation POST, to /generations and /entities alike. Any string from 1 to 200 characters works; a UUID per logical generation is the easy choice.

The key is claimed BEFORE we dispatch or bill anything, which gives you these guarantees:

  • Two concurrent requests with the same key can never double-bill. The second one returns 409 (empty body) while the first is still in flight.
  • A retry with the same key within 24 hours returns the ORIGINAL generation (202 with the original id and price) instead of creating and billing a new one. Retrying a timed-out HTTP call with the same key is always safe.
  • If a request fails before work starts (for example 402 insufficient funds), the key is released. You can retry with the same key after topping up.
  • After 24 hours a key is purged and may be reused for a fresh generation.

If a call fails with a 5xx, retry with the SAME key. You will either replay the original result or dispatch cleanly, never pay twice.

Lifecycle and statuses#

A generation moves through four public statuses, always lowercase:

StatusMeaning
queuedAccepted, waiting for a worker.
processingA worker is generating.
succeededDone. Assets and/or result are ready.
failedTerminal failure. The charge is refunded to your wallet automatically.

Details worth knowing:

  • The create response always says queued.
  • Renders (image, video, audio, model3d) report processing from the first poll onward, then succeeded or failed. The output appears in assets.
  • Entity generations (npc, settlement, and friends) report queued until a worker picks them up. On success, the structured entity is in result (a JSON object) and the automatically rendered portrait appears in assets once it finishes. assets can be empty while the portrait is still rendering, so poll until it shows up if you want the image.
  • result is omitted from the JSON entirely for renders.

Poll every few seconds. Images usually finish in well under a minute; video can take several minutes; entities typically take under a minute for the text plus the portrait render time.

What you can generate#

Renders#

type is one of image, video, audio, model3d. The input object takes:

FieldApplies toNotes
modelallRequired. Exact name from the catalog (see Models below).
promptallThe generation prompt.
referenceImageUrlimage, video, model3dURL of a reference image. Required for models flagged "needs reference image".
referenceVideoUrlaudioURL of a reference video (for models that score to picture).
sizeimageShortcut for requestFields.Size, e.g. "1024x1024".
numImagesimageShortcut for requestFields["Number of Images"].
requestFieldsallPer-model options, listed below.

requestFields accepts ONLY these keys; anything else is silently dropped:

KeyValidationTypical use
Size<width>x<height>, e.g. 1024x1024Image dimensions
Number of Imagesinteger 1-10Image batch size
Qualityfree textModel-specific quality tier
Premium QualityLow, Medium, or HighGPT Image and Ideogram tiers
Durationnumber 1-600Audio length in seconds
Video Durationinteger 1-60Video length in seconds
Resolutionfree textVideo resolution
Aspect Ratiofree textOutput aspect ratio
Generate Audiotrue / falseVeo soundtrack toggle

All values are strings, including numbers and booleans: "requestFields": {"Video Duration": "5", "Generate Audio": "true"}.

Entities#

Entities use their own endpoint, POST /partner/v1/entities. type is one of the 18 entity types:

text
npc, character, monster, faction, magicitem, hazard, settlement, region, tavern,
shop, building, dungeon, quest, pantheon, species, spellbook, poetry, puzzle

Every type accepts prompt, a freeform seed, plus typed fields specific to that generator. A monster takes crLevel, monsterType, role, difficulty and more; a character takes className, level, background; a dungeon takes numberOfLevels, trapDensity and a reproducible seed. The API reference lists every field per type. All fields are optional, and anything you leave out gets a sensible default.

bash
curl -s -X POST "https://api.char-gen.com/partner/v1/entities" \
  -H "Authorization: Bearer $CHARGEN_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "monster",
    "input": {
      "prompt": "a swamp-dwelling horror that mimics drowned voices",
      "monsterType": "Undead",
      "crLevel": "9",
      "partyLevel": "5",
      "difficulty": "Deadly"
    }
  }'

Polling is the same for both families: the returned id resolves at GET /partner/v1/generations/{id}.

Each entity generation returns the structured entity JSON in result and automatically renders a portrait image, which lands in assets. The portrait is billed separately as an image render (see Pricing).

Models#

The model catalog is the contract: model must match a catalog name EXACTLY, including spaces and capitalisation. An unknown name returns 400 before anything is billed, with a pointer to the list.

Two ways to read the catalog:

  • GET /partner/v1/models (requires the read scope) returns every model with the price YOUR organisation pays, computed from your pinned price version.
  • The public pricing table on this page shows the current standard prices without authentication.

Each catalog row carries:

  • priceUsd: the price for a representative base configuration (image: 1024x1024, one image; video: default clip; per-1000-character pricing for some TTS models).
  • pricingNote: present when request options change the price (longer durations, premium quality, generated audio, larger sizes). If a model has a note, read it before assuming the base price.
  • requiresReferenceImage: when true, the model only works image-to-image and a request without referenceImageUrl is rejected with 400.

Four video models require an explicit duration. Calls without requestFields["Video Duration"] return 400 for: Kling 2.6 Pro, Kling O1, Kling O1 Standard, and Grok Imagine Video.

Pricing#

Your organisation has a prepaid wallet, held in US dollars. Every generation draws from it at dispatch time; the create response tells you what it drew.

  • Renders are priced per model. The exact figure comes from your price version (below) and the model's cost; the pricing table on this page lists every model.
  • Entities charge a flat per-type fee. Most types use the default fee; heavier multi-part generators (character, spellbook, pantheon, dungeon) cost slightly more. The live fees are in the table below.
  • Entity portraits: the automatic image render attached to an entity generation is billed separately, as a normal image render at your image rate.
  • Failures refund automatically. If a generation fails on our side, the amount drawn for it is credited back to your wallet. You never pay for failed work.

Price versions and grandfathering#

Prices are published as dated price versions. Your organisation is pinned to a version when you sign up, and you stay on it (you are grandfathered) until you opt in to a newer one or we migrate you with at least 30 days' email notice. Upstream cost changes never silently reprice your existing integration.

Funding the wallet#

Top up through Stripe Checkout, either in the portal billing tab or via the API:

bash
curl -s -X POST "https://api.char-gen.com/partner/v1/billing/checkout-session" \
  -H "Authorization: Bearer $CHARGEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "amountUsdCents": 5000 }'
json
{ "checkoutUrl": "https://checkout.stripe.com/c/pay/...", "sessionId": "cs_..." }

Open checkoutUrl in a browser to pay. Top-ups are between $10.00 (1000 cents) and $100,000.00 (10000000 cents). The wallet is credited when the payment clears, usually within a moment of checkout completing. Where we are required to collect tax, Stripe Tax adds it at checkout; listed prices exclude taxes.

When the wallet cannot cover a generation, the API returns 402 (see Errors) and nothing is dispatched or billed.

API credits are not Gold#

CharGen consumer accounts use Gold. The API does not. API usage draws from your organisation's prepaid USD wallet, which is entirely separate from any Gold balance on a personal CharGen account, including subscriptions. Gold cannot be spent through the API and API credits cannot be spent on char-gen.com.

Rate limits#

Each API key may make up to 120 requests per minute. The bucket refills continuously, so short bursts above 2 requests/second are fine until the bucket drains. Over the limit, the API returns 429 with a Retry-After: 60 header:

json
{
  "type": "https://docs.chargen.example/errors/rate-limited",
  "title": "Too Many Requests",
  "status": 429,
  "detail": "Per-key rate limit exceeded. Retry after 60 seconds.",
  "code": "rate_limited"
}

Back off and retry after the indicated delay. The limit is per key, so separate keys for separate workloads also separate their budgets.

Errors#

Errors are application/problem+json objects with a machine-readable code property. Example, a wallet that cannot cover the price:

json
{
  "type": "https://docs.chargen.example/errors/insufficient-funds",
  "title": "Payment Required",
  "status": 402,
  "detail": "Insufficient partner balance",
  "code": "insufficient_funds",
  "balanceUsd": 0.52,
  "requiredUsd": 1.08
}

The full set:

StatuscodeWhen
400bad_requestInvalid input: unknown model, missing required field, out-of-range value. The detail says what to fix.
401(empty body)Missing, malformed, revoked, or expired API key.
402insufficient_fundsWallet balance below the price. Includes balanceUsd and requiredUsd. Top up and retry with the same Idempotency-Key.
403(empty body)The key lacks the required scope.
404not_foundUnknown generation id, or a generation that belongs to a different organisation.
409(empty body)A request with the same Idempotency-Key is still in flight. Wait and poll, or retry shortly to replay its result.
429rate_limitedOver 120 requests/min on this key. Honour Retry-After.
500server_error, wallet_missingOur fault. Safe to retry with the SAME Idempotency-Key.

End-to-end examples#

Generate, poll until done, and surface a top-up link when funds run out.

JavaScript (fetch)#

javascript
const BASE = "https://api.char-gen.com";
const KEY = process.env.CHARGEN_API_KEY;
 
async function createGeneration() {
    const res = await fetch(`${BASE}/partner/v1/generations`, {
        method: "POST",
        headers: {
            Authorization: `Bearer ${KEY}`,
            "Idempotency-Key": crypto.randomUUID(),
            "Content-Type": "application/json",
        },
        body: JSON.stringify({
            type: "image",
            input: {
                model: "GPT Image 2",
                prompt: "a dwarf battle cook in a cluttered tavern kitchen, oil painting",
                size: "1024x1024",
            },
        }),
    });
    if (res.status === 402) {
        const problem = await res.json();
        const topUpUrl = await createTopUp(2000);
        throw new Error(
            `Balance $${problem.balanceUsd} is below $${problem.requiredUsd}. Top up: ${topUpUrl}`
        );
    }
    if (!res.ok) throw new Error(`create failed: HTTP ${res.status}`);
    const created = await res.json();
    return created.id;
}
 
async function waitForGeneration(id, timeoutMs = 300_000) {
    const deadline = Date.now() + timeoutMs;
    while (Date.now() < deadline) {
        const res = await fetch(`${BASE}/partner/v1/generations/${id}`, {
            headers: { Authorization: `Bearer ${KEY}` },
        });
        if (!res.ok) throw new Error(`poll failed: HTTP ${res.status}`);
        const body = await res.json();
        if (body.status === "succeeded" || body.status === "failed") return body;
        await new Promise((r) => setTimeout(r, 3000));
    }
    throw new Error(`generation ${id} did not finish in ${timeoutMs}ms`);
}
 
async function createTopUp(amountUsdCents) {
    const res = await fetch(`${BASE}/partner/v1/billing/checkout-session`, {
        method: "POST",
        headers: {
            Authorization: `Bearer ${KEY}`,
            "Content-Type": "application/json",
        },
        body: JSON.stringify({ amountUsdCents }),
    });
    if (!res.ok) throw new Error(`top-up failed: HTTP ${res.status}`);
    const body = await res.json();
    return body.checkoutUrl;
}
 
const id = await createGeneration();
const done = await waitForGeneration(id);
console.log(done.status, done.assets);

Python (requests)#

python
import os
import time
import uuid
 
import requests
 
BASE = "https://api.char-gen.com"
KEY = os.environ["CHARGEN_API_KEY"]
AUTH = {"Authorization": f"Bearer {KEY}"}
 
 
def create_generation():
    resp = requests.post(
        f"{BASE}/partner/v1/generations",
        headers={**AUTH, "Idempotency-Key": str(uuid.uuid4())},
        json={
            "type": "image",
            "input": {
                "model": "GPT Image 2",
                "prompt": "a dwarf battle cook in a cluttered tavern kitchen, oil painting",
                "size": "1024x1024",
            },
        },
        timeout=30,
    )
    if resp.status_code == 402:
        problem = resp.json()
        top_up_url = create_top_up(2000)
        raise RuntimeError(
            f"Balance ${problem['balanceUsd']} is below ${problem['requiredUsd']}. "
            f"Top up: {top_up_url}"
        )
    resp.raise_for_status()
    return resp.json()["id"]
 
 
def wait_for_generation(generation_id, timeout_seconds=300):
    deadline = time.time() + timeout_seconds
    while time.time() < deadline:
        resp = requests.get(
            f"{BASE}/partner/v1/generations/{generation_id}", headers=AUTH, timeout=30
        )
        resp.raise_for_status()
        body = resp.json()
        if body["status"] in ("succeeded", "failed"):
            return body
        time.sleep(3)
    raise TimeoutError(f"generation {generation_id} did not finish in {timeout_seconds}s")
 
 
def create_top_up(amount_usd_cents):
    resp = requests.post(
        f"{BASE}/partner/v1/billing/checkout-session",
        headers=AUTH,
        json={"amountUsdCents": amount_usd_cents},
        timeout=30,
    )
    resp.raise_for_status()
    return resp.json()["checkoutUrl"]
 
 
generation_id = create_generation()
result = wait_for_generation(generation_id)
print(result["status"], result.get("assets"))

Terms#

Use of the API is governed by the CharGen API Terms of Service (v1.1). Highlights: the wallet is prepaid, failed generations are auto-refunded, you may embed Output in your own product but not re-expose the raw API, and price versions are grandfathered with 30 days' notice before any migration.

Live pricing table

Current standard prices in USD, straight from the API. Your organisation's exact prices come from GET /partner/v1/models with your key.

ModelBase pricePricing notes
Amazon Nova Canvas$0.072
Chroma$0.048
CogView 4$0.108
Ernie Image$0.036
Ernie Image Turbo$0.036
Flux.Dev$0.012
Flux Krea$0.024
Krea 2 Large$0.096
Krea 2 Medium$0.048
Flux.Pro$0.072
Flux.Pro (Ultra)$0.096
Flux.Pro (Ultra) ReduxNeeds reference image$0.084
Flux.Schnell$0.012
Flux SubjectNeeds reference image$0.048
Flux 2 Dev$0.024
Flux 2 Flash$0.012
Flux 2 Flex$0.072
Flux 2 Pro$0.048
Flux 2 Max$0.084
Hunyuan Image 2.1$0.048
Hunyuan Image 3.0$0.108
Hunyuan Image 3.0 Instruct$0.120
Ideogram 3.0$0.048Premium Quality multiplies the price: Medium x2, High x3.
Ideogram V4$0.060Premium Quality multiplies the price: Medium x2, High x3.
MAI Image 2.5$0.096Priced per image by prompt length.
Cosmos 3 Super$0.072
Imagen 4$0.060
Imagen 4 (Ultra)$0.084
Imagen 3$0.060
Imagen 3 Fast$0.036
Jib Mix Qwen$0.036
Juggernaut Flux$0.012
Juggernaut Flux Lightning$0.012
Juggernaut Flux Pro$0.036
Gemini Flash 2.5$0.048
GPT Image 1$0.120Priced by Premium Quality (Low/Medium/High); shown price is Medium.
GPT Image 1.5$0.072Priced by Premium Quality and Size; shown price is Medium at 1024x1024.
GPT Image 2$0.096Priced by Premium Quality (Low/Medium/High); shown price is Medium.
Grok Imagine Image$0.036
Kling 3.0 Image$0.048
Kontext Dev$0.024
Kontext Pro$0.048
Kontext Max$0.084
Lumina Image V2$0.084
Midjourney V7$0.120
MiniMax Image$0.024
Nano Banana Pro$0.156Sizes above 2048px double the price.
Nano Banana 2$0.096Price scales with Size.
Nano Banana 2 Fast$0.048Sizes above 2048px double the price.
Nano Banana 2 Lite$0.048
Neta Lumina$0.024
OmniGen V2$0.204
Phoenix 1.0$0.048
Qwen Image$0.036
Qwen Image Max$0.084
Qwen Image 2.0$0.036
Qwen Image 2.0 Pro$0.084
Recraft V3$0.060
Recraft V4$0.060
Recraft V4 Pro$0.300
Reve V1Needs reference image$0.048
Riverflow 2.0 Pro$0.144
RunDiffusion Photo Flux$0.024
Sana$0.012Price scales with Size (per megapixel).
Seedream 3.0$0.036
Seedream 3.1$0.036
Seedream 4.0$0.048
Seedream 4.5$0.048
Seedream 5.0 Lite$0.048
Wan 2.2 Image$0.036
Wan 2.2 Realism$0.036
Wan 2.5 Image$0.048
Wan 2.6 Image$0.048
Wan 2.7 Image$0.048
Wan 2.7 Image Pro$0.096
Z Image Turbo$0.012
Z Image Base$0.012

Entity generation fees

Flat fee per generation. The automatic portrait render is billed separately at the image rate.

Entity typeAPI type stringFee
NPCnpc$0.040
Charactercharacter$0.060
Monstermonster$0.040
Factionfaction$0.040
Magic itemmagicitem$0.040
Hazardhazard$0.040
Settlementsettlement$0.040
Regionregion$0.040
Taverntavern$0.040
Shopshop$0.040
Buildingbuilding$0.040
Dungeondungeon$0.080
Questquest$0.040
Pantheonpantheon$0.080
Speciesspecies$0.040
Spellbookspellbook$0.060
Poetrypoetry$0.040
Puzzlepuzzle$0.040