What anex.sh is
anex.sh runs an open language model over a whole dataset as one job, instead of one request at a time. You give it a file with one row per prompt, choose a model, and get back one result file with a response per row. Rows are text, and on a media-capable model they can also carry images, audio clips, and video.
Before anything runs, the job is estimated and held at an approval gate. Estimates are free, and an approved job never consumes more credits than the estimate you approved. Payment is prepaid credits, not an invoice after the fact.
It fits work that arrives as a file of rows rather than as live chat traffic - anything you would otherwise run as a long loop of per-row API calls.
Two ways to use it
- Browser console
- Sign in and submit, approve, track, and download jobs from the browser. Nothing to install. See the console guide.
- HTTP API
- Call the batch API with an API key from a script or from CI. It can also ingest an input file from a URL, which the console cannot. See the API reference.
Both work on the same jobs and the same organization: a job submitted from a script shows up in the console, and a job submitted in the console can be polled with an API key.
Where to start
- Create an account. The first person to sign in for a new organization becomes its owner. The owner can then invite teammates into the same organization and promote them - up to owner - rather than each person creating an organization of their own; roles and invitations are covered in the console guide.
- Buy credits. The service is prepaid, and an owner buys them from the Settings page; what a job costs is on the pricing page.
- Submit a job from the Jobs page: an input file and a model.
- Approve the estimate when the job reaches cost_ready, or turn on auto-approve so that happens for you.
- Preview and download the result once the job is done.
From a script, the same path is five calls:
POST /inputs to upload or
ingest the file,
POST /jobs to submit,
GET /jobs/{id} until the job
is cost_ready,
POST /jobs/{id}/approve to
start the paid work, and
GET /jobs/{id}/result once
it is done. The API reference covers each one.
Job lifecycle
A job reports one status at a time, the same value in the console and in the API. The normal path runs top to bottom:
- submitted
- Just created. The input has passed its format, size, and row-count checks and is queued for costing.
- estimating
- The output length is being measured from a sample of your rows, so the job can be priced. A job that was submitted with an explicit max tokens per row is priced from that number instead and can go straight to cost_ready.
- cost_ready
- Estimated and waiting for approval - yours, or an auto-approve rule. No credits are held yet.
- approved
- Approved. The estimated cost is now held against your balance and a GPU is being obtained for the job. This is usually the longest a job waits - starting a machine takes minutes, and on a short job that is most of the elapsed time.
- in_progress
- A GPU worker has picked up the first piece of work. Rows are being processed, and the console counts them off as they finish.
- merging
- GPU work is done; the per-row outputs are being combined into one result file. Too late to cancel from here on.
- done
- Finished. The result can be previewed and downloaded.
Off that main line, a job can also end up:
- cancelling
- A cancel was requested after work had started; rows already in flight are finishing, then the job stops.
- cancelled
- Stopped by a cancel. If nothing had started, no charge. If rows had already run, those are delivered and billed and the rest is dropped.
- failed
- Something went wrong; the reason is reported with the status. If some rows had already completed, they are still available as a partial result.
Limits
- Input file size
- 1 GiB per input.
- Rows per job
- 10,000,000.
- Jobs awaiting approval
- 100 per organization across
submittedandcost_ready; submit answers 429 past the cap. - Approval window
- 24 hours in
cost_ready, then the job is cancelled automatically, free of charge. - Inline images
- 20 MB per image, for JSONL rows that embed an image directly in the request.
- Inline audio
- 60 MB per clip, for an
input_audiopart or adata:URI inaudio_url. - Inline video
- 150 MB per clip, for a
data:URI invideo_url. - Audio formats
- wav, mp3, m4a, aac, ogg, opus, flac, aiff. Anything else is rejected at submit.
- Video formats
- mp4, webm, mov, mkv, avi.
- Image formats
- png, jpeg, webp, gif, bmp, tiff.
- Audio duration
- Up to 2,400 s (40 min) per clip - in practice narrower still, bounded by the model's own context length.
- Video duration
- Up to 120 s (2 min) per clip.
- Media items per row
- Up to 2 images; 1 audio clip and 1 video clip, on the
chat-completions models that take them. An embedding model that
takes media (
qwen3-vl-embedding-8b,qwen3-vl-embedding-2b- see embedding jobs) allows up to 4 images and 1 video clip instead, and never audio. A row carrying more than its model allows is not rejected up front - the surplus is refused at serving time and can fail that row. - Max tokens per row
- 32,768 - the highest per-row output cap you can request when you set max tokens yourself instead of letting the platform estimate it. A submit with a larger value is rejected.
These are enforced on the input itself, whichever way the job was
submitted. A file that clears the browser's own check can still be
rejected once the server reads it. The image/audio/video sizes cap the
bytes accepted inline per media item; a remote URL is never
size-checked - its fetch is bounded by a timeout at serving time
instead. The inline caps are checked on a sample of the file's first
rows at submit, then on every row while the job is estimating: a
violation past the sampled rows fails the job with
failure_code: invalid_input and the same message the
submit-time rejection uses. A model with no encoder for a modality
(see the API reference) isn't caught by
the size check - it fails the job later instead. An embedding model's
image and video parts are checked the same way; an embedding model
never accepts audio, so an audio part on one is rejected as an
unsupported modality rather than a size violation.
A media part's format is read from what the row already carries, in
order: the format field on input_audio, a
data: URI's media type, or the file extension on a URL.
We also check the first bytes of anything sent inline, so a clip
whose contents contradict what it claims is rejected rather than
failing on a GPU later. An audio or video URL with no file extension
is resolved from the Content-Type its host returns on a
HEAD request made while the job is being estimated; when
a host answers with application/octet-stream, or
names something off the list, that leaves us nothing to go on and the
job is rejected with failure_code: invalid_input. The
probe covers the first few dozen URLs in a job, so a clip we could not
ask about is admitted and can still fail at serving time. Give the
URL a file extension, serve it with a real content type, or send the
bytes inline. Images are never probed this way, so an extensionless
image URL with no data: media type passes through to the
model as-is.