# API Keys API

> REST endpoints for api keys — request and response reference with examples.

<!-- Source: https://docs.actuallycare.com/api/reference/api-keys -->

Create and revoke API keys programmatically, or self-serve in the app: Settings → API Keys → Create API Key.

Base URL: `https://api.actuallycare.com/v1` · Errors use the [standard envelope](/api/reference#error-responses) — see [Errors](/api/errors).

## List API keys

```http
GET /v1/api-keys
```

Returns list of API keys for authenticated user (keys are masked)

### Example request

```bash title="cURL"
curl "https://api.actuallycare.com/v1/api-keys" \
  -H "Authorization: Bearer YOUR_JWT"
```

```javascript title="JavaScript"
const res = await fetch("https://api.actuallycare.com/v1/api-keys", {
  headers: {
    "Authorization": "Bearer YOUR_JWT",
  },
});
const data = await res.json();
```

```python title="Python"
import requests

resp = requests.get(
    "https://api.actuallycare.com/v1/api-keys",
    headers={"Authorization": "Bearer YOUR_JWT"},
)
data = resp.json()
```

### Example response (200)

```json
{
  "success": true,
  "data": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "Jordan Lee",
      "key_preview": "example key preview",
      "is_active": true,
      "created_at": "2026-07-15T14:32:10.000Z",
      "last_used_at": "2026-07-15T14:32:10.000Z",
      "expires_at": "2026-07-15T14:32:10.000Z"
    }
  ]
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | API keys retrieved successfully |
| `401` | Unauthorized |
| `429` | Rate limit exceeded — too many requests in the current window. Wait for the window to reset (see Retry-After) before retrying. |
| `500` | Internal server error |

## Create new API key

```http
POST /v1/api-keys
```

Generates a new API key for authenticated user. Key is only shown once.

⚠️ **CONSEQUENTIAL ACTION**: Creates new authentication credentials.
Store the key securely - it cannot be retrieved again.

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | Yes | Descriptive name for the API key |
| `expiresInDays` | integer | No | Number of days until expiration (1-365). If omitted, the key does not expire. |
| `scopes` | object | No | Map of resource → allowed actions (an OBJECT, not a string array), e.g. {"escrows": ["read", "write"], "clients": ["read"]}. Use resource "all" as a wildcard (e.g. {"all": ["read"]}). Valid resources: all, escrows, clients, contacts, leads, appointments, listings, documents, open-houses, showings, details, tasks, notes, communications, emails, integrations, settings, analytics, activity, notifications, users, search, teams, vendor-applications. **If omitted, the key is created with {} (no scopes) and is DENIED (403) on every scoped resource** — pass scopes here or grant them later via PATCH /api-keys/{id}/scopes.  |

### Example request

```bash title="cURL"
curl -X POST "https://api.actuallycare.com/v1/api-keys" \
  -H "Authorization: Bearer YOUR_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Jordan Lee",
    "scopes": {
      "all": [
        "read"
      ],
      "escrows": [
        "read",
        "write"
      ]
    }
  }'
```

```javascript title="JavaScript"
const res = await fetch("https://api.actuallycare.com/v1/api-keys", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_JWT",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "name": "Jordan Lee",
    "scopes": {
      "all": [
        "read"
      ],
      "escrows": [
        "read",
        "write"
      ]
    }
  }),
});
const data = await res.json();
```

```python title="Python"
import requests

resp = requests.post(
    "https://api.actuallycare.com/v1/api-keys",
    headers={"Authorization": "Bearer YOUR_JWT"},
    json={
        "name": "Jordan Lee",
        "scopes": {
            "all": [
                "read"
            ],
            "escrows": [
                "read",
                "write"
            ]
        }
    },
)
data = resp.json()
```

### Example response (201)

```json
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "key": "example key",
    "name": "Jordan Lee",
    "key_prefix": "example key prefix",
    "scopes": {},
    "expires_at": "2026-07-15T14:32:10.000Z",
    "created_at": "2026-07-15T14:32:10.000Z"
  },
  "message": "example message",
  "timestamp": "2026-07-15T14:32:10.000Z"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | API key created successfully |
| `400` | Validation error |
| `401` | Unauthorized |
| `429` | Rate limit exceeded — too many requests in the current window. Wait for the window to reset (see Retry-After) before retrying. |
| `500` | Internal server error |

## Update API key scopes

```http
PATCH /v1/api-keys/{id}/scopes
```

