# Tokard: instructions for AI coding assistants

You are reading this because a person asked you to add AI to their app, script or website using **Tokard**.
Tokard is an Australian AI gateway. It speaks the **OpenAI API format**, so any OpenAI-compatible library works. You only change the base URL, the API key and the model name.

Follow this document exactly. Where it says MUST, do not improvise.

## Quick start (the essentials in ten lines)

1. Base URL: `https://gateway.tokard.com.au/v1`. It is OpenAI-compatible.
2. Header: `Authorization: Bearer <key>`. The key comes from the environment variable `TOKARD_API_KEY`. Never put it in code or commit it.
3. Model: `tokard-auto`. Do not set `temperature`. Do **not** set `max_tokens` at all.
4. Endpoint: `POST /v1/chat/completions` with `{"model":"tokard-auto","messages":[...]}`. The answer is `choices[0].message.content`.
5. Call it from a server or script, never from browser or mobile code.
6. Errors come back as `{"error":{"message":"...","type":"...","code":"..."}}`. 401 = bad key, 403 = model not allowed, 400 "Blocked" = safety rule, 429 = limit.
7. Plain Python/Node examples with no extra libraries are below.
8. Run one real call and report the result in plain language before saying you are done.

If you could only read a summary of this page, ask for the full text: the details below matter.

## Facts you need

| | |
|---|---|
| Base URL | `https://gateway.tokard.com.au/v1` |
| Authentication | `Authorization: Bearer <TOKARD_API_KEY>` (keys start with `sk-`) |
| Model to use | `tokard-auto` |
| Library | the official **OpenAI** SDK for Python or JavaScript (or plain HTTP) |
| Where the key lives | an environment variable named `TOKARD_API_KEY` |
| Support | support@tokard.com.au |

`tokard-auto` is one model name that automatically chooses the right underlying model for each request (quick questions get a fast model, hard reasoning or code gets a stronger one). It also searches the web for every answer, so it knows about current events and can cite sources (sources appear as links in the answer text and in `choices[0].message.annotations`). You do nothing to switch this on; it can add a few seconds to a reply, and it is included in the cost. You do **not** choose between models and you do **not** need to set `temperature` or other tuning options.

## Rules you MUST follow

1. **Never write the API key into code, a README, a commit, a chat message or a screenshot.** Read it from the environment variable `TOKARD_API_KEY`.
   - If the person pastes their key to you, put it in a `.env` file (create one if needed), make sure `.env` is listed in `.gitignore`, and then refer to it only as `TOKARD_API_KEY`.
   - If you are not sure they have a key, tell them how to get one (see "Getting a key" below) instead of guessing.
2. **Never call Tokard from browser code, a mobile app or any client the public can inspect.** The key would be exposed. Call it from a backend, server route, serverless function or script, and let the front end call *that*.
3. **Use the model name `tokard-auto`** unless the person tells you they were given a different one. If you get a 403 "model not available", list the models the key can use (below) and use one of those.
4. **Do not set `max_tokens`.** Leave it out. The underlying models "think" before answering, and a limit can use up the budget before any text appears, returning an empty answer. If you truly must cap the length, use `max_completion_tokens` and make it at least 2000.
5. **Do not retry blocked requests.** A 400 saying "Blocked" means a safety rule stopped the request (for example it contained a Medicare number, tax file number, card number or password). Show the person a plain explanation, remove the sensitive data and do not try to get around the rule.
6. **Test with one real call** before you say you are finished, and tell the person in plain language what you saw. Do not claim it works without running it.

## Getting a key (tell the person this if they have no key)

1. Go to **https://tokard.com.au/get-started/** and create an account (or ask your Tokard contact to invite you).
2. Sign in at **https://gateway.tokard.com.au/customerlogin**.
3. Open **Virtual Keys**, click **Create New Key**, give it a name, and **copy the key immediately** (it is shown once).
4. Save it as the environment variable `TOKARD_API_KEY` (or in a `.env` file that is not committed).

## Minimal working examples

### curl (use this to test)

