# For AI agents Source: https://docs.letsgen.app/docs/agents A skill gives your coding agent the instructions it needs to build with Lets Gen: discover models, upload owned references, submit with a budget, poll tasks, and save outputs. ## Install Run this in your project directory with Node.js installed: ```bash npx skills add https://docs.letsgen.app ``` Choose your coding agent and installation scope in the installer. The site publishes the `letsgen-generation` skill using standard well-known skill discovery. The skill lives with these docs in the Character AI repository and is published alongside the release documentation. If your agent uses the terminal, install the [Lets Gen CLI](/docs/cli). It supports JSON output, request previews, durable recovery, and its own bundled agent skill. The docs-hosted skill below covers both CLI workflows and direct HTTP integrations. Preview available skills without installing: ```bash npx skills add https://docs.letsgen.app --list ``` ## Give your agent access Create a personal key in **Lets Gen → Settings → API keys**, with read/generate scopes and a monthly Gem cap. Store it as `LETSGEN_API_KEY` in your shell or secret manager, then start the agent from that environment. Use a secure prompt or platform secret settings to enter the value; never paste it into your agent conversation or source code. The API base URL is `https://letsgen.app`. This skill teaches agents to call the generation API; the text endpoint supports a chat-completions subset and does not provide the full protocols needed to switch a coding agent's own inference backend. ## Ask for an outcome > Add a server-side image generation feature. Discover the current models, use one output, and require my confirmation before any live generation test. My per-request ceiling is 10 Gems. > Build a speech integration using my owned voice. Persist request identities and task IDs so a reload cannot create duplicate generations. > Generate one approved image for my project and save it to ./public. Use at most 10 Gems. Ask me if the selected configuration needs more. Installing the skill does not itself make API calls or spend Gems. A live upload can process media; a live generation or extraction request spends account allowance. The agent should obtain the user's intended spending budget and respect the key's cap. ## What the agent does 1. Discover current model IDs and read the relevant reference. 2. Confirm the requested output, inputs, and budget. 3. Upload only required references and keep returned owned IDs. 4. Persist a stable request identity and exact body before submitting. 5. Poll a media task or consume text/extraction, preserving uncertain identities. 6. Download successful outputs locally and report IDs and actual reported charges. Read [Agent workflow](/docs/agents/workflow) for integration details and [Troubleshooting](/docs/agents/troubleshooting) for setup failures. --- # Agent troubleshooting Source: https://docs.letsgen.app/docs/agents/troubleshooting ## The installer cannot find a skill Use the exact docs origin: ```bash npx skills add https://docs.letsgen.app --list ``` The discovery index is [/.well-known/agent-skills/index.json](/.well-known/agent-skills/index.json). It points to a digest-verified `SKILL.md`. If the index is unreachable, check network/DNS and whether the docs release is live. Updating the skills CLI may be necessary for well-known domain discovery. ## The agent cannot see LETSGEN_API_KEY An agent inherits environment variables from the process that starts it. Restart it from the configured shell, or use its supported secret settings. Check whether the variable exists without printing its value. Never paste the secret into the conversation to diagnose it. ## 401 or 403 Use `Authorization: Bearer`, not an `apikey` header. A missing/expired/revoked secret returns 401; missing read/generate scope returns 403. A key with generate scope alone cannot discover or poll. Account and regional policies continue to apply. ## Model or settings rejected Discover current model IDs and capabilities. Use the public ID, not a provider-specific name. Select a supported resolution/duration/ratio and use `sourceImage` for an image-to-video first frame. Uploaded references must be owned asset IDs, not remote URLs. ## Gem ceiling or monthly cap reached `PRICE_CHANGED` refers to your request ceiling. `API_KEY_LIMIT` refers to key allowance. Reduce settings, inspect outstanding tasks, or request an explicit budget change. Do not switch keys to bypass a spending limit. ## Request timed out Keep the original payload, API key, and `Idempotency-Key`. If you have a media task ID, read it. Otherwise replay the exact request with backoff. `REQUEST_UNCERTAIN` is not permission to create a fresh request. Support may need to investigate persistent uncertainty. ## Output link expired Read the same task for a current signed URL and download again. Do not generate again. Retain successful output files locally. --- # Agent workflow Source: https://docs.letsgen.app/docs/agents/workflow ## Build integrations Read [llms.txt](/llms.txt) to find focused pages, or [llms-full.txt](/llms-full.txt) for the entire reference. Download [OpenAPI](/openapi.json) for schemas. Each page has a Markdown URL, for example `/markdown/api/image.md`. Use server-side bearer authentication. Keep secrets out of client code. Discover model IDs through `GET /api/generate/models` and constrain inputs to that model's capabilities. Model discovery is not a quote or full per-model schema endpoint. A useful durable client record contains the originating API-key identifier (not its secret), idempotency key, exact request body, submission timestamp, returned task/request ID, and current state. Persist this before dispatch. Retain it across reloads and process restarts. ## Generate files Before spending, establish the user's requested output and numeric Gem ceiling. Submit only the approved quantity and settings. Include `maxGems` and never silently increase it. Report `PRICE_CHANGED` so the user can reduce settings or revise their budget. Upload owned files only when needed. Image/video/audio references use asset IDs; private cloning uses the separate voice upload and the returned voice profile ID. Confirm voice rights with the user before setting `rightsConfirmed=true`. For media, poll with backoff and download successful outputs. Read the task again for expired URLs. For text or extraction, replay the exact request identity to obtain retained output. In uncertain cases, keep the identity and stop duplicate dispatch. ## Verify without paid inference For integration development, start with mocked API responses, schema checks, and authenticated read-only model/voice discovery. Do not treat successful build/typecheck as proof of a real provider generation. Only perform a live generation test when the user has approved that spending and a numeric budget. After a live media call, report task ID, terminal state, quoted/charged/released Gems, successful files, and remaining validation limits. Text/extraction responses do not include the media task's Gem breakdown; do not invent charges from token counts. --- # Upload assets Source: https://docs.letsgen.app/docs/api/assets ## POST /api/generate/assets Requires **generate** scope. Send raw file bytes with the matching media `Content-Type`. This is not multipart upload. Optional `X-Filename` records the original name, truncated to 255 characters. Maximum size is 30 MiB (30 × 1024 × 1024 bytes); specific workflows can impose lower limits. ```bash curl https://letsgen.app/api/generate/assets \ -H "Authorization: Bearer $LETSGEN_API_KEY" \ -H 'Content-Type: image/png' \ -H 'X-Filename: reference.png' \ --data-binary @reference.png ``` The declared type must be an image, video, or audio type supported by the canonical media validation. An accepted upload returns HTTP `201`: ```json { "asset": { "id": "asset_example", "kind": "image", "state": "ready", "url": "https://CURRENT_ASSET_URL" } } ``` Use the asset **ID**, not its URL, as a generation reference. Check readiness before using a file; the URL can be null. Uploading bytes does not start media generation and does not authorize publishing. Ownership and media validation still apply. This upload endpoint has no idempotency guarantee. Do not blindly repeat an ambiguous upload. Voice reference recordings use the separate multipart [voice upload](/docs/api/voices), which also transcribes the reference. --- # Generate audio Source: https://docs.letsgen.app/docs/api/audio ## POST /api/generate/audio Requires **generate** scope and `Idempotency-Key`. Uses the shared media envelope and additionally requires `operation`: `speech`, `voice_clone`, or `music`. Returns a [media task](/docs/api/tasks). ## Speech and voice cloning Use model `letsgen-voice` and `parameters.voiceProfileId`. Speech text is supplied in `prompt` (up to 2,000 characters). Select a usable voice through [Voices](/docs/api/voices). `voice_clone` uses your private reference voice; it does not train or publish a voice. ```json { "model": "letsgen-voice", "operation": "speech", "prompt": "Welcome. Let's make something together.", "parameters": {"voiceProfileId": "VOICE_ID"}, "maxGems": 20 } ``` Optional speech controls are `speed` (0.5–2), `volume` (-6–6 dB), and `temperature` (0–1, expressiveness). They default to 1, 0, and 0.7 respectively. The same voice profile input is required for `voice_clone`. ## Music Use model `suno-v6` with `operation: "music"`. Simple mode describes the desired song in `prompt` (up to 5,000 characters). ```json { "model": "suno-v6", "operation": "music", "prompt": "Warm acoustic instrumental for an early morning walk", "parameters": {"musicMode": "simple", "instrumental": "on"}, "maxGems": 100 } ``` Advanced mode uses `musicMode: "advanced"`, `style` (up to 1,000 characters), `title` (up to 80 characters), optional `negativeTags` (up to 200), `vocalGender` (`auto`, `m`, `f`), and `duration` (`off`, `30`, `60`, `120`, `180`, `240`). `instrumental` accepts `on` or `off`. For an audio cover, upload an owned recording with [Upload assets](/docs/api/assets) and supply one ID in `referenceAssetIds` or `parameters.coverAudio`. Speech/cloning takes a voice profile, not top-level media references. A monthly key cap and request `maxGems` still apply; model availability comes from discovery. --- # Errors Source: https://docs.letsgen.app/docs/api/errors ## Error envelope Most API failures use the following shape. Some errors omit `code`; inspect the HTTP status and safe `message` as well. ```json { "error": { "code": "PRICE_CHANGED", "message": "The current price exceeds your requested Gem limit." } } ``` | HTTP | Code | What to do | | --- | --- | --- | | 401 | `INVALID_API_KEY` | Check secret format, expiration, and revocation. | | 403 | `INSUFFICIENT_SCOPE` | Use a key with the required scope. | | 403 | `ACCOUNT_BLOCKED` / `ACCOUNT_UNAVAILABLE` | Resolve account access or finish account setup. | | 402 | `API_KEY_LIMIT` | Check cap and outstanding reservations; do not raise the cap automatically. | | 402 | `LLM_QUOTA_EXHAUSTED` | Check the account's LLM allowance and paid-Gem settings. | | 409 | `PRICE_CHANGED` | Reduce request cost or obtain approval for a higher ceiling. | | 409 | `IDEMPOTENCY_CONFLICT` | Restore the original body for this identity; use a new identity only for a distinct authorized request. | | 409 | `REQUEST_UNCERTAIN` | Keep the original key, body, and identity. Check/replay with backoff; never blindly dispatch a new request. | | 409 | `REQUEST_FAILED` | The original claim was released. A new authorized generation needs a new identity. | | 409 | `REFERENCES_PROCESSING` | Keep the same identity and check reference readiness. | | 409 | `ANALYSIS_FAILED` | Analysis could not be parsed; measured usage may already be billed. | | 404 | `NOT_FOUND` | Check the route/task ID and ownership. | | 413 | optional `INVALID_REQUEST` | Reduce payload size. | | 422 | `INVALID_REQUEST` / `INVALID_REFERENCE` / `MODEL_UNAVAILABLE` | Check fields, references, and discovered models. | | 503 | `WORKFLOW_UNAVAILABLE` / `PROVIDER_UNAVAILABLE` / `TEMPORARILY_UNAVAILABLE` | Preserve the original request identity and apply backoff. | Other canonical generation/policy errors can be returned. Do not bypass account restrictions or moderation by rewriting requests or switching keys. Provider failures can leave uncertain reservations; a 5xx is not permission to resubmit with a new identity. For support, retain safe IDs, status, code, and timestamps. Exclude bearer keys and signed media URLs from logs. For SSE, inspect in-stream errors and require `[DONE]` before reporting a complete text result. --- # Extract a video Source: https://docs.letsgen.app/docs/api/extract ## POST /api/generate/extract/video Requires **generate** scope and `Idempotency-Key`. Use an owned ready video asset uploaded through [Upload assets](/docs/api/assets). The video must be under 25 MiB and shorter than 120 seconds; authoritative metadata, ownership, and private delivery are checked. | Field | Type | Meaning | | --- | --- | --- | | `model` | string, required | Fixed `qwen/qwen3.7-flash`. | | `videoAssetId` | string, required | Owned ready video ID, 1–128 characters. | | `prompt` | string | Optional analysis instructions, up to 2,000 characters; default empty. | | `maxGems` | integer | Optional ceiling, 0–1,000,000. | Unknown fields are rejected. The JSON request is bounded to 16,000 bytes. ```json { "model": "qwen/qwen3.7-flash", "videoAssetId": "OWNED_VIDEO_ASSET_ID", "prompt": "Describe the scene changes and suggest representative frames.", "maxGems": 100 } ``` ## Response HTTP `200` returns `{id, extraction}`. Extraction includes `analysis`, `durationSeconds`, `frames`, and optional `segments`. Frame times are unique and in range; segments do not overlap. The response provides frame suggestions, not captured image assets. Capture frames locally from your video if needed. The API reserves a conservative analysis allowance based on 262,144 video input tokens plus 8,192 output tokens and the reviewed rate card. Actual measured usage settles once and cannot exceed that reservation. Account LLM quota and moderation apply. An exact same-identity replay returns a saved result. Pending/uncertain work returns `REQUEST_UNCERTAIN` without another dispatch. A billed malformed result returns `409 ANALYSIS_FAILED` and retains that result for replay; do not mistake it for an unbilled failure. Missing usage can retain a conservative reservation for investigation. Extraction is synchronous JSON, not a media task. --- # Generate images Source: https://docs.letsgen.app/docs/api/image ## POST /api/generate/image Requires **generate** scope and `Idempotency-Key`. Returns a [media task](/docs/api/tasks): HTTP `202` for new work, `200` for an accepted replay. ## Request | Field | Type | Notes | | --- | --- | --- | | `model` | string, required | Image ID from [discovery](/docs/api/models), up to 150 characters. | | `prompt` | string, required | Envelope limit 30,000 characters; generator/model limits may be lower. | | `parameters` | object | Defaults to `{}`. Accepted generator inputs depend on the model. | | `referenceAssetIds` | string[] | Up to 10 owned asset IDs; model limits can be lower. Defaults to `[]`. | | `maxGems` | integer | Optional request ceiling, 0–1,000,000,000. | Unknown top-level fields and unsupported parameter names are rejected. Provider routing is selected by Lets Gen: do not include `parameters.provider`, `model`, `prompt`, or `sourceTool`. ## Common parameters | Parameter | Use | | --- | --- | | `ratio` | Supported aspect ratio from the model's capabilities. | | `resolution` | Supported model resolution. | | `outputCount` | Image count, 1–4, subject to model constraints. | | `negativePrompt` | Optional negative prompt, up to 1,000 characters where supported. | | `magicPrompt` | Optional prompt optimization; defaults to false for public API images. | | `sourceImage` | Owned source asset ID for an edit model requiring a source image. | | `background` | `auto`, `transparent`, or `opaque` for supporting models. | | `variant` | Model-specific variant for supporting models. | Specialized style/reference/LoRA inputs are model-specific. The service remains authoritative; unknown options are not silently forwarded to providers. ## Example ```json { "model": "MODEL_ID_FROM_DISCOVERY", "prompt": "A watercolor illustration of a quiet harbor", "parameters": {"outputCount": 1, "magicPrompt": false}, "maxGems": 10 } ``` Upload images with [Upload assets](/docs/api/assets). Pass returned owned IDs in `referenceAssetIds` for visual guidance, or `parameters.sourceImage` where the chosen editing model requires it. Remote reference URLs are not accepted. Reference ownership and readiness are checked before generation. Read [Budgets and retries](/docs/budgets-and-retries) before adding automatic retries. --- # Generation API Source: https://docs.letsgen.app/docs/api ## Base URL ```text https://letsgen.app ``` Authenticate using `Authorization: Bearer $LETSGEN_API_KEY`. Send JSON with `Content-Type: application/json` except for the raw-media and multipart voice uploads. ## Endpoints | Method | Path | Scope | Reference | | --- | --- | --- | --- | | GET | `/api/generate/models` | read | [Discover models](/docs/api/models) | | POST | `/api/generate/image` | generate | [Images](/docs/api/image) | | POST | `/api/generate/video` | generate | [Videos](/docs/api/video) | | POST | `/api/generate/audio` | generate | [Audio](/docs/api/audio) | | GET / HEAD | `/api/generate/tasks/{id}` | read | [Tasks](/docs/api/tasks) | | POST | `/api/generate/assets` | generate | [Upload assets](/docs/api/assets) | | GET | `/api/generate/voices` | read | [Voices](/docs/api/voices) | | POST | `/api/generate/voices` | generate | [Voices](/docs/api/voices) | | POST | `/api/generate/text/v1/chat/completions` | generate | [Text and streaming](/docs/api/text) | | POST | `/api/generate/extract/video` | generate | [Video extraction](/docs/api/extract) | Image, video, audio, text, and extraction submissions require `Idempotency-Key`. Asset and voice uploads are explicit mutations and do not implement generation idempotency. Avoid automatically repeating an upload after an ambiguous response. ## Machine-readable reference Download [OpenAPI 3.1](/openapi.json) for API tooling. Agents can use [llms.txt](/llms.txt), [the full Markdown reference](/llms-full.txt), or **Read as Markdown** on any page. Schemas describe the public envelope; model-specific parameters are validated by the generation service. There is no public webhook registration, cancellation endpoint, model-detail endpoint, or caller-supplied provider routing in this API. Poll media tasks; consume text as JSON or SSE. --- # Discover models Source: https://docs.letsgen.app/docs/api/models ## GET /api/generate/models Requires **read** scope. Returns `{models, accountingPeriod: "UTC calendar month"}`. ```bash curl https://letsgen.app/api/generate/models \ -H "Authorization: Bearer $LETSGEN_API_KEY" ``` Each model contains `id`, `name`, `kind` (`image`, `video`, `audio`, or `text`), and `capabilities`. Image entries also include `available` and `baseGems`. Reviewed text and audio entries include `available`. Video entries currently do not include an availability flag or price. A catalog entry is not a guarantee that every configuration can be submitted. ## Choose inputs Image capabilities describe supported dimensions, resolutions, reference inputs, and output constraints. Video capabilities include `inputs`, `aspectRatios`, `durations`, `resolutions`, `audio`, and applicable reference counts. Use these values to constrain your UI. Set video `parameters.mode` explicitly to a supported workflow. Some limits change with resolution or references: check `maxReferenceImagesByResolution` and `referenceDurations` when provided. Do not invent a universal ratio, duration, resolution, or provider ID. Audio currently lists `letsgen-voice` (speech and voice cloning) and `suno-v6` (music). Text IDs come from the enabled reviewed text catalog. Discover them at runtime rather than copying a provider's full catalog. `baseGems` is a base cost hint for images, not a binding quote for every configuration. Submit with an approved `maxGems`; the server computes the authoritative price. The API does not currently expose a separate public quote endpoint or full per-model input schema. --- # Read tasks Source: https://docs.letsgen.app/docs/api/tasks ## GET `/api/generate/tasks/{id}` Requires **read** scope. Returns `{task}` for a non-deleted public API media run owned by the account. Unknown, deleted, other-account, and non-public-API runs return `404 NOT_FOUND`. HEAD authenticates identically but has no response body. ## Response The following is an illustrative successful response; IDs, Gem amounts, and URLs are examples. ```json { "task": { "id": "airun_example", "status": "succeeded", "quotedGems": 10, "chargedGems": 10, "releasedGems": 0, "error": null, "outputs": [{ "index": 0, "status": "succeeded", "asset": { "id": "asset_example", "kind": "image", "mimeType": "image/webp", "url": "https://SIGNED_OUTPUT_URL" } }] } } ``` | Status | Client action | | --- | --- | | `queued` / `processing` | Keep the task ID and poll with backoff. | | `succeeded` | Save the successful outputs. | | `partial` | Save successful outputs and report failed outputs. | | `failed` / `cancelled` | Stop polling and surface the sanitized error. | Newly created/reserved runs appear as `queued`. Outputs may have `asset: null` while waiting or when no asset exists. `error` is either null or `{code, message}`. Do not assume that every terminal task has a downloadable asset. ## Polling and retention Persist task IDs across reloads and process restarts. A practical polling schedule is 2, 4, 8, then 15 seconds with jitter, and a bounded application wait time. If your wait expires, retain the task for a later read instead of resubmitting it. Signed media URLs can expire. Read the same task to obtain a current URL, then download immediately. Retain files you need; this API does not promise permanent output storage. Task reads are read-only. They do not progress generation, settle billing, repair records, or copy media. No public task-cancellation or webhook-subscription endpoint is available. Text and extraction responses use same-request replay, not this media-task endpoint. --- # Text and streaming Source: https://docs.letsgen.app/docs/api/text ## POST /api/generate/text/v1/chat/completions Requires **generate** scope and `Idempotency-Key`. Discover a reviewed text model before submitting. This is a subset of the OpenAI chat-completions shape; it is not a Responses, tools, or multimodal chat endpoint. | Field | Type | Limits / default | | --- | --- | --- | | `model` | string, required | Enabled reviewed text model ID, 1–150 characters. | | `messages` | array, required | 1–60 objects with `role` and string `content`. | | `messages[].role` | string | `system`, `user`, or `assistant`. | | `messages[].content` | string | Up to 30,000 characters each. | | `stream` | boolean | false. | | `max_tokens` | integer | 1–8,192; default 4,096. | | `temperature` | number | Optional, 0–2. | | `top_p` | number | Optional, 0–1. | | `response_format` | object | Optional `{type: "text"}` or `{type: "json_object"}`. | | `maxGems` | integer | Optional conservative admission ceiling, 0–1,000,000. | Request bytes are bounded to 160,000; serialized conversation bytes to 128,000. Unknown fields, tools, provider-routing overrides, remote references, and array-valued message content are rejected. ## Non-streaming example ```bash curl https://letsgen.app/api/generate/text/v1/chat/completions \ -H "Authorization: Bearer $LETSGEN_API_KEY" \ -H 'Content-Type: application/json' \ -H 'Idempotency-Key: my-project-text-001' \ --data '{ "model": "TEXT_MODEL_ID_FROM_DISCOVERY", "messages": [{"role": "user", "content": "Write a short scene about a moonlit harbor."}], "max_tokens": 256, "maxGems": 10 }' ``` HTTP `200` returns `id`, `object: "chat.completion"`, Unix `created`, `model`, `choices` with assistant content and `finish_reason`, and usage token counts. ## Streaming Set `stream: true` and consume `Content-Type: text/event-stream`. Use curl `-N` to disable buffering. SSE `data:` frames contain `chat.completion.chunk` objects. Accumulate `choices[].delta.content`; the final successful chunk includes usage, followed by `data: [DONE]`. A stream can also contain `{error: {code: "REQUEST_UNCERTAIN", message}}`. Treat an error or a stream ending without `[DONE]` as incomplete; never assume HTTP 200 alone proves success. Disconnecting does not cancel provider spending. ## Budgets and replay The API reserves a conservative whole-Gem maximum from the reviewed rates, input size, and requested output limit before inference. Measured usage settles once through the account LLM Gem allowance and consent-gated paid Gems. The charge cannot exceed the admitted reservation. Repeat the exact body, including `stream`, with the same originating key and identity to retrieve a saved response. A completed streaming replay may return the whole saved answer in one chunk instead of reproducing the original chunk timings. Pending or ambiguous responses return `409 REQUEST_UNCERTAIN`; they never dispatch again. See [Budgets and retries](/docs/budgets-and-retries). --- # Generate videos Source: https://docs.letsgen.app/docs/api/video ## POST /api/generate/video Requires **generate** scope and `Idempotency-Key`. Uses the same `model`, `prompt`, `parameters`, `referenceAssetIds`, and `maxGems` envelope as [images](/docs/api/image). Returns a [media task](/docs/api/tasks). ## Parameters | Parameter | Use | | --- | --- | | `mode` | `text-to-video` or `image-to-video`, according to the model. | | `sourceImage` | Owned image asset ID used as a first frame for image-to-video. | | `endFrame` | Owned image asset ID used as a last frame where supported. | | `duration` | Supported video duration in seconds. | | `ratio` | Supported aspect ratio. | | `resolution` | Supported resolution. | | `audio` | Audio setting supported by the selected model. | | `referenceVideo` / `referenceVideos` | Owned video IDs where supported. | | `referenceAudios` | Owned audio IDs where supported. | Top-level `referenceAssetIds` map to visual references, not automatically to the first frame. Use `sourceImage` for a required first frame. Do not pass remote URLs or provider routing parameters. ## Example: text to video Choose a discovered video model that supports `text-to-video`. Add only duration, ratio, and resolution values confirmed by its capabilities. ```json { "model": "VIDEO_MODEL_ID_FROM_DISCOVERY", "prompt": "A paper boat drifts slowly across a still pond", "parameters": {"mode": "text-to-video"}, "maxGems": 100 } ``` ## Example: image to video ```json { "model": "VIDEO_MODEL_ID_FROM_DISCOVERY", "prompt": "Slow camera push toward the subject", "parameters": { "mode": "image-to-video", "sourceImage": "OWNED_IMAGE_ASSET_ID" }, "maxGems": 100 } ``` Upload input files with [Upload assets](/docs/api/assets). Respect capability-specific counts and durations. Poll the returned task with backoff; task reads do not trigger provider polling or generation processing. --- # Voices Source: https://docs.letsgen.app/docs/api/voices ## GET /api/generate/voices Requires **read** scope. Returns voice summaries with `id` and `name`, plus pagination data. | Query | Default | Meaning | | --- | --- | --- | | `scope` | `mine` | Your owned private voices; `explore` lists approved public voices. | | `page` | `1` | Integer 1–10,000. Pages contain up to 50 voices. | | `language` | omitted | Optional supported voice language filter. | ```bash curl 'https://letsgen.app/api/generate/voices?scope=explore&page=1' \ -H "Authorization: Bearer $LETSGEN_API_KEY" ``` Use returned IDs in `parameters.voiceProfileId` for [speech](/docs/api/audio). Public catalog discovery does not make private voices public. ## POST /api/generate/voices Requires **generate** scope. Send multipart form data: | Field | Required | Meaning | | --- | --- | --- | | `file` | yes | MP3, WAV, OGG, M4A, AAC, FLAC, or WebM audio, 1 byte–50 MiB. | | `duration` | yes | Recording duration in seconds, 15–300. | | `rightsConfirmed` | yes | Literal string `true`, only after permission is confirmed. | ```bash curl https://letsgen.app/api/generate/voices \ -H "Authorization: Bearer $LETSGEN_API_KEY" \ -F 'file=@reference.wav;type=audio/wav' \ -F 'duration=30' \ -F 'rightsConfirmed=true' ``` Let your HTTP client set the multipart boundary; do not set the Content-Type manually. Only attest rights when you own or have permission to clone the voice. An accepted upload transcribes the recording and returns HTTP `201` with `{voice: {id, name}}`. The voice is an owned private draft for zero-shot cloning. This does not train or publish a voice, and does not start synthesis. Submit a separate audio request using the returned ID and `operation: "voice_clone"`. Voice upload does not support generation idempotency. Transcription must succeed for the upload to finish; do not automatically repeat an uncertain upload. --- # Authentication Source: https://docs.letsgen.app/docs/authentication ## Bearer authentication Send your personal key on every generation API request: ```http Authorization: Bearer lgk_… ``` The `lgk_` secret contains 64 lowercase hexadecimal characters. Session cookies are not a substitute for bearer authentication on the public API. Never send a key in a query string. ## Scopes | Scope | Access | | --- | --- | | `read` | List models and voices; read owned public API tasks. | | `generate` | Submit media, text, or video analysis; upload reference media or voices. | A generate-only key cannot poll tasks or list models. Use both scopes for a complete generation workflow. Use read-only access for discovery and monitoring. ## Manage keys Create and revoke keys through **Settings → API keys** in the signed-in Lets Gen app. You can name a key, set a future UTC expiration, and set a calendar-month Gem cap. There is a limit of 50 non-revoked keys. Secret values are shown only once; the platform stores hashes, not plaintext keys. Key management endpoints use signed-in account authentication, not personal keys, and are separate from the generation API. Revocation or expiration stops further access, including saved text-response replay. Revoking a key does not erase billing evidence or cancel existing provider work. You can also manage these same personal keys on [WeGen](https://wegen.art/developer/api-keys), using its standalone **API keys** page in the workspace or account menu. ## Account policies Your account's Gem balance, LLM allowance, paid-Gem consent, content preference, network/account blocks, and regional restrictions continue to apply. An API key does not bypass moderation or publish private content. `401 INVALID_API_KEY` means the key is missing, malformed, revoked, or expired. `403 INSUFFICIENT_SCOPE` means the key lacks the required scope. See [Errors](/docs/api/errors) for other failures. --- # Budgets and retries Source: https://docs.letsgen.app/docs/budgets-and-retries ## Two spending controls - **Key monthly Gem cap:** limits the key's completed charges in the UTC calendar month plus outstanding reservations, including reservations from prior months. - **Request `maxGems`:** optional maximum admitted Gem amount for that request. Media uses its authoritative quote; text and video analysis use a conservative maximum. An omitted ceiling does not mean the request is free. Both are separate from account balance and LLM quota. A request can pass one check and fail another. Reservations reduce available allowance before generation; measured settlement releases unused allowance when it completes. A cap is a limit, not an allocation of Gems to your account. `PRICE_CHANGED` rejects a request whose quote or conservative maximum exceeds `maxGems`. `API_KEY_LIMIT` rejects a request that cannot reserve more key allowance. Ask before increasing a user's budget. ## Stable request identities Every media, text, and extraction submission requires `Idempotency-Key`: 1–128 characters from ASCII letters, digits, `_`, `.`, `:`, and `-`. Persist the identity, exact request body, and originating key before dispatch. One identity belongs to one logical request for that API key. Reusing it with a changed request returns `409 IDEMPOTENCY_CONFLICT`. Text's `stream` setting is part of its identity. An exact replay of a completed text or extraction request returns the retained response without provider inference or another charge. Media replay returns the existing durable run after current preparation checks; saved media remains accessible through the task GET even if replay preparation now fails. All access still requires valid key/account authorization. ## Uncertain outcomes A lost response, provider timeout, or concurrent request can leave `409 REQUEST_UNCERTAIN`. Preserve the original identity and payload. Check an existing task ID, or retry the exact submission with backoff using the same API key and idempotency key. Do not change keys or generate a new identity to retry an uncertain outcome. A pending text or extraction replay only checks its retained outcome; it never dispatches again. If the reservation stays uncertain, contact support with the request/task ID and time, without sharing the secret. There is no automatic timeout release or public cancellation endpoint. Closing a stream does not cancel provider consumption or settlement. `REQUEST_FAILED` means the original claim was definitively released. A new generation requires a new identity and the user's existing spending authorization. ## Downloads are separate If a download fails or a signed output URL expires, read the same task for a refreshed URL and retry the download. Never create a new generation just to retrieve an existing file. Read APIs do not advance generation or perform billing settlement. --- # CLI Source: https://docs.letsgen.app/docs/cli The [Lets Gen CLI](https://github.com/LetsGenLab/letsgen-cli) is a standalone executable named `letsgen`. Discover models, upload owned references, generate image/video/audio, recover requests, and download completed outputs. Use the HTTP API for [text streaming](/docs/api/text) and [video analysis](/docs/api/extract), which are not CLI commands yet. ## Install Find packaged executables and checksum-verified installers on the [GitHub releases page](https://github.com/LetsGenLab/letsgen-cli/releases). Supported targets are macOS, Linux, and Windows on x86-64 and arm64. If a packaged release is not available yet, install from source with Go 1.25 or later: ```bash go install github.com/LetsGenLab/letsgen-cli/cmd/letsgen@v0.1.0-alpha.1 letsgen version ``` Add Go's binary installation directory to your `PATH` if `letsgen` is not found. Follow the repository's [installation guide](https://github.com/LetsGenLab/letsgen-cli#install) for packaged installation and upgrades. ## Sign in Use this API base URL for sign-in and subsequent commands. ```bash letsgen --origin https://letsgen.app auth login letsgen --origin https://letsgen.app auth status ``` Browser login creates a revocable 30-day personal key. Choose read-only access with `--read-only` or set `--monthly-gem-cap N`; a cap of `0` disables spending. `--no-browser` prints the sign-in URL, but the browser must still reach the terminal's loopback callback. For remote terminals, use `auth login --api-key` and enter the key at its hidden prompt. Never put credentials in command arguments or chat. For automation, provide `LETSGEN_API_KEY` securely in the environment; it overrides saved credentials. Set `LETSGEN_API_ORIGIN=https://letsgen.app` to avoid repeating `--origin`. `LETSGEN_CONFIG_DIR` selects a different credential/request storage directory. ## Discover, preview, then generate ```bash letsgen --origin https://letsgen.app --json models list --kind image letsgen --origin https://letsgen.app models inspect MODEL_ID # Preview only: no network calls, uploads, or Gem spending. letsgen --origin https://letsgen.app --json generate image \ --model MODEL_ID --prompt 'A red fox in the snow' --max-gems 10 --dry-run # Run only after approving this request's Gem budget. letsgen --origin https://letsgen.app generate image \ --model MODEL_ID --prompt 'A red fox in the snow' --max-gems 10 --output ./out ``` Replace `MODEL_ID` with a current catalog ID. `baseGems` is not an exact quote. Pass only supported model settings in `--parameters`, for example `'{"ratio":"1:1"}'`. Repeat `--reference ASSET_ID` for owned references after `assets upload FILE`. Use `generate video` for video, or `generate audio --operation speech|music|voice_clone` for audio. Speech and voice cloning need an authorized `voiceProfileId` in parameters; discover voices with `voices list --scope mine`. See [image](/docs/api/image), [video](/docs/api/video), and [audio](/docs/api/audio) for input limits. ## Tasks and request recovery Generation waits up to 10 minutes by default. `--timeout 20m` changes the wait; `--async` returns the durable task immediately. A timeout does not cancel the server task. ```bash letsgen --origin https://letsgen.app tasks get TASK_ID letsgen --origin https://letsgen.app tasks wait TASK_ID --timeout 10m letsgen --origin https://letsgen.app tasks download TASK_ID --output ./out # Recover an uncertain submission with its saved identity and exact payload. letsgen --origin https://letsgen.app requests list letsgen --origin https://letsgen.app requests retry REQUEST_ID --async ``` Retry with the original origin and credential. Never create another request to recover an ambiguous submission. Downloads refresh signed URLs and never generate again or overwrite existing files. Partial tasks may still contain successful outputs. ## Coding agents and scripts `--json` keeps stdout machine-readable; progress and errors go to stderr. Agents need explicit spending permission and a numeric Gem budget before generation, with `--max-gems` on every approved request. A per-request ceiling does not replace a total session budget. The CLI bundles its own `letsgen` skill: ```bash letsgen skills list letsgen skills show letsgen skills install --target codex letsgen skills install --target claude ``` These install under `~/.codex/skills/letsgen` or `~/.claude/skills/letsgen`. Choose another skill root with `--path DIR`; existing skills are not overwritten. The docs-hosted [generation skill](/docs/agents) also links to this CLI guide and the HTTP reference. Exit codes: `0` success, `1` request/local failure, `2` invalid usage, `3` authentication/access, `4` timeout/interruption, `5` failed task/no outputs. Use `letsgen help` for the complete command list. ## Sign out ```bash letsgen --origin https://letsgen.app auth logout ``` Logout revokes the saved credential and removes it locally. Remove any separately supplied environment credential from your shell or secret manager as needed. --- # Quick start Source: https://docs.letsgen.app/docs ## 1. Create a personal API key Open [Lets Gen](https://letsgen.app), sign in, and open **Settings → API keys**. Create a key with **read** and **generate** access. Set a monthly Gem cap and an expiration appropriate for your integration. Copy the secret when it is shown: it is displayed only once. Store it on your server as `LETSGEN_API_KEY`. Never include it in a browser bundle, public repository, screenshot, or prompt. Use a secret manager or your platform's environment settings. You can also manage these same personal keys on [WeGen](https://wegen.art/developer/api-keys), using its standalone **API keys** page in the workspace or account menu. ## 2. Discover models All URLs in this reference use the API base URL `https://letsgen.app`. Prefer a terminal workflow? Start with the [Lets Gen CLI](/docs/cli). ```bash curl https://letsgen.app/api/generate/models \ -H "Authorization: Bearer $LETSGEN_API_KEY" ``` Choose an image model ID from the response. Check `available` when present and its `capabilities`; some video entries do not report an availability flag. Do not infer availability from an absent flag. Model IDs and capabilities can change; discover them instead of copying provider model names. ## 3. Submit an image This example spends account Gems. Set `maxGems` to your approved per-request ceiling, and replace `MODEL_ID` with an image model from discovery. A low ceiling can return `PRICE_CHANGED`; do not silently raise it. ```bash curl https://letsgen.app/api/generate/image \ -H "Authorization: Bearer $LETSGEN_API_KEY" \ -H 'Content-Type: application/json' \ -H 'Idempotency-Key: my-project-image-001' \ --data '{ "model": "MODEL_ID", "prompt": "A white ceramic mug on a sunlit kitchen table", "parameters": {"outputCount": 1}, "maxGems": 10 }' ``` Persist the body and idempotency key **before** sending. A new request returns HTTP `202` with `{task, created: true}`. Record `task.id` immediately. An accepted media replay normally returns HTTP `200` with the same task and `created: false`. ## 4. Read the task and save outputs ```bash curl https://letsgen.app/api/generate/tasks/TASK_ID \ -H "Authorization: Bearer $LETSGEN_API_KEY" ``` Poll with backoff, for example 2, 4, 8, then 15 seconds. Stop at `succeeded`, `partial`, `failed`, or `cancelled`. Download successful `task.outputs[].asset.url` values and retain the files. URLs can expire: read the task again for a current URL rather than generating again. ## Next steps - [Authentication](/docs/authentication): key scopes and account policies. - [Budgets and retries](/docs/budgets-and-retries): reservations, idempotency, and uncertain outcomes. - [Generation API](/docs/api): every supported public generation endpoint. - [For AI agents](/docs/agents): install the skill and work in plain language.