# Start a CLI sign-in

Source: https://immiscible.fly.dev/docs/api/post-oauth-device

`POST /oauth/device`

The first step of [RFC 8628](https://www.rfc-editor.org/rfc/rfc8628). The CLI sends its client id and, for PKCE, the S256 challenge of a verifier it keeps; the answer is a device code (kept by the CLI), a user code (shown to the person) and where to enter it. The code lasts ten minutes. Form-encoded or JSON.

| Field | Description |
|---|---|
| `client_id` | `immiscible-cli` |
| `scope` | optional; any of `agents:read agents:write approvals:read status:read` (the default is all four) |
| `code_challenge`, `code_challenge_method` | optional PKCE: the base64url SHA-256 of a verifier, and `S256`. With a challenge, only a poll carrying the matching `code_verifier` gets the token. |
| `client_name` | shown on the approval page, such as "Immiscible CLI on build-7" |

Then poll [`POST /oauth/token`](https://immiscible.fly.dev/docs/api/post-oauth-token.md) every `interval` seconds with `grant_type=urn:ietf:params:oauth:grant-type:device_code`, `device_code`, `client_id` and `code_verifier`. Until the person decides, the answer is `400` with `error`: `authorization_pending`; polling faster than the interval answers `slow_down` and adds five seconds to it; a denial is `access_denied`; a code past its ten minutes is `expired_token`. The success answer is `{ access_token, token_type: "Bearer", expires_in, scope, token_id, workspace, user }`, once.

## Authentication

Public. No credential. Public routes are rate limited per address. The first step of the developer CLI's sign-in (RFC 8628 device authorization). Rate limited per address.

## Request

curl:

```bash
curl -X POST "https://immiscible.fly.dev/oauth/device" \
  -H "content-type: application/x-www-form-urlencoded" \
  -d 'client_id=immiscible-cli&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&client_name=Immiscible%20CLI%20on%20laptop'
```

Node:

```ts
const res = await fetch('https://immiscible.fly.dev/oauth/device', {
  method: 'POST',
  headers: {
    'content-type': 'application/x-www-form-urlencoded',
  },
  body: "client_id=immiscible-cli&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&client_name=Immiscible%20CLI%20on%20laptop",
});
const data = await res.json();
console.log(res.status, data);
```

Python:

```python
import requests

res = requests.post(
    "https://immiscible.fly.dev/oauth/device",
    headers={
        "content-type": "application/x-www-form-urlencoded",
    },
    data="client_id=immiscible-cli&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&client_name=Immiscible%20CLI%20on%20laptop",
)
print(res.status_code, res.json())
```

## Response

```json
{
  "device_code": "Jx3v0yQk2m4W8rT1b9Lh6Zc5sN7pD0aEfGuIoKjYlMn",
  "user_code": "BCDF-GHJK",
  "verification_uri": "https://immiscible.fly.dev/app/device",
  "verification_uri_complete": "https://immiscible.fly.dev/app/device?code=BCDF-GHJK",
  "expires_in": 600,
  "interval": 5
}
```