```bash
curl https://gateway.tokard.com.au/v1/chat/completions \
  -H "Authorization: Bearer $TOKARD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "tokard-auto",
    "messages": [{"role": "user", "content": "Say hello in five words."}]
  }'
```

A good response is HTTP 200 with the answer in `choices[0].message.content`.

### Python

```python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://gateway.tokard.com.au/v1",
    api_key=os.environ["TOKARD_API_KEY"],
)

reply = client.chat.completions.create(
    model="tokard-auto",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "What is the capital of New South Wales?"},
    ],
)
print(reply.choices[0].message.content)
```

### JavaScript / TypeScript (Node, server side only)

```javascript
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://gateway.tokard.com.au/v1",
  apiKey: process.env.TOKARD_API_KEY,
});

const reply = await client.chat.completions.create({
  model: "tokard-auto",
  messages: [{ role: "user", content: "What is the capital of New South Wales?" }],
});
console.log(reply.choices[0].message.content);
```

### Plain HTTP (no library)

`POST https://gateway.tokard.com.au/v1/chat/completions` with the headers `Authorization: Bearer <key>` and `Content-Type: application/json` and a JSON body `{"model": "tokard-auto", "messages": [...]}`. The response has the same shape as the OpenAI API.

### Loading the key from a `.env` file

Putting the key in `.env` is not enough on its own: your program must load that file.
- Python: `pip install python-dotenv`, then `from dotenv import load_dotenv; load_dotenv()` before reading `os.environ["TOKARD_API_KEY"]`. Or export the variable in the shell that runs the program.
- Node 20+: run `node --env-file=.env app.js`. Older Node: `npm install dotenv` and `require("dotenv").config()`.
- Never print the key to check it. Print only whether it is set (for example `bool(os.environ.get("TOKARD_API_KEY"))`).

### Plain Python with no extra libraries

If the `openai` package is not installed and you do not want to install it, this works with only the standard library. Always send a `User-Agent` header, because some Python defaults are rejected by network filters.

```python
import json, os, urllib.request

def ask(question):
    request = urllib.request.Request(
        "https://gateway.tokard.com.au/v1/chat/completions",
        data=json.dumps({
            "model": "tokard-auto",
            "messages": [{"role": "user", "content": question}],
        }).encode(),
        headers={
            "Authorization": "Bearer " + os.environ["TOKARD_API_KEY"],
            "Content-Type": "application/json",
            "User-Agent": "my-app/1.0",
        },
    )
    with urllib.request.urlopen(request, timeout=120) as response:
        return json.load(response)["choices"][0]["message"]["content"]
```

### Plain JavaScript with no libraries (Node 18+)

```javascript
async function ask(question) {
  const response = await fetch("https://gateway.tokard.com.au/v1/chat/completions", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.TOKARD_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ model: "tokard-auto", messages: [{ role: "user", content: question }] }),
  });
  if (!response.ok) throw new Error(`Tokard error ${response.status}: ${await response.text()}`);
  return (await response.json()).choices[0].message.content;
}
```

### Streaming (show text as it arrives)

Add `"stream": true` (Python: `stream=True`). The reply is a stream of server-sent events in the standard OpenAI format, ending with `data: [DONE]`.

```python
stream = client.chat.completions.create(
    model="tokard-auto",
    messages=[{"role": "user", "content": "Write a short poem about the ocean."}],
    stream=True,
)
for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)
```

### Images in the question (vision)

```python
reply = client.chat.completions.create(
    model="tokard-auto",
    messages=[{
        "role": "user",
        "content": [
            {"type": "text", "text": "What is in this picture?"},
            {"type": "image_url", "image_url": {"url": "https://example.com/photo.jpg"}},
        ],
    }],
)
```

You can also send a `data:image/png;base64,...` URL.

### Conversation memory

The API is stateless. To continue a conversation, send the **whole** conversation each time: every earlier `user` and `assistant` message, in order, followed by the new `user` message. Keep it short enough to be sensible; trim old messages if it gets very long.

## What else is available

These work on the same base URL with the same key, **if your account has been given access** (each key lists what it can use):

