September 12, 2026
YOUR FIRST MODEL API CALL

You now know what an API is, how a request is assembled, how a response reports success or failure, and why credentials need careful handling. This lesson turns that into one real thing: a single API request you send, a JSON reply you read, and a cost entry you log.
By the end, you can send one OpenRouter chat request from your terminal, read the reply and token usage from the JSON, and record model + cost for your policy.
What you are about to touch
A model API is software talking to software over HTTPS. In this call, you will use the five pieces Class 13 has taught you:
| Piece | What it is here |
|---|---|
| Endpoint | The URL you POST to: https://openrouter.ai/api/v1/chat/completions |
| Key | Your OpenRouter key, sent as a Bearer header. It identifies the account that is allowed to make the request. |
| Request JSON | The model you want plus a messages array. What you ask for. |
| Response JSON | The model's reply plus a usage block with token counts. What you got and what it metered. |
You (curl) → POST endpoint + key + request JSON
→ OpenRouter → provider → selected model
→ response JSON (reply + usage) → you + your cost log
Nothing here changes your model policy. It proves that the policy machinery is real.
Setup first, call second
1. Create an OpenRouter account and key at openrouter.ai. Add a small credit (a few dollars is plenty) and set a low credit limit per Lesson 12.3. 2. Keep the key in .env, never in code. In your project root:
OPENROUTER_API_KEY=sk-or-v1-...
Load it for this session only (source .env or your shell's equivalent) and confirm .env is in .gitignore. If a key ever touches GitHub, rotate it — Class 14 makes this automatic habit.
3. Pick a cheap model ID from the live catalog at openrouter.ai/models. Names change, so copy one marked inexpensive with a small context need — for example a DeepSeek, Qwen, or GLM efficient route. Save it as:
MODEL_ID=<paste the exact id from the catalog>
Send one request
This asks for a two-sentence summary. Small input, tiny output, easy to price:
curl https://openrouter.ai/api/v1/chat/completions \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"model\": \"$MODEL_ID\",
\"messages\": [
{\"role\": \"user\", \"content\": \"In two sentences, what does a coding agent do that plain chat does not?\"}
],
\"max_tokens\": 200
}"
A successful reply looks like this (trimmed):
{
"id": "gen-abc123",
"model": "deepseek/deepseek-chat",
"choices": [
{
"message": {
"role": "assistant",
"content": "A coding agent can read files, run commands, and apply edits..."
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 42,
"completion_tokens": 58,
"total_tokens": 100
}
}
Read three things: choices[0].message.content (the answer), usage (what the meter saw), and model (who actually served it — confirm it matches what you asked for).
Find that model's input/output price on its OpenRouter page, multiply roughly, and append one line to your notes or MODEL-POLICY.md:
2026-09-11 — MODEL_ID=... — 42 in / 58 out — ~$0.0001 — test: agent-vs-chat summary — pass
That line is the whole Models tab in miniature: task fit, routing evidence, context discipline, cost per result.
When it fails, use the Lesson 13.3 debugging order
Work in the same order every time: status → message → docs → reduce to the smallest reproducing request.
| Status + evidence | Verdict (whose problem?) | Next safe action |
|---|---|---|
200 + choices and usage | Success — use the data | Save the reply excerpt and log the meter reading |
400 + missing-field message | Malformed request — your body needs attention | Compare model and messages spelling to the quickstart, fix one field, retry once |
401 + invalid-key message | Identity problem — service does not know who you are | Re-source .env, re-paste the key, never hardcode it; Class 14 builds on this exact split |
402 + billing message | No credit — same family logic as 4xx: fix your side | Top up a small amount, check the per-task budget from Lesson 12.3 |
404 + model-not-found message | Wrong resource — stale model ID from an old post | Re-copy the exact ID from the live models catalog |
429 + rate-limit message | Too many requests — slow down, read the limit | Wait, read the documented limit, retry once; do not hammer it |
Reply looks wrong on 200 | Brief problem, not an API problem | Narrow the prompt and confirm the model class fits the task |
If anything surprises you, do not paste secrets, customer data, or your full key into a chat window to debug it. Redact the key to sk-or-...last4, and never silently retry a spend or write action — report the status, the message, and your proposed fix, as Lesson 13.3 requires.
Practical exercise
Finish line: one sent request, one saved reply excerpt, and one cost line in your project notes.
1. Send the curl above with your cheap MODEL_ID and max_tokens: 200. 2. Save the content (2–3 sentences) and the usage block. 3. Compute rough cost from the catalog price and write the one-line log. 4. Answer in one sentence: what would Class 13 call the URL, the key header, and the JSON you sent?
Verify: re-run with max_tokens: 20. The reply should truncate (check finish_reason: length) and completion_tokens should drop. If it does, you are reading the meter correctly.
Common failure mode: estimating from memory instead of the usage block. The block is the bill — read it every time.
Check your understanding
1. What four pieces make up the API call, and where does each appear in the curl? 2. Where do token counts live in the response, and why do they matter for Lesson 12.1's formula? 3. Why does the key belong in .env and never in a commit or screenshot? 4. Which part of this call is the endpoint, the authorization header, the request body, and the response body?
ARTICLE DISCUSSION
JOIN THE
CONVERSATION.
Got a question, a take, or a better way to do this? Log in and leave a comment.
