# Authentication and API keys
Source: https://www.desktopaccountingapi.com/docs/get-started/authentication/

> Secret keys, the publishable key, the Daapi-End-User-Id header, test and production projects, and key rotation.

Every API call authenticates with a project secret key in the `Authorization` header. Calls that touch QuickBooks also name the end user whose company file they use.

```http
GET /v1/quickbooks-desktop/customers HTTP/1.1
Host: api.desktopaccountingapi.com
Authorization: Bearer sk_test_...
Daapi-End-User-Id: eu_01j9x4m6v4c8k2t7q0r5s3w1zb
```

## Key types

| Key | Looks like | How many | Use it for |
| --- | --- | --- | --- |
| Secret key | `sk_test_` or `sk_live_` followed by 40 letters and digits | As many as you need per project, each with a name | Every API call, from your server only |
| Publishable key | `pk_test_` or `pk_live_` followed by 24 letters and digits | Exactly one per project | The `publishableKey` field when you create a setup link |

`test` and `live` follow the project's environment: test projects issue `sk_test_` and `pk_test_` keys, production projects issue `sk_live_` and `pk_live_` keys.

The last six characters of a secret key are a checksum. The SDKs use it to reject a mistyped key before making a network call, and secret scanners use the prefix to find leaked keys in code.

### Secret keys

