Sign in from a script #
Prompt:
Give this script a token for one of Turn Zero's own products without pasting one into the chat.
Also works:
- "Sign the cron job in to the product from a terminal."
- "I have no MCP client on this machine. How does a script get a token?"
- "Print me a code I can approve in the browser."
Some of Turn Zero's own products sign you in with a product token. An MCP client gets one by opening the sign-in in your browser. A script with no MCP client gets one with the device grant instead. The script registers a public client and prints a code and a link, opening the link where its computer has a browser. You allow it, and the script's next poll prints the token. Nothing listens on your computer, and the token reaches no chat.
What your tool does #
- Registers a public client at
/oauth/registerwith a name you will recognise on the approval page (step 1). - Requests the codes at
/oauth/device_authorization, naming the product's endpoint as the resource (step 2). - Prints the code and the link, opens the link in a browser where its computer has one, and asks you to sign in and allow the request (step 3).
- Polls
/oauth/tokenevery five seconds under the device-code grant until the answer carries the token (step 4). - Writes the token into the script's secret store, never into a file it commits or into the chat.
- Asks you for the product's endpoint address where the prompt does not name the product.
What you need #
- A Turn Zero account that has the product. The approval page refuses an account without it.
- The product's MCP endpoint address, which its own documentation gives. That address is the
resourceevery request names. - A browser on any computer, for the one approval. It need not be the computer the script runs on.
Before your AI starts #
This section is for your AI coding tool: what it checks and gathers before it begins. You don't need to do these steps yourself.
- The platform origin the script talks to:
https://turnzero.ai, orhttps://dev.turnzero.aifor the development platform. - The product's endpoint address for
resource. The device grant serves the products whose sign-in answers a signed token; it refuses the platform's own MCP endpoint. - A place the script keeps the token, such as an environment variable set from a secret store.
Steps #
1. Register the script as a client #
Every client of the sign-in registers itself; nobody issues a client identifier by hand. The script names the device grant as its only grant_types and gives no redirect, because the device grant redirects nothing. The answer's client_id is used in every later request.
curl -s -X POST https://turnzero.ai/oauth/register -H 'content-type: application/json' -d '{"grant_types":["urn:ietf:params:oauth:grant-type:device_code"],"client_name":"My script"}'
The platform deletes a registration a day after nothing references it. A registration your token names stays while the token does, so a script that starts the sign-in again registers again.
2. Request the codes #
The request names the client, the product's endpoint as resource, and the product's scope, or no scope, which selects it. The answer carries device_code, which the script keeps and never shows, and user_code, which it prints. It also carries verification_uri, verification_uri_complete, expires_in of 600 seconds, and interval of 5.
curl -s -X POST https://turnzero.ai/oauth/device_authorization -d client_id=<client_id> -d resource=<product endpoint>
3. Open the link and allow it #
The script prints the code and the complete link. Where its computer has a browser, it also opens the link there: open on macOS, start on Windows, or xdg-open on Linux. Otherwise open the link in a browser on any computer, or open https://turnzero.ai/oauth/device and type the code there.
Sign in to Turn Zero as you do on the website. The page then shows the code and the script's registered name, and asks whether to allow it. Check the code against the one the script printed, then press Allow. The request lasts ten minutes. If the page refuses the account you signed in with, press Cancel, and the script stops at its next poll.
4. Poll for the token #
Once a request has been made, the script asks the token endpoint whether it was allowed. It polls at most once every five seconds, with the same client_id.
curl -s -X POST https://turnzero.ai/oauth/token -d grant_type=urn:ietf:params:oauth:grant-type:device_code -d client_id=<client_id> -d device_code=<device_code>
Until you allow it, the answer is {"error":"authorization_pending"}, status 400. A poll inside the interval gets slow_down, and a poll after the ten minutes expired_token, each at 400. A request you cancelled answers access_denied at 400, including one cancelled on the page that refused your account. Left alone, a refused request answers authorization_pending until its ten minutes run out. Once you allow it, the next poll gets the token once, in the answer access_token, with expires_at, resource, and token_id. A second poll with the same code is refused, so the script keeps the value from the one answer.
5. Use the token #
Send the token as the bearer on the product's own endpoint, where its documentation says. On this platform the token reaches list_tokens, the public reads, and the few account reads its product's grant names. It lasts 180 days and is not renewed; run the script's sign-in again after. revoke_token with its token_id ends it early.
Expected result #
The script prints a code such as BCDF-GHJK and a link. After you allow the request in the browser, the page says the tool is connected, and the script's next poll prints one token, which list_tokens then lists under the script's registered name. A second poll is refused invalid_grant.
Refusals #
This table lists the refusals these steps most often meet; the refusals page lists all of the platform's refusals, with the cause and remedy of each. A backend package's own failures are listed on its package page.
| Refusal | Status | Cause | Remedy |
|---|---|---|---|
invalid_target |
400 | The resource is not a product endpoint the device grant serves, or the request named none. |
Name the product's MCP endpoint exactly, as its documentation gives it. |
invalid_scope |
400 | The request asked for a scope that is not the product's. | Leave scope out, or name the product's scope. |
invalid_client_metadata |
400 | The registration gave no redirect and did not name the device grant as its only grant_types, gave a redirect that is not an HTTPS or loopback URL, or named a token_endpoint_auth_method other than none. |
Register as step 1 shows. |
invalid_client |
401 | No registration names the client_id, for example after the daily sweep deleted it. |
Register again with step 1, then start the sign-in again. |
invalid_grant |
400 | The device code is unknown, already answered, or named another client, or the sign-in ended before the poll. | Start the sign-in again from step 2. |
The four answers the poll gives while the request stands, authorization_pending, slow_down, expired_token, and access_denied, are step 4's; the script handles each as that step says.
Related #
- Sign-in, sessions, and tokens compares every credential and says what ends a product token.
- Mint a token mints a token for a script that calls this platform's own actions.
- Connect Claude Code or Codex describes the sign-in an MCP client runs for you.
- Glossary defines the terms used on this page.