Asset jobs
Run an AI action on a standalone image, with no design involved: remove its background, generate an image from a prompt, or upscale it. Each action is a job — the call answers 202 with a job_id at once, and the result arrives by polling or by webhook.
To apply the same AI features to an image layer inside a design, use remove_bg, text_to_image and upscale in an asynchronous generation instead.
How it works
- Start the job — one POST per action. It answers
202with ajob_idin milliseconds, before anything is processed. - Follow it — poll
GET /asset-jobs/{jobId}untilstatusisCOMPLETEDorFAILED, or pass acallback_urland receive theASSET_JOB_FINALIZEDwebhook when the job ends. - Read the result —
outputs[]lists the produced files,creditswhat the job cost.
Actions
| Action | Endpoint | operation | Inputs | Output | AI credits |
|---|---|---|---|---|---|
| Remove a background | POST /images/remove-background | images_remove_background | image_url, crop | 1 WebP image, transparent background | 2–12, by model |
| Generate an image from a prompt | POST /images/text-to-image | images_text_to_image | prompt, aspect_ratio, quality, reference_image_urls | 1 image | 1–89, by model and quality |
| Upscale an image | POST /images/upscale | images_upscale | image_url, upscale_factor | 1 WebP image | By the size of the result |
Every action also takes model and callback_url. Each page lists the inputs, the output and a sample request in cURL, Node.js and Python.
Actions are grouped by the media they act on — /images/… today — and each one's operation, on the job, the webhook and GET /ai-models, is that media and the action: images_upscale is POST /images/upscale.
Response to a start
202, for every action, before anything is processed:
{ "job_id": "8f0c2a4e-4869-11f0-96f2-0a00d9eb8f78", "status": "PENDING" }Get the result
Two ways, which you can combine:
| Method | How | When to use |
|---|---|---|
| Callback | Pass callback_url when starting the job | You can receive inbound webhooks |
| Polling | GET /asset-jobs/{jobId} every couple of seconds | You cannot, or as a fallback |
There is no dashboard subscription for asset jobs: the webhook goes only to the job's own callback_url. Its payload is the job exactly as the poll returns it, plus event_type — see Asset job events.
Poll the job
Always 200 — branch on status, not on the HTTP status. A failed job is still a 200, with error saying why.
status | Meaning | Final |
|---|---|---|
PENDING | Queued. | No |
PROCESSING | Running. | No |
COMPLETED | Done: outputs and the credits are set. | Yes |
FAILED | Ended without a result: error says why. | Yes |
Most jobs finish within seconds, but none is bounded — poll until the status is final rather than waiting a fixed time.
curl -H "x-api-key: {YOUR-API-KEY}" https://api.abyssale.com/asset-jobs/{jobId}// No SDK method yet — plain fetch (Node 18+). Set ABYSSALE_API_KEY before running.
const headers = { 'x-api-key': process.env.ABYSSALE_API_KEY };
let job;
do {
await new Promise((resolve) => setTimeout(resolve, 2000));
job = await (await fetch(`https://api.abyssale.com/asset-jobs/${jobId}`, { headers })).json();
} while (job.status === 'PENDING' || job.status === 'PROCESSING');
if (job.status === 'COMPLETED') console.log(job.outputs[0].url, job.credits.ai_credits);
else console.error(job.error.code, job.error.message);# No SDK method yet — plain requests. Set ABYSSALE_API_KEY before running.
import os
import time
import requests
headers = {"x-api-key": os.environ["ABYSSALE_API_KEY"]}
while True:
time.sleep(2)
job = requests.get(f"https://api.abyssale.com/asset-jobs/{job_id}", headers=headers, timeout=30).json()
if job["status"] in ("COMPLETED", "FAILED"):
break
if job["status"] == "COMPLETED":
print(job["outputs"][0]["url"], job["credits"]["ai_credits"])
else:
print(job["error"]["code"], job["error"]["message"])Response
| Field | Type | Description |
|---|---|---|
job_id | string (uuid) | The job. |
operation | string | images_remove_background, images_text_to_image, images_upscale. Not a closed list — treat an unknown value as generic. |
status | string | See the table above. |
outputs | array | The files the job produced — see Outputs. Empty until COMPLETED, and on a failed job. |
credits | object | What the job has spent so far, final once COMPLETED: 0 and 0 until then. Same two balances, with the same names, as GET /credits. |
credits.generation_credits | integer | Generation credits spent. 0 for every action today. |
credits.ai_credits | integer | AI credits spent. |
error.code / error.message | string | Why the job failed — see Errors. error is null unless FAILED. |
created_at / finalized_at | string (date-time) | Start and end, UTC. finalized_at is null until the status is final. |
{
"job_id": "8f0c2a4e-4869-11f0-96f2-0a00d9eb8f78",
"operation": "images_remove_background",
"status": "COMPLETED",
"outputs": [
{ "type": "image", "url": "https://production-banners.s3.eu-west-1.amazonaws.com/tmp_ai/d3b07384-d113-4ec6-a3b5-2f7c6e1a9b10/8f0c2a4e-4869-11f0-96f2-0a00d9eb8f78.webp", "mime_type": "image/webp" }
],
"credits": { "generation_credits": 0, "ai_credits": 3 },
"error": null,
"created_at": "2026-09-21T14:13:20Z",
"finalized_at": "2026-09-21T14:13:27Z"
}A failed job carries outputs: [], both credits 0, and "error": { "code": "invalid_input_asset", "message": "The image https://example.com/product.jpg was not found (HTTP 404). Check the URL." }.
Outputs
Each entry of outputs is one produced file, and its type says which fields it carries:
type | Fields | Description |
|---|---|---|
image | url, mime_type | url: link to the image, valid 7 days (see below). mime_type: its media type, e.g. image/webp, image/png — null in the rare case it could not be read; url is always there. |
Every action produces image outputs today. type is not a closed list: skip an output whose type you do not know rather than failing on it.
Who can see a job
Any API key of the workspace can read a job started with another key. A job of another workspace answers 404 asset_job_not_found, exactly like one that does not exist. Jobs started from Abyssale's MCP integration are not visible on this API.
How long a job and its files last
| What | Lasts | Then |
|---|---|---|
| The job | 7 days after finalized_at | Polling it answers 404 asset_job_not_found. |
An output url | 7 days from the response that carried it | The link answers 403. Poll again for a fresh one while the job lasts. |
Download the files you want to keep. An output url is a signed link: it answers GET only, so a HEAD request on it answers 403.
List the AI models
The models the workspace can use for each action, and the default one used when a request names none. Models disabled in the workspace settings are left out.
| Query parameter | Required | Description |
|---|---|---|
operation | No | images_remove_background, images_text_to_image or images_upscale: that action alone. Omitted: every action, as {"operations": [...]}. |
curl -H "x-api-key: {YOUR-API-KEY}" \
"https://api.abyssale.com/ai-models?operation=images_text_to_image"{
"operation": "images_text_to_image",
"default_model": "nano-banana-2",
"models": [
{
"id": "nano-banana-2",
"name": "Nano Banana 2",
"is_default": true,
"aspect_ratios": {
"allowed": ["1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3", "21:9", "5:4", "4:5", "4:1", "1:4", "8:1", "1:8"],
"default": "1:1"
},
"qualities": { "allowed": ["0.5K", "1K", "2K", "4K"], "default": "0.5K" },
"max_reference_images": 14
}
]
}One model shown; the response lists every model the workspace can use.
default_modelisnullwhen every model of the action is disabled.aspect_ratiosandqualitiesappear onimages_text_to_imagemodels only, and only when the model takes the parameter.max_reference_imagesappears onimages_text_to_imagemodels that accept reference images: how manyreference_image_urlsthe model takes at most.upscale_factorsappears onimages_upscalemodels: the factors the model accepts, and its default.- No prices. A price is the cost of one call with its inputs, not a property of a model — see Credits.
Credits and limits
Never retry a POST blindly
Every 202 is a new job, charged on its own, and nothing is retried for you. Keep the job_id and follow that job rather than starting another. A 503 means no job was created and nothing was charged.
AI credits
Every action spends AI credits, not generation credits, and only once the job completes — the cost is on the finished job, in credits.ai_credits. A failed job costs nothing.
Before anything is queued, the workspace's AI balance is checked against the price of that exact call, and a short balance answers 402 insufficient_credits. That check is not a reservation: a job can still end FAILED with insufficient_credits if the balance runs out before it runs.
Prices depend on the model and, for text to image, on quality, aspect_ratio and reference images — see Credits → AI credits.
Upscale is the exception: its price depends on the size of the result, so only a non-empty AI balance is checked before queueing — see Upscale → Credits and errors.
Running jobs per workspace
At most 5 asset jobs run at once per workspace, PENDING and PROCESSING together, all actions combined. Past that, a start answers 429 too_many_asset_jobs: wait for one to finish, then start the next.
The POSTs have no per-minute budget of their own — that cap is what bounds them, under the global ceiling. GET /asset-jobs/{jobId} is in the Polling tier, GET /ai-models in Read.
Errors
The starting call refuses the request before anything is queued:
id | Status | Trigger |
|---|---|---|
missing_required | 400 | A required field is missing, e.g. image_url or prompt. |
wrong_type | 400 | A field has the wrong type, e.g. a string for crop. |
unknown_enum_value | 400 | An unknown model; aspect_ratio, quality or upscale_factor not accepted by the model; or reference images sent to a model that takes none. errors[].expected lists the accepted values. |
out_of_range | 400 | More reference images than the model takes (max_reference_images), or an empty reference_image_urls. |
invalid_payload | 400 | Any other validation failure, e.g. a prompt under 3 words, or several problems of different kinds. Read errors[]. |
ai_model_disabled | 400 | The model, or every model of the action, is disabled in the workspace settings. |
insufficient_credits | 402 | Not enough AI credits for this call (for upscale: an empty AI balance). |
asset_job_not_found | 404 | Polling: no such job in this workspace, or it finished more than 7 days ago. |
too_many_asset_jobs | 429 | 5 jobs already running in the workspace. Wait for one to finish. |
asset_jobs_disabled | 503 | The feature is switched off. Nothing created or charged; retry later. |
ai_service_unavailable | 503 | A service it depends on cannot be reached. Nothing created or charged; retry later. |
A job that started but could not finish ends FAILED, with one of these in error.code:
error.code | Meaning |
|---|---|
invalid_input_asset | An input image — the image to process, or a reference image — could not be downloaded or read, is over 20 MB, or is in a format the action doesn't take (PNG, JPEG and WebP everywhere, SVG too for remove background). For upscale, also an image under 128 px or a result over the model's cap. |
invalid_input | The provider refused the inputs, typically a prompt it would not generate. |
insufficient_credits | The AI balance did not cover the job when it ran. |
provider_error | The AI service failed to process the image. |
interrupted / internal_error | The job failed on our side. |
When the cause is on your side, error.message says exactly what to fix, naming the image by the URL you sent. For example:
| Cause | error.message |
|---|---|
| Image not found | The image https://example.com/product.jpg was not found (HTTP 404). Check the URL. |
| Image not public | The image https://example.com/product.jpg isn't public: its server refused access (HTTP 403). |
| Not an image | The image URL https://example.com/product.jpg doesn't point to an image (its server sent text/html). Use a PNG, JPEG or WebP image. |
| Too large | The image https://example.com/product.jpg is 24.3 MB; the limit is 20 MB. |
| Too slow to download | The image https://example.com/product.jpg took too long to download. |
| Private address | The image https://example.com/product.jpg points to a private or local address. Use a public URL. |
| Server unreachable | The server hosting the image https://example.com/product.jpg couldn't be reached. |
| Damaged file or other format | The image https://example.com/product.jpg couldn't be read as an image: the file is damaged, or not PNG, JPEG or WebP. |
| Upscale: image too small | The image https://example.com/product.jpg is 100×80 px; upscale needs at least 128 px on each side. |
| Upscale: result too large | Upscaling ×4 would give 9000×6000 px, over this model's 8192 px per side. Use a smaller factor, a smaller image or another model. |
| Reference image refused | One of the reference images is too small for this model. |
For a reference image, the message says "The reference image" instead of "The image".
Codes are added over time: treat one you do not know as generic and show error.message, which is safe to display to an end user. Do not restart a failed job unless the cause is fixed — it is a new, charged job. The full envelope is on Errors.
Related
- Remove a background · Generate an image from a prompt · Upscale an image — one page per action
- Asset job events — the
ASSET_JOB_FINALIZEDpayload - Credits — AI credit prices per model
- Image element properties — the same AI features on an image layer inside a design
- API Reference: removeBackground · textToImage · upscale · getAssetJob · listAiModels