- Create them in the dashboard under **API keys**. Give each key a name that says where it runs, such as `billing-worker production`.
- The full key is shown once, when you create it. Afterwards the dashboard shows only its last four characters, its creator and when it was last used.
- A full-access secret key can do everything in its project: create end users, read and write every connected company file, and manage setup links. Treat it like a database password.
- A read-only secret key can call every `GET` operation and send passthrough requests that contain only queries (`...QueryRq`). Every other call returns `403 API_KEY_READ_ONLY` before anything is queued or sent to QuickBooks. Choose **Read-only** when you create the key. Use read-only keys for reporting jobs, AI agents and [MCP clients](https://www.desktopaccountingapi.com/docs/guides/mcp/) that should never change data.
- Use it only from your server. The API accepts browser requests from one origin only, the **Try it** panel of the [API reference](https://www.desktopaccountingapi.com/docs/api/reference/), and never with cookies or other credentials. Any other site, including your own app's frontend, cannot call the API from a browser, and that is deliberate. In **Try it**, prefer a test key (`sk_test_`); the page keeps it in memory only.

### The publishable key

The publishable key identifies a project but grants no access by itself. You send it in `POST /v1/auth-sessions` together with your secret key, and it must belong to the same project (otherwise `403 PUBLISHABLE_KEY_PROJECT_MISMATCH`). You can rotate it in the dashboard; it cannot be deleted.

## Selecting the end user

QuickBooks Desktop operations, everything under `/v1/quickbooks-desktop/`, need to know which company file to use. Send the end user's ID in the `Daapi-End-User-Id` header.

**TypeScript**

```ts
import { DesktopAccountingApi } from "@desktopaccountingapi/quickbooks-desktop";

const client = new DesktopAccountingApi({ apiKey: process.env.DAAPI_SECRET_KEY });

// Per call
await client.qbd.customers.list({ limit: 5 }, { endUserId: "eu_01j9..." });

// Or a client scoped to one end user
const acme = client.forEndUser("eu_01j9...");
await acme.qbd.customers.list({ limit: 5 });
```

**Python**

```python
import os
from desktopaccountingapi import DesktopAccountingApi

client = DesktopAccountingApi(api_key=os.environ["DAAPI_SECRET_KEY"])

# Per call
client.qbd.customers.list(limit=5, end_user_id="eu_01j9...")

# Or a client scoped to one end user
acme = client.for_end_user("eu_01j9...")
acme.qbd.customers.list(limit=5)
```

**C#**

```csharp
var client = new DesktopAccountingApiClient(new ClientOptions { ApiKey = Environment.GetEnvironmentVariable("DAAPI_SECRET_KEY") });

// Per call
await foreach (var c in client.Qbd.Customers.ListAsync(new CustomerListParams { Limit = 5 }, new RequestOptions { EndUserId = "eu_01j9..." })) { }

// Or a client scoped to one end user
var acme = client.ForEndUser("eu_01j9...");
```

**Java**

```java
DesktopAccountingApiClient client = new DesktopAccountingApiClient(
    ClientOptions.builder().apiKey(System.getenv("DAAPI_SECRET_KEY")).build());

// A client scoped to one end user
DesktopAccountingApiClient acme = client.forEndUser("eu_01j9...");
acme.qbd().customers().list(new CustomerListParams().limit(5));
```

**curl**

```sh
curl "https://api.desktopaccountingapi.com/v1/quickbooks-desktop/customers?limit=5" \
  -H "Authorization: Bearer $DAAPI_SECRET_KEY" \
  -H "Daapi-End-User-Id: eu_01j9..."
```

Platform operations (`/v1/end-users`, `/v1/auth-sessions`, `/v1/requests`) ignore the header. Passthrough takes the end user from its path instead: `/v1/end-users/{id}/passthrough/quickbooks_desktop`.

If the header is missing on a QuickBooks Desktop operation, the API returns `400 END_USER_ID_MISSING`. If the ID is unknown, or belongs to another project, it returns `404 RESOURCE_MISSING`. The API never reveals whether an end user exists in someone else's project.

## Test and production projects

| | Test project | Production project |
| --- | --- | --- |
| Keys | `sk_test_`, `pk_test_` | `sk_live_`, `pk_live_` |
| Price | Free | Billed per active company file |
| Meant for | Sample company files, QuickBooks trial installations, your own test data | Your customers' real books |
| Setup link redirect | `https` URLs and `http://localhost` | `https` URLs only |

A new organization gets a test project called **Development**. When you are ready for real customers, create a production project in the dashboard under **Projects**, create a `sk_live_` key there and deploy it. Your code does not change; the key decides which project, and therefore which end users, a call reaches.

> **Note:**
> Keep test and production apart. A `sk_test_` key cannot reach production end users, and the reverse is also true. Store the two keys in different environment variables or secret stores.

## Rotating a key

1. Create a new secret key in the dashboard.
2. Deploy it to every service that uses the old key.
3. Check **Last used** on the old key. When it stops changing, click **Revoke**.

Revocation takes effect within 30 seconds. Calls with a revoked key return `401 API_KEY_INVALID`.

If a key leaks, revoke it first and rotate afterwards. Revoking does not affect your end users' connections; their Web Connector credentials are separate.

## Authentication errors

| Code | HTTP | Meaning |
| --- | --- | --- |
| [`API_KEY_MISSING`](https://www.desktopaccountingapi.com/docs/errors/#api_key_missing) | 401 | No `Authorization: Bearer` header |
| [`API_KEY_INVALID`](https://www.desktopaccountingapi.com/docs/errors/#api_key_invalid) | 401 | Unknown, malformed or revoked key. The message shows only the key's last four characters |
| [`PUBLISHABLE_KEY_INVALID`](https://www.desktopaccountingapi.com/docs/errors/#publishable_key_invalid) | 401 | Unknown publishable key in an auth session request |
| [`PUBLISHABLE_KEY_PROJECT_MISMATCH`](https://www.desktopaccountingapi.com/docs/errors/#publishable_key_project_mismatch) | 403 | The publishable key belongs to a different project than the secret key |
| [`END_USER_ID_MISSING`](https://www.desktopaccountingapi.com/docs/errors/#end_user_id_missing) | 400 | A QuickBooks Desktop operation without `Daapi-End-User-Id` |

Your end users never see these details: an authentication problem reaches them as a neutral "contact the application provider" message in `userFacingMessage`.
