For agents
Instructions for a coding agent that sets up a project to call the Albastar API, from the key to the report.
Instructions for a coding agent that adds the Albastar API to a project. Read the page in full before changing
files. The same page as markdown: https://docs.albastov.com/get-started/agents.md.
The API
| Fact | Value |
|---|---|
| Base URL | https://api.albastov.com/v1 |
| Header | Authorization: Bearer $ALBASTAR_API_KEY on every route except the docs, status and samples routes |
| Key | abk_ and random characters; one key acts as one albastov.com account |
| OpenAI-compatible routes | GET /v1/models, POST /v1/chat/completions, POST /v1/audio/speech, POST /v1/audio/transcriptions |
| Media jobs | POST /v1/media/jobs, GET /v1/media/jobs/{id}, GET /v1/media/models, POST /v1/media/uploads |
| Docs index | https://api.albastov.com/v1/docs/llms.txt |
Inspect the project
Find the server runtime, the package manager, the env file the server loads and any OpenAI or Albastar client already in use. Read only the directories the change needs.
Set f to the env file the server loads for secrets (.env when the project has none yet); every step below uses
the same f. Check whether ALBASTAR_API_KEY is set without printing the value. The check prints one word:
Present, Absent or Inaccessible.
f=.env
if [ -n "${ALBASTAR_API_KEY:-}" ]; then echo Present
elif [ ! -e "$f" ]; then echo Absent
elif [ ! -r "$f" ]; then echo Inaccessible
elif grep -q '^ALBASTAR_API_KEY=.' "$f"; then echo Present
else echo Absent
fiInaccessible is not Absent: never replace a key the check cannot read. Never print the env file or the
environment.
Get a key
- Key in the instructions: The Copy instructions for your agent button on
Quick start, pressed by a signed-in albastov.com account with
API access, makes a key named
Coding agent <YYYY-MM-DD HH:MM>with the API scopes the account may put on a key (llm,media:read,media:generate), and puts the key in the copied text. Use that key. - Key present: Keep the key the project has. With a key in the instructions as well, ask the user which key to keep.
- No key: Stop before editing project files. Ask the user for a key, or ask the user to write the key into the
env file. An albastov.com account with API access makes keys at
https://albastov.com: sign in, open the avatar menu, then Keys, and choose Create key. API access is granted by Albastar Corp. at [email protected]. A key is shown once, at creation.
Key handling: Authentication.
Store the key
- Use the env file
fthe server loads; without one, create.envin the project root and setf=.env. - If git tracks
f, stop and tell the user: the key never goes into a tracked file. - If git does not ignore
f, append its name to.gitignoreon a line of its own, then check again. - Write the line
ALBASTAR_API_KEY=abk_...intofwith a file edit, never with a shell command that echoes the key.
if git ls-files --error-unmatch "$f" >/dev/null 2>&1; then
echo Tracked
elif ! git check-ignore -q "$f"; then
printf '\n%s\n' "$f" >> .gitignore
git check-ignore -q "$f" || echo "Not ignored"
fiTracked or Not ignored means stop. The leading newline keeps the name off the last line of a .gitignore
without a final newline. The key stays on the server: never in client code, an env name the build exposes to the
browser (NEXT_PUBLIC_ or VITE_ names), a URL, a log, a commit or a copied example. A browser app calls the
API through a server route of the project.
Check the key
GET /v1/me returns the key's account, scopes, call limits and the calls left. Make this one call to check the key.
The route takes an account key (abk_) only and counts as one call against the key's limits. Read the key from f
with sed, as below. Never source the env file: . ./.env runs each line as shell code and exports every secret
in the file.
export ALBASTAR_API_KEY="$(sed -n 's/^ALBASTAR_API_KEY=//p' "$f")"
curl -s https://api.albastov.com/v1/me \
-H "Authorization: Bearer $ALBASTAR_API_KEY"{
"ok": true,
"account": {"label": "Ada", "address": "[email protected]"},
"scopes": ["llm", "media:read", "media:generate"],
"limits": {"perMinute": 60, "perDay": 5000},
"usage": {"minuteRemaining": 59, "dayRemaining": 4999}
}| Field | Value |
|---|---|
account.label | The account's name |
account.address | The account's address |
scopes | The key's scopes the account may use now; a media: scope is left out while the account has no media access |
limits.perMinute, limits.perDay | The key's calls per minute and per rolling 24 hours |
usage.minuteRemaining, usage.dayRemaining | The calls left in each window after this call |
| Status | Code | Next step |
|---|---|---|
| 200 | none | Report scopes to the user |
| 401 | invalid_key | Missing, unknown, revoked or not an abk_ key: stop and ask the user for a key |
| 403 | account_cannot | The account is not active: stop and tell the user |
| 429 | rate_limited | The key is over its calls per minute or per day: see Retries |
Scopes
| Scope | Opens |
|---|---|
llm | GET /v1/models, POST /v1/chat/completions |
media:read | GET /v1/media/models, GET /v1/media/jobs/{id}, GET /v1/media/voices and the other voice GET routes |
media:generate | POST /v1/audio/speech, POST /v1/audio/transcriptions, POST /v1/media/jobs, POST /v1/media/uploads, voice writes |
media:generate does not include media:read: code that creates a job and reads the job needs both. A route
outside the key's scopes fails with 403. The key in the copied instructions carries API scopes only. A key made
under Keys on albastov.com may carry scopes for albastov.com apps; the docs document no routes for those scopes,
so leave them unused.
Docs
- The index lists every page as markdown:
https://api.albastov.com/v1/docs/llms.txt. - Each page as markdown:
https://api.albastov.com/v1/docs/<section>/<page>.md; rendered athttps://docs.albastov.com/<section>/<page>. - Fetch the page of a route before writing code for the route. Take model ids, parameters and limits from the API and the pages, never from memory.
- A request with the default
Python-urllibUser-Agent fails with403and the plain-text bodyerror code: 1010; send aUser-Agentheader.
Chat completions
- Keep the project's OpenAI SDK and point the SDK at the Albastar API:
base_url(Python) orbaseURL(JavaScript)https://api.albastov.com/v1, the key fromALBASTAR_API_KEY. Do not add a second SDK. - Send
modelon every request, with an id fromGET /v1/modelsor Chat models. "stream": truestreams the reply as server-sent events.- Thinking is on by default.
"chat_template_kwargs": {"enable_thinking": false}turns thinking off; the Python SDK sends the field throughextra_body(Thinking). - One chat model is loaded at a time. A request for a model whose
stateinGET /v1/modelsis notloadedloads that model and waits up to 80 s, then returns503 model_loadingwithRetry-After(Model loading).
GET /v1/models checks an llm key without running a model:
Python
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.albastov.com/v1",
api_key=os.environ["ALBASTAR_API_KEY"],
)
print([m.id for m in client.models.list().data])JavaScript
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.albastov.com/v1",
apiKey: process.env.ALBASTAR_API_KEY,
});
const models = await client.models.list();
console.log(models.data.map((m) => m.id));Audio and media jobs
POST /v1/audio/speechandPOST /v1/audio/transcriptionstake OpenAI SDK calls and run as media jobs.POST /v1/media/jobscreates a speech, transcription or image job with the model's own parameters.GET /v1/media/modelslists the media models and their parameters. Send only parameters from that list.- A job's
stateisqueued,waiting_device,running,done,failed,cancelledorexpired.waiting_deviceis a queued job while the model service is offline or busy; the job keeps its place. ?wait=<s>onPOST /v1/media/jobsandGET /v1/media/jobs/{id}holds the request until the job settles, 85 s at most.202means the job has not settled: keep the jobidand readGET /v1/media/jobs/{id}?wait=30again. Do not poll in a tight loop.- Send an
Idempotency-Keyheader (oridempotency_keyin the job body) on every job creation. The same key with the same request returns the first job for 7 days. When the result of a create is unclear, send the same request with the same key again; a new key makes a second job. - On the audio routes,
503 queuedcarriesjob_id: the job continues, so readGET /v1/media/jobs/{id}instead of sending the request again. - A
donejob lists its files inresults[], each withurl,content_typeandbytes;urlfetches the file without a key. POST /v1/media/uploadsstores an audio or image file of 20 MB at most and returns an idup_…; pass that id where a parameter takes a file.
Read Media jobs before writing job code, and Uploads before uploading.
Retries
POST /v1/chat/completions,GET /v1/models,POST /v1/audio/speechandPOST /v1/audio/transcriptionsreturn errors as{"error": {"code", "type", "message"}}; every other route as{"ok": false, "error", "code", "message"}. Branch on the HTTP status andcode, never onmessage.- Retry only as the Retries table says. A
400,401,403,404,409,413,415or422outside the codes the table lists is not retried. - On a
500, retry once, then report theX-Request-Idresponse header. - When calls fail with
502or503, readGET /v1/status(no key) before retrying.
Rules
- Ask the user before any chat completion, speech, transcription, image job, upload or voice write: each one runs
a model and counts against the account's daily limits
(Rate limits).
GET /v1/me,GET /v1/models,GET /v1/media/models,GET /v1/statusand the docs routes need no confirmation. - On a
401or403, stop and ask the user. Do not try another key, route or account. - Never print, log or commit the key. A key that was printed, logged or committed is leaked: tell the user to revoke the key under Keys on albastov.com and create a new one.
- Do not disable TLS checks. Do not work around a denied tool call with another command; give the command to the user.
Report
End with a short report to the user:
- The files changed.
- The key state: from the instructions, already present, absent and requested, or inaccessible. Never the value.
- The
GET /v1/meresult: the account label,scopesandlimits, or the status andcode. - Every other API call made, with its status and
X-Request-Id, or that none was made. - The one action left for the user, if any.