Skip to content
FreshApp Demo

DEVELOPER DOCUMENTATION

Generation API documentation

Use the generation API to create generations with the site's models from your own code. A request names a model and its input; the answer is a generation that you follow until it ends, and its files download from this site. The models, prices, limits and error codes on this page are the ones in force.

Creating generations through the API needs a paid credit pack or an active paid subscription, and spends paid credits only. Reading, listing and downloading work for any key of the account.

Quick start

Create an API key on the API keys page, keep it on your server, and set it as API_KEY. The examples create a generation with seed-audio-1.0, read it until it ends, and download its first file.

Send a new Idempotency-Key with each new request. When a request gets no answer, send it again with the same key and the same body: you get the generation the first request made, and you are charged once.

cURL

export API_ORIGIN='https://demo.freshapp.ai'
export API_KEY='sk_YOUR_API_KEY'

curl --fail-with-body "$API_ORIGIN/api/v1/generations" \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: order-1001' \
  -d '{"model":"seed-audio-1.0","input":{"prompt":"A lighthouse on a cliff at dawn, calm sea, soft light."}}'

# Read it every 5 to 10 seconds until its status ends.
curl --fail-with-body "$API_ORIGIN/api/v1/generations/GENERATION_ID" \
  -H "Authorization: Bearer $API_KEY"

# A delivered output's url is a signed link: no key needed.
curl --fail-with-body -o result "OUTPUT_URL"

TypeScript client

import { createClient } from './open-api-client';

const client = createClient({
  baseUrl: "https://demo.freshapp.ai",
  apiKey: process.env.API_KEY, // on your server only
});
const generation = await client.create(
  {
    "model": "seed-audio-1.0",
    "input": {
      "prompt": "A lighthouse on a cliff at dawn, calm sea, soft light."
    }
  },
  { idempotencyKey: "order-1001" }
);
const done = await client.wait(generation.id);
for (const output of done.outputs)
  if (output.status === 'delivered') {
    const file = await client.download(output);
    // file.body is the output, as output.content_type says
  }

React hook

import { createClient } from './open-api-client';
import { useGeneration } from './open-api-react';

// In a browser on this site: no key, the visitor's own session.
const client = createClient();

export function Progress({ id }: { id: string }) {
  const { generation, error, done } = useGeneration(client, id);
  if (error) return <p>{String(error)}</p>;
  return <p>{generation?.status ?? '…'}{done ? ' ✓' : ''}</p>;
}

Keys and permissions

Every request carries Authorization: Bearer with a key from the API keys page. A key is sk_ followed by 43 letters, digits, hyphens or underscores, and it is shown once, when it is created or rotated. A request with Authorization is judged by its key alone, never by a signed-in session beside it.

A key either only reads (it lists, reads and downloads) or may also generate (it creates and cancels). A key that only reads gets 403 insufficient_scope when it tries to create or cancel, and nothing is written.

A key may carry an IP allowlist of addresses and networks: from any other address it gets 403 ip_not_allowed. Giving a key more permission, raising a limit or widening an allowlist needs a recent sign-in; narrowing never does.

Rotating a key gives it a new value at once and the old value stops. Revoking a key stops it for good: its generations not yet sent to a model are canceled and their credits returned, and generations already under way still finish.

Only accounts with paid access create generations through the API (403 not_eligible otherwise), and the API spends paid credits only (402 insufficient_credits). Gift and check-in credits stay for the website.

Generation API endpoints

All paths are on this site. Request and response bodies are JSON, except the result files.

https://demo.freshapp.ai

MethodPathPermissionWhat it does
POST/api/v1/generationsgenerations:writeCreates a generation. Needs an Idempotency-Key; answers 202 with the generation.
GET/api/v1/generationsgenerations:readThe account's generations, newest first, a page at a time (limit, cursor, module). Text generations are read by id only.
GET/api/v1/generations/{id}generations:readOne generation of the account, with its outputs, its credits and why it ended.
POST/api/v1/generations/{id}/cancelgenerations:writeCancels a generation not yet sent to its model and returns its credits.
GET, HEAD/api/media/{id}generations:read (or a signed link)An output's file, with one byte range at most. A signed link needs no key and works from any address.

Models and prices

The models enabled on this site, as the API takes them: use the id as model, and the fields as input. A request reserves its most expensive outcome when it is created; when it ends, you pay for what was delivered.

Model idNameKindPriceMost per requestInput fields
seed-audio-1.0Seed Audio 1.0Audio75 credits per minute; at least 10 for a delivered result150 creditsprompt (required), voice, audio_references, image_url, output_format, sample_rate, speed, volume, pitch, multilingual

Idempotency

