Skip to content
ACS Infra

Changelog

Each entry lists the changes that you can notice as a User of the API or the App, by date, newest first.

2026-11-03

New

  • The App has a Request more tokens page, under Usage (Ask for more tokens). You say how many tokens you need, for which models and which project, and how you will use them. An admin approves the request with a Grant, or rejects it with a note, and the page lists each request with its answer.
  • From 90% of your tokens used, a notice under the title of each page of the App says how many are left, with Request more. It takes the place of the notes on Usage and API Keys that told you to email for a Grant.
  • Admin has a Token requests tab, where an admin approves a request with a number of tokens, such as 50K, or rejects it (Decide token requests). Grant tokens, Set the Budget, and Set the monthly allowance take a number written the same way.
  • The API takes steering at chosen positions on every model. Give each Layer a list of vectors, with position_indices, or without them for positions 0, 1, 2, and so on (Steer chosen positions).
  • A Steering vector takes position_spans, the old platform's spans of positions. One vector for each Layer goes to each position of the spans (Steer a span of positions).
  • An admin can type a User's organization and Discord handle on the User's page (Open a User's page).

Changed

  • When your own tokens cannot hold a request, the message of 429 budget_exceeded ends with the address of the form, Ask for more tokens at https://v2.infra.acsresearch.org/usage/request. (Not enough tokens). The code stays budget_exceeded. In the Playground, the note says how many tokens the request can use and links to Request more tokens, without the address.
  • A Steering vector position that no forward pass of the request reaches gets 400 on apply_steering_vectors, before the request holds any tokens. Before, positions were refused (Positions that a request reaches).
  • llama-8b and llama-405b take 131,072 tokens of prompt and generated text together, the whole context of Llama 3.1. Before, they took 8,192 (Served models). trinity-truebase keeps 8,192, the model's own context.
  • With the longer context, a capture of llama-8b without max_tokens gets 400. Left out, max_tokens is the room left in the context, which passes the capture's size cap (Requests that the API refuses). Set max_tokens with every capture.
  • temperature takes a value from 0 to 2. The old platform's API took up to 100, but Inference refuses a value above 2. So the API refuses it with 400 invalid_request before the request holds any tokens (Request fields). In the App, Temperature's slider goes from 0 to 2, and the field marks a value outside the range.
  • The API refuses the other sampling values that Inference refuses, with 400 invalid_request before the request holds any tokens (How to sample). These are top_k below -1, min_p outside 0 to 1, presence_penalty and frequency_penalty outside -2 to 2, and repetition_penalty of 0 or less. The API also refuses n above 1 at a temperature below 0.00001, where Inference samples greedily. Before, such a request woke the model and then failed. The App's fields mark these values too.
  • The narrower sampling ranges, top_p's of 2026-10-28 too, apply to new requests only. A Run or a Loom Node saved with a value outside them still lists, opens, and exports with that value. A Run, a Node, or a Job that waits to run with such a value runs, and Inference decides on the value. In the Playground, Use these settings puts the value in the form, which marks it (Find your Runs again).
  • Jobs have the routes and the shape of the old platform's API, so code written for its Jobs runs unchanged (Run a Job). POST /v1/completion-jobs takes one body of POST /v1/completions and answers 202 with a Location header. GET /v1/completion-jobs/{job_id} gives status queued, running, succeeded, failed, or cancelled, ISO times, status_url, and result_url. GET /v1/completion-jobs/{job_id}/result answers the Completion, or the error with the status that POST /v1/completions would have answered. A cancel of an ended Job answers the Job. POST /v1/jobs and its routes are gone.
  • Harvests have the routes and the shape of the old platform's API, so its polling loop runs unchanged (Harvest Activations). POST /v1/harvest answers 202 with job_id, n_prompts, and reserved_tokens. GET /v1/harvest/{job_id} gives status pending, running, done, failed, or cancelled, ISO times, completed_at, error as a message, and, once done, result with the links of the files. DELETE /v1/harvest/{job_id} cancels a harvest, and a done or failed one gets 409 harvest_not_cancellable. POST /v1/harvests and its routes are gone.
  • An Idempotency-Key sent again with another request gets 409 idempotency_conflict with x-should-retry: false, in place of 400. A full Job cap is 429 job_capacity, and an expired Job result is 410 job_result_expired.
  • The API takes the X-Request-Id that you send as the id of the request, as the old platform does (Headers). It must have 1 to 64 letters, digits, -, or _.
  • A Steering vector's activations can be encoded as the old platform's encode_vector writes them (Encode a Steering vector). The numbers are bfloat16 as int16, or float32, in zstd and base64. An encoded vector takes about a tenth of the bytes of JSON numbers, so eight vectors over all 32 Layers of meta-llama/Llama-3.1-8B fit the body cap. The API checks it as it checks numbers, and sends it to Inference as it is.
  • The API counts a text prompt's tokens with the model's own tokenizer, as Inference does (The Hold). Before, it counted the UTF-8 bytes. A request without max_tokens gets the context that its tokens leave. A 9,000-character prompt to trinity-truebase gets about 6,190 tokens, where it got 1. The Hold, the cut near the end of your Budget, and the caps of a capture and of prompt_logprobs: -1 use this count too.
  • prompt_logprobs: -1 takes several prompts of at most 256 tokens in all × n, and keeps 256 with Steering vectors. Only output_residual_stream lowers it to 16 (Size caps).
  • The API's address sends the old platform's page addresses to their pages. /docs and its pages and /llms.txt lead to these docs, and /swagger to the API reference. The old example downloads lead to the Cookbook, and /usage to the App's Usage page. An old page such as /docs/api or /docs/bulk-harvest leads to the page that took its place.

Fixed

  • Inference's answer can miss a choice, or have activations that are missing or of another shape than the request asked for. The API then answers 502 upstream_server_error, and the request costs nothing. Before, such an answer came back as a 200 and cost its tokens. In a stream, the error comes as the last event, and you pay for the tokens before it (Decode the tensor).
  • A Loom Node that fails keeps the text that it grew and its own error. This is also true when the API could not save its end at the first try. Before, a Node that failed early lost its text, and its error said only that the server had an error (Read a failed Node).
  • In Compare, a model whose Run cannot start shows why in its own place (Compare models and settings). Earlier Runs no longer stay in view beside the new ones.
  • A Run says which stop sequence ended it, or that the model ended the text, also when Top logprobs shown is 0.
  • The likeliest tokens at a token stay on the screen, and the list scrolls when it is long.
  • Select a Loom's title to rename it (Open or start a Loom). Rename left the buttons by the title.
  • On Admin, an Application's unanswered questions show as Not answered.
  • An email link in the docs and in the App opens your mail app without a blank tab.

2026-11-02

Changed

  • The API takes the API keys of the old platform, which start with acs-bm-, as well as keys that start with sk-acs- (Use API keys).
  • Requests and GET /v1/models name each Base model by the id of the old platform: llama-8b, llama-405b, and trinity-truebase (Models). A request without model runs on llama-8b. Each answer's model still names the weights that ran it, such as meta-llama/Llama-3.1-8B.
  • GET /v1/models lists each model in the old platform's shape: its id, its served_model_name, its gpu_shape, default, and capabilities with n_layers, hidden_size, max_activation_prompt_tokens, and the limits of a request (Model fields). The App keeps its own list. GET /v1/models/{model}/status gives boot with stage, stage_label, and stage_age_s, and the same in activation_boot (Check whether a model is Warm). Both routes take a served_model_name with its slash, such as /v1/models/meta-llama/Llama-3.1-8B.
  • Each choice of a Completion has stop_reason, the stop text that ended it, in an answer and in a stream's chunks (Response fields). stream_options.continuous_usage_stats alone puts the usage so far in each chunk of a stream, without include_usage.
  • A stream has a : keepalive comment line every 5 seconds, as the old platform's streams do. A client's read timeout then does not cut a stream while Inference is silent (Requests with a stream).
  • Every error of the API has the error object of the old platform's API. Code written for that API branches on the same error.code (Status codes). request_id is at the top of the body, beside error. A request that the Budget cannot hold is 429 budget_exceeded, a rate limit 429 rate_limited, and a full line of Completions 429 queue_full. A request with no key is 401 missing_authorization, and a revoked or paused key is 401 key_revoked or key_disabled. An unknown model is 400 model_not_found, so the openai SDK raises BadRequestError for it.
  • A failure of Inference has the type upstream_error and a code that names it, such as 502 upstream_unreachable, vllm_oom, or upstream_server_error, with retryable and retry_after_seconds (Answers about Inference). Inference's own 500 is now 502, and its timeout is 504 upstream_deadline.
  • POST /v1/completions takes the values that the old platform's API takes (Request fields). max_tokens: null is the same as leaving it out, and output_residual_stream: false reads no Activations. output_residual_stream takes its Layers in any order and with a Layer twice. The Activations hold each Layer once, in ascending order. priority takes null, a text of digits such as "5", and true or false.
  • The API reads the body of a /v1 request as JSON whatever its Content-Type, as the old platform's API does. curl -d without -H 'Content-Type: application/json' works, and a body of text/plain no longer gets 415. A body sent in chunks, without Content-Length, no longer gets 411. The API checks the key first, then reads the body and answers 413 request_too_large when it passes the body cap.
  • GET /health answers in the old platform's shape: status, wrapper with uptime_seconds and the database, backends with each model's model_id, gpu_shape, and breaker, degraded_backends, and degraded_reasons. It still answers 503 only when the database does not answer.

2026-10-30

New

  • Each row of the Playground's Runs table ends with a menu, in place of Delete. It has Show, Use these settings, Copy settings, Copy prompt, Copy output, Copy as API request, and Delete. Copy as API request copies a curl command for POST /v1/completions that reads your key from $ACS_API_KEY (Find your Runs again). The menu stays at the right edge of the table when the table scrolls sideways.
  • The Models tab of Admin keeps models Warm (Keep a model Warm). An admin sets hours each week, or keeps a model Warm at once for 1 to 24 hours. The tab shows each model's Floor and what its windows cost.

Changed

  • In the App, Support has Email, Discord, Feedback, and Help. Discord opens the invite to ACS's Discord server in a new tab. Before, it was called Community and opened a page with Join the Discord server. Old links to that page open the invite (Discord).
  • Feedback under Support opens Send feedback, which was a button of its own in the navigation (Send feedback). Help opens Support in these docs. Before, it was called Help and FAQ.
  • Profile no longer shows your role or your token budget. Usage shows your token budget and monthly allowance (Read your usage).
  • Send feedback sends your notes to Linear, the team's issue tracker, with your account's email. The team can then answer you. An anonymous note goes without your email (Send feedback).
  • The Feedback tab of Admin links each note to its Linear issue.
  • API Keys lists only your active and paused keys. A revoked key leaves the page, and the line of revoked keys at the bottom is gone. With no such key, the page says No API keys yet, also when you once had keys. By key on Usage still shows a revoked key's tokens under its name (Revoke a key).
  • While API Keys loads, it shows the head of its table and three gray rows. With keys, the table stays in place when the list arrives, and New key shows in the header. With no key, the page shows No API keys yet and New key in its middle, with no table. The notes, such as No tokens left., show below the table, or at the bottom of the page with no key. A key's name shows on one line, cut short when it does not fit.
  • Jobs shows Run Jobs from a script in the middle of the page, as API Keys does with no key.
  • API Keys no longer counts your keys beside its title, such as 3 of 10 keys. At 10 keys, New key is still off, and the note below the table says why (Manage your keys).
  • The Analytics tab of Admin has five tabs: Users, Budget, Platform, Usage, and Spend. The days at the top of Analytics apply to each of them. Usage and Spend were tabs of Admin, and old links to them open their tab in Analytics. The Section filter is gone (Read the platform's analytics).
  • On Admin, Applications shows the day of each Application under Submitted, and the day and the admin of its decision under Reviewed. Feedback shows the day of each note under Submitted. Before, these columns were Received, Decided, and Sent.

2026-10-29

New

  • Harvests read the residual stream of up to 4,096 prompts at the Layers that you choose, on GPUs of their own. POST /v1/harvests queues one, and GET /v1/harvests/{harvest_id} reads it, with wait to wait for a change. GET /v1/harvests/{harvest_id}/results gives links to its safetensors files, and POST /v1/harvests/{harvest_id}/cancel cancels it (Harvest Activations).
  • The files of a harvest have the old platform's layout, version 2, so programs that read those files read these too. Each answer of the results route makes new links that work 7 days, and the API keeps the files (Kept data).
  • A harvest holds its prompts' tokens while it runs. It pays the tokens that Inference read when it completes, and nothing when it fails (Holds of background work). The App's Usage page shows its tokens under the source Harvests.
  • Each API key may start 200 harvests a month. Each User may have 3 harvests on Llama 3.1 8B queued or running, and 1 on Llama 3.1 405B or Trinity Large (Harvests).
  • The Cookbook's recipes link to their whole programs as files to download. Run a Job from a shell script links to job.sh, for example.
  • The Cookbook has Evaluate with Inspect. It runs an Inspect eval through POST /v1/completions, with a task to download.
  • The Cookbook has Run the workshop notebook. Its Jupyter notebook goes from a first Completion to a steered Base model.
  • An admin's Admin page has an Analytics tab with the platform's numbers (Read the platform's analytics). It counts Users, monthly allowances, refusals, server errors, sources, and hours. The numbers that were above the tabs moved into it.
  • Admin has a Spend tab with the GPU spend of each model and day, and who used each model in 48 hours (Watch the spend). Its Lock and Clear pause and resume new GPU work. Analytics shows the GPU spend of its days.
  • The API pauses new GPU work when the GPUs cost $3,000 in 24 hours, and an admin can pause it too. While the pause is on, a Completion, a Job, or a Branch that you start gets 429 spend_cap with Retry-After: 3600, and queued work waits. Work that runs finishes (Spend limit).
  • An admin can put tags on Users, such as hiring-2026-08, and an Invite can give its new User a tag. Users on Admin lists each User's tags and has a Tag filter (Group Users with tags).
  • Users on Admin shows each User's organization, engagement, active weeks, and models, and has an Engagement filter (Manage Users).
  • An admin can select many Users on Users and act on all of them at once (Act on many Users at once). The actions are suspend, restore, tag, set the monthly allowance, and delete.
  • Each User has a page on Admin, which their email on Users opens. It has their account, tags, Budget and usage, API keys, Application, and notes (Open a User's page).

Changed

  • On API Keys, New key opens a dialog for the key's name, then shows the key once in Copy your key. The page title counts your keys that are not revoked, such as 3 of 10 keys. At 10, New key is off, and the page says why. Before, a form at the top of the page made a key (Make an API key).
  • Each key on API Keys has one menu for Rename, Pause or Resume, View usage, and Revoke. Before, each row had three buttons. A key's tokens this month open Usage for that key. Revoked keys fold into one line at the bottom of the page, such as 2 revoked keys. A time today shows in UTC, such as 05:02 UTC, and an earlier one shows its date (Manage your keys).
  • API Keys no longer shows the tokens of your month and your Budget above the table. Usage shows them (Read your usage).
  • The App's Usage page starts with four numbers. They are the tokens used, the Completions, and the tokens left in the monthly allowance and in the token budget. At the top, choose the days to count, up to 366 UTC days, and one key or one model. The address keeps them. Tokens per day charts each day's input and output tokens. By model and By source split the tokens. Before, the page counted this month and the last 30 days only (Read your usage).
  • Usage shows each Completion's source and key. Its status is Running, Completed, Failed, or Stopped. Before, it was running or settled. Usage no longer shows the tokens that running Completions hold, because the tokens left already leave them out.
  • Usage shows how each Completion's tokens were counted, Reported, Partial, or Held, and how long the Completion took (Read your usage).
  • By source on Usage counts each source's Completions and its input and output tokens.
  • By key on Usage shows each key's tokens last month and in all, and its own Budget. Select a key's name to count that key alone.
  • With one key chosen, Usage shows the tokens left in that key's own Budget, when it has one. The meter of the monthly allowance turns orange from 90% used, and red once the allowance is spent.
  • API Keys shows the tokens left in each key's own Budget. It reads its numbers again every minute, and keeps them when a read fails. When you have no tokens left, it says No tokens left. and whom to ask for a Grant (Manage your keys).
  • Copy your key asks before it closes while you have not copied the key, and before the tab closes. It links to Set the client for long requests.
  • Usage has a Refresh button beside Updated. When a read fails, the page keeps the numbers that it read last and says Could not refresh. Before, the error took the place of the numbers. The Usage and Analytics tabs of Admin do the same.
  • The App says input tokens and output tokens where it said prompt tokens and completion tokens. A Run's line and the Read Layers table use the new words. The API's fields prompt_tokens and completion_tokens keep OpenAI's names.
  • The App shows the logo of each model's maker before the model's name. It does so in the Playground's model list, Models, Runs, a Run's heading, and on Usage.
  • The App names each model by a short name, such as Llama 3.1 8B, Llama 3.1 405B, or Trinity Large. The model's full id shows when you hover over its name. Exports and the page's address keep the full id.
  • A Run in the App's Playground leaves the prompt as you wrote it, and its text shows under the prompt. Continue the text is gone. Before, a Run of one choice wrote its text into the prompt as it streamed. To go on from a Run, copy its text into the prompt (Run a prompt).

2026-10-28

Changed

  • top_p must be more than 0 and at most 1, as OpenAI and vLLM take it. Another value gets 400 with param set to top_p before the request reaches Inference, and costs nothing (Strict validation). A Loom Branch checks it too, when you make the Branch (Branch under a Node).
  • Each Run of the App's Playground keeps the number that it got when it started, after a reload and after a delete. The newest Run shows when you come back, and a delete asks you to confirm. The Runs table shows all its columns, and the page scrolls its rows (Find your Runs again).
  • Playground in the App's navigation goes back to the settings and Compare that this browser used last. It does so until you sign out or sign in again (Run a prompt).
  • The Playground says a setting's limit in plain words, such as Max tokens: at most 32,000.. When the prompt and Max tokens do not fit in the model's context, it names the context in tokens.
  • A Branch, a split, or a sibling that would give a Loom more than 2,000 Nodes gets a 409. Its message says how many more Nodes fit (Change the tree).
  • Import a Loom in the App names the new Loom after the file's title, with (imported) after it. The list then tells it from the Loom it came from. POST /v1/looms/import keeps the file's title, as before.

Fixed

  • A Loom that you open again sets Branches to the number of Nodes of its last Branch. It does so also after you split or delete one of them. Before, it often showed 1.
  • A Loom whose last Branch asked for no log probabilities opens with Top logprobs shown at 5, so the next Branch has a heatmap. A Branch through the API may ask for none. Before, the box was empty.
  • Import a Loom in the App says what is wrong with a file that is not a Loom file, such as "nodes" is missing. Before, it said Missing key and told you to correct a setting and select Branch.
  • Split at the cursor splits a Node only where you put the cursor or selection inside its text. Before, s and then Split at the cursor cut at the start. The Node was left empty, and all its text went to the new child.
  • A Loom page remembers Tree or Graph in every browser, also over plain HTTP. Before, a browser without the Cookie Store API opened the next Loom in Tree.
  • A Loom's Graph marks a failed, stopped, or growing Node as the tree does. Its labels fill their column, with the whole first line on hover. Before, a failed Node read No text yet with no mark, and labels stopped after 18 characters.
  • On a Loom page, a long title shows in full when you hover over it. Your Looms scrolls under its heading and fills the column beside New Loom. Tree and Graph stay in sight in a long tree. Before, the title stopped after about 20 characters, and the list was a box of 3 rows on every page. The switch scrolled away with the Nodes.
  • An Invite's page says that its form sets you up and approves you at once. It no longer speaks of a review. After you send its form, you choose your password. No approval email follows, because its link would be used already.
  • When your session ends, as when your access is suspended, the next page that you open in the App takes you to sign-in. Sign-in says that your session has ended, and opens the page again after you sign in. Before, the page stayed open with only an error box.
  • After too many tries to sign in, the sign-in page says how many seconds to wait. It keeps the email that you typed.
  • On Usage scoped to one key, the monthly allowance and the token budget say that they are for all keys. Their labels read Your account's monthly allowance and Your account's token budget.
  • Each Copy in the App, such as a new key's, works when the browser does not let the App copy, as over plain HTTP. The App then shows the text selected, for you to copy by hand. Before, nothing was copied, and nothing on the page said why. The Loom's Copy text showed an error page.
  • In the App, Cmd+B or Ctrl+B opens and closes only the navigation, not the settings of the Playground or a Loom. On a phone, a new key's dialog keeps a long key inside its edges.

2026-10-27

New

  • Sign in to the App with GitHub, Google, or Hugging Face, when the App shows its button. The provider must have verified the email address that you use for ACS Infra. A provider does not give you access, so apply for access first. On Profile, link and unlink each provider (Sign in).
  • Send feedback in the App's navigation sends the team a bug, a feature request, or another note. It takes up to 5,000 characters and 5 screenshots. Unless you send it anonymously, the App's recent errors and request ids go with it, and the dialog shows them first.
  • An admin can invite you by email. The email's link opens the Apply form for your address. You are approved as soon as you send the form, and go straight on to set your password. The link works once, for 7 days.
  • Every request that makes or changes something takes an Idempotency-Key header. The same request sent again with the key does its work once and is charged once. It gets the first answer, or 409 no_answer for a stream (Send a request again safely).
  • A page in a browser can call /v1 with your API key. The API answers any origin with CORS, with no cookie. Call the API from a browser says how to keep the key safe.
  • Each approved User has a monthly allowance of 5,000,000 tokens. A Completion spends this month's allowance while it fits, then your Budget. The allowance starts again on the first day of each month (UTC), as Your monthly allowance explains.
  • logprobs takes up to 100 top tokens.
  • "output_residual_stream": true reads every Layer of the model (Read Activations).
  • stream_options.continuous_usage_stats with include_usage puts the usage of each chunk's choice so far in each chunk.
  • GET /v1/models lists with each model the limits that the API enforces on it (List the models).
  • A JSON answer of 500 bytes or more comes gzipped when your client asks for it, as the openai SDKs do.
  • A Completion that is not streamed and runs longer than 150 seconds gets 200 at once. Spaces follow every 5 seconds, then its JSON or its error (Requests without a stream).
  • The App's Playground keeps its settings in its link. The link holds each model of Compare with its settings, Single or Compare, the Heatmap, and Continue the text. A reload, or a link that you send to another User, opens the same Playground. The link never holds your prompt, a Run, or a Steering vector. Your prompt stays in your browser until you sign out.
  • In an empty prompt box of the Playground or of a new Loom, Tab puts in the gray example. The next Tab moves on.
  • The App remembers when you close the navigation or the Playground's settings.
  • The App's Usage page shows this month's tokens and your monthly allowance, and what running Completions hold. It has a chart of the last 30 days with its table, each key's tokens, and the tokens of each Surface. It shows the history of your Budgets, with the date and the amount of each Grant. ?key= shows one key only. The page reads again every minute while it is open.

Changed

  • The App's Loom page has the tree changes and the keys g, e, s, i, Delete, and ?. It has Stop, a graph view with zoom and fit, and the likeliest tokens at a token. Its Export writes the Loom file shape of the old platform. It also has Path as text and Import a Loom (Use the Loom page).
  • The App's Loom opens the Loom you changed last, and lists your Looms beside each Loom, pinned first. It has New Loom, Rename, and Pin. A Loom opens with the settings of its last Branch.
  • Export a Loom with GET /v1/looms/{loom_id}/export, and make one from a file with POST /v1/looms/import. The file has the shape of the old platform's Loom export. Files of either then import into the other (Export and import a Loom).
  • Edit, split, and write Nodes of a Loom, and give it a new root. A Loom holds at most 2,000 Nodes (Change the tree). A Loom lists its Nodes depth first, each before its next sibling.
  • Stop a Loom's growing Nodes with POST /v1/looms/{loom_id}/cancel. Each becomes cancelled, keeps the text it grew, and pays for it (Stop a Branch).
  • Rename or pin a Loom with POST /v1/looms/{loom_id}. A Loom answers its title and pinned, and its first Branch names it after its root. GET /v1/looms lists pinned Looms first, then the others by their last change (Rename or pin a Loom).
  • A Branch of a Loom without a seed draws one. Each Node keeps its own seed + i in its request, so you can sample any Node again (Branch under a Node).
  • A Branch of a Loom takes n from 1 to 8 and max_tokens up to 1,000. More gets 400 with param set to the field (Branch under a Node).
  • On API Keys you can rename, pause, and resume a key, and a revoked key stays listed. Two of your live keys cannot share a name. A paused key gets 401 This API key is paused. Resume it on API Keys in the App to use it again. A revoked one gets This API key has been revoked. Before, both got Incorrect API key provided.
  • The Apply form gives every Application the same answer. While you have an open Application, a second one from your email is not stored, and the first stays as it was. Before, it got 409.
  • The API runs up to 4 Jobs at once, not one. While a User has a Job running, a newer Job of another User starts before that User's next one.
  • The API waits up to 14 minutes for Inference, not 600 seconds.
  • The Playground's heatmap colors a likelier token darker. Before, a less likely token was darker.
  • A model that the API does not serve gets 404 model_not_found before the API holds any tokens. The message names the models it serves.
  • Steering at chosen positions gets 400 on apply_steering_vectors until its test on a GPU passes. That is position_indices, or a list of vectors for each Layer (Steer chosen positions).
  • POST /v1/chat/completions gets 400 with a message that names POST /v1/completions. Before, it got 404.
  • A negative seed, stream_options without stream: true, and more than 33,554,432 top log probabilities of the prompt in one request get 400 (Size caps).
  • When Inference refuses the API's own request, the 502 has the header x-should-retry: false, so the openai SDK does not send the request again.
  • Runs, Jobs, and Branches that you start in the App count against a limit of 600 a minute for each User.
  • When Inference refuses or fails a Completion before its first token, a timeout included, the API gives its Hold back. You pay nothing, and the Usage page leaves it out. Before, the Hold was settled at 0 tokens. A Job's request that times out costs nothing.
  • A Completion that stops before the API sends it to Inference spends nothing. The running request of a Job that you cancel before it reached Inference spends nothing too.
  • A Job or a Branch made with an API key that is paused waits in the queue, holding no tokens. It runs when the key is resumed.
  • POST /v1/completions without max_tokens uses the model's context left after the prompt, at most 32,000. Before, it used 16. A request with more than one prompt, or with n above 1, must set it. Jobs and Looms keep 16.
  • Near the end of your tokens, POST /v1/completions lowers max_tokens to what fits and runs, with the header X-Acs-Max-Tokens-Clamped. It answers 429 insufficient_quota only when not even one token for each choice fits (Near the end of your tokens).
  • POST /v1/completions without stream and with a max_tokens above 15,600 gets 400 deadline_infeasible and X-Should-Retry: false. It cannot finish within the API's 14-minute wait. A max_tokens that you leave out is lowered to 15,600 instead.
  • A 429 insufficient_quota says which limit refused the request. The message names your monthly allowance and your Budget, with the day the allowance refills, or the API key's own Budget.
  • When the API's database is busy or out of reach as a Completion ends, you still get your answer. The API records what it spent a moment later.
  • A Job or a Node whose API process crashed runs again. Within about 18 minutes, the next API process gives back the tokens that the crash held, and queues it again. A Job does not pay again for a request that already has its result. Before, the Job or the Node stayed running with its tokens held.
  • A Job says when the API started it (started_at), when it ended (ended_at), and when its results expire (results_expire_at). Its results answer 202 with the Job and Retry-After: 1 while it is queued or running. Before, they answered 409. The API keeps the results for 30 days after the Job ended, then answers 410 (Download the results).
  • Jobs have caps. An API key may have 8 queued or running, a User 32, and all Users 128. A User may have 100 completed Jobs with kept results, and all Users 1,000. One more gets 429 rate_limit_exceeded (Limits).
  • A Job keeps at most 64 MiB of results. Once its lines reach that, its later requests do not run, and each gets an error line (Limits).

2026-10-26

New

  • Every error answer has the id of its request in error.request_id, the same id as in its X-Request-Id header (The error object). The error event that ends a stream has it too. A Job's result line and a Node's error have no request id.
  • When Inference does not answer within 600 seconds, the 502 Could not reach Inference: Request timed out. has the header x-should-retry: false. The openai SDK then does not send the request again. Your client gets this answer only with a timeout above 600 seconds, for example 660 seconds.

Changed

  • The API's own 500, The server had an error while processing your request., has the header x-should-retry: false. The openai SDK then does not send the request again. The API does not know how much of the work it did before the error. Another try could run a Completion or a Job two times. Before you send the request again, look for its result, as Which errors to try again says.

Fixed

  • When Inference answers with a redirect that the API cannot follow, the API answers 502, as for another refusal of its own request. Before, a Completion got 500, and a stream ended with Inference ended the stream before it was complete.
  • A text that holds the character U+0000 (NUL) gets 400 invalid_request_error, with the message A text cannot hold the character U+0000 (NUL). and its field in param. The check covers a prompt, a stop text, model, user, a Loom's prompt, a field of the application form, and an email address. Before, a Job, a Loom, a Branch, and the forms answered 500, because the database cannot store the character. A Completion with it gets 400 too, because a Job's requests are Completion requests.

2026-10-24

Changed

  • prompt_logprobs: -1 takes one prompt of at most 256 prompt tokens × n, or 16 with Activations or Steering vectors. The API counts the tokens as the Hold does. It refuses a larger request, or one with several prompts, with 400 and param prompt_logprobs, before Inference. Before, only Inference refused it, for a prompt of more than 1,024 tokens, and one request could have several such prompts. Refer to the size caps.

2026-10-12

Changed

  • The rate limit counts each API key, and it is 600 requests a minute instead of 60. Each key has two limits of 600 a minute. One is for the requests that start a Completion, a Job, or a Branch, and one is for all other requests. Reading a Job or a Loom then never uses up the limit for Completions. A new key adds requests.
  • When an API key has 16 Completions running, the next ones wait for a place instead of getting 429. Up to 64 Completions wait, and they run in the order they came. A waiting Completion holds no tokens until it runs. If you close the connection, the Completion leaves the line and never runs. With 64 waiting, one more gets 429 rate_limit_exceeded with Retry-After: 5.
  • Only Completions count toward the 16 in flight. Jobs, Looms, and the other routes answer at once while 16 Completions run.
  • The App's calls have no rate limit, because the limits belong to API keys.

2026-10-11

New

  • An API key has at most 16 requests in flight. One more gets 429 rate_limit_exceeded, with no Retry-After.
  • A User has at most 10 API keys. The App refuses an 11th one with 409.
  • Every answer has the header X-Request-Id, a new id for each request (The error object). Send it when you report a bug.
  • A request body is at most 8 MiB, on every route. A larger body gets 413, and a body without a Content-Length header gets 411.
  • One request has size caps. It asks for at most 32,000 generated tokens, prompt_logprobs from -1 to 100, and at most 4 stop texts. It captures at most 2^25 numbers, and has at most 8 Steering vectors of 2^21 numbers. A request over a cap gets 400.
  • When an admin approves your Application, you get the email "Your ACS Infra application is approved". Its link to choose your password works for 7 days. Before, approval sent the password reset email, whose link works for 1 hour.
  • The App no longer makes a User from its own form. To get access, apply with the form on the ACS Infra website, as Apply for access shows.
  • An admin can suspend your access. Your API keys then get 401 invalid_api_key. Its message is Your access to ACS Infra is suspended. Write to infra@acsresearch.org if you think this is a mistake. The Completions of your queued Jobs and Nodes fail with the same error, so they do not reach Inference.

Changed

  • The rate limit counts each User, not each API key. All your keys and the App share a burst of 60 requests, then 1 more each second. A new key adds no requests.
  • GET /v1/models lists the models from the API's settings, so it never waits for Inference to start. Each model also has its Layers (num_hidden_layers) and its hidden_size, and its max_model_len is never null. The list fails only with 401 or 429.
  • The App's Playground replaces its Completions and Activations pages. It runs one Completion with every setting of the API, and colors each token by its probability. It reads the Activations at the Layers that you choose, and adds a Steering vector.
  • The approval, password reset, and email verification emails have the look and the words of the ACS Infra website.

Fixed

  • The Playground shows the text that the API returns. Before, it could show a stop string that the API had cut from the text.
  • When you sign in after a session ended, the App keeps nothing from the User before you in that browser tab. Before, it could show that User's new API key and prompt.

2026-10-10

New

  • Every /v1 route needs an API key in the header Authorization: Bearer <key>. A request without a key, or with a wrong or revoked key, gets 401 with the code invalid_api_key.
  • You make API keys in the App, and every key starts with sk-acs-. The API Keys page shows each key as sk-acs- and its next 4 characters.
  • Jobs, Looms, and Nodes belong to the User who made them. The id of another User's Job, Loom, or Node gets 404.
  • Each Completion spends tokens from your Budget. A new User has 0 tokens until an admin adds tokens with a Grant.
  • Before Inference runs a request, the API takes a Hold of the most tokens that the request can use. If your Budget cannot hold them, the API answers 429 with the type and code insufficient_quota. The request does not reach Inference.
  • A text prompt holds 1 token for each byte of its UTF-8 text, and 1 more for the BOS token that Inference adds. With add_special_tokens: false, the prompt does not hold the extra token.
  • A Completion that is not streamed and stops before Inference answers spends its whole Hold. The running request of a Job that you cancel also spends its whole Hold.
  • An API key can have its own Budget, which limits that key further. An admin fills it with a Grant for that key.
  • A Job or a Branch whose Budget cannot hold even one of its requests gets 429 insufficient_quota when you submit it, and the API saves nothing. Each request of a Job and each Node still takes its own Hold when it runs.
  • When you revoke a key, its queued Jobs and Nodes do not reach Inference. Each of their Completions fails with 401 invalid_api_key and the message The API key that made this request was revoked.
  • Each API key has a rate limit on /v1 of 60 requests a minute, as a burst of 60 and then 1 more each second. Over the limit, the API answers 429 with the type rate_limit_error and the code rate_limit_exceeded. The answer has a Retry-After header in seconds.
  • To get access, apply with the form at /apply on the ACS Infra website. When an admin approves your Application, you get an email with a link to set your password.
  • One email can send one Application. The form answers a second one with An application from this email already exists.
  • If you forget your password, open /forgot-password in the App, and follow the link in the email to /reset-password (Reset your password). A new password ends all your sessions.
  • Signing in, and making a User in the App, allow 10 tries a minute from one IP address. More tries get 429.
  • The docs have five sections: API, Cookbook, Tutorials, Changelog, and Support.
  • The API section has new pages for Authentication and API keys, Models, Completions, Steering, Loom, and the Cheatsheet.
  • Each docs page has a button that copies the page as Markdown, and a menu that opens the page in ChatGPT, Claude, or Cursor.

Changed

  • n is at most 128 in a Completion and in a Branch. A larger n gets 400 with param set to n.
  • A Job has at most 1,000 requests. A longer list gets 400 with param set to requests.
  • priority is 0 or more. A negative priority gets 400 with param set to priority.
  • A 409, and a 429 with the code insufficient_quota, have the header x-should-retry: false. The openai SDK then raises the error without trying the request again.
  • When the API stops, it queues a running Job or Node again. The Completion that it stopped gives back its Hold. When the API starts again, a Job runs only the requests that have no result line. You pay for each request once.
  • The text of a running Node changes at most every 250 ms while you follow a Branch.

Fixed

  • A Job that you cancel while it starts again after an API restart no longer pays again for a request with a result.
  • With echo and max_tokens: 0, the Hold counts 1 generated token for each choice, because Inference generates and counts 1. Before, such a request could spend more tokens than its Hold.
  • A request with several prompts asks for at most 128 choices in all, n for each prompt. More gets 400 with param set to n, or to requests for a request in a Job.
  • If Inference answers with a status outside 100 to 599, the API answers 502 with the type server_error.
  • A model name such as toString or constructor gets 404 model_not_found, as does any model that Inference does not have.

2026-10-09

New

  • POST /v1/completions continues a prompt with a Base model. It follows OpenAI's completions API, so the openai SDK works with it.
  • A prompt is text or a list of token ids, and one request can send several prompts.
  • A field that you leave out gets OpenAI's default, not the model's own. For example, max_tokens is 16 and temperature is 1.
  • logprobs returns the log probabilities of the top tokens at each generated token. prompt_logprobs returns them for each token of the prompt.
  • With stream: true, the API sends the Completion as Server-Sent Events that end with data: [DONE].
  • GET /v1/models lists the Base models, with the context length of each one in max_model_len.
  • output_residual_stream returns the Activations at the Layers that you list, in activations.residual_stream of the same response. Its shape is [layers, tokens, hidden size].
  • apply_steering_vectors adds Steering vectors to the residual stream at the Layers that you choose.
  • A Job runs a list of Completion requests in the background, one after another. You can check on it, cancel it, and download its results as JSON Lines, one line for each request.
  • A Loom is a tree of Nodes that you grow from a prompt, one Branch at a time. The API writes the Nodes of a Branch in the background, and GET /v1/looms/{loom_id} returns every Node.
  • Every error has OpenAI's error object, with message, type, param, and code.
  • The docs site has a Quickstart, guides, and an API reference made from the API's OpenAPI spec, with a playground. /llms.txt, /llms-full.txt, and the Markdown of each page give the docs to an AI assistant.

On this page