spicyapiDocs
Main content

Command-line tool (CLI)

Use the spicyapi command to browse models, check prices, generate images and videos and download the results from a terminal, without writing code.

What the CLI is

spicyapi is a small program you run in a terminal, the window where you type commands instead of clicking buttons. Each command does one job: list the models your account can use, tell you what a generation will cost, start an image or video generation, wait for it to finish and give you a link to download the result.

You do not write any code. You type a command, press Enter and read the answer.

It is a good fit when you want to:

  • try models and compare prices before anyone writes an integration;
  • generate images or videos from your own computer and save the files;
  • repeat the same kind of generation without clicking through a page each time;
  • check a model's exact settings before you build something on top of it.

The CLI covers image and video generation. Text and chat models are called through the chat-compatible endpoints described in Text and streaming.

There are three other official tools. Pick the CLI if you want to type commands yourself:

ToolChoose it when
SDKYou are writing a JavaScript or TypeScript program that calls SpicyAPI
MCP serverYou want an AI coding assistant to run these same steps for you
Agent SkillYou want to teach an AI agent the safe way to use SpicyAPI

Writing in another language? There are official SDKs for Python, Go, PHP and Java too.

Nothing on this page spends money by accident

Only two commands can cost money: tasks create and tasks retry. Both stop and ask you before anything is charged. Everything else is free to run as often as you like.

What you can do

I want to…CommandCosts money?Needs an API key?
Check that SpicyAPI is reachablespicyapi statusNoNo
Search this documentationspicyapi docs search <words>NoNo
See which models I can use, and their pricesspicyapi models listNoYes
See which settings one model acceptsspicyapi models get <model>NoYes
Check my balancespicyapi balanceNoYes
See how much a key has spentspicyapi usageNoYes
Get the exact price of a generation without starting itspicyapi tasks quoteNoYes
Start an image or video generationspicyapi tasks createYes, after asking youYes
Wait for a generation to finishspicyapi tasks wait <task-id>NoYes
Look at one generation and its result linksspicyapi tasks get <task-id>NoYes
List my recent generationsspicyapi tasks listNoYes
Run a failed generation againspicyapi tasks retry <task-id>Yes, after asking youYes
Permanently delete a generation's files and promptspicyapi tasks purge <task-id>No, and nothing is refundedYes
Upload a picture, video or audio file to use as inputspicyapi files upload <path>NoYes
Get a fresh download link for a resultspicyapi files download-url <task-id>NoYes
Check that a callback really came from SpicyAPIspicyapi webhooks verifyNoNo, it uses your webhook signing secret

In the commands above, a word in angle brackets such as <task-id> means "put your own value here", without the brackets.

Before you start

You need four things: a terminal, Node.js, an API key and a way to run the CLI. This takes about ten minutes the first time.

Open a terminal

A terminal is a text window for running commands. Everything on this page is typed or pasted there, then run with Enter.

Press Cmd + Space, type Terminal and press Enter. A window opens with a line ending in % or $: that is where you type. Paste with Cmd + V.

Install Node.js 22.13 or newer

The CLI runs on Node.js, a free program. Check whether you already have it:

node -v

If it prints v22.13.0 or a higher number, such as v22.14.0 or v24.4.1, you are ready. If it prints a lower number, or says command not found or is not recognized:

  1. Go to nodejs.org, open the download page and pick the LTS version for your system.
  2. Run the installer and accept the default options.
  3. Close the terminal and open a new one, then run node -v again.

Node.js comes with npm and npx, the two helpers used in the last step.

Create an API key

The API key is how SpicyAPI knows the request comes from your account.

  1. Create an account at spicyapi.ai/register. Accounts may be opened in batches; if sign-up is not open for you yet, join the waitlist.
  2. Add funds under Billing in the console. Generations are paid from your balance, in US dollars.
  3. Open API keys and click Create key. Give it a name you will recognize later, such as my laptop. Only the name is required.
  4. Copy the key immediately. It starts with sk-spicy- and is shown only once. You can also use Save as a file.

New keys have no spending limit unless you set one. A daily limit means a mistake or a leaked key cannot run up a large bill, and you can change it later on the same page.

Treat the key like a password

Anyone who has your key can spend your balance. Do not send it in chat or email, do not show it in a screenshot or a support ticket, and never save it in a project folder that goes into Git or GitHub. If it leaks, revoke it on the API keys page and create a new one.

Store the key in SPICY_API_KEY

The CLI has no login command and never takes the key as a command option. It reads an environment variable called SPICY_API_KEY: a named value that your terminal passes to the programs it runs.

For the current window only (it is forgotten when you close the window):

export SPICY_API_KEY="sk-spicy-paste-your-key-here"

Permanently, add that same export line to the end of your shell's startup file, which runs every time you open a terminal. On macOS the file is ~/.zshrc; on most Linux systems it is ~/.bashrc.

nano ~/.zshrc

Paste the export line at the bottom, press Ctrl + O then Enter to save, and Ctrl + X to exit. On Linux, use nano ~/.bashrc instead. Open a new terminal window for the change to take effect.

Check that it is set, without printing the whole key:

