Skip to content

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

FactValue
Base URLhttps://api.albastov.com/v1
HeaderAuthorization: Bearer $ALBASTAR_API_KEY on every route except the docs, status and samples routes
Keyabk_ and random characters; one key acts as one albastov.com account
OpenAI-compatible routesGET /v1/models, POST /v1/chat/completions, POST /v1/audio/speech, POST /v1/audio/transcriptions
Media jobsPOST /v1/media/jobs, GET /v1/media/jobs/{id}, GET /v1/media/models, POST /v1/media/uploads
Docs indexhttps://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.

Shell
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
fi

Inaccessible 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

  1. Use the env file f the server loads; without one, create .env in the project root and set f=.env.
  2. If git tracks f, stop and tell the user: the key never goes into a tracked file.
  3. If git does not ignore f, append its name to .gitignore on a line of its own, then check again.
  4. Write the line ALBASTAR_API_KEY=abk_... into f with a file edit, never with a shell command that echoes the key.
Shell
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"
fi

Tracked 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.

Shell
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"
Response
{
  "ok": true,
  "account": {"label": "Ada", "address": "[email protected]"},
  "scopes": ["llm", "media:read", "media:generate"],
  "limits": {"perMinute": 60, "perDay": 5000},
  "usage": {"minuteRemaining": 59, "dayRemaining": 4999}
}
FieldValue
account.labelThe account's name
account.addressThe account's address
scopesThe key's scopes the account may use now; a media: scope is left out while the account has no media access
limits.perMinute, limits.perDayThe key's calls per minute and per rolling 24 hours
usage.minuteRemaining, usage.dayRemainingThe calls left in each window after this call
StatusCodeNext step
200noneReport scopes to the user
401invalid_keyMissing, unknown, revoked or not an abk_ key: stop and ask the user for a key
403account_cannotThe account is not active: stop and tell the user
429rate_limitedThe key is over its calls per minute or per day: see Retries

Scopes

ScopeOpens
llmGET /v1/models, POST /v1/chat/completions
media:readGET /v1/media/models, GET /v1/media/jobs/{id}, GET /v1/media/voices and the other voice GET routes
media:generatePOST /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 at https://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-urllib User-Agent fails with 403 and the plain-text body error code: 1010; send a User-Agent header.

Chat completions

  • Keep the project's OpenAI SDK and point the SDK at the Albastar API: base_url (Python) or baseURL (JavaScript) https://api.albastov.com/v1, the key from ALBASTAR_API_KEY. Do not add a second SDK.
  • Send model on every request, with an id from GET /v1/models or Chat models.
  • "stream": true streams 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 through extra_body (Thinking).
  • One chat model is loaded at a time. A request for a model whose state in GET /v1/models is not loaded loads that model and waits up to 80 s, then returns 503 model_loading with Retry-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/speech and POST /v1/audio/transcriptions take OpenAI SDK calls and run as media jobs. POST /v1/media/jobs creates a speech, transcription or image job with the model's own parameters.
  • GET /v1/media/models lists the media models and their parameters. Send only parameters from that list.
  • A job's state is queued, waiting_device, running, done, failed, cancelled or expired. waiting_device is a queued job while the model service is offline or busy; the job keeps its place.
  • ?wait=<s> on POST /v1/media/jobs and GET /v1/media/jobs/{id} holds the request until the job settles, 85 s at most. 202 means the job has not settled: keep the job id and read GET /v1/media/jobs/{id}?wait=30 again. Do not poll in a tight loop.
  • Send an Idempotency-Key header (or idempotency_key in 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 queued carries job_id: the job continues, so read GET /v1/media/jobs/{id} instead of sending the request again.
  • A done job lists its files in results[], each with url, content_type and bytes; url fetches the file without a key.
  • POST /v1/media/uploads stores an audio or image file of 20 MB at most and returns an id up_…; 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/speech and POST /v1/audio/transcriptions return errors as {"error": {"code", "type", "message"}}; every other route as {"ok": false, "error", "code", "message"}. Branch on the HTTP status and code, never on message.
  • Retry only as the Retries table says. A 400, 401, 403, 404, 409, 413, 415 or 422 outside the codes the table lists is not retried.
  • On a 500, retry once, then report the X-Request-Id response header.
  • When calls fail with 502 or 503, read GET /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/status and the docs routes need no confirmation.
  • On a 401 or 403, 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/me result: the account label, scopes and limits, or the status and code.
  • 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.