---
name: publik-api
description: Add publik API to an app as a provider option. Users get AI with no key and $0.05 free at first link; the builder earns a share. Use when asked to "add publik API" with a publik slug. Do not create, edit or run anything before the human says yes to the step 1 plan.
---

# Add publik API to this app

Read this file raw (curl), not through a summarising fetch; the shell blocks must run exactly as written.

This needs an interactive session: the human approves in the browser mid-run. In a non-interactive run (`claude -p`, `codex exec`), at each wait print what the human needs (checklist, or URL and code), stop, and tell them to re-run once they have answered.

publik API is OpenAI-compatible at `https://publikhq.com/api/v1`. An installed app provisions its own `pk_` key on first launch with a public `pat_` app token. Follow the steps in order; use only the endpoints named here.

**Rules.** Never read `.env*` files or print a secret (`pat_`, `pk_`, `pbt_`). Never commit a `pk_` key. Use tier models (`publik-fast` etc.), never a vendor model name. Keep the user's own-key path working.

## 1. Plan, then stop and wait for a yes
**Do not create, edit or run anything before the human answers. Print the checklist, then stop and wait.** (Reading files to write it is fine.)

Detect the stack (Tauri, Electron, Swift, Python, Node), where the app calls a model today, and who runs the code (table in step 2; ask if unsure). The checklist:
- get an app token for `<slug>` (one browser approval) and where it goes
- add a publik module for the stack and wire it as a provider option
- a first-run card and a 402 message in the UI
- an AGENTS.md block; one real test call

If a publik module or `publik-app-token.txt` exists, update it; never add a second.

**No yes yet? Stop here. Nothing below runs until the human says yes.**

## 2. Get the app token (device flow)
Where the token goes, by who runs the code:
| Who runs it | Token | Commit it? |
|---|---|---|
| People install and run it themselves: desktop, a local web app ("runs in the browser, locally"), a CLI | `publik-app-token.txt`: next to `Cargo.toml` (Tauri), a target resource (Swift), else the repo root (the module looks in its folder and parents) | yes |
| A server only the builder deploys | `PUBLIK_APP_TOKEN=` appended to its `.env` (`OUT=` empty) | no |

A listing is installed by its users: first row unless the builder hosts it. A `pat_` in a public repo is intended: it only mints capped keys for this app, and source installs need it.

Skip this step if `publik-app-token.txt` already exists (or the human says `PUBLIK_APP_TOKEN` is configured). Otherwise run this; it prints a URL and code:
```sh
B=https://publikhq.com/api/v1; umask 077; D=$(mktemp -d "${TMPDIR:-/tmp}/publik.XXXXXX"); echo "work dir: $D"
curl -s -X POST $B/device/code -H 'content-type: application/json' -d '{"client_name":"<your agent> in <folder>"}' > "$D/device"
tr , '\n' < "$D/device" | sed -n 's/.*"verification_uri_complete":"\([^"]*\)".*/Open \1/p; s/.*"user_code":"\([^"]*\)".*/Code: \1/p'
```
Start every later block with `D=<work dir>`. Tell the human: open the URL, check the code, click Approve. Then poll (timeout ≥ 10 minutes):
```sh
B=https://publikhq.com/api/v1; D=<work dir>; umask 077
{ printf '{"device_code":"'; sed -n 's/.*"device_code":"\([^"]*\)".*/\1/p' "$D/device" | tr -d '\n'; printf '"}'; } > "$D/poll.json"; I=5
while sleep $I; do
  R=$(curl -s -X POST $B/device/token -H 'content-type: application/json' --data-binary @"$D/poll.json")
  case "$R" in
    *access_token*) { printf 'Authorization: Bearer '; echo "$R" | sed -n 's/.*"access_token":"\([^"]*\)".*/\1/p'; } > "$D/pbt.hdr"; echo approved; break;;
    *authorization_pending*|*temporarily_unavailable*) ;;
    *slow_down*) I=$((I+5));;
    *) echo "$R" | sed -n 's/.*"error":"\([^"]*\)".*/stopped: \1/p'; break;;
  esac
done
```
`expired_token`: redo step 2. `access_denied`: stop, ask the human. Mint the token unprinted (`OUT`: token file, or empty for a server):
```sh
B=https://publikhq.com/api/v1; D=<work dir>; OUT=<path>; umask 077
R=$(curl -s -X POST $B/apps/<slug>/token -H @"$D/pbt.hdr")
case "$R" in *'"token":"pat_'*) echo "$R" | sed -n 's/.*"token":"\(pat_[^"]*\)".*/\1/p' > "$D/pat"; echo saved;; *) echo "$R";; esac
if [ -s "$D/pat" ]; then if [ -n "$OUT" ]; then cp "$D/pat" "$OUT"; else { printf '\nPUBLIK_APP_TOKEN='; cat "$D/pat"; } >> .env; fi; fi
echo "$R" | sed -n 's/.*"products":\(\[[^]]*\]\).*/products: \1/p'; rm -f "$D/device" "$D/poll.json" "$D/pbt.hdr"
```
`403 claim_first`: the human opens `claim_url`, clicks Claim, you re-run this. Wire only the `products` listed (`ai.chat`, `ai.embeddings`, `ai.transcribe`, `ai.speech`, `ai.image`, `search`), or chat if empty; `search` has its own steps: https://publikhq.com/skills/search.md. Server: confirm `.env` is git-ignored.