Creating a generation needs an Idempotency-Key header of 1 to 128 letters, digits, dots, colons, underscores or hyphens. Use an id from your own system, such as an order number, so that the same piece of work always gets the same key.

The same key with the same request returns the same generation, however often it is sent, and it is charged once. The same key with a different request is 409 idempotency_conflict, and nothing is created.

A timeout or a broken connection does not mean the request failed. Send it again with the same key and the same body, and keep both until you have an answer. A 5xx, a 429 or a 408 is answered the same way: wait as Retry-After says, then send the request again unchanged.

Statuses and polling

Read a generation with GET /api/v1/generations/{id} every 5 seconds at first and then up to every 10 seconds, until its status ends. A 429 says in Retry-After when to read again.

StatusMeaning
queuedAccepted and waiting to be sent to its model. It can still be canceled.
processingSent to its model, or its files are being saved and settled.
succeededEvery output was delivered and the credits are settled.
partially_succeededSome outputs were delivered and charged; the others' credits came back.
failedNothing was delivered; error says why, and the reserved credits came back.
canceledCanceled before it was sent, by you or because its key was revoked; the credits came back.

billing.reserved_credits is what creating the generation held. Once it settles, charged_credits is what you paid and refunded_credits what came back; until then both are null and held_credits shows what is held.

A generation that waits for a manual check shows review with the time the check began, and it keeps its credits until the check ends; then it continues or its credits come back.

Result files

Each delivered output has a url on this site. For a key, it is a signed link that works for an hour without the key and from any address, so you can hand it to a download tool. After an hour, read the generation again for a fresh link.

With the key itself, GET /api/media/{id} returns the file too, within the key's allowlist. Both answer HEAD and one byte range (Range: bytes=0-1023), and never redirect to another site.

A file referenced in an input, such as an audio sample or an image, must be one this account uploaded on the website, one of the site's own samples, or a link from a source the site allows; anything else is refused before credits are held.

Limits

Every limit below is the one this site enforces now, and every refusal says which one was reached.

  • One request reserves at most 150 credits, the most expensive request of the enabled models. A key may be set lower, and a request's max_credits lowers it for that request; over it the answer is 400 request_limit_exceeded.
  • Each key spends at most 5,000 credits a UTC day unless you set its own limit, up to 1,000,000; past it, the key gets 429 key_daily_limit_exceeded until the next UTC day.
  • All keys of an account, with its AI clients, spend at most 10,000 credits a UTC day unless you set the account's own limit; past it, the answer is 429 account_daily_limit_exceeded.
  • Each key and each account sends at most 20 creates and cancels and 240 reads and downloads a minute; past it, 429 rate_limited with Retry-After.
  • At most 2 generations of an account run at once; another one waits with 429 too_many_active.
  • A request body is at most 32 KiB of JSON; a larger one is 413 request_too_large.

Error codes

An error is {"error": {"code": …, "message": …}}. The code is always one of these, and the message is English for developers. A 429 or a 503 says in Retry-After how many seconds to wait. A generation that failed or was canceled carries one of the codes marked as a generation's in its error field.

