Create with the V4V API.
Generate images, video and audio from your own applications. One API key. Your existing V4V credits.
https://v4v.ai/api/v1curl https://v4v.ai/api/v1/models \
-H "Authorization: Bearer $V4V_API_KEY"Authentication
Create a named key in Profile → API Keys. Save the API secret and webhook signing secret when they appear; they are shown only once. Multiple keys can be created and revoked independently. Key secrets are stored as SHA-256 hashes.
Send Authorization: Bearer v4v_live_… on every API request. Keep this key on your server, outside browser applications and source control. Revocation blocks future requests; already accepted generations continue.
Base URL: https://v4v.ai/api/v1.
Discover LAB models
GET /models lists current LAB model IDs, enabled state, inputs, parameters, defaults, conditions, and limits. GET /models/{model} describes one model. IDs and schemas come from the same catalog and defaults used by LAB. Studio configuration is separate.
Model names in examples below are actual catalog IDs. Use discovery to find the models currently enabled on your account. Field names are model-specific: retain the names returned by discovery, including camelCase names such as aspectRatio or referenceImageUrls. Select values are strings; numeric values for numeric-looking select options are also accepted. Fields may be supplied in input or parameters, but never both. Unknown fields are rejected. Models with selectable versions expose parameters.variant; use the values and defaults returned by discovery.
curl https://v4v.ai/api/v1/models \
-H "Authorization: Bearer $V4V_API_KEY"GPT-6 Astra chat
Discover chat.gpt_6_astra with GET /models/chat.gpt_6_astra (slug: gpt-6-astra). It has type chat and returns text. Discovery includes token rates and the rounding rule.
{
"model": "chat.gpt_6_astra",
"input": {
"prompt": "Explain what is in this image.",
"imageUrls": [
"https://example.com/image.png"
]
},
"parameters": {
"reasoningEffort": "high",
"webSearch": true,
"stream": false
}
}Send this body to POST /lab. Images are optional; up to 12 URLs or owned file IDs are accepted. Text-only requests use string input upstream; image requests use an array of message content. Reasoning effort: low, medium, high, xhigh (default low). Web search and streaming default to false. Streamed responses are returned as a completed task after generation finishes.
To offer a function, pass input.functionTools as a JSON string containing 1–10 definitions with type: "function", name, description, and parameters (an object JSON Schema). Set parameters.webSearch to false. The upstream request uses tool_choice: "auto"; function requests are returned in results[].function_calls for your application to handle. The model node does not execute functions or continue the conversation with a function result.
Chat reserves 1 credit before entering the queue. This is a minimum reservation, not a fixed price or spending cap. Rates per 1M tokens: input 560, cached input 56, cache writes 700, output 2,800 credits. The combined token cost is rounded up once to a whole credit, with a minimum charge of 1. After the response, any additional charge is deducted atomically from the available balance. If the balance is insufficient, the task fails and the unused reservation is released.
GET /tasks/{task_id} and completion callbacks return inline text without requiring a media file:
{
"status": "completed",
"credits_reserved": 3,
"credits_used": 3,
"results": [
{
"type": "text",
"text": "The image shows…",
"incomplete": false,
"function_calls": [],
"usage": {
"input_tokens": 4443,
"uncached_input_tokens": 4443,
"cached_input_tokens": 0,
"cache_write_tokens": 0,
"output_tokens": 47
},
"credits": 3
}
]
}credits_reserved reflects the expanded reservation after settlement. incomplete: true means the output limit was reached; consumed tokens are still billed. Repeated polling or retrying the same idempotency key does not create another charge.
LAB with an external image
Media inputs accept an external URL or a V4V file ID in the same field. Arrays can mix both. URLs must return actual media files without browser cookies or interactive login. HTTPS and sufficiently long-lived signed URLs are recommended. V4V validates and copies external media before generation; internal network URLs and unsafe redirects are rejected.
curl https://v4v.ai/api/v1/lab \
-H "Authorization: Bearer $V4V_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: product-edit-001' \
-d '{"model":"ai.image_edit","input":{"prompt":"Place this product on a marble counter","referenceImageUrls":["https://example.com/product.jpg"]},"parameters":{"variant":"nano-banana-2","aspectRatio":"9:16","resolution":"2K"}}'Upload private media
POST /files accepts a file field using multipart/form-data. Images, video, and audio are supported, subject to model limits. The default upload limit is 100 MiB, further constrained by the deployment's storage upload limit and proxy limit. Files must contain decodable media; audio/video inspection requires ffprobe on the server.
curl https://v4v.ai/api/v1/files \
-H "Authorization: Bearer $V4V_API_KEY" \
-F 'file=@product.png'{
"id": "6aa962444bd1dd7ab65bc6b6",
"type": "image",
"content_type": "image/png",
"size": 123456
}Use the returned ID directly, without a file_ prefix. GET /files/{id} looks up your file's metadata without charging credits.
{
"model": "ai.image_edit",
"input": {
"prompt": "Create a studio product photograph",
"referenceImageUrls": [
"6aa962444bd1dd7ab65bc6b6",
"https://example.com/side.jpg"
]
},
"parameters": {
"variant": "nano-banana-2"
}
}Studio with external or private media
POST /studio uses the same selected Studio workflow, preparation, model execution, pricing multiplier, and subscription discount as the Studio UI.
{
"input": {
"product_name": "Example Product",
"product_description": "Lightweight everyday backpack",
"product_images": [
"https://example.com/front.jpg",
"6aa962444bd1dd7ab65bc6b6"
],
"prompt": "Show the product in everyday use"
},
"parameters": {
"duration": 10,
"aspect_ratio": "9:16",
"style": "UGC",
"language": "en",
"avatar": null
},
"callback_url": "https://partner.example/webhooks/v4v"
}For a fully private-media flow, first upload each product image using POST /files, then put the returned IDs in input.product_images. Upload avatar, audio, and video references in the same way.
Studio accepts product_id (an owned saved product), avatar_id (an owned or active public library avatar, with public: prefix supported), product_name, product_description, prompt, product_images, reference_images, avatar_media, audio, and video. Each media field accepts references; arrays support multiple files. parameters.avatar accepts a library avatar ID, a single image URL or file ID, or null. Choose either input.avatar_id or parameters.avatar. Explicit product fields override library defaults. Active style names/IDs apply the saved style instructions. Parameters also include style, language, voice, placement, duration, aspect_ratio, and quality.
The selected workflow determines limits. ProVid supports 4–15 integer seconds, 480p/720p/1080p, and ratios 9:16, 16:9, 1:1, 4:5, 4:3, 3:4, 21:9, adaptive. OmniVid supports 4/6/8/10 seconds, 360p/720p/1080p/4k, and 9:16 or 16:9. Invalid options return a validation error. Media combination limits also apply. OmniVid supports audio reference IDs in LAB but does not accept raw audio reference files in Studio; unsupported references return a validation error.
Tasks and credits
Generation returns HTTP 202 after input validation and, when capacity is available, credit reservation, without waiting for generation:
{
"task_id": "task_6aa962444bd1dd7ab65bc6b6",
"status": "queued",
"credits_reserved": 20,
"created_at": "2026-09-15T18:00:00Z"
}Optional project name
Both POST /lab and POST /studio accept project_name at the top level, alongside input, parameters, and callback_url:
{
"project_name": "Summer campaign — product photo",
"model": "image-upscale-topaz",
"input": {
"image_url": "https://example.com/product.png"
},
"parameters": {
"upscale_factor": "2"
}
}The trimmed name (1–200 characters, not blank) becomes the title in Projects and, for LAB, Old runs. If omitted, the existing default title is used. The task response includes project_name and project_id. Changing the name with the same idempotency key is a conflict, just like changing the generation inputs.
Task status
The server refreshes generation status in the background. Polling returns the latest saved status: queued, processing, completed, failed, or cancelled. While the task is queued or processing, show a loading indicator. When status is completed, read chat text from results[].text or download media files from results. For failed, inspect error (including the failed stage, brief_completed, retryable and suggested retry_after_seconds where known) and credits_used. Successful brief generation can retain its charge when rendering fails; a transient retry hint does not start or authorize a new paid attempt. cancelled is also terminal.
Webhooks are optional. Without callback_url, repeat the following request every 3–5 seconds until status is completed, failed, or cancelled:
curl "$V4V_BASE_URL/api/v1/tasks/$TASK_ID" \
-H "Authorization: Bearer $V4V_API_KEY"On completion, read text results inline; download media files from results[].url. While generated files are being saved, the task stays processing; generation success alone is not completion. Repeat the status request to refresh expired download URLs. Polling does not start another generation or charge credits.
Poll GET /tasks/{task_id}, or provide a callback URL. Status is queued, processing, completed, failed, or cancelled. Polling requires an API key owned by the same account as the task.
{
"task_id": "task_6aa962444bd1dd7ab65bc6b6",
"source": "lab",
"status": "completed",
"credits_used": 18,
"results": [
{
"type": "video",
"url": "https://v4v.ai/api/v1/results/…",
"content_type": "video/mp4",
"size": 18392832,
"expires_at": "2026-09-16T00:00:00Z"
}
]
}Every media result includes stable asset_id and file_id values. Use file_id in subsequent generation media inputs. Responses also include project_id. The result list supports multiple images, videos, and audio files. Result links are signed and usable without V4V cookies or authentication. Fetch task status again to renew expired links. Anyone holding a result link can use it until expiry.
For single LAB/Studio requests, estimated credits are held when account capacity is available. If the configured concurrent-task limit is full (default 3), the request returns the same durable queued task with zero credits_reserved, credits_estimated, and queue containing limit, retry_after_seconds: 5, and estimated_start_at: null. The worker admits waiting tasks without another submission. It reserves credits atomically when a slot opens; insufficient balance at that point fails the task at stage: "admission" and schedules its webhook. Reuse the original idempotency key after network errors; do not create another task to bypass the wait. For bulk batches, each item's estimate is held only when that item is admitted to start. Generation charges consume that reservation. Failed generation charges follow the existing refund path; unused held credits are released at completion or failure. Successful steps in a partially failed Studio workflow retain their normal charges. Discovery, polling, file lookup, callbacks, and callback retries have no generation charge.
GET /balance returns currently available credits. GET /usage returns the latest 100 generation requests for the account. Profile shows lifetime request/generation counts and settled credits for each key, plus recent task history. Uploads remain in the user's LAB library and can be deleted there.
Idempotency
Send Idempotency-Key (1–200 characters) when submitting LAB or Studio requests. The same account, key value, endpoint, and JSON content return the original task, including across different API keys on that account. Object property order is ignored. Reusing a key for different content returns HTTP 409 IDEMPOTENCY_CONFLICT. If the original request is still validating, HTTP 409 REQUEST_IN_PROGRESS asks you to retry with the same key. A rejected submission remains associated with its key; correct it using a new key.
Persistent unique indexes prevent concurrent retries from creating another generation or reservation. Idempotency records remain with task history.
Signed callbacks
Provide an optional public callback_url. V4V posts task.completed or task.failed with the task fields, result list, and stable event_id. Callbacks have X-V4V-Timestamp and X-V4V-Signature: v1=<hex> headers. Verify using the webhook signing secret shown when the API key was created.
The signature is HMAC-SHA256 over the timestamp, a literal period, and the exact raw request body. Reject stale timestamps (for example, more than five minutes old) and compare signatures in constant time.
import crypto from 'node:crypto';
const expected = crypto.createHmac('sha256', webhookSecret)
.update(`${timestamp}.${rawBody}`).digest('hex');
const valid = /^v1=[a-f0-9]{64}$/.test(signature)
&& Math.abs(Date.now() / 1000 - Number(timestamp)) < 300
&& crypto.timingSafeEqual(Buffer.from(signature.slice(3), 'hex'), Buffer.from(expected, 'hex'));Return a 2xx response promptly. Delivery is at least once: deduplicate by event_id. Network failures, timeouts, HTTP 408/429, and 5xx responses are retried with exponential backoff, beginning at 30 seconds and capped at one hour, for up to 12 attempts by default. Other non-success responses, including redirects, stop delivery. Retries never create generations or charge credits. Poll task status if callback delivery is exhausted.
Studio batch callbacks
POST /studio/batches also accepts an optional callback_url. The same signed-callback verification applies. Events are batch.updated, batch.completed, batch.paused, and batch.cancelled, with a stable event_id, batch_id, batch_revision, observed_at, state_token, counts and credit totals. Read GET /studio/batches/{batch_id} for current paginated results. Events contain compact snapshots rather than expiring download URLs; intermediate changes can be coalesced between worker ticks.
Delivery is durable and retried independently of generation. Deduplicate by event_id and tolerate out-of-order events; repeated delivery retains the same body. Slow or failing receivers do not create new generations or block the generation queue. Cancellation may emit a further update when already admitted work finishes. A later requested restart or rework reactivates monitoring. A webhook is an HTTP integration and does not itself wake a finished browser chat.
Batch responses include state_token, which changes with meaningful progress or controls, and stays stable when only signed URLs renew. MCP additionally exposes wait_studio_batch for bounded server-side waiting during an active conversation; see MCP monitoring.
Errors and limits
Errors use one shape:
{
"error": {
"code": "INVALID_PARAMETER",
"message": "Allowed values: 5, 10.",
"field": "parameters.duration"
}
}HTTP 400/422 indicates invalid input or parameters; 401 an invalid/revoked key; 402 insufficient credits; 403 disabled models or denied access; 404 unavailable models/files/tasks; 409 an idempotency conflict or ongoing validation; 429 a rate/concurrency limit; 500 an internal error; and 503 temporary service unavailability. Error codes include INVALID_API_KEY, INSUFFICIENT_CREDITS, MODEL_NOT_FOUND, MODEL_DISABLED, INVALID_INPUT, INVALID_PARAMETER, INVALID_MEDIA_URL, MEDIA_NOT_ACCESSIBLE, UNSUPPORTED_MEDIA_TYPE, MEDIA_TOO_LARGE, FILE_NOT_FOUND, FILE_ACCESS_DENIED, RATE_LIMIT_EXCEEDED, GENERATION_FAILED, GENERATION_SERVICE_ERROR, and SERVICE_UNAVAILABLE. GENERATION_SERVICE_ERROR identifies a reported internal model-service error; it does not establish that the input size or parameters were invalid. Missing and inaccessible file IDs share a 404 response to avoid revealing other accounts' files.
Default limits are 120 requests per minute per key, 10 generation submissions per minute per account, and 3 concurrent accepted generations per account. Rate-limit responses include Retry-After: 60. Concurrency becomes available when a task settles.
Studio bulk generation (API and MCP)
Bulk generation is available through the API and MCP. There is no bulk workspace in the Studio interface yet. Submit up to 300 products in one JSON request. Each item creates one Studio workflow with its own task, saved workflow/settings, results and credit accounting. Product import and reference downloads happen in the background; an invalid item fails independently while the remaining items continue.
Studio, LAB, REST, MCP, Shopify and avatar generation share a persistent server queue with an absolute ceiling of 10 active generation requests for this deployment. Waiting for a generation result occupies a slot. A finished generation frees that slot even while its file is being saved. Ordinary requests have priority over bulk requests; bulk uses the available capacity, subject to an administrator's global bulk and per-account limits. The defaults are 2 concurrent bulk workflows and 1 per account. Multiple batches belonging to one account share that limit. Processing continues without an open browser or client polling.
Create a batch
POST /api/v1/studio/batches — returns HTTP 202. Supply a required Idempotency-Key header of 1–200 characters. Reuse the same key and identical JSON after a network interruption. A changed body with the same key returns HTTP 409; acceptance never creates a second batch for a retry.
curl "$V4V_BASE_URL/api/v1/studio/batches" \
-H "Authorization: Bearer $V4V_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: autumn-catalogue-001' \
--data '{
"name": "Autumn catalogue",
"max_credits": 500,
"input": { "prompt": "Show three benefits of this product in a calm reveal" },
"parameters": { "duration": 8, "aspect_ratio": "9:16", "quality": "720p" },
"items": [
{ "sku": "MUG-01", "product_id": "0123456789abcdef01234567" },
{ "sku": "BAG-02", "product_url": "https://shop.example/products/bag?variant=blue" },
{
"sku": "LAMP-03",
"input": {
"product_name": "Desk lamp",
"product_description": "Adjustable warm light",
"product_images": ["https://shop.example/lamp.jpg"]
},
"parameters": { "style": "Cinematic" }
}
]
}'Use the duration, format and quality supported by your deployment's current Studio workflow, as for POST /api/v1/studio.
| Field | Meaning |
|---|---|
name | Required batch name, 1–200 characters |
max_credits | Required positive credit budget; spent credits plus active reservations may never exceed it |
input, parameters | Optional shared Studio fields, using the same names as a single Studio request |
items | Required array of 1–300 product objects |
items[].sku | Optional client reference, at most 200 characters |
items[].product_id | Optional Products library ID owned by this account |
items[].product_url | Optional public product page to import; variant/query parameters are preserved except eBay tracking, which is canonicalized |
items[].input, items[].parameters | Item overrides; precedence is item → batch → Studio defaults |
test_count | Optional value 5; processes only the first five items and then waits for an explicit resume |
Use a product library ID, a product page URL, or product details/images in input. When both product ID and URL are supplied, the library product is used. Input fields explicitly supplied by the caller override imported values. Reference media is copied into owned storage by the same service used for single API generations. API product-page imports use bounded public HTTP requests with validated redirects. eBay listing links also have a protected browser fallback. A store can still reject an import or require sign-in; in that case supply an owned product ID or prepared product data/images. The request body limit is 1 MiB. Send JSON product lists; CSV-file upload is not part of this API release.
The response includes batch_id, status, total_items, max_credits, credits_used, credits_committed, counts and a first page of items. Initial acceptance reserves no credits for the full batch. Tasks may appear on the next worker tick, so an accepted item can initially have an empty attempts array.
Progress and results
GET /api/v1/studio/batches?offset=0&limit=50lists this account's batches.GET /api/v1/studio/batches/{batch_id}?offset=0&limit=50returns item progress. Pagination is by zero-based item index, with a maximum page size of 100.- Each item includes its
index, optionalsku, status andattempts. An attempt includes a normaltask_id,version,credits_usedand status; completed attempts include signed result URLs. GET /api/v1/tasks/{task_id}retrieves a single attempt and refreshes its signed result links. Normal task ownership rules apply across keys owned by the same account.
An item with IMPORT_FAILED failed before paid generation admission. Its error message identifies a blocked eBay/security page when known and suggests a retry or prepared product data. PREPARATION_FAILED identifies an unexpected generation-preparation failure after import; include the task_id when contacting support. Validation/media errors retain their specific codes. Other items continue. Each failed attempt is terminal: resume does not retry it. After correcting the input or deploying a fix, use retry_failed or rework below to create a new attempt in the same batch. Reusing the original creation key returns the original batch; it does not rerun it.
DIRECTOR_REQUEST_FAILED means the AI Director's text-model request was rejected before it could produce a video brief. REFERENCE_MEDIA_UNAVAILABLE identifies an unreadable, unsupported or oversized Director reference image. Report the task_id to support; do not treat either error as a KIE video failure or automatically spend credits on another attempt. Saved account images are sent directly from storage to the Director as binary image inputs, so this stage does not need anonymous CDN access.
Poll no more often than once every five seconds. A batch reports completed when all its items have reached a terminal status; inspect counts and individual errors because completion can include failed items. An item held behind a five-item sample reports waiting_for_sample. The sample pauses with TEST_REVIEW_REQUIRED after its attempts settle.
Pause, resume, cancel and increase the budget
POST /api/v1/studio/batches/{batch_id}:
{
"action": "pause"
}{
"action": "resume",
"max_credits": 1000
}{
"action": "cancel"
}Pause and cancel prevent future starts. Attempts already admitted to start retain their reservations and finish normally. Resume continues the rest after a sample, a balance top-up, or an increased budget. Budgets may only increase. Cancellation stops the remaining items. These controls are idempotent while the requested batch state is unchanged; a cancelled batch cannot resume, but it can explicitly restart or rework terminal items.
Retry failures, rework items, or restart a batch
Use the same POST /api/v1/studio/batches/{batch_id} endpoint. These actions create new, potentially paid attempts and require a new Idempotency-Key header (1–200 characters) for each intentional operation. Reuse that key and identical JSON after a network interruption. A changed body returns IDEMPOTENCY_CONFLICT. Keys for these operations are scoped to the account and batch, separately from batch creation. The request body is limited to 1 MiB.
| Action | What runs |
|---|---|
retry_failed | Only items whose latest attempt failed. Optional item_indexes selects a subset; omit it for all failures. |
rework | Selected terminal items, including successful or cancelled ones. Required items entries identify exactly one zero-based index or current task_id, plus optional product_id, product_url, input, or parameters. |
restart | Every item, including successes, with a new attempt. Wait for admitted work to finish first. The sample gate is removed. |
curl "$V4V_BASE_URL/api/v1/studio/batches/$BATCH_ID" \
-H "Authorization: Bearer $V4V_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: catalogue-retry-1" \
-d '{"action":"retry_failed","max_credits":800}'To correct only one product without regenerating successful items:
{
"action": "rework",
"items": [
{
"index": 1,
"product_url": "https://www.ebay.de/itm/227541601859",
"input": {
"prompt": "A slow close-up of the bag and its details."
},
"parameters": {
"duration": 8
}
}
]
}To remake results you dislike, copy their current items[].task_id from the batch response. You can keep the same inputs or supply a new prompt:
{
"action": "rework",
"items": [
{
"task_id": "task_0123456789abcdef01234567"
},
{
"task_id": "task_abcdef0123456789abcdef01",
"input": {
"prompt": "Slower close-up, softer lighting"
}
}
]
}Use the existing batch URL and a new operation Idempotency-Key. The batch returns to running, the selected items become queued, and their top-level task_id/results/error are cleared until the new attempt is materialized/completed. Other completed items remain unchanged. Earlier output is retained only in attempts history; consumers should display items[].results for the current result. Exactly one of index or task_id is allowed per entry. A foreign/unrelated task returns TASK_NOT_FOUND; a task from an already replaced version returns BATCH_ITEM_VERSION_CHANGED (409), so stale selections cannot silently rerun a newer result. Replaying an accepted key remains safe even after its selected task becomes historical.
For a full rerun, send {"action":"restart"} with a new key. All three actions accept optional operation-wide input and parameters changes, plus an increased max_credits. Changes merge over that item's previous request; per-item changes win. Changing product_id or product_url clears the previous alternative. Earlier request snapshots and results stay unchanged. Subsequent retries inherit the last accepted corrections and prepare a fresh workflow.
Selected attempts must be terminal. BATCH_ITEMS_ACTIVE (409) means work is still queued/admitted/processing, a selected retry item did not fail, or another retry was just accepted. For a full restart, cancel pending work and wait for admitted attempts to finish. GENERATION_UNRESOLVED (409) blocks retries while an earlier generation request is queued, running, dispatching, or uncertain; reconcile its outcome first. NO_FAILED_ITEMS (409) means there is nothing to retry. Retrying and reworking starts processing explicitly, including other pending items in that batch; an existing five-item sample gate is retained unless resume or restart removes it.
max_credits remains a cumulative limit: earlier charges plus current reservations still count. A retry does not refund successful earlier steps or reset the budget. No full-batch reservation is taken. New versions use the same global and per-account bulk queues. API/MCP Credit Logs retain batch, item and version attribution.
Responses retain every attempt in items[].attempts, each with its own task/project and increasing version (numbers can skip). items[].version, item status and counts describe the latest intended version; a new version initially reports queued even before its task is materialized. credits_used includes all versions. A batch completes only when the latest version of every item is terminal. Replayed operations never start extra attempts. Up to 100 version-creating operations and 8 MiB of accumulated changes are retained per batch; BATCH_HISTORY_LIMIT (409) requests a new batch when full.
INSUFFICIENT_CREDITS or BUDGET_EXCEEDED pauses new starts. Adding account credits does not resume a batch automatically: call resume explicitly. Budget admission and account reservation are atomic within their respective MongoDB documents, so concurrent workers cannot exceed the batch budget or debit the same attempt twice. No MongoDB replica set is required.
A status lookup timeout does not prove a generation failed. If a generation submission response is lost, its slot stays occupied for reconciliation; the system does not submit a replacement paid request automatically. Once generation succeeds, file-storage retries reuse the existing generation result. Explicitly rejected rate-limited submissions can retry with bounded backoff. Completed result review, version approval, ZIP export and a Studio bulk UI from the broader #194 plan are outside this API release; individual signed downloads are available through normal task results.
Credit Logs
Administrators can filter Credit Logs by API or MCP. Entries include the API key name/ID, task/project/job IDs, generation source, and bulk batch/item/version markers when applicable. Key secrets are never logged.
Reservations and settlement are audit events with a zero credit delta; metadata records the reserved amount. Charges and refunds carry the actual credit delta exactly once. balanceAfter is the available account balance, which excludes outstanding reservations. Historical API charges are restored from stored job receipts without charging the account again; where the historic available balance cannot be reconstructed, the admin interface says so.