## 3. Add the module and wire it
Copy the code block of the stack's reference: `https://publikhq.com/sdk/publik.ts.md`, `/sdk/publik.py.md`, `/sdk/publik.swift.md`, `/sdk/publik.rs.md`. Set the slug and version. Wire this order where the app picks its client:
1. the user's own key, if set, exactly as today
2. `PUBLIK_API_KEY` / `PUBLIK_API_BASE_URL` env
3. the per-app credential file (the module's `credentialPath`, mode 0600)
4. first launch: show the module's `DISCLOSURE`; on yes call `provision()` (`POST /installs`), store the key
5. an OpenAI-compatible client at `base_url` with tier models
6. if provisioning fails or is declined: ask for the user's own key

At startup: if `PUBLIK_SELF_TEST` is set, print the module's self-test line and exit.

UI (https://publikhq.com/developers): after provisioning a card with the balance line ("$0.00 until linked", then `x-publik-balance`), `disclosure.cost` verbatim, and a button **Sign in with publik** opening `claim_url`. On `402`: `error.message` plus one link, `error.top_up_url`; own key stays usable.

## 4. AGENTS.md block
Append (or update) this block in `AGENTS.md` at the repo root:
```md
## publik API
- Base URL: https://publikhq.com/api/v1 (OpenAI Chat Completions, Responses, Anthropic Messages)
- Models: publik-fast, publik-balanced, publik-smart only; never a vendor model name
- Key: per-install `pk_` from POST /installs, stored at the publik credential path; env PUBLIK_API_KEY / PUBLIK_API_BASE_URL override
- App token: publik-app-token.txt (public by design, committed) or PUBLIK_APP_TOKEN
- 402: show error.message plus error.top_up_url only
- Never print or log a key
- Docs: https://publikhq.com/llms.txt, https://publikhq.com/developers, https://publikhq.com/skills/api.md
```

## 5. Prove it with one real call
Keys ride in files, never argv. `TOK` is `"$D/pat"`, or `publik-app-token.txt` with `D=$(mktemp -d)` if step 2 was skipped (server token only in env: skip, give the human the check command):
```sh
B=https://publikhq.com/api/v1; D=<work dir>; TOK=<token file>; umask 077
U=$(uuidgen 2>/dev/null || od -An -N16 -tx1 /dev/urandom | tr -d ' \n'); J=$(mktemp "$D/j.XXXXXX"); H=$(mktemp "$D/h.XXXXXX")
{ printf '{"app_token":"'; tr -d '\n' < "$TOK"; printf '","app_slug":"<slug>","app_version":"0.0.0-agent-test","os":"macos","install_id":"%s"}' "$U"; } > "$J"
curl -s -o "$D/inst" -w 'installs: %{http_code}\n' -X POST $B/installs -H 'content-type: application/json' --data-binary @"$J"
{ printf 'Authorization: Bearer '; sed -n 's/.*"key":"\(pk_[^"]*\)".*/\1/p' "$D/inst"; } > "$H"
if grep -q '"key":"pk_' "$D/inst"; then curl -s -o "$D/chat" -D "$J" -w 'chat: %{http_code}\n' $B/chat/completions -H @"$H" -H 'content-type: application/json' -d '{"model":"publik-fast","messages":[{"role":"user","content":"Say hello in five words."}]}'; sed -n 's/.*"content":"\([^"]*\)".*/reply: \1/p' "$D/chat"; grep -i '^x-publik-charge-micros' "$J"; fi
cat "$D/inst" "$D/chat" 2>/dev/null | sed -n 's/.*"error":{.*"message":"\([^"]*\)".*/error: \1/p'
```
Show the reply and the charge line, and give the human a `chat/completions` command to re-check (key in a header file). `402` is normal on an unlinked install: show its message and `top_up_url`, link there, rerun. `503`: wait 10 seconds, run the block once more; if it fails again, stop, report the message verbatim, and say plainly the wiring is done and only the proof call failed. Never loop. Then `rm -rf <work dir>`, and run the built-app test: https://publikhq.com/skills/setup.md step 6.