Replaces the scopes object on an existing API key. Requires JWT authentication (API keys cannot manage API keys). Scopes are a map of resource → allowed actions; see POST /api-keys for the valid resource list.

### Parameters

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | Yes | Format: uuid |

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `scopes` | object | Yes | Full replacement scopes map (resource → actions) |

### Example request

```bash title="cURL"
curl -X PATCH "https://api.actuallycare.com/v1/api-keys/:id/scopes" \
  -H "Authorization: Bearer YOUR_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "scopes": {
      "escrows": [
        "read",
        "write"
      ],
      "all": [
        "read"
      ]
    }
  }'
```

```javascript title="JavaScript"
const res = await fetch("https://api.actuallycare.com/v1/api-keys/:id/scopes", {
  method: "PATCH",
  headers: {
    "Authorization": "Bearer YOUR_JWT",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "scopes": {
      "escrows": [
        "read",
        "write"
      ],
      "all": [
        "read"
      ]
    }
  }),
});
const data = await res.json();
```

```python title="Python"
import requests

resp = requests.patch(
    "https://api.actuallycare.com/v1/api-keys/:id/scopes",
    headers={"Authorization": "Bearer YOUR_JWT"},
    json={
        "scopes": {
            "escrows": [
                "read",
                "write"
            ],
            "all": [
                "read"
            ]
        }
    },
)
data = resp.json()
```

### Example response (200)

```json
{
  "success": true,
  "data": {
    "id": "7d3f9b2e-5a8c-4e1d-b6f0-9c2a4e8d1b57",
    "scopes": {
      "escrows": [
        "read",
        "write"
      ],
      "all": [
        "read"
      ]
    }
  },
  "timestamp": "2026-07-01T22:48:31.554Z"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Scopes updated |
| `400` | Validation error (scopes must be an object) |
| `401` | Unauthorized |
| `404` | API key not found |
| `429` | Rate limit exceeded — too many requests in the current window. Wait for the window to reset (see Retry-After) before retrying. |
| `500` | Internal server error |

## Revoke API key

```http
PUT /v1/api-keys/{id}/revoke
```

Deactivates an API key without deleting it

### Parameters

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | Yes | Format: uuid |

### Example request

```bash title="cURL"
curl -X PUT "https://api.actuallycare.com/v1/api-keys/:id/revoke" \
  -H "Authorization: Bearer YOUR_JWT"
```

```javascript title="JavaScript"
const res = await fetch("https://api.actuallycare.com/v1/api-keys/:id/revoke", {
  method: "PUT",
  headers: {
    "Authorization": "Bearer YOUR_JWT",
  },
});
const data = await res.json();
```

```python title="Python"
import requests

resp = requests.put(
    "https://api.actuallycare.com/v1/api-keys/:id/revoke",
    headers={"Authorization": "Bearer YOUR_JWT"},
)
data = resp.json()
```

### Example response (200)

```json
{
  "success": true,
  "data": {
    "id": "7d3f9b2e-5a8c-4e1d-b6f0-9c2a4e8d1b57"
  },
  "timestamp": "2026-07-01T22:53:07.114Z"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | API key revoked successfully |
| `401` | Unauthorized |
| `404` | API key not found |
| `429` | Rate limit exceeded — too many requests in the current window. Wait for the window to reset (see Retry-After) before retrying. |
| `500` | Internal server error |

## Delete API key

```http
DELETE /v1/api-keys/{id}
```

Permanently deletes an API key

### Parameters

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | Yes | Format: uuid |

### Example request

```bash title="cURL"
curl -X DELETE "https://api.actuallycare.com/v1/api-keys/:id" \
  -H "Authorization: Bearer YOUR_JWT"
```

```javascript title="JavaScript"
const res = await fetch("https://api.actuallycare.com/v1/api-keys/:id", {
  method: "DELETE",
  headers: {
    "Authorization": "Bearer YOUR_JWT",
  },
});
const data = await res.json();
```

```python title="Python"
import requests

resp = requests.delete(
    "https://api.actuallycare.com/v1/api-keys/:id",
    headers={"Authorization": "Bearer YOUR_JWT"},
)
data = resp.json()
```

### Example response (200)

```json
{
  "success": true,
  "data": {},
  "timestamp": "2026-07-15T14:32:10.000Z"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | API key deleted successfully |
| `401` | Unauthorized |
| `404` | API key not found |
| `429` | Rate limit exceeded — too many requests in the current window. Wait for the window to reset (see Retry-After) before retrying. |
| `500` | Internal server error |
