Developer API
Authenticate with API keys, submit generations, track credits, and handle errors safely.
Overview
VisioArt exposes a developer-facing REST API for the same generation pipeline used by the product UI. API requests run against the caller's account, consume the same credit balance, and write the same generation records and usage events that appear in the dashboard.
Use the Developer API when you need to:
- discover the active model catalog, supported modes, and pricing evidence for your workspace
- create generations from your own application
- poll job status without scraping the dashboard
- download generated assets through a stable API route
- reconcile credit charges and refunds with your own backend
Base URL
Use your deployed VisioArt origin as the API base:
https://your-domain.com/api/v1Authentication
Create API keys from Settings -> API Keys. Each key inherits the account's balance and scoped permissions.
API key creation is available to paid plans only.
You can send the key in either header:
Authorization: Bearer visioart_your_api_keyor
x-api-key: visioart_your_api_keyDo not send both headers in the same request unless the values are identical. Mixed credentials are rejected.
Credit Model
- API generations use the same credit wallet as the signed-in product experience.
- A generation is charged when the job is queued successfully.
- Failed or cancelled jobs refund credits automatically when the job transitions to a refundable final state.
- If the account balance is lower than the estimated cost, generation creation fails with
402 insufficient_credits.
Idempotency
POST /api/v1/generations requires an Idempotency-Key header.
Rules:
- Replaying the same body with the same key returns the original request result.
- Reusing the same key with a different request body returns
409 idempotency_conflict. - The header accepts up to 200 characters and only allows
A-Z a-z 0-9 . _ : -.
Example:
Idempotency-Key: 6d1d0f58-d65b-474a-8d1d-7a91fd8bd8dcResponse Shape
Successful JSON responses include a request identifier:
{
"request_id": "38285b04-43a5-440f-8bb8-5e8c32e86920",
"data": {}
}Error responses use the same envelope:
{
"request_id": "1f1f8258-ae62-4c65-8e06-f9633dc518df",
"error": {
"code": "insufficient_credits",
"message": "Insufficient credits"
}
}The same value is also returned in the x-request-id response header.
Endpoints
List models and capabilities
curl -X GET "https://your-domain.com/api/v1/models" \
-H "Authorization: Bearer visioart_your_api_key"Response:
{
"request_id": "2b4fa4fa-3419-4d92-95ad-d4d08a838bea",
"data": {
"object": "list",
"total": 26,
"items": [
{
"object": "model",
"model_id": "qwen2",
"display_name": "Qwen2",
"availability": "active",
"modes": [
{
"generator_mode": "text-to-image",
"supported_aspect_ratios": ["16:9", "9:16", "1:1"],
"supported_duration_seconds": [5],
"supported_output_formats": ["png", "jpg"],
"public_model_options": [],
"pricing": {
"variants": [
{
"id": "default",
"request_model_options": null,
"credit_matrix": [
{
"aspect_ratio": "16:9",
"duration_seconds": 5,
"credits": 6,
"source_type": "legacy_exact_matrix",
"source_updated_at": "2026-04-22T11:09:35.262Z"
}
]
}
]
}
}
]
}
]
}
}Model directory notes:
- The endpoint returns the workspace's currently active, enumerable models only. Unsupported or partially configured models are omitted instead of guessed.
modes[*].public_model_optionslists only option families that are currently valid on the publicPOST /generationsschema and accepted by the business validation path.pricing.variants[*].request_model_optionsintentionally mirrors themodelOptionsrequest payload shape fromPOST /generations, so you can copy the object directly.credit_matrix[*].source_typeshows where each credit value came from:workspace_override,market_catalog,legacy_exact_matrix, orbuilt_in_estimate.credit_matrix[*].source_updated_atis populated when the value came from a reviewed market catalog rule or the generated exact credit matrix. Override-backed or heuristic rows may returnnull.
Read credit balance
curl -X GET "https://your-domain.com/api/v1/credits/balance" \
-H "Authorization: Bearer visioart_your_api_key"Response:
{
"request_id": "38285b04-43a5-440f-8bb8-5e8c32e86920",
"data": {
"balance": 1000,
"unit": "credits"
}
}Create a generation
curl -X POST "https://your-domain.com/api/v1/generations" \
-H "Authorization: Bearer visioart_your_api_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6d1d0f58-d65b-474a-8d1d-7a91fd8bd8dc" \
-d '{
"title": "veo31-fast text-to-video",
"modelId": "veo31-fast",
"generatorMode": "text-to-video",
"prompt": "A clean product reveal with cinematic camera movement",
"aspectRatio": "16:9",
"durationSeconds": 8,
"uploads": []
}'Response:
{
"request_id": "d587724c-df59-4333-9d4a-2ce8d2988466",
"idempotent_replay": false,
"data": {
"id": "9a87675c-1e1a-4b16-873c-19ffd8e79cdc",
"object": "generation",
"status": "processing",
"title": "veo31-fast text-to-video",
"model_id": "veo31-fast",
"generator_mode": "text-to-video",
"prompt": "A clean product reveal with cinematic camera movement",
"aspect_ratio": "16:9",
"duration_seconds": 8,
"output_format": null,
"estimated_credits": 30,
"error_message": null,
"result_url": null,
"outputs": [],
"queued_at": "2026-04-05T13:27:59.824Z",
"completed_at": null,
"created_at": "2026-04-05T13:27:59.824Z",
"updated_at": "2026-04-05T13:28:01.903Z"
}
}Request body rules:
- The public create-generation schema is strict. Unknown or internal-only fields are rejected with
400 invalid_request. uploadsonly accepts objects shaped like{ "kind": "...", "url": "https://..." }.- Each upload
urlmust already point to a trusted remote asset URL from workspace storage or a trusted provider-hosted origin. modelOptions, when provided, must only use public option families that apply to the selectedmodelId. Irrelevant or unknown nested option fields are rejected.imageCount, when present insidemodelOptions, is only accepted on models whose/modelsentry exposespublic_model_options[].path = "modelOptions.imageCount"; pricing variants show the exact credit cost before submission.- Omit empty no-op objects such as
modelOptions: {}ormodelOptions: { "kling30": {} }. - Do not send internal fields such as
landingRecordId,landingKind,landingPathname,estimatedCredits,storagePath,fileName, orrequired.
Generation response fields:
| Field | Type | Notes |
|---|---|---|
id | string (UUID) | VisioArt generation job id. |
object | "generation" | Constant literal. |
status | "queued" | "processing" | "completed" | "failed" | "cancelled" | Terminal states: completed, failed, cancelled. |
title | string | Defaults to "{modelId} {generatorMode}" when omitted in the request. |
model_id | string | The model the request ran against. |
generator_mode | string | text-to-video, image-to-video, video-to-video, effect, image-editor, text-to-image, or video-compression. |
prompt | string | May be empty for modes that do not require a prompt. |
aspect_ratio | string | One of 16:9, 9:16, 1:1, 4:3, 3:4, 3:2, 2:3. |
duration_seconds | number | Integer in 3-15. |
output_format | "png" | "jpg" | "webp" | null | Only populated for image modes that accept an output format. |
estimated_credits | number | Credits charged at queue time; refunded on failed or safely cancelled. |
error_message | string | null | Populated only on failed. |
result_url | string | null | Shortcut for outputs[0].url. Null while outputs is empty. |
outputs | GenerationOutput[] | See table below. Empty until the job produces assets. |
queued_at | string (ISO-8601) | When the job entered the queue. |
completed_at | string | null (ISO-8601) | Set when the job reaches a terminal state. |
created_at | string (ISO-8601) | Row creation timestamp. |
updated_at | string (ISO-8601) | Last state-transition timestamp. |
Generation output object (outputs[i]):
| Field | Type | Notes |
|---|---|---|
id | string (UUID) | Asset id; usable against /assets/{assetId}. |
kind | string | Typically video or image; mirrors the generator mode. |
url | string | VisioArt asset route (/api/v1/assets/{id}), not the raw provider URL. |
mime_type | string | null | Populated once the provider reports the asset mime type. |
width | number | null | Pixels; null until the asset metadata is known. |
height | number | null | Pixels; null until the asset metadata is known. |
duration_seconds | number | null | Null for image outputs. |
created_at | string (ISO-8601) | Asset row creation timestamp. |
Read a generation
curl -X GET "https://your-domain.com/api/v1/generations/9a87675c-1e1a-4b16-873c-19ffd8e79cdc" \
-H "Authorization: Bearer visioart_your_api_key"Cancel a generation
curl -X POST "https://your-domain.com/api/v1/generations/9a87675c-1e1a-4b16-873c-19ffd8e79cdc/cancel" \
-H "Authorization: Bearer visioart_your_api_key"Cancellation is only available while the job is still in VisioArt's local queue and has not started upstream submission yet.
In practice, that means the job must still be queued, must not have a provider task ID yet, and must not already be claimed by a worker for submission.
If cancellation succeeds, the job transitions to cancelled and writes a refund event for the previously charged credits.
Once a job has started the upstream submission flow, the API returns 409 generation_not_cancellable. This avoids promising a cancellation that the upstream provider may not actually support.
Read usage events
curl -X GET "https://your-domain.com/api/v1/usage?limit=20&offset=0" \
-H "Authorization: Bearer visioart_your_api_key"Query parameters:
| Name | Type | Default | Range | Notes |
|---|---|---|---|---|
limit | number | 20 | 1-100 | Page size. |
offset | number | 0 | 0-10000 | Zero-based offset. |
apiKeyId | string | — | 1-255 chars | Optional filter restricting the response to events produced by a specific API key. |
Response envelope:
{
"request_id": "…",
"data": {
"items": [
{
"id": "…",
"api_request_id": "…",
"api_key_id": "…",
"generation_job_id": "…",
"credit_transaction_id": "…",
"event_type": "charge",
"credits_delta": -20,
"balance_before": 1000,
"balance_after": 980,
"pricing_snapshot": "{\"schemaVersion\":1,\"modelId\":\"veo31-fast\",\"generatorMode\":\"text-to-video\",\"aspectRatio\":\"16:9\",\"durationSeconds\":8,\"modelOptions\":null,\"variantKey\":null,\"credits\":20,\"listCredits\":20,\"hasPromotion\":false,\"promotionLabel\":null,\"promotionStartsAt\":null,\"promotionEndsAt\":null,\"sourceType\":\"market_catalog\",\"sourceUpdatedAt\":\"2026-05-15\"}",
"note": null,
"created_at": "2026-04-05T13:27:59.824Z"
}
],
"pagination": { "limit": 20, "offset": 0, "total": 137 }
}
}Usage item fields:
| Field | Type | Notes |
|---|---|---|
id | string (UUID) | Usage event id. |
api_request_id | string (UUID) | Links the event to the originating API request record. |
api_key_id | string | The key that produced the event. |
generation_job_id | string | null | The job that produced the charge or refund. |
credit_transaction_id | string | null | Credit ledger row id. |
event_type | "charge" | "refund" | charge is written when a job is queued; refund is written when a previously charged job transitions to failed or a safely cancellable cancelled state. |
credits_delta | number | Negative for charges, positive for refunds. |
balance_before | number | null | Wallet balance before the event, in credits. |
balance_after | number | null | Wallet balance after the event, in credits. |
pricing_snapshot | string | null | JSON-stringified pricing metadata captured at charge time. Parse it if you need structured fields. |
note | string | null | Free-form operator note, if any. |
created_at | string (ISO-8601) | Event timestamp. |
The pagination.total field reflects the total number of events matching the query, not the current page.
Download an asset
curl -L "https://your-domain.com/api/v1/assets/510522c3-f7fb-4265-82ae-471f12aea62b" \
-H "Authorization: Bearer visioart_your_api_key" \
-o output.mp4Common Error Codes
VisioArt validates generation requests in two layers:
- Schema layer (
400 invalid_request) — Shape, type, and format checks. Unknown fields, wrong types, values outside the declared enums or lengths, or internal-only keys (for examplelandingRecordId,estimatedCredits) are rejected here before any business logic runs. - Business layer (
422 invalid_generation_request) — Semantic checks that depend on workspace state or the resolved model capability. A well-formed request can still fail here if themodelIdis not active in the workspace, the combination ofmodelId×generatorMode×aspectRatio×durationSecondsis not supported, a required upload is missing, an upload URL is not trusted, or the resolved pricing rule is missing.
The set of active models and their supported options is workspace-scoped and can be overridden by administrators, so there is no hard-coded modelId enum on the public schema. Read GET /api/v1/models before large batch submissions or treat 422 invalid_generation_request as a retryable intent to fix input.
| HTTP | Code | Meaning |
|---|---|---|
400 | ambiguous_api_key | More than one different API key credential was sent. |
400 | invalid_content_length | Request metadata included an invalid Content-Length value. |
400 | invalid_request | JSON schema validation failed (shape, type, format). |
400 | invalid_json | Request body is not valid JSON. |
400 | missing_idempotency_key | Create-generation request omitted Idempotency-Key. |
400 | invalid_idempotency_key | Idempotency-Key format is invalid. |
404 | feature_disabled | Developer API is disabled in this workspace. |
401 | missing_api_key | No API key was provided. |
401 | invalid_api_key | API key is malformed, unknown, or not recoverable. |
401 | api_key_disabled | API key exists but is disabled. |
401 | api_key_expired | API key exists but is expired. |
401 | invalid_authorization_header | Authorization header is not using Bearer. |
403 | insufficient_scope | The API key does not include the required permission scope. |
402 | insufficient_credits | The account balance is lower than the generation estimate. |
409 | idempotency_conflict | The same idempotency key was reused with a different body. |
409 | generation_not_cancellable | The job is outside VisioArt's local cancellation window and can no longer be cancelled safely. |
413 | body_too_large | Request body exceeds the current limit. |
415 | unsupported_media_type | Endpoint only accepts JSON payloads. |
422 | invalid_generation_request | Request shape is valid but the resolved generation intent is not supported (unsupported model, incompatible option combination, untrusted upload URL, missing pricing rule). |
429 | concurrent_job_limit | The account already has the maximum number of active API generation jobs. |
429 | rate_limited | API key request rate limit has been exceeded. |
500 | internal_error | An unexpected developer API error occurred before route-specific handling. |
500 | models_read_failed | The model directory could not be loaded. |
500 | request_state_invalid | Stored idempotency state is incomplete and cannot be replayed safely. |
503 | provider_unavailable | Provider balance or provider availability blocked submission. |
Endpoint-Specific Error Notes
Model directory
500 models_read_failed
Generation creation
400 invalid_request402 insufficient_credits409 idempotency_conflict429 concurrent_job_limit422 invalid_generation_request500 request_state_invalid503 provider_unavailable500 generation_create_failed
Generation read
400 invalid_generation_id404 generation_not_found500 generation_read_failed
Generation cancel
400 invalid_generation_id404 generation_not_found409 generation_not_cancellable500 generation_cancel_failed
Billing Edge Cases
402 insufficient_creditsis returned before a new generation job is inserted. No generation ID is created, no credit charge is written, and no refund event is needed.- Refunds are only written when a previously charged job transitions to
failedor to a safely cancellablecancelledstate. - Job status transition, refund ledger mutation, developer API refunded counters, and refund usage events are persisted in the same database transaction. If any local refund persistence step fails, VisioArt rolls the transition back instead of leaving a partial refund state behind.
Credit balance
500 credits_read_failed
Usage list
400 invalid_query500 usage_read_failed
Asset read
400 invalid_asset_id404 asset_not_found500 asset_read_failed
Permission Scopes
New API keys receive these default scopes:
{
"generation": ["create", "read", "cancel"],
"credits": ["read"],
"usage": ["read"],
"assets": ["read"],
"models": ["read"]
}If a key was created before scoped permissions existed, VisioArt preserves backward compatibility and still allows requests unless the key is explicitly restricted later. If a key carries an explicit permissions payload, VisioArt enforces that payload as-is; backward compatibility only applies to legacy keys that predate scoped permissions and therefore have no stored permissions field.
Operational Notes
- The asset URL inside a generation response points to the VisioArt API asset route, not to the raw provider URL.
- Refunds are written to both the user credit ledger and the developer API usage log.
- Credit charges and refunds are part of the same database lifecycle as the generation state transition, which prevents partial refund states where balance changes but job status does not.
Recommended integration flow
- Read
/modelsto discover the workspace's current model catalog, valid option families, and credit evidence. - Read
/credits/balancebefore large batch submissions. - Create jobs with a unique
Idempotency-Key. - Poll
/generations/{jobId}until the job reaches a final state. - Use
/usageto reconcile charges and refunds in your own system.
VisioArt Docs