echo "${SPICY_API_KEY:0:9}"

It should print sk-spicy-.

Where the key may and may not live

Your shell startup file and your Windows account settings stay on this computer, which is fine for personal use. Never put the key in a file inside a project, never commit it, and never paste it into a shared document. Terminals also remember the commands you type, so on a shared computer prefer the startup file or the Windows settings dialog over typing the key into a command.

Choose how to run the CLI

There are two ways, and the commands are the same.

npx downloads the CLI when needed and runs it:

npx @spicyapi/cli status

The first time, npm asks Need to install the following packages: @spicyapi/cli@… Ok to proceed? (y). Press y and Enter.

With this method, write npx @spicyapi/cli wherever this page writes spicyapi. For example, spicyapi models list becomes npx @spicyapi/cli models list.

Your first result

This walkthrough generates one image and downloads it. Each step shows the command, roughly what you will see, and what just happened. Your numbers, IDs and times will differ.

Replace MODEL_ID_FROM_CATALOG with a real model ID

MODEL_ID_FROM_CATALOG is a placeholder, not a model. Step 3 shows how to pick a real ID from the live catalog (spicyapi models list, the same list as GET /api/v1/models). Paste your ID wherever this page shows the placeholder. Sending the placeholder itself only returns a "not found" error.

Check the connection

spicyapi status
SpicyAPI is reachable — api.spicyapi.ai answered in 523 ms.

What happened: the CLI asked SpicyAPI whether it is up. This needs no key and costs nothing. If you see "could not be reached", check your internet connection or proxy; see Troubleshooting.

Check your balance

spicyapi balance
available: 12.34
held: 0.06
total: 12.40

What happened: this is the first command that uses your key. Amounts are US dollars. available is what you can spend now, held is set aside for generations that are still running, and total is the two added together. You may also see a funding line that breaks the balance down into prepaid funds and promotional grants.

If you see Error: SPICY_API_KEY is required for authenticated API operations, the key is not set in this terminal window. The Next: line below it shows how to set it; see also Before you start.

Pick a model

spicyapi models list --modality image --json
{
  "total": 1,
  "items": [
    {
      "model": "MODEL_ID_FROM_CATALOG",
      "displayName": "Example image model",
      "modality": "image",
      "tasks": ["text-to-image"],
      "enabled": true,
      "available": true,
      "pricing": [
        { "variant": "", "unit": "per_image", "price": "0.03", "currency": "USD" }
      ]
    }
  ]
}

This example is shortened: the real list has more models and more fields.

What happened: the CLI fetched the models your account can call today, with your prices. Choose one whose enabled and available are both true, and copy its model value, for example by selecting it with the mouse. That value is the model ID. IDs read like maker/model/task.

  • price is in US dollars per unit: per_image, per_second of video, per_request or per_1k_tokens.
  • Several pricing entries mean the price depends on your settings, such as resolution.
  • To find a model by name, add --search and part of its name.

--json prints the list as readable, indented JSON. Without it, the whole list is squeezed onto one long line.

See which settings that model accepts

spicyapi models get MODEL_ID_FROM_CATALOG --json
{
  "model": "MODEL_ID_FROM_CATALOG",
  "enabled": true,
  "available": true,
  "inputSchema": {
    "type": "object",
    "required": ["prompt"],
    "properties": {
      "prompt": { "type": "string" },
      "aspect_ratio": { "type": "string", "enum": ["1:1", "16:9", "9:16"] }
    },
    "additionalProperties": false
  }
}

What happened: inputSchema is the model's list of settings, which the rest of this page calls the input.

  • required lists the settings you must send. Here, only prompt.
  • properties lists every setting the model accepts, and nothing else is allowed: one unknown or misspelled name gets the whole request rejected.
  • enum lists the only values allowed for that setting.

Every model has its own settings, so always read them for the model you chose. For ready-made inputs that the server has already checked, run spicyapi models list --search <name> --include-examples --json and look at examples.

Write your input to a file

Put the input in a file called input.json, in the folder your terminal is in. A file avoids quoting problems, especially on Windows.

Paste all three lines at once:

cat > input.json <<'EOF'
{"prompt": "A paper lantern glowing on a rainy street at night, cinematic light"}
EOF

