How to create an OAuth client
API version 2026-10-affable-towhee
Before you press Create client on the Integrations page, decide three things: what the client may do, which process holds its secret, and who pays for its calls. The Authentication guide states the rules; this page applies them to two fictional companies, Luba and Farducks.
Luba: the Dive Deck
Luba runs autonomous submarines as a transport and ride-share service. Its Dive Deck shows each passenger a phrase of the dive on the cabin screen, in the language they are learning, and Luba's operations team watches how much of the allowance is left.
Luba creates one client, Luba Dive Deck, allowed to Generate vocabulary (vocab:generate) and Read usage (usage:read), billed to Your plan's allowance (allowance). Two processes share it, and each asks at the exchange for only the scope it needs. An exchange that leaves out scope gets every scope the client is allowed; one that asks for a scope the client is not allowed is refused whole with invalid_scope, never narrowed silently.
The dispatch server writes each dive's phrases. It asks for vocab:generate alone, so a token that leaks from it cannot read Luba's usage.
export LINGARA_TOKEN="$(curl -sS --fail-with-body -X POST "https://api.getlingara.com/oauth/token" \
-u "$LINGARA_CLIENT_ID:$LINGARA_CLIENT_SECRET" \
-d "grant_type=client_credentials" \
--data-urlencode "scope=vocab:generate" | jq -r '.access_token // error(.error)')"curl -N -X POST "https://api.getlingara.com/v1/vocab/stream" \
-H "Authorization: Bearer $LINGARA_TOKEN" \
-H "Content-Type: application/json" \
-d '{"level":2,"source_lang":"en","target_lang":"zh","count":8}'The operations dashboard is a second server-side process with the same client ID and secret. It asks for usage:read alone, and reads what is left of the allowance.
export LINGARA_TOKEN="$(curl -sS --fail-with-body -X POST "https://api.getlingara.com/oauth/token" \
-u "$LINGARA_CLIENT_ID:$LINGARA_CLIENT_SECRET" \
-d "grant_type=client_credentials" \
--data-urlencode "scope=usage:read" | jq -r '.access_token // error(.error)')"curl "https://api.getlingara.com/v1/usage" \
-H "Authorization: Bearer $LINGARA_TOKEN"Both processes run on Luba's servers. The tablet in the cabin asks the dispatch server for phrases and never holds the secret or a token, because anything on a device a passenger can touch can be read. The Authentication guide's Keep secrets and tokens on a server says why.
The same client as a project you can clone and run: integrations/luba-dive-deck
Farducks: Batter Rewards
Farducks is a chain of fish and chip convenience stores. While an order fries, the Batter Rewards loyalty app asks Farducks' own backend for a short lesson plan, then reads it back. The app and the tills call Farducks' backend, never Lingara, so neither holds the secret.
Farducks creates one client, Farducks Batter Rewards, allowed to Create lesson plans (lesson_plans:write) and Read lesson plans (lesson_plans:read), billed to Your plan's allowance (allowance). That is the only billing a client can be created with today, and a client's billing is fixed when it is created. The other mode, metered (Pay per use), is described under Who pays for a call in the Authentication guide.
The backend asks for lesson_plans:write alone and creates the plan. The response streams, and its started event carries the plan's plan_id.
export LINGARA_TOKEN="$(curl -sS --fail-with-body -X POST "https://api.getlingara.com/oauth/token" \
-u "$LINGARA_CLIENT_ID:$LINGARA_CLIENT_SECRET" \
-d "grant_type=client_credentials" \
--data-urlencode "scope=lesson_plans:write" | jq -r '.access_token // error(.error)')"curl -N -X POST "https://api.getlingara.com/v1/lesson-plans" \
-H "Authorization: Bearer $LINGARA_TOKEN" \
-H "Content-Type: application/json" \
-d '{"context":"Ordering food at a night market","source_lang":"en","target_lang":"zh","level":2}'Set ID to that plan_id, then ask for lesson_plans:read and read the plan back. One exchange may ask for several of the client's scopes, space-separated in scope; each command here asks for one, because each is composed from a single call.
export LINGARA_TOKEN="$(curl -sS --fail-with-body -X POST "https://api.getlingara.com/oauth/token" \
-u "$LINGARA_CLIENT_ID:$LINGARA_CLIENT_SECRET" \
-d "grant_type=client_credentials" \
--data-urlencode "scope=lesson_plans:read" | jq -r '.access_token // error(.error)')"curl "https://api.getlingara.com/v1/lesson-plans/$ID" \
-H "Authorization: Bearer $LINGARA_TOKEN"One day the secret is pasted into a support ticket. On the Integrations page, Farducks presses New secret and deploys it to the backend, waits until the old secret's Last used date stops changing, then presses Revoke on the old secret. Every access token the old secret was exchanged for is refused from that moment. Rotate a secret, in the Authentication guide, has the two-secret limit and why a client's only secret cannot be revoked.
The same client as a project you can clone and run: integrations/farducks-batter-rewards
Where to go next
The Authentication guide covers the exchange's errors, what to do when a token expires, and the rotation rule. The Versions guide covers the version a client is pinned to, and how to choose another for one call.