Authentication
One bearer key per project, shown once, revoked instantly.
Every request past the base URL carries a bearer token in the Authorization header, exactly the way an OpenAI client already sends one — there's no second header, no signature, no query parameter.
Authorization: Bearer $LARSA_API_KEYKey prefixes
Every key starts with sk-larsa-, followed by cryptographically random characters. The console never shows the whole thing again after creation — only sk-larsa-XXXXXXXX...XXXX, the first few characters and the last four, enough to tell two keys apart in a list without exposing a working credential on screen.
The fixed prefix is deliberate: a secret-scanning tool — GitHub's, your CI's, your own — can recognise sk-larsa- in a diff the moment it's committed. Give a leaked-key commit a chance to be caught by something other than an invoice.
Scoping a key to projects and models
Accounts are organisations, organisations hold projects, and a key belongs to exactly one project — never to the organisation directly. That's the first scope: a key made under *Website backend* can't be used to read usage from *Internal tools*, even inside the same account.
The second scope is the model list. Creating a key, you can name exactly which models it may call; leave the list empty and it inherits every model your plan allows. Either way, your plan is a ceiling a key cannot widen — naming a model your plan doesn't include doesn't grant it, it's simply refused.
| Field | What it tells you |
|---|---|
key_prefix | The only part of the key you'll ever see again. |
allowed_models | Empty means every model your plan allows. |
status | active or revoked. There is no paused — a key either authenticates or it doesn't. |
last_used_at | The fastest way to notice a key nothing should still be calling. |
Rotation
There is no single call that swaps a key's value in place — that would mean two different secrets were, briefly, the same credential. Rotation is three ordinary steps on API keys: create the new key, deploy it wherever the old one was configured, then revoke the old one once nothing depends on it.
If a key leaks
- Revoke it first, ask questions after. On API keys, revoking deletes the credential at the gateway before the console record updates — the key stops authenticating immediately, not on the next sync.
- Create a replacement and deploy it before you forget which services were using the old one.
- Check what it was used for in Request logs, filtered to that key, for the window between the leak and the revoke.
- If it reached a public repository, purging the file isn't enough — rewrite the history that contains it, the same as for any other committed secret.
Why the secret is shown once
The value you copy at creation is never written down in a form that could be read back — what's stored is a peppered hash, the same shape a well-built login system stores a password in. Showing it again would mean keeping the plaintext somewhere, and a database that can produce your key is a database that can leak it. The dialog says this outright when you create one: this key is shown only this once and cannot be recovered.
Reading the key from the environment
Every sample in these docs reads LARSA_API_KEY from the environment rather than writing it inline — do the same in your own code, in every language:
curl https://api.console.larsa.larsima.com/v1/models \
-H "Authorization: Bearer $LARSA_API_KEY"import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["LARSA_API_KEY"],
base_url="https://api.console.larsa.larsima.com/v1",
)
for model in client.models.list():
print(model.id)
import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.LARSA_API_KEY,
baseURL: "https://api.console.larsa.larsima.com/v1",
});
const models = await client.models.list();
for (const model of models.data) {
console.log(model.id);
}
import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.LARSA_API_KEY,
baseURL: "https://api.console.larsa.larsima.com/v1",
});
async function main(): Promise<void> {
const models = await client.models.list();
for (const model of models.data) {
console.log(model.id);
}
}
main();
package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.console.larsa.larsima.com/v1/models", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("LARSA_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}
// Cargo.toml:
// [dependencies]
// reqwest = { version = "0.12", features = ["blocking"] }
use std::env;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let api_key = env::var("LARSA_API_KEY")?;
let client = reqwest::blocking::Client::new();
let body = client
.get("https://api.console.larsa.larsima.com/v1/models")
.bearer_auth(api_key)
.send()?
.text()?;
println!("{body}");
Ok(())
}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class ListModels {
public static void main(String[] args) throws Exception {
String apiKey = System.getenv("LARSA_API_KEY");
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.console.larsa.larsima.com/v1/models"))
.header("Authorization", "Bearer " + apiKey)
.GET()
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
using System.Net.Http.Headers;
var apiKey = Environment.GetEnvironmentVariable("LARSA_API_KEY");
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
var body = await client.GetStringAsync("https://api.console.larsa.larsima.com/v1/models");
Console.WriteLine(body);
<?php
$apiKey = getenv('LARSA_API_KEY');
$ch = curl_init('https://api.console.larsa.larsima.com/v1/models');
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ["Authorization: Bearer $apiKey"],
CURLOPT_RETURNTRANSFER => true,
]);
echo curl_exec($ch), PHP_EOL;
curl_close($ch);
require "net/http"
require "uri"
api_key = ENV.fetch("LARSA_API_KEY")
uri = URI("https://api.console.larsa.larsima.com/v1/models")
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer #{api_key}"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
puts response.body