> For the complete documentation index, see [llms.txt](https://bountyv.gitbook.io/vdocs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://bountyv.gitbook.io/vdocs/account/device-auth.md).

# Desktop app login

How the Substance desktop app exchanges your account email/password for a device-scoped API key, and how it signs that device out again. This is the pairing flow the native (Tauri) client uses instead of storing your long-lived `sk_live_...` key directly — the app authenticates once here, gets back a key of its own, and stores that key in the OS keychain for every later call.

```
POST /api/v1/auth/device-login
POST /api/v1/auth/device-logout
```

Both calls are synchronous — each returns a final result immediately.

## Authentication

* `POST /api/v1/auth/device-login` — **no** Bearer key. Authenticates with `email` + `password` in the body instead, since the device doesn't have an API key yet.
* `POST /api/v1/auth/device-logout` — requires the Bearer key returned by `device-login`. See [Authentication](/vdocs/authentication.md).

## `POST /api/v1/auth/device-login`

Verifies your Substance account `email` + `password` — the same credentials as the website — then mints a brand-new API key scoped to that device, and returns the account's entitlements and pricing so the app can configure itself immediately — no follow-up calls needed to start using the product.

Logging in again with the same `device_name` first revokes the previous key issued for that device, so re-logging in doesn't pile up abandoned keys on the account.

### Request

**Content-Type:** `application/json`

| Field         | Type   | Required | Default     | Description                                                                                                                         |
| ------------- | ------ | -------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `email`       | string | ✅        | —           | Your Substance account email. `username` is still accepted as a legacy alias                                                        |
| `password`    | string | ✅        | —           | Your Substance account password                                                                                                     |
| `device_name` | string | —        | `"Desktop"` | Label for this device; truncated to 60 characters. Re-using the same name on a later login revokes the key from that previous login |

### Response

```json
{
  "api_key": "sk_live_...",
  "key_prefix": "sk_live_ab12",
  "username": "jane@example.com",
  "balance_cents": 4250,
  "products": {
    "image": true,
    "nsfw": false,
    "video": true,
    "soul": true,
    "clip": true,
    "youtube": true
  },
  "pricing": {
    "text_to_image": 7,
    "image_edit": 7,
    "image_to_image": 7,
    "image_to_video": 25,
    "identity_create": 50,
    "identity_image": 10,
    "thumbnail": 5,
    "wan_reel": 150,
    "lyric_video": 100,
    "music_short": 30,
    "clean_clip": 20,
    "clip_split": 20,
    "clip_stitch": 20
  }
}
```

| Field           | Type    | Description                                                                                                                                                                                                                                                                                                                                |
| --------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `api_key`       | string  | Newly minted `sk_live_...` key for this device — shown once, store it in the OS keychain immediately                                                                                                                                                                                                                                       |
| `key_prefix`    | string  | Non-secret prefix of the key, safe to display in a "signed in as" UI                                                                                                                                                                                                                                                                       |
| `username`      | string  | The authenticated account — this is your email; the field keeps its original name for compatibility                                                                                                                                                                                                                                        |
| `balance_cents` | integer | Current account balance, in cents                                                                                                                                                                                                                                                                                                          |
| `products`      | object  | Which product surfaces this account can use — `image`, `nsfw`, `video`, `soul`, `clip`, `youtube` (each `true`/`false`). `nsfw` defaults to `false`; the rest default to `true`                                                                                                                                                            |
| `pricing`       | object  | Per-endpoint cost in cents at this account's cost tier, keyed by feature name (`text_to_image`, `image_edit`, `image_to_image`, `image_to_video`, `identity_create`, `identity_image`, `thumbnail`, `wan_reel`, `lyric_video`, `music_short`, `clean_clip`, `clip_split`, `clip_stitch`) — use these to render cost labels in the app's UI |

HTTP status: `200 OK`.

### Example

```bash
curl -X POST https://substance-api.com/api/v1/auth/device-login \
  -H "Content-Type: application/json" \
  -d '{"email": "jane@example.com", "password": "hunter2", "device_name": "Jane'\''s MacBook"}'
```

### Errors

| Status | When                                                                                                                                                                |
| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | `email` or `password` missing                                                                                                                                       |
| `401`  | Invalid email or password. Also returned for **V-Auth accounts**, which have no password — sign in on the website and copy your key from the billing portal instead |
| `500`  | Could not create the device session (key insert failed) — safe to retry                                                                                             |

## `POST /api/v1/auth/device-logout`

Revokes the device key presented in the `Authorization` header. Call this on sign-out, then delete the key from the OS keychain client-side — the server does not track keychain state, only the key's `revoked_at`.

### Request

No body. Just the `Authorization` header identifying the key to revoke.

### Response

```json
{ "ok": true }
```

| Field | Type    | Description              |
| ----- | ------- | ------------------------ |
| `ok`  | boolean | Always `true` on success |

HTTP status: `200 OK`. Revoking an already-revoked key still returns `{ "ok": true }` — logout is idempotent.

### Example

```bash
curl -X POST https://substance-api.com/api/v1/auth/device-logout \
  -H "Authorization: Bearer sk_live_..."
```

### Errors

| Status | When                                           |
| ------ | ---------------------------------------------- |
| `401`  | Missing, malformed, or already-revoked API key |
| `403`  | Account disabled — contact support             |
| `429`  | Rate limit exceeded for your key               |

See [Errors](/vdocs/errors.md) for the full error format.

## Pricing

Free — neither endpoint is billed. `device-login`'s `pricing` field reports what *other* endpoints cost at your account's tier; it does not charge you for logging in.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://bountyv.gitbook.io/vdocs/account/device-auth.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