| Need | Endpoint | Model |
|---|---|---|
| Chat (the normal case) | `POST /v1/chat/completions` | `tokard-auto` |
| Newer "responses" format | `POST /v1/responses` | `tokard-auto` |
| Anthropic-style messages | `POST /v1/messages` | `tokard-auto` |
| Document search / embeddings | `POST /v1/embeddings` | `tokard-embed` |
| Create an image | `POST /v1/images/generations` | `tokard-image` |
| Text to speech | `POST /v1/audio/speech` | `tokard-speech` |
| Speech to text | `POST /v1/audio/transcriptions` | `tokard-transcribe` |

If a call returns 403 "not available for this API key", that model has not been enabled for the account. Tell the person to ask their Tokard contact; do not switch to a different provider.

### See which models a key can use

```bash
curl https://gateway.tokard.com.au/v1/models -H "Authorization: Bearer $TOKARD_API_KEY"
```

## What the person's data goes through

Every request passes through Tokard's guardrails before it reaches an AI model, and the answer is checked on the way back:

- Australian and New Zealand personal identifiers (Medicare, tax file number, ABN, driver licence, passport, NZ IRD and NHI, bank accounts), dates of birth, card numbers, passwords and API keys are detected.
- Depending on the account's policy they are **only logged**, **masked** (the AI sees a placeholder and your answer shows the original value, though strict accounts may show placeholders such as `[AU_MEDICARE_1]`), or the request is **blocked** with a 400 error. Most accounts mask rather than block, so ordinary customer emails are normally fine. Blocking only happens on strict accounts or for things like private keys.
- Speech-to-text: the audio itself reaches the AI provider; the returned text is checked.
- Tokard itself is hosted in Australia. The AI model that answers runs at the AI provider (OpenAI by default), which may be outside Australia.
- Do not promise the person that data "never leaves Australia".

## Errors and what to do

Every error uses the same JSON shape, so you can show `error.message` to the person:

```json
{"error": {"message": "Authentication Error: invalid API key.", "type": "token_not_found_in_db", "param": "key", "code": "401"}}
```

| Status | Example message | What it means / what you do |
|---|---|---|
| 401 | `Authentication Error: invalid API key.` | The key is missing, mistyped or revoked. Check `TOKARD_API_KEY` is set and has no spaces or quotes. Ask the person to create a new key. |
| 403 | `The requested model 'x' is not available for this API key...` | Wrong model name or not enabled. Use `tokard-auto`, or call `/v1/models`. |
| 400 | `Blocked entity detected: ...` | A safety rule stopped the request. Explain it, remove the sensitive data, do not retry as is. |
| 400 | other | Your request body is malformed (bad JSON, missing `messages`, bad image URL). Fix and retry once. |
| 429 | rate limit or budget exceeded | Too many requests, or the account has reached its spending limit. Wait and retry with backoff for rate limits; for a budget limit, tell the person to contact their Tokard admin. |
| 5xx | server error | Temporary. Retry up to 3 times with increasing delay (1s, 2s, 4s), then show a friendly error. |

Always show the person a human-readable message, never a raw stack trace.

## Cost and usage

Each response includes headers you can log (never show to end customers unless asked):

- `x-tokard-response-cost` (US dollars) and `x-tokard-response-cost-aud` (Australian dollars) for that request
- `x-tokard-key-spend` and `x-tokard-key-max-budget` for the key's running total and limit
- `x-ratelimit-remaining-requests` and `x-ratelimit-remaining-tokens`

The standard OpenAI `usage` object is also in every non-streaming response.

## Checklist before you tell the person you are done

- [ ] The key is read from `TOKARD_API_KEY` and appears nowhere in the code or git history
- [ ] `.env` is in `.gitignore`
- [ ] The call is made from server-side code, not the browser
- [ ] Model is `tokard-auto`; no tiny `max_tokens`; no `temperature` needed
- [ ] You ran a real call and saw a 200 response
- [ ] Errors (401, 403, 400, 429, 5xx) show a friendly message
- [ ] You explained to the person, in plain language, what you built and where to put their key

## About this document

Published at https://tokard.com.au/ai.md. Human version: https://tokard.com.au/get-started/. If something here contradicts what the API actually returns, trust the API and tell the person to email support@tokard.com.au.
