> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.empiriolabs.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.empiriolabs.ai/_mcp/server.

# Sign in with EmpirioLabs AI

Your app can ask people to connect their EmpirioLabs AI account instead of pasting an API key. They approve your app on the EmpirioLabs AI consent screen, your app receives an access token of its own for that account, and they can disconnect it at any time under **Settings > Connected applications**. Usage is billed to the account that approved it, at the same prices and limits as the API.

The [EmpirioLabs AI CLI](/cli) signs in this way, and so do apps that connect to the [MCP server](/mcp).

#### Standard OAuth 2.1

Authorization code with PKCE (S256). Discover every endpoint from the authorization server metadata.

#### No secret to ship

Desktop, mobile and command-line apps are public clients: PKCE protects the exchange, and redirects to `127.0.0.1` may use any port.

#### A token per connection

Each approval gives your app its own access token. Send it as `Authorization: Bearer`, the same way as an API key.

#### The account owner stays in control

Each connection is listed under **Settings > Connected applications**, where it can be disconnected.

## Endpoints

| Endpoint                                 | URL                                                                      |
| ---------------------------------------- | ------------------------------------------------------------------------ |
| Authorization server metadata (RFC 8414) | `https://platform.empiriolabs.ai/.well-known/oauth-authorization-server` |
| Authorization                            | `https://platform.empiriolabs.ai/dashboard/oauth/authorize`              |
| Token                                    | `https://platform.empiriolabs.ai/api/oauth/token`                        |
| Revocation (RFC 7009)                    | `https://platform.empiriolabs.ai/api/oauth/revoke`                       |
| Client registration (RFC 7591)           | `https://platform.empiriolabs.ai/api/oauth/register`                     |

Read the metadata document at startup rather than hard-coding the other URLs.

## Identify your app

Use one of these as your `client_id`:

* **A client metadata document (recommended).** Host a JSON document at an `https://` URL you control and use that URL as the `client_id`. The consent screen shows its `client_name` and the site your redirect URI returns to.

  **`https://example.com/oauth/client.json`**

  ```json title="https://example.com/oauth/client.json"
  {
    "client_id": "https://example.com/oauth/client.json",
    "client_name": "Example App",
    "client_uri": "https://example.com",
    "redirect_uris": ["https://example.com/oauth/callback", "http://127.0.0.1/callback"],
    "token_endpoint_auth_method": "none"
  }
  ```

* **Dynamic client registration.** `POST` the same fields (without `client_id`) as JSON to the registration endpoint and store the `client_id` it returns. Up to 20 redirect URIs are accepted.

Redirect URIs must use `https`, or plain `http` on `127.0.0.1`, `localhost` or `[::1]` for apps running on the user's computer. A loopback redirect may use any port, so a native app can listen on whichever port is free (RFC 8252).

## Connect an account

#### Send the user to the authorization endpoint

Create a random `code_verifier` and its S256 `code_challenge`, and a random `state`. Then open:

```text
https://platform.empiriolabs.ai/dashboard/oauth/authorize
  ?response_type=code
  &client_id=<your client_id>
  &redirect_uri=<one of your redirect URIs>
  &scope=api
  &resource=https://api.empiriolabs.ai
  &code_challenge=<code_challenge>
  &code_challenge_method=S256
  &state=<state>
```

`resource=https://api.empiriolabs.ai` and `scope=api` ask for access to the API. Without them, the request is for the MCP server instead.

#### Receive the code

After the user approves, the browser returns to your redirect URI with `code` and `state`. Check that `state` matches the one you sent. If the user declines, you receive `error=access_denied` instead.

#### Exchange the code for an access token

```bash
curl https://platform.empiriolabs.ai/api/oauth/token \
  -d grant_type=authorization_code \
  -d code=<code> \
  -d redirect_uri=<the same redirect URI> \
  -d client_id=<your client_id> \
  -d code_verifier=<code_verifier> \
  -d resource=https://api.empiriolabs.ai
```

**`Response`**

```json title="Response"
{
  "access_token": "sk-empiriolabs-...",
  "token_type": "Bearer",
  "scope": "api"
}
```

The code works once and expires after 10 minutes.

#### Call the API

```bash
curl https://api.empiriolabs.ai/v1/chat/completions \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"model": "<model-id>", "messages": [{"role": "user", "content": "Hello"}]}'
```

The access token works with every API endpoint until the user disconnects your app or you revoke it. It does not expire, and there is no refresh token. Store it as you would a password.

## Disconnect

When the user signs out of your app, revoke the access token:

```bash
curl https://platform.empiriolabs.ai/api/oauth/revoke \
  -d token=$ACCESS_TOKEN \
  -d client_id=<your client_id>
```

The endpoint answers `200` whether or not the token was still active. A disconnected or revoked token receives `401` from the API; ask the user to connect again.

## Errors

| Error             | Meaning                                                                                       |
| ----------------- | --------------------------------------------------------------------------------------------- |
| `access_denied`   | The user declined the request, or the connection cannot be created for this account           |
| `invalid_request` | A parameter is missing or malformed, for example PKCE without S256                            |
| `invalid_scope`   | The scope is not `api`                                                                        |
| `invalid_target`  | The `resource` is not `https://api.empiriolabs.ai`, or differs from the authorization request |
| `invalid_grant`   | The code is invalid, expired, already used, or the `code_verifier` does not match             |
| `invalid_client`  | The `client_id` is unknown, or its metadata document could not be read                        |
| `server_error`    | The connection could not be completed; try again                                              |

## Requirements

* Use PKCE with `S256` on every request, and check `state` on every callback.
* Request only `scope=api` with `resource=https://api.empiriolabs.ai`.
* Keep the access token out of URLs, logs and client-side code that other people can read.
* Give your app a recognizable `client_name`, and redirect to your own site, so people recognize it on the consent screen.