# Authentication

> How to authenticate with the API.

**URL:** https://inference.sh/docs/api/authentication
**Last updated:** 2026-10-12

---

How to authenticate with the API.

---

## Get an API Key

1. Go to [settings → workspace → api keys](https://app.inference.sh/settings/team/keys)
2. Click **Create API Key**. Workspace owners and admins choose whether it acts as them (**personal**) or as the workspace (**workspace**, for CI and servers). See [API keys](/docs/org/api-keys).
3. Copy the key (starts with `inf_`)

Keys can be scoped to least privilege — for example `apps:execute` without `secrets:write`. See [API Keys API](/docs/api/rest/api-keys) for the scope catalog (`GET /scopes`), preset bundles, and programmatic create/list/revoke.

---

## Keep it Secret

- Don't commit to git
- Don't share publicly
- Use environment variables

```bash
export INFERENCE_API_KEY="inf_your_key"
```

> **Browser apps?** Never expose API keys in client-side code. Use a [Server Proxy](/docs/api/sdk/server-proxy) to keep credentials safe.

---

## Using the Key

### With SDKs

<div data-type="codegroup">

```python
from inferencesh import inference

# Direct
client = inference(api_key="inf_your_key")

# From the INFERENCE_API_KEY environment variable
client = inference()
```

```typescript
import { inference } from '@inferencesh/sdk';

// Direct
const client = inference({ apiKey: 'inf_your_key' });

// From environment variable
const client = inference({ apiKey: process.env.INFERENCE_API_KEY });
```

```swift
import Foundation
import InferenceSDK

// Direct
let client = InferenceClient(apiKey: "inf_your_key")

// From environment variable
let client = InferenceClient(apiKey: ProcessInfo.processInfo.environment["INFERENCE_API_KEY"] ?? "")
```

</div>

The Python SDK reads `INFERENCE_API_KEY` when `api_key` is omitted (**inferencesh ≥ v0.22.0**); an explicit `api_key` wins. With neither, creating the client raises `ValueError`. The JavaScript and Swift clients take the key only as an argument.

### With the CLI

```bash
belt login                                 # browser approval, stores a session
export INFERENCE_API_KEY="inf_your_key"    # or: an API key for CI and headless machines
belt app run infsh/echo --input '{"message": "Hello!"}'
```

The CLI reads the same `INFERENCE_API_KEY` as the SDKs and curl. `INFSH_API_KEY` still works but is deprecated: belt prints a warning, and `INFERENCE_API_KEY` wins when both are set. See [CLI setup: login](/docs/extend/cli-setup#login).

### With REST API

```bash
curl https://api.inference.sh/apps/run \
  -H "Authorization: Bearer inf_your_key" \
  -H "Content-Type: application/json" \
  -d '{"app": "infsh/echo", "input": {"message": "Hello!"}}'
```

See [REST overview — response format](/docs/api/rest/overview#response-format) for response and error formats.

---

## Workspace context

Most API resources are scoped to a workspace (a team in the API). Which workspace a request uses depends on how the API key was created:

| Key source | Workspace context |
|------------|--------------|
| **[settings → workspace → api keys](https://app.inference.sh/settings/team/keys)** (manually created) | Fixed to the workspace selected when the key was created. A [personal key](/docs/org/api-keys) acts as you there and stops working if you leave; a [workspace key](/docs/org/api-keys) acts as the workspace and is the one to use for CI and automation. |
| **[Device authorization](/docs/api/rest/device-auth)** (`belt login`, CLI) | Acts as you. Can switch workspaces with `X-Team-ID` or [`belt auth switch`](/docs/extend/cli-setup#switch-workspaces). |
| **[`belt auth token`](/docs/extend/cli-setup#temporary-token-for-scripts)** (temporary key for scripts) | Fixed to the workspace active when it was minted, like a manually created key. Expires in 15 minutes by default, 24 hours at most. See [temporary keys](/docs/api/rest/api-keys#temporary-keys). |

To act as another workspace you belong to with a **device-auth** key, send its ID:

```bash
curl https://api.inference.sh/apps/my \
  -H "Authorization: Bearer inf_your_key" \
  -H "X-Team-ID: team_abc123"
```

You must be a member of that workspace. If the header names a workspace you cannot access, it is ignored and the key's current workspace is used.

**Manually created keys and temporary keys ignore `X-Team-ID`.** A leaked CI or automation key cannot be pointed at your other workspaces via a header. Create separate keys per workspace, or use device authorization for interactive tooling that needs to switch workspaces.

In the CLI, use [`belt auth switch`](/docs/extend/cli-setup#switch-workspaces) instead of setting the header manually.

---

## Sign-in APIs

API keys are for programmatic access. To sign users into the web app (browser session) or CLI, use the dedicated auth endpoints:

| Flow | Use case | Guide |
|------|----------|-------|
| **Magic link + login code** | Email passwordless sign-in for the web app | [Magic link API](/docs/api/rest/magic-link) |
| **Device authorization** | CLI, IDE, and headless login (`belt login`) | [Device authorization API](/docs/api/rest/device-auth) |
| **Standard OAuth device grant** | Off-the-shelf OAuth 2.0 client libraries | [Device authorization — RFC endpoints](/docs/api/rest/device-auth#standard-oauth-endpoints-rfc-8628) |

Magic link sign-in returns a session cookie. Device authorization returns a **session token** (recommended; `token_kind: "session"`) or a legacy **API key** on the **first successful poll** after browser approval (one-shot delivery). Session tokens are sent as `Authorization: Bearer …` and behave like web sessions, including switching workspaces with `X-Team-ID` and revocation from [settings → personal → sessions](https://app.inference.sh/settings/sessions) or via `POST /oauth/revoke` ([RFC 7009](/docs/api/rest/device-auth#revoke-a-credential-rfc-7009)).

Sign-in sessions (browser cookie or CLI bearer session token) share a **sliding idle window**: when less than half of the current lifetime remains, each authenticated API request extends expiry to seven days from now without rotating the token. Sessions approved with a custom `expires_in` shorter than seven days keep a fixed expiry and are not extended. See [Device authorization — Sliding lifetime](/docs/api/rest/device-auth#sliding-lifetime).

New signups cannot use disposable or temporary email domains. The rule applies to magic link, OAuth, and SAML sign-up. See [REST overview: Signup email restrictions](/docs/api/rest/overview#signup-email-restrictions).

---

## Authenticator app (TOTP)

Any signed-in user can enroll an authenticator app (Google Authenticator, 1Password, and similar) as a second factor. Admin accounts require 2FA at every sign-in: a code from the app when TOTP is enrolled (`otp_method: "totp"`), an emailed code otherwise. Non-admin accounts require 2FA at sign-in only after they enroll TOTP.

All four routes require an authenticated request.

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/auth/totp/status` | GET | `{ "enabled": true \| false }` for the current user |
| `/auth/totp/enroll` | POST | Generate a new secret. Returns `{ "secret": "…", "url": "otpauth://…" }` for QR scanning. TOTP is not active until confirmed. |
| `/auth/totp/confirm` | POST | Body: `{ "code": "123456" }`. Verifies a code from the app and activates TOTP. |
| `/auth/totp/disable` | POST | Body: `{ "code": "123456" }`. Removes TOTP after verifying a current code. |

```bash
# 1. Enroll
curl -X POST https://api.inference.sh/auth/totp/enroll -b cookies.txt

# 2. Scan the otpauth:// URL or enter the secret in your authenticator app

# 3. Confirm with a 6-digit code from the app
curl -X POST https://api.inference.sh/auth/totp/confirm \
  -H "Content-Type: application/json" \
  -b cookies.txt \
  -d '{"code": "123456"}'
```

On the next sign-in, the auth response includes `otp_method: "totp"`. Submit a code from the authenticator app to `POST /auth/otp/verify`. Resend is not available for TOTP (`totp_no_resend`). See [Magic link: Admin OTP](/docs/api/rest/magic-link#admin-otp-second-factor).

| Error code | HTTP | When |
|------------|------|------|
| `totp_already_enabled` | 409 | Enroll or confirm when TOTP is already active |
| `no_enrollment` | 400 | Confirm before calling enroll |
| `invalid_code` | 400 | Wrong TOTP code at confirm or disable |
| `totp_not_enabled` | 400 | Disable when TOTP is not active |

---

## Install SDKs

<div data-type="codegroup">

```python
pip install inferencesh

# With async support
pip install inferencesh[async]
```

```typescript
npm install @inferencesh/sdk
// or: yarn add @inferencesh/sdk
// or: pnpm add @inferencesh/sdk
```

```swift
// Package.swift
.package(url: "https://github.com/inference-sh/sdk-swift", from: "0.1.0")
// target dependency: .product(name: "InferenceSDK", package: "sdk-swift")
```

</div>

---

## Next

→ [SDK Overview](/docs/api/sdk/overview) — Running apps, streaming, files
→ [Server Proxy](/docs/api/sdk/server-proxy) — Protect API keys in browser apps