What happened: you described what to generate, using the setting names from the previous step. The quotes must be plain straight quotes ("), not curly ones.

Check the price

spicyapi tasks quote --model MODEL_ID_FROM_CATALOG --input-file input.json
quoteId: eyJ2IjoxLCJtIjoi…
model: MODEL_ID_FROM_CATALOG
estimatedCost: 0.03
maxCharge: 0.03
currency: USD
quantity: 1
unit: per_image
expiresAt: 2026-09-17T08:05:00Z

What happened: SpicyAPI priced this exact request. Nothing was started and nothing was charged. estimatedCost is what it is expected to cost, and maxCharge is the most it can cost. This step is optional: the next command fetches the price again and shows it to you before it spends anything.

Start the generation and wait for it

spicyapi tasks create --model MODEL_ID_FROM_CATALOG --input-file input.json --wait

The CLI first shows the price and asks you to confirm:

Estimated charge: USD 0.03; maximum charge: USD 0.03. Quote expires: 2026-09-17T08:05:00Z.
Idempotency-Key: 4f6755a5-0d72-4420-9f6b-9faf0e2b0241
Create a billable task with model MODEL_ID_FROM_CATALOG? Estimated charge: USD 0.03; maximum charge: USD 0.03. Quote expires: 2026-09-17T08:05:00Z. Idempotency-Key: 4f6755a5-0d72-4420-9f6b-9faf0e2b0241. Continue? [y/N]

Type y and press Enter. Anything else cancels, and nothing is charged. Answer within five minutes, before the quote expires.

As soon as SpicyAPI accepts the job, the CLI prints its task ID:

Accepted: job_01k3m8x9q2z4v7n5p6r8s0t1w3
If waiting is interrupted: spicyapi tasks get job_01k3m8x9q2z4v7n5p6r8s0t1w3

Images usually take seconds and videos take minutes. When it finishes, the result appears:

idempotencyKey: 4f6755a5-0d72-4420-9f6b-9faf0e2b0241
accepted: {"taskId":"job_01k3m8x9q2z4v7n5p6r8s0t1w3","state":"queued","estimatedCost":"0.03","deadlineAt":"2026-09-17T08:30:00Z"}
task: {"taskId":"job_01k3m8x9q2z4v7n5p6r8s0t1w3","model":"MODEL_ID_FROM_CATALOG","state":"succeeded","output":{"assets":[{"key":"tasks/…/result.png","url":"https://…","expiresAt":"2026-09-17T08:20:00Z","mime":"image/png","width":1024,"height":1024}]},"cost":"0.03","settled":true,…}

What happened: after your y, SpicyAPI accepted the generation, which from then on is called a task, and set the estimated amount aside from your balance. The CLI then checked on the task every few seconds until it finished. In the last line, look for "state":"succeeded" and the "url" inside output. cost is the final charge once settled is true. Add --json to the command if you prefer the result spread over several readable lines.

Closing the window does not cancel the task

Once accepted, a task always runs to the end and is charged normally. Pressing Ctrl + C or closing the terminal only stops the waiting. Keep the task ID and pick it up again with spicyapi tasks wait <task-id>; see Pick up a task after the wait was interrupted.

Download the result

The url is a direct download link that works for about 20 minutes and needs no key. Paste it into a browser and save the file, or download it from the terminal (keep the quotes, because the link contains & characters):

curl -L -o result.png "PASTE_THE_URL_HERE"

Use the file extension that matches mime: .png for image/png, .jpg for image/jpeg, .mp4 for video/mp4.

If the link has expired, get a new one with spicyapi files download-url <task-id>, or run spicyapi tasks get <task-id>, which returns fresh links.

What happened: you copied the result to your own computer. Do this soon: SpicyAPI keeps generated files for about 14 days at most (less if your account is set shorter), then deletes them for good. See Retention and destruction.

How to read the output

  • Normal output shows one name: value per line. Values that contain several parts, such as output or retention, are printed as JSON on a single line.
  • --json prints the complete answer as indented JSON. It works with every command, before or after the command name: spicyapi --json balance and spicyapi balance --json are the same.
  • Results go to the screen's normal output; messages go to the error output. Prices, the idempotency key, the Accepted: lines and errors are messages. So spicyapi --json tasks get <task-id> > task.json saves only the result in task.json, while the messages still appear on screen.
  • Errors look like Error: <what went wrong> (request req_…), usually followed by a Next: line that says what to do. With --json, an error is a one-line JSON object with error, type, status, code and requestId, and no Next: line. When no key is set, type is MissingApiKeyError. Quote the req_… value if you contact support.
  • Money is a US dollar amount written as plain text without a $ sign, such as 0.03.
  • Times are in UTC. The Z at the end of 2026-09-17T08:05:00Z means UTC.
  • Exit status is 0 when a command succeeds and not 0 when it fails, which scripts can check.

Command reference

Every command also has built-in help: add --help, for example spicyapi tasks create --help.

Global options and environment variables

These options work with every command.

OptionWhat it doesDefaultRequired
--jsonPrint the complete result as JSONOffNo
--timeout-ms <milliseconds>How long to wait for one answer from SpicyAPI before giving up, from 1 to 12000030000 (30 seconds)No
--max-retries <count>How many times to try again automatically after a network error, 429 or server error, for requests that are safe to repeat, from 0 to 52No
--api-base-url <url>Send API requests to a different address. You normally never need thishttps://api.spicyapi.ai/api/v1No
--service-base-url <url>The address status checks. You normally never need thishttps://api.spicyapi.aiNo
-V, --versionPrint the CLI version—No
-h, --helpShow help for the command—No
Environment variableWhat it is for
SPICY_API_KEYYour API key. Needed by every command except status, docs search and webhooks verify
SPICY_WEBHOOK_SECRETYour webhook signing secret, read by webhooks verify
SPICY_API_BASE_URL, SPICY_SERVICE_BASE_URLSame as the two address options above. Addresses must use https://

status

What it does: checks whether SpicyAPI is up and reachable from your computer.

spicyapi status

No options. No key needed. Free.

Output: one sentence. SpicyAPI is reachable — … answered in … ms. means everything is fine. could not be reached means your computer got no answer: check your network or proxy. is not healthy or reports it is not ready means a problem on our side; current incidents are listed at status.spicyapi.ai.

With --json you see the raw checks. operational: true is the answer that matters; a readiness entry with status 404 is normal on the public address.

What it does: finds the documentation page for a topic. It searches a small index built into the CLI, so it works without a key and without calling SpicyAPI. Each result links to the page on this documentation site, docs.spicyapi.ai.

spicyapi docs search [words] [--limit <count>]
Argument or optionWhat it doesDefaultRequired
wordsWords to look for. Leave it out to list pagesEmptyNo
--limit <count>Maximum number of results, from 1 to 2510No
spicyapi docs search retention
slug: retention
title: Retention and destruction
summary: What the platform caps are, how far down you can dial them, how to destroy a task's content, and what we cannot promise.
keywords: ["privacy","delete","storage","retention","per-request retention","purge","destroy"]
url: https://docs.spicyapi.ai/docs/retention

When there are several results, they are printed one after another, and each starts with slug:. If nothing matches, it says so and still exits with status 0:

No documentation matched "refnd". Try fewer or different words, or run `spicyapi docs search` without words to browse pages.

With --json, no match prints an empty list, []. Free.

models list

What it does: lists the models your account can call today, with the prices your account pays.

spicyapi models list [options]
OptionWhat it doesDefaultRequired
--modality <type>Only image, video or text models. The option also accepts audio, but no audio models are offered todayAllNo
--task <task>Only one kind of job, such as text-to-image, edit, image-to-video, text-to-video or reference-to-videoAllNo
--provider <name>Only models from one model maker, the company or lab that built the model. Must match the provider value exactlyAllNo
--search <text>Only models whose ID or display name contains this text, ignoring upper and lower caseNoneNo
--include-schemaAdd every model's full settings (inputSchema). Makes the output much largerOffNo
--include-examplesAdd ready-made example inputs that the server has already checked (examples)OffNo
spicyapi models list --modality video --task image-to-video --json

Output: total and items. Use --json, because without it all items are printed on one line. The most useful fields of each item:

FieldMeaning
modelThe model ID to use in other commands
displayName, provider, modality, tasksName, model maker, type of output and kind of job
enabled, availableUse models where both are true
pricingOne entry per price level: variant (for example a resolution), unit, price in USD, plus regularPrice and offerLabel when a discount applies
taskTimeoutSecondsHow long a generation may run before it ends as expired
maxOutputDurationSecondsThe longest video length this model allows, when it has one

Free. The model list counts toward your account's request rate, so there is no need to run it before every generation.

models get

What it does: shows one model in full, including the settings it accepts.

spicyapi models get <model>
ArgumentWhat it doesDefaultRequired
modelThe exact model ID. The slashes in it are fine as they are—Yes
spicyapi models get MODEL_ID_FROM_CATALOG --json

Output: the same fields as models list, plus inputSchema. In inputSchema, read required (settings you must send), properties (every allowed setting and its type), enum (the only allowed values), minimum and maximum (number limits) and default (used when you leave the setting out). Entries named x-ui are hints for building forms; you can ignore them. Free.

balance

What it does: shows how much money is available on your account.

spicyapi balance

No options. Free.

Output: available (what you can spend now), held (set aside for tasks that are still running) and total, all in US dollars. It may also include funding, which splits the balance into prepaid funds and promotional grants, including when grants expire and which models they pay for. You do not need to check the balance before every generation: tasks create checks it for you.

usage

What it does: shows how many generations the current API key ran and how much they cost, grouped by day and by model.

spicyapi usage [--from <date>] [--to <date>]
OptionWhat it doesDefaultRequired
--from <date>First day to include, as YYYY-MM-DD in UTCThe last seven days, including todayNo
--to <date>The day after the last day to include, as YYYY-MM-DD in UTC. The range can be at most 92 daysThe last seven days, including todayNo
spicyapi usage --from 2026-09-01 --to 2026-10-01

This covers all of September: --to is the first day that is not included. Leave out both options to see the last seven days, including today.

Output: from, to, currency, totalCalls, totalSpend, then days (per day: calls, succeeded, failed, spend) and models (the same numbers per model). totalSpend only counts final charges. days and models describe the same tasks in two ways, so do not add them together. For your whole account rather than one key, use the Usage page in the console.

Free. It is limited to 30 calls a minute, so use it to check totals, not in a loop.

tasks quote

What it does: tells you exactly what one generation would cost, without starting it and without setting any money aside.

spicyapi tasks quote --model <model> --input-file <path>
OptionWhat it doesDefaultRequired
--model <model>The exact model ID—Yes
--input-file <path>Read the input from a JSON file. Use - to read it from what is piped into the command—Use this or --input-json
--input-json <json>Give the input directly as JSON text. Quoting is easier with --input-file—Use this or --input-file
--callback-url <url>The callback address you plan to use, since it is part of the priced requestNoneNo

Output: quoteId, model, estimatedCost, maxCharge, currency, quantity (for example the number of seconds being charged), unit and expiresAt. A quote is valid for five minutes.

Free, no confirmation. Use it to compare models or settings. You do not have to run it before tasks create, which gets its own quote.

tasks create

What it does: starts an image or video generation. This costs money. The CLI always shows the price and asks first.

spicyapi tasks create --model <model> --input-file <path> [options]
OptionWhat it doesDefaultRequired
--model <model>The exact model ID—Yes
--input-file <path>Read the input from a JSON file. - reads it from what is piped into the command, which also requires --yes—Use this or --input-json
--input-json <json>Give the input directly as JSON text—Use this or --input-file
--waitStay until the task finishes, then print the resultOff: print the task ID and returnNo
--yesSkip the confirmation question. Only use it once you know the price, typically in scriptsOff: askNo
--idempotency-key <key>A name for "this one generation". Running the command again with the same key within 24 hours gives you the same task instead of a new one. Up to 128 charactersA new random key each runNo
--retention <duration>Delete this task's generated media, result payload, prompt and other input text sooner than usual: 30m, 1h, 7d or a number of seconds. It can only shorten. 0 deletes them once the task finishes. Billing records are always keptYour account settingNo
--callback-url <url>A public address starting with https:// that SpicyAPI calls when the task finishes. Plain http://, localhost and private network addresses are refused. Only useful if you run a server; see WebhooksNoneNo
--poll-interval-ms <milliseconds>With --wait: check every this many milliseconds, from 100 to 60000Starts at 2 seconds and slows down to 10 secondsNo
--wait-timeout-ms <milliseconds>With --wait: stop waiting after this long, from 1 to 86400000. The task keeps running600000 (10 minutes)No

What happens, in order:

  1. The CLI reads your input and checks that it is a JSON object. If you gave no input, it stops here.
  2. If nobody can answer a question, for example in a script, and you did not pass --yes, it stops here.
  3. It gets a quote and prints the estimated charge, the maximum charge, when the quote expires and the idempotency key.
  4. It asks Continue? [y/N]. Only y or yes continues. The default is no.
  5. It creates the task at the quoted price. The estimated amount is set aside from your balance, and that amount is also the most the task can cost.
  6. Without --wait, it prints idempotencyKey, taskId, state (queued), estimatedCost and deadlineAt, then returns. With --wait, it prints Accepted: <task-id> and a recovery command straight away, waits, then prints idempotencyKey, accepted and the finished task.

deadlineAt is the latest time the task can finish before it ends as expired. It is not when your download link or files expire.

spicyapi tasks create --model MODEL_ID_FROM_CATALOG --input-file input.json --wait

On macOS and Linux you can also give the input inline:

spicyapi tasks create --model MODEL_ID_FROM_CATALOG --input-json '{"prompt": "A paper lantern on a rainy street"}'

If waiting fails, the task is still running

A wait can end early because of the time limit, a network problem or Ctrl + C. The task itself carries on and is charged normally. The Accepted: line printed at the start has its ID; continue with spicyapi tasks wait <task-id>. Do not simply run the same create command again without --idempotency-key: that starts, and charges for, a second generation.

When the wait fails with an error (rather than Ctrl + C), the last lines repeat everything you need, so you do not have to scroll back for the Accepted: line:

Error: task job_01k3m8x9q2z4v7n5p6r8s0t1w3 did not reach a terminal state within 600000ms
Task job_01k3m8x9q2z4v7n5p6r8s0t1w3 was accepted and may still be running; only the wait stopped.
Recover: spicyapi tasks get job_01k3m8x9q2z4v7n5p6r8s0t1w3
To replay the same request without creating a second task, reuse --idempotency-key 4f6755a5-0d72-4420-9f6b-9faf0e2b0241.

If the error was one with a known fix, such as 429 or a server error, a Next: line follows. With --json, the same facts are in the error object as taskId, idempotencyKey and recover.

tasks wait

What it does: waits for a task you already started, until it finishes.

spicyapi tasks wait <task-id> [options]
Argument or optionWhat it doesDefaultRequired
task-idThe task ID, such as job_01k3m8x9q2z4v7n5p6r8s0t1w3—Yes
--poll-interval-ms <milliseconds>Check every this many milliseconds, from 100 to 60000Starts at 2 seconds and slows down to 10 secondsNo
--wait-timeout-ms <milliseconds>Stop waiting after this long, from 1 to 86400000600000 (10 minutes)No
spicyapi tasks wait job_01k3m8x9q2z4v7n5p6r8s0t1w3 --wait-timeout-ms 1800000

Output: the finished task, with the same fields as tasks get, once its state is succeeded, failed or expired. If the task succeeded but its files are still being copied, it keeps waiting a little longer.

If the time limit runs out, you see Error: task … did not reach a terminal state within 600000ms. That is not a failure: the task is still running. Wait again, with a longer --wait-timeout-ms if needed. Stopping a wait never cancels a task. Free.

tasks get

What it does: shows one task right now: its state, cost, result links and when its content will be deleted.

spicyapi tasks get <task-id>
ArgumentWhat it doesDefaultRequired
task-idThe task ID—Yes

Use the same API key that created the task: another key gets "not found".

Output fields:

FieldMeaning
statequeued (waiting to start), running, succeeded, failed or expired (ran out of time). Older tasks can also show canceled
output.assetsThe results. Each has a url (a download link valid for about 20 minutes), expiresAt (when that link stops working), mime (file type), size details and a key. pending: true means the file is still being copied, so try again in a few seconds. unavailable: true means it cannot be recovered
errorCode, errorMessageWhy the task failed. See Errors
cost, settledThe charge in USD. It is final once settled is true
inputThe settings you sent. Removed when your prompt retention runs out
createdAt, completedAt, deadlineAtWhen it was created, when it finished, and the latest time it could finish
contentStatepresent (content still stored), expired (removed by retention rules) or purged (you deleted it)
retentionoutputsExpireAt (when result files are deleted), promptsExpireAt (when your prompt text is erased) and source (which setting decided)
sourceTaskIdPresent when this task is a retry of another task

Normal output ends with a plain-language summary, such as Outputs expire: … or Content: Destroyed — you removed the outputs …. Each call returns fresh download links. Free.

tasks list

What it does: lists tasks created with the current API key, a page at a time.

spicyapi tasks list [options]
OptionWhat it doesDefaultRequired
--from <date>First day to include, YYYY-MM-DD in UTCA seven-day window ending todayNo
--to <date>The day after the last day to include, YYYY-MM-DD in UTC. The range can be at most 92 daysSee aboveNo
--state <state>Only tasks in one state: queued, running, succeeded, failed, canceled or expiredAllNo
--model <model>Only tasks for one exact model IDAllNo
--limit <count>Tasks per page, from 1 to 10020No
--cursor <cursor>Continue from the previous page, using its nextCursor valueFirst pageNo
spicyapi --json tasks list --from 2026-09-01 --to 2026-09-08 --state failed

Output: items (each with taskId, model, state, cost, settled, createdAt, deadlineAt and completedAt), hasMore and nextCursor. If hasMore is true, run the same command again with the same --from and --to, adding --cursor and the nextCursor value. The list has no download links; use tasks get for the task you want. Free.

tasks retry

What it does: runs a failed or expired task again, as a new task with a new ID. This costs money.

spicyapi tasks retry <task-id> [options]
Argument or optionWhat it doesDefaultRequired
task-idThe failed or expired task to run again—Yes
--waitStay until the new task finishesOffNo
--yesSkip the confirmation questionOff: askNo
--idempotency-key <key>Running the retry again with the same key gives you the same new task instead of another oneA new random key each runNo
--poll-interval-ms <milliseconds>With --wait: how often to checkStarts at 2 seconds and slows down to 10 secondsNo
--wait-timeout-ms <milliseconds>With --wait: stop waiting after this long600000 (10 minutes)No

It asks Retry source task … Continue? [y/N]. Unlike tasks create, it does not show a price before asking. The new task is charged at the model's current price, so check it first with spicyapi models get <model> if the price matters.

Output: idempotencyKey, the new taskId, sourceTaskId (the original), state, estimatedCost (the amount set aside) and deadlineAt.

  • Only failed and expired tasks can be retried, plus older canceled ones. For another version of a successful result, use tasks create.
  • The original input is reused unchanged, so a retry only helps when the failure was temporary. Check errorCode first; see Retry a failed task and Errors.
  • If the input pointed at an uploaded file that has since expired, the retry fails; upload the file again and use tasks create.
  • The retry keeps the original task's retention setting.
  • If tasks create lost its connection before you saw a task ID, that is not a failed task. Use the recovery steps in Pick up a task after the wait was interrupted instead.

tasks purge

What it does: permanently deletes a finished task's generated media, result payload (the stored result data), prompt and other input text, right away instead of waiting for them to expire.

spicyapi tasks purge <task-id> [--yes]
Argument or optionWhat it doesDefaultRequired
task-idA finished task: succeeded, failed, expired or canceled—Yes
--yesSkip the confirmation questionOff: askNo

A task that is still queued or running cannot be purged. It cannot be cancelled either — there is no cancellation command or API — so wait for it to finish with spicyapi tasks wait <task-id>, then purge it.

It explains what will be deleted and asks you to confirm, because it cannot be undone. Download anything you want to keep first.

Output: taskId, contentState (purged), purgedAt, contentRemovedBy (user), billingRetained (always true) and mediaDeletionPending, followed by a line naming what was destroyed and saying the stored media is removed within about a minute. If contentState is still present, nothing was removed this time; try again later.

Free, and not a refund: the ledger entry, charged amount, model, state, timestamps and request ID stay on record. See Retention and destruction.

files upload

What it does: uploads a file from your computer, such as the starting picture for an image-to-video generation, and gives you a spicy:// address to put in your input.

spicyapi files upload <path> [--content-type <type>]
Argument or optionWhat it doesDefaultRequired
pathThe file to upload—Yes
--content-type <type>The file type, when it cannot be told from the file name: image/jpeg, image/png, image/webp, image/gif, video/mp4, video/webm, audio/mpeg or audio/wavDetected from the extensionNo

Supported files are .jpg, .jpeg, .png, .webp and .gif images up to 10 MiB, .mp4 and .webm videos, and .mp3 and .wav audio up to 90 MiB and 600 seconds. A model can set lower limits of its own.

spicyapi files upload ./start-frame.jpg
fileId: fil_01k3m8x9q2z4v7n5p6r8s0t1w3
status: ready
bytes: 402118
contentType: image/jpeg
sha256: a91f4c2e5b7d16809e3f2a4c8d0b61e7c4a890e5d2f71b3c609ae8427d15f0c6
uri: spicy://f/fil_01k3m8x9q2z4v7n5p6r8s0t1w3
expiresAt: 2026-09-18T09:12:11Z
width: 1280
height: 720

Output: copy the uri value exactly as printed into the model's file setting, often image_url. It can be used until expiresAt, normally one day. After that, upload the file again. Uploading is free.

files download-url

What it does: creates a new download link for a finished task's result. It prints the link; it does not save the file.

spicyapi files download-url <task-id> [--key <key>]
Argument or optionWhat it doesDefaultRequired
task-idA task that succeeded—Yes
--key <key>Which result to link to, when a task produced several. Use the key value from output.assets in tasks getThe first resultNo

Output: key, url and expiresAt. The link works for about 20 minutes; open it in a browser or download it with curl. A task that has not succeeded yet answers "not found". If the file is still being copied you get a conflict error; wait a few seconds and try again. Once the task's files have been deleted, no new link can be made. Free.

webhooks verify

What it does: if you run a server that receives SpicyAPI callbacks (see --callback-url), this checks that one saved callback is genuine and unmodified. It runs entirely on your computer and needs no API key.

spicyapi webhooks verify --body-file <path> --timestamp <seconds> --signature <base64> --payload-version <version>
OptionWhat it doesDefaultRequired
--body-file <path>A file with the callback body exactly as received, byte for byte—Yes
--timestamp <seconds>The X-Webhook-Timestamp header value—Yes
--signature <base64>The X-Webhook-Signature header value—Yes
--payload-version <version>The X-Webhook-Payload-Version header value: 1 or 2—Yes
--secret-env <name>The name of the environment variable that holds your webhook signing secret. The secret itself is never typed on the command lineSPICY_WEBHOOK_SECRETNo
--tolerance-seconds <seconds>How far the timestamp may be from your computer's clock300No

Output: on success, payload (the callback content), payloadVersion, taskId, deliveryId (version 2) and timestamp. If anything does not match, it prints an error and exits with a non-zero status. Free.

A signing secret is created automatically when you register, but its value is never shown. To get one you can copy, open Webhooks in the console and click Generate (or Rotate if one is configured). The new secret is shown only once. Rotating takes effect immediately, with no overlap: the old secret stops signing at once. See Webhooks.

Common recipes

Generate an image

spicyapi models list --modality image --json
spicyapi models get MODEL_ID_FROM_CATALOG --json
spicyapi tasks create --model MODEL_ID_FROM_CATALOG --input-file input.json --wait

Pick an ID from the first command, build input.json from the settings shown by the second, then run the third and confirm with y. The walkthrough in Your first result explains each step.

Turn a picture into a video

Find an image-to-video model and its settings

spicyapi models list --modality video --task image-to-video --json
spicyapi models get MODEL_ID_FROM_CATALOG --json

In inputSchema, find the setting for the starting picture (usually image_url), the length setting (usually duration_seconds) and anything in required.

Upload your picture

spicyapi files upload ./start-frame.jpg

Copy the uri value, which starts with spicy://.

Write the input

Create input.json as in Your first result, using the setting names from the model and your own uri:

{
  "prompt": "Slow push-in, rain running down the window",
  "image_url": "spicy://f/fil_01k3m8x9q2z4v7n5p6r8s0t1w3",
  "duration_seconds": 5
}

Check the price, then generate

spicyapi tasks quote --model MODEL_ID_FROM_CATALOG --input-file input.json
spicyapi tasks create --model MODEL_ID_FROM_CATALOG --input-file input.json --wait --wait-timeout-ms 1800000

Videos are usually charged per second, so the length you choose changes the price. Videos can take several minutes, so this example waits up to 30 minutes instead of the default 10. Download the .mp4 from the url as in Your first result.

The uploaded picture can be used for one day. After that, upload it again.

Pick up a task after the wait was interrupted

A task keeps running even if the terminal closes, the network drops, your computer sleeps or the wait times out. How you get back to it depends on what you saw:

  • You saw Accepted: job_…, or a Recover: line under the error. You have the task ID. Wait again, or just look at it:

    spicyapi tasks wait job_01k3m8x9q2z4v7n5p6r8s0t1w3 --wait-timeout-ms 1800000
    spicyapi tasks get job_01k3m8x9q2z4v7n5p6r8s0t1w3
  • You only saw the Idempotency-Key: line, for example because the connection dropped while the task was being created. Run the same command again, with the same model and the same input, and add that key:

    spicyapi tasks create --model MODEL_ID_FROM_CATALOG --input-file input.json --idempotency-key 4f6755a5-0d72-4420-9f6b-9faf0e2b0241 --wait

    The CLI shows the price and asks again, but if the first attempt was accepted, SpicyAPI hands back that same task instead of starting and charging for a second one. This works for 24 hours with the same API key. If the first attempt never arrived, the task is created now, once.

  • You have neither. List your recent tasks and find it by time and model:

    spicyapi --json tasks list --limit 10

Retry a failed task

First find out why it failed:

spicyapi tasks get job_01k3m8x9q2z4v7n5p6r8s0t1w3

Then read errorCode. Failed or expired tasks normally release the full hold. Some content refusals are charged at the accepted quote only when the model page explicitly discloses this exception. Check settled and cost for the final charge. tasks retry sends the same input again, so it only helps when the cause was temporary:

errorCodeWhat to do
rate_limited, upstream_unavailable, timeout, upstream_failedWait a little, then retry once. If it fails again, stop retrying: for timeout, try a shorter duration or a lower resolution with tasks create
generation_failedDo not retry. Change the prompt or the image, then use tasks create: the same input usually fails the same way
invalid_assetDo not retry, because a retry reuses the old file. Upload the file again, then use tasks create with the new uri
invalid_request, unsupported_combinationDo not retry unchanged. Change the settings, then use tasks create
content_rejectedDo not retry unchanged. The model declined this request: change the prompt or choose another model, then use tasks create

The full list is in Errors.

When a retry makes sense, run it. It creates, and charges for, a new task:

spicyapi tasks retry job_01k3m8x9q2z4v7n5p6r8s0t1w3 --wait

Export your usage

Save a month of usage for the current key to a file:

spicyapi --json usage --from 2026-09-01 --to 2026-10-01 > usage-2026-09.json

The file holds daily and per-model totals. For a task-by-task list, save spicyapi --json tasks list --from 2026-09-01 --to 2026-09-08 --limit 100 in the same way, following nextCursor while hasMore is true. Account-wide figures and invoices are in the console.

Run the CLI from a script

In a script nobody can answer Continue? [y/N], so billable commands refuse to run unless you add --yes. When you do:

  • quote first and check the price in the script;
  • give every generation its own fixed --idempotency-key, such as poster-2026-09-17-v1, so that running the script twice does not charge twice;
  • add --json and read state from the result: a command that exits with status 0 has not necessarily produced a successful task;
  • set spending limits on the key in the console.
spicyapi --json tasks create --model MODEL_ID_FROM_CATALOG --input-file input.json --idempotency-key poster-2026-09-17-v1 --yes --wait > result.json

Costs, confirmation and safety

Which commands cost money. Only tasks create and tasks retry. Prices are in US dollars. Quoting, listing, checking, uploading, downloading and deleting are free.

Seeing the price first. tasks quote prices a request without starting anything. tasks create gets its own quote and shows Estimated charge and maximum charge before it asks. When the task is accepted, the estimated amount is set aside (held) and is also the most you can be charged. When the task succeeds you pay for what was actually produced, never more than that amount, and anything left over goes back to your balance.

The confirmation. Continue? [y/N] means the answer is no unless you type y. --yes answers for you, so only use it once you know the price. If the CLI runs somewhere that cannot ask, such as a script or with piped input, and there is no --yes, it refuses instead of assuming yes. tasks purge asks in the same way, because deleting cannot be undone. tasks retry asks too, but does not show a price first.

The idempotency key, in plain words. It is a label for one generation, like an order number. Every tasks create and tasks retry prints one. If your internet connection drops and you are not sure whether the generation started, run the same command again with --idempotency-key and the printed value: you get the original task back, and you are not charged twice. The label works for 24 hours with the same API key. Do not reuse it for a different generation: the same key with a different input is rejected.

No cancelling. Once accepted, a task cannot be cancelled. Ctrl + C, closing the terminal or revoking the key only stops the waiting; the task finishes and is charged normally.

Failed or expired tasks normally release the full hold. Some content refusals are charged at the accepted quote only when the model page explicitly discloses this exception. Check settled and cost for the final charge.

Download what you want to keep. Result files are kept for about 14 days at most, or less if your account or --retention says so, and then deleted for good. Download links last about 20 minutes, but you can always ask for a new one while the file exists. Uploaded files can be used for one day. Prompt text is erased after 30 days by default. Billing records are kept. See Retention and destruction.

Spending limits. Each key can have daily, monthly and lifetime limits, set on the API keys page. New keys have no limit until you set one. A task that would go over a limit is refused before it starts.

Your key. The CLI only reads the key from SPICY_API_KEY, never prints it, and in error hints shows at most its first nine characters (sk-spicy-).

More detail: Billing, Idempotency, Task commitment and retry.

Troubleshooting

Find the message you see. Errors from SpicyAPI end with (request req_…); include that value if you contact support, but never send your key.

Every error code is explained in Errors, and recovery steps are in Troubleshooting and recovery.

Source

The package is MIT-licensed and developed in the open at github.com/spicyapi-ai/spicy-cli. That is where to read the code behind any command on this page, report behaviour that differs from what is described here, or propose a change.

Next steps

On this page