# Get started with the BottleSpeed API

Public guide: /docs/get-started. Navigation: [Get started](/docs/get-started) · [Customer accounts](/docs/customer-accounts-integration) · [Bottle counters](/docs/bottle-counter-integration).

Examples use fictional IDs, contacts, credentials, and storage URLs. Replace path placeholders with real values. JSON under Request is the HTTP body. Timestamps are ISO 8601 strings. See the response object field reference for nested field types.

## Choose your integration

- [Customer accounts](/docs/customer-accounts-integration): use a system API key belonging to an active system Owner or PrimaryOwner.
- [Bottle counters](/docs/bottle-counter-integration): use a device license key and counter API key provisioned by a master admin at /system-management/bottle-counters. System API keys do not replace these credentials.

## Create your first system API key

1. Sign in as an active Owner or PrimaryOwner of the intended system.
2. Open /application/{systemSlug}/profile and select **Developer**. The slug is in your application URL, such as north-buffalo.
3. Choose **Permissions**: Read for retrieval, or Read-Write for mutations.
4. Choose **Expiration**: Never, 7 days, 30 days, or 90 days.
5. Select **Generate API key**, then **Copy API key**. The complete secret is revealed only once; save it before leaving the result.
6. Store the key in server-side secret storage, outside browser bundles, source control, and request logs.

Key permission does not grant a system role. Customer-account management requires the creator's current active Owner/PrimaryOwner membership; an account-specific Owner is different. Keys also authorize other system APIs permitted for their creator, with no account-only scope.

## Make your first request

Use https://bottlespeed.com. Set SYSTEM_SLUG and API_KEY in your server environment:

```sh
curl --fail-with-body "https://bottlespeed.com/api/system/$SYSTEM_SLUG/customer-account?page=1&pageSize=25" -H "x-api-key: $API_KEY"
```

Alternatively use Authorization: Bearer <system-api-key>. x-api-key takes precedence when both are sent. JSON write requests use Content-Type: application/json. An empty system returns:

```json
{
  "customerAccounts": [],
  "pagination": {
    "page": 1,
    "pageSize": 25,
    "totalItems": 0,
    "totalPages": 0,
    "hasPreviousPage": false,
    "hasNextPage": false
  }
}
```

See the [customer-account reference](/docs/customer-accounts-integration) for populated objects and writes.

## Create an API key

Create a key for the authenticated user in this system. The first key is created through the signed-in application; this HTTP example uses an existing key.

**Authentication:** Active system session or an existing Read-Write system API key. All active system roles can manage their own keys; customer-account APIs separately require Owner/PrimaryOwner.

### Request

```http
POST /api/auth/system/{systemSlug}/api-keys
x-api-key: <existing-read-write-system-key>
Content-Type: application/json
```

#### Path parameters

| Field | Type | Details |
| --- | --- | --- |
| systemSlug | string · required | Slug of the system where the caller is an active member. |

| Field | Type | Details |
| --- | --- | --- |
| permissions | Read \| Read-Write · required | Read permits GET requests. Read-Write also permits mutations allowed for the owning user. |
| expiresAt | ISO timestamp \| null · optional | Omit or use null for no expiration. Otherwise a future ISO 8601 timestamp with a timezone; an empty string is invalid. |


```json
{
  "permissions": "Read-Write",
  "expiresAt": null
}
```


### Response · 200 OK


```json
{
  "apiKey": "<new-secret-shown-once>",
  "apiKeyRecord": {
    "id": "99999999-9999-4999-8999-999999999999",
    "permissions": "Read-Write",
    "expiresAt": null,
    "createdAt": "2026-09-15T12:00:00.000Z",
    "isExpired": false
  }
}
```


| Field | Type | Details |
| --- | --- | --- |
| apiKey | string | Full credential, returned only by creation. Store it securely before closing the result. |
| apiKeyRecord | ApiKeyRecord | Metadata. id is used to revoke the key; it is not the secret. |

## List your API keys

List the authenticated user’s keys for this system. Secrets cannot be recovered from this endpoint.

**Authentication:** Active system session or a Read/Read-Write system API key.

### Request

```http
GET /api/auth/system/{systemSlug}/api-keys
x-api-key: <system-api-key>
```

#### Path parameters

| Field | Type | Details |
| --- | --- | --- |
| systemSlug | string · required | Slug of the system where the caller is an active member. |

Request body: none.

### Response · 200 OK


```json
{
  "apiKeys": [
    {
      "id": "99999999-9999-4999-8999-999999999999",
      "permissions": "Read-Write",
      "expiresAt": null,
      "createdAt": "2026-09-15T12:00:00.000Z",
      "isExpired": false
    }
  ]
}
```


| Field | Type | Details |
| --- | --- | --- |
| apiKeys | ApiKeyRecord[] | Current user’s key metadata, newest first. Empty when no keys exist. |

## Revoke an API key

Delete one of the authenticated user’s keys in the current system. The secret stops authorizing subsequent requests.

**Authentication:** Active system session or a Read-Write system API key.

### Request

```http
DELETE /api/auth/system/{systemSlug}/api-keys/{apiKeyId}
x-api-key: <read-write-system-key>
```

#### Path parameters

| Field | Type | Details |
| --- | --- | --- |
| systemSlug | string · required | Slug of the system where the caller is an active member. |
| apiKeyId | UUID · required | apiKeyRecord.id from creation or apiKeys[].id from listing. Must belong to this user and system. |

Request body: none.

A missing, already revoked, or other-user key returns 400 with { "error": "API key not found." }. Keys cannot be edited or renewed. Rotate by creating a replacement, updating the integration, then revoking the old key.

### Response · 200 OK


```json
{
  "success": true,
  "apiKeyId": "99999999-9999-4999-8999-999999999999"
}
```


| Field | Type | Details |
| --- | --- | --- |
| success | boolean | true after deletion. |
| apiKeyId | UUID | Deleted key metadata id. |

## Response object fields

### ApiKeyRecord

Metadata only; the secret and hash are never included.

| Field | Type | Details |
| --- | --- | --- |
| id | UUID | Key metadata identifier for revocation. |
| permissions | Read \| Read-Write | Maximum key permission; system role checks still apply. |
| expiresAt | ISO timestamp \| null | Expiration instant or null for no expiration. |
| createdAt | ISO timestamp | Creation instant. |
| isExpired | boolean | Whether expiresAt has passed when this response was built. |

## Error responses

Success responses above use HTTP 200. Ordinary errors have a string error field. HTTP 400 means invalid fields/IDs, missing records, or failed preconditions. HTTP 401 means invalid credentials, inactive membership/counter, insufficient role, or a mutation attempted with a Read key. Unexpected failures, including malformed JSON, may return 500. Reconcile uncertain writes before retrying. Missing records use 400 and authorization failures use 401.


```json
{
  "error": "API key not found."
}
```


Validation errors return error: "ValidationError" and an issues array. Each issue includes code, path (field names or array indices), and message, with additional rule-specific fields. For example:


```json
{
  "error": "ValidationError",
  "issues": [
    {
      "code": "custom",
      "path": [
        "expiresAt"
      ],
      "message": "Expiration must be in the future."
    }
  ]
}
```