HTTPCodeMeaningWhat to do
401invalid_credentialThe API key is missing, malformed, unknown, revoked or replaced.Check the API key, its permission and its allowlist.
401credential_revokedThe key was revoked or rotated while the request ran; a queued generation of a revoked key is canceled and its credits returned.Check the API key, its permission and its allowlist.
403insufficient_scopeThe key may not do this: the error names the permission it needs in required_scope.Check the API key, its permission and its allowlist.
403ip_not_allowedThe key cannot be used from this address: it is outside the key's IP allowlist.Check the API key, its permission and its allowlist.
401unauthorizedThe website session ended: sign in again.Change the request before sending it again.
403invalid_originA website request must come from this site itself.Change the request before sending it again.
403forbiddenThe request came without a trusted client address.Ask the site's support.
400invalid_requestThe request is malformed: its JSON, its headers (Idempotency-Key, Content-Type) or its query.Change the request before sending it again.
400invalid_inputThe input does not fit the model's fields, or the model could not use it.Change the request before sending it again.
413request_too_largeThe request body is too large.Change the request before sending it again.
409idempotency_conflictThis Idempotency-Key was already used with a different request.Change the request before sending it again.
404model_unavailableThe model is unknown or not available now.Change the request before sending it again.
404not_foundNo such route, generation or file for this account.Change the request before sending it again.
405method_not_allowedThe path does not take this method; Allow lists the ones it takes.Change the request before sending it again.
409not_cancelableThe generation was already sent to its model, or has ended.Change the request before sending it again.
409not_readyThe file is not ready yet.Wait as Retry-After says, then send it again.
410expiredThe file was kept for the retention period and has been deleted.Change the request before sending it again.
400request_limit_exceededThe request would cost more than its max_credits, or than the key's or the site's limit for one request.Change the request before sending it again.
403not_eligibleGenerating through the API needs a paid account.Buy a credit pack or a plan: the API spends paid credits only.
403email_unverifiedVerify the account's email address to generate.Change the request before sending it again.
402insufficient_creditsNot enough credits: the API spends paid credits only.Buy a credit pack or a plan: the API spends paid credits only.
403generation_restrictedGenerating is paused for this account after repeated content refusals; ends_at says until when.Ask the site's support.
403trial_pausedWebsite trials are paused for this account after several trial outputs went past the trial's limits; ends_at says until when. Requests without a trial go on as usual.Change the request before sending it again.
429key_daily_limit_exceededThe key's daily spending limit is reached.Wait as Retry-After says, then send it again. Retry-After: until the next UTC day.
429account_daily_limit_exceededThe account's daily spending limit for the API is reached.Wait as Retry-After says, then send it again. Retry-After: until the next UTC day.
429rate_limitedToo many requests in a short time.Wait as Retry-After says, then send it again. Retry-After: the limit's window.
429too_many_activeToo many of the account's generations are running.Wait as Retry-After says, then send it again. Retry-After: 30 s.
429too_many_under_reviewSome of the account's generations wait for a manual check.Wait as Retry-After says, then send it again. Retry-After: 3,600 s.
503busyToo many generations are running on the site right now.Wait as Retry-After says, then send it again. Retry-After: 30 s.
503pausedGenerating is paused for now.Wait as Retry-After says, then send it again. Retry-After: 60 s.
503temporarily_unavailableThe request could not be completed right now.Send the same request again with the same Idempotency-Key. Retry-After: 60 s.
503api_unavailableGenerating is not available yet on this site.Wait as Retry-After says, then send it again. Retry-After: 60 s.
503retrySomething changed while the request ran, such as the key's settings.Send the same request again with the same Idempotency-Key. Retry-After: 60 s.
503unavailableFiles are temporarily unavailable.Wait as Retry-After says, then send it again. Retry-After: 60 s.
500internal_errorSomething went wrong on the site.Send the same request again with the same Idempotency-Key.
400request_failedThe request was refused for a reason without a code of its own.Ask the site's support.
400captcha_requiredComplete the website's security check.Change the request before sending it again.
403captcha_failedThe website's security check did not pass.Change the request before sending it again.
503captcha_unavailableThe website's security check is unavailable.Wait as Retry-After says, then send it again. Retry-After: 60 s.
400reference_unavailableA referenced upload is gone, or is not this account's.Change the request before sending it again.
413reference_too_largeReferenced uploads may total at most 10 MB.Change the request before sending it again.
503reference_storage_unavailableThe storage of references is unavailable.Wait as Retry-After says, then send it again. Retry-After: 60 s.
409storage_quota_exceededThe account's storage is full.Ask the site's support.
In a generationcanceledThe generation was canceled before it was sent; its credits came back.Change the request before sending it again.
In a generationsubmission_expiredThe generation could not be sent in time; its credits came back.Send the same request again with the same Idempotency-Key.
In a generationmodel_busyThe model was busy; the credits came back.Wait as Retry-After says, then send it again.
In a generationcontent_rejectedContent safety checks declined the input or the output.Change the request before sending it again.
In a generationresult_expiredThe result could not be saved in time.Ask the site's support.
In a generationgeneration_failedThe generation failed.Ask the site's support.
In a generationdelivery_failedThe result could not be delivered; its credits came back.Ask the site's support.
In a generationtrial_limit_exceededThe website trial's output went past the trial's limits and was not delivered; its credits came back.Change the request before sending it again.

TypeScript client and React hook

The TypeScript client, open-api-client.ts, needs no other package and runs on servers, in workers and in browsers. On a server, give it baseUrl and apiKey; in a browser on this site, give it neither, and requests use the visitor's own session.

create sends an Idempotency-Key, yours or a new random one, and sends the same request again after a broken connection, a 5xx, a 429 or a 408, waiting as Retry-After says. When it gives up, its error carries the key, so you can send the request again later. With a store such as localStorage, a request keeps its first key across a page reload.

wait reads a generation until it ends, 5 seconds apart at first and then up to 10, and stops when you abort it. download fetches an output's file, and sends the key only to this site's own unsigned links.

The React hook, open-api-react.ts, follows one generation with the client's wait and stops when the component unmounts. Download both files, keep them side by side and rename them to .ts.

Downloads