# API Endpoints

Solo's API provides endpoints for managing identities, agents, workflows, and usage tracking.

## Base URL

All API requests use this base URL:

```
https://solomail.io/api/v1
```

## Endpoints

### Identity Endpoints

#### Get Identity

Get information about your Solo Identity.

**Endpoint**: `GET /identity`

**Headers**:
```
Authorization: Bearer <your-api-key>
```

**Query Parameters** (optional):
| Parameter | Type | Description |
|-----------|------|-------------|
| `messageId` | string | Message UUID - returns `contextualFromAddress` if the message was sent to a whitelisted handle |
| `conversationId` | string | Conversation UUID - returns `contextualFromAddress` if the conversation originated from a whitelisted handle |

**Response**:
```json
{
  "id": "identity-id",
  "handle": "yourname",
  "address": "yourname@solomail.io",
  "scope": "business",
  "displayName": "Acme Inc",
  "profile": {
    "tagline": "Your trusted partner for innovative solutions",
    "about": "We are a leading provider of...",
    "website": "https://example.com",
    "services": [
      {
        "id": "service-uuid",
        "name": "Consulting Session",
        "description": "One-on-one consulting session",
        "price": 150.00,
        "currency": "USD",
        "billingInterval": "one-time",
        "active": true
      }
    ]
  },
  "whitelistedAddresses": [
    {
      "handle": "sales",
      "address": "sales@solomail.io",
      "description": "Sales inquiries and business development"
    }
  ],
  "contextualFromAddress": "sales@solomail.io",
  "createdAt": "2025-01-01T00:00:00Z",
  "updatedAt": "2025-01-11T12:00:00Z"
}
```

**Response Fields**:
- `id` (string) - Identity UUID
- `handle` (string) - Unique handle
- `address` (string | null) - SoloMail address
- `scope` (string) - Identity scope: `"identity"` or `"business"`
- `displayName` (string | null) - Display name for the identity
- `profile` (object | null) - Profile information (see Profile Structure below)
- `whitelistedAddresses` (array | null) - Whitelisted handles assigned to this identity that can be used as "from" addresses
- `contextualFromAddress` (string | null) - Only present when `messageId` or `conversationId` is provided and the conversation originated from a whitelisted handle. Use this as the "from" address for replies.
- `createdAt` (string) - ISO 8601 timestamp
- `updatedAt` (string | null) - ISO 8601 timestamp

**Whitelisted Address Object**:
- `handle` (string) - The handle (e.g., "sales")
- `address` (string) - Full email address (e.g., "sales@solomail.io")
- `description` (string | null) - Description of what this handle is for

**Profile Structure**:
The `profile` object contains:
- `tagline` (string, optional) - Short one-liner describing the identity
- `about` (string, optional) - Detailed description (max 1000 characters)
- `website` (string, optional) - Website URL
- `services` (array, optional) - Array of services/products offered:
  - `id` (string) - Service UUID
  - `name` (string) - Service name
  - `description` (string, optional) - Service description
  - `price` (number) - Price amount
  - `currency` (string) - Currency code (e.g., "USD", "EUR", "GBP")
  - `billingInterval` (string) - `"one-time"` | `"monthly"` | `"yearly"`
  - `active` (boolean) - Whether the service is currently active

#### Update Identity Profile

Update identity profile information. Supports partial updates - only include fields you want to update.

**Endpoint**: `PATCH /identity`

**Headers**:
```
Authorization: Bearer <your-api-key>
Content-Type: application/json
```

**Request Body**:
```json
{
  "displayName": "Acme Inc",
  "profile": {
    "tagline": "Your trusted partner",
    "about": "We provide innovative solutions...",
    "website": "https://example.com",
    "services": [
      {
        "id": "service-uuid",
        "name": "Consulting Session",
        "description": "One-on-one consulting",
        "price": 150.00,
        "currency": "USD",
        "billingInterval": "one-time",
        "active": true
      }
    ]
  }
}
```

**Request Fields**:
- `displayName` (string | null, optional) - Update display name
- `profile` (object, optional) - Update profile fields (merges with existing profile):
  - `tagline` (string, optional)
  - `about` (string, optional, max 1000 characters)
  - `website` (string, optional)
  - `services` (array, optional) - Array of services (see Profile Structure above)

**Note**: The `profile` object is merged with existing profile data. To remove a field, set it to `null` or omit it from the request.

**Response**:
Returns the updated identity object (same structure as GET /identity).

**Example: Update Tagline Only**
```json
{
  "profile": {
    "tagline": "New tagline here"
  }
}
```

**Example: Add a Service**
```json
{
  "profile": {
    "services": [
      {
        "name": "New Service",
        "description": "Service description",
        "price": 99.99,
        "currency": "USD",
        "billingInterval": "monthly",
        "active": true
      }
    ]
  }
}
```

**Note**: When updating `services`, you can provide services without `id` fields - they will be auto-generated. To update an existing service, include its `id`.

**Error Responses**:

**400 Bad Request** - Invalid request body:
```json
{
  "error": "Invalid request body",
  "message": "Request body must be a JSON object"
}
```

**404 Not Found** - Identity not found:
```json
{
  "error": "Identity not found"
}
```

### Customer Endpoints (CRM)

Identity-scoped customer/contact records for CRM. Customers link to `external_contacts` (email-based). Create customers intentionally via API—not auto-created on inbound email. One customer can have multiple linked contacts (e.g., work + personal email).

#### List Customers

**Endpoint**: `GET /identity/customers`

**Query Parameters**:
| Parameter | Type | Description |
|-----------|------|-------------|
| `limit` | number | Max results (default: 50, max: 100) |
| `offset` | number | Pagination offset |
| `tag` | string | Filter by tag |
| `query` or `q` | string | Search company/notes |
| `email` | string | Filter by contact email |

**Response**:
```json
{
  "customers": [
    {
      "id": "uuid",
      "identityId": "uuid",
      "company": "Acme Corp",
      "phone": "+1-555-0100",
      "tags": ["lead", "vip"],
      "notes": "Met at conference",
      "metadata": {},
      "contacts": [
        {
          "guid": "uuid",
          "email": "jane@acme.com",
          "displayName": "Jane Doe",
          "isPrimary": true
        }
      ],
      "createdAt": "2025-01-01T00:00:00Z",
      "updatedAt": "2025-01-11T12:00:00Z"
    }
  ],
  "count": 1
}
```

#### Get Customer by ID

**Endpoint**: `GET /identity/customers/[id]`

#### Get Customer by Contact Guid

**Endpoint**: `GET /identity/customers/by-contact/[guid]`

Lookup customer by external contact guid (UUID v5 from email). Use when you have an email and need the customer record.

#### Create Customer

**Endpoint**: `POST /identity/customers`

**Request Body**:
```json
{
  "contacts": [
    { "email": "jane@acme.com", "displayName": "Jane Doe" },
    { "guid": "existing-contact-uuid" }
  ],
  "company": "Acme Corp",
  "phone": "+1-555-0100",
  "tags": ["lead"],
  "notes": "Met at conference",
  "metadata": { "lead_source": "conference" }
}
```

- `contacts` (required): Array of `{ email, displayName? }` or `{ guid }`. At least one required. Creates `external_contact` if email provided and contact doesn't exist.
- `company`, `phone`, `tags`, `notes`, `metadata` (optional)

#### Update Customer

**Endpoint**: `PATCH /identity/customers/[id]`

**Request Body**: Partial update (company, phone, tags, notes, metadata).

#### Delete Customer

**Endpoint**: `DELETE /identity/customers/[id]`

#### Add Contact to Customer

**Endpoint**: `POST /identity/customers/[id]/contacts`

**Request Body**: `{ "email": "work@acme.com" }` or `{ "guid": "uuid" }`

#### Remove Contact from Customer

**Endpoint**: `DELETE /identity/customers/[id]/contacts/[guid]`

Cannot remove the last contact—delete the customer instead.

### Agent Endpoints

#### List Agents

Get agents for your identity with optional filtering and search.

**Endpoint**: `GET /identity/agents`

**Headers**:
```
Authorization: Bearer <your-api-key>
```

**Query Parameters**:

| Parameter | Type | Description |
|-----------|------|-------------|
| `query` or `q` | string | Wildcard search on name, kind, and roleId. Supports `*` (any characters) and `?` (single character). |
| `status` | string | Filter by status: `active`, `paused`, or `archived` |
| `kind` | string | Filter by kind (exact match or wildcard pattern) |
| `limit` | number | Max results to return (default: 100, max: 100) |
| `offset` | number | Number of results to skip for pagination |

**Example Requests**:
```bash
# Get all agents
GET /identity/agents

# Search for agents with "manager" in name/kind/roleId
GET /identity/agents?query=*manager*

# Filter by status
GET /identity/agents?status=active

# Filter by kind with wildcard
GET /identity/agents?kind=inbox*

# Combine filters with pagination
GET /identity/agents?status=active&query=*support*&limit=10&offset=0
```

**Response**:
```json
{
  "agents": [
    {
      "id": "agent-uuid",
      "name": "Inbox Manager",
      "kind": "inbox-manager",
      "roleId": "inbox-manager",
      "email": "yourname+inbox-manager@solomail.io",
      "status": "active",
      "avatarUrl": "/avatars/agent-avatar.png",
      "activeTasks": 3,
      "lastActiveAt": "2025-01-11T12:00:00Z",
      "createdAt": "2025-01-01T00:00:00Z",
      "updatedAt": "2025-01-11T12:00:00Z"
    }
  ],
  "count": 1,
  "total": 5,
  "pagination": {
    "limit": 100,
    "offset": 0,
    "hasMore": false
  },
  "filters": {
    "query": "*manager*",
    "status": "active"
  }
}
```

**Response Fields**:
| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Unique agent identifier (UUID) |
| `name` | string | Agent display name |
| `kind` | string | Agent type/category (e.g., `inbox-manager`, `assistant`) |
| `roleId` | string | Unique role ID used for email routing (kebab-case) |
| `email` | string | Agent's email address for direct routing |
| `status` | string | Agent status: `active`, `paused`, or `archived` |
| `avatarUrl` | string | URL to agent's avatar image |
| `activeTasks` | number | Current number of active tasks |
| `lastActiveAt` | string\|null | ISO 8601 timestamp of last activity |
| `createdAt` | string | ISO 8601 timestamp when created |
| `updatedAt` | string\|null | ISO 8601 timestamp of last update |
| `count` | number | Number of agents in current response |
| `total` | number | Total number of agents matching filters |
| `pagination` | object | Pagination metadata (`limit`, `offset`, `hasMore`) |
| `filters` | object | Applied filters (for debugging) |

#### Get Agent Details

Get full configuration for a specific agent.

**Endpoint**: `GET /identity/agents/{agentId}`

**Headers**:
```
Authorization: Bearer <your-api-key>
```

**Response**:
```json
{
  "id": "agent-uuid",
  "name": "Inbox Manager",
  "kind": "inbox-manager",
  "roleId": "inbox-manager",
  "email": "yourname+inbox-manager@solomail.io",
  "status": "active",
  "avatarUrl": "/avatars/agent-avatar.png",
  "config": {
    "persona": {
      "tone": "professional",
      "traits": ["helpful", "efficient"],
      "bio": "I help manage your inbox...",
      "signature": "Best regards,\nInbox Manager"
    },
    "tools": {
      "email": { "enabled": true },
      "calendar": { "enabled": false }
    },
    "context": {
      "enabled": true,
      "namespace": "identity-id:agent-id"
    }
  },
  "stats": {
    "activeTasks": 5,
    "completedTasks": 45,
    "hoursSaved": 12.5,
    "completionRate": 92,
    "escalations": 3,
    "totalActions": 150,
    "lastActiveAt": "2025-01-11T12:00:00Z"
  },
  "createdAt": "2025-01-01T00:00:00Z",
  "updatedAt": "2025-01-11T12:00:00Z"
}
```

**Response Fields**:
| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Unique agent identifier (UUID) |
| `name` | string | Agent display name |
| `kind` | string | Agent type/category |
| `roleId` | string | Unique role ID for email routing |
| `email` | string | Agent's email address for direct routing |
| `status` | string | Agent status: `active`, `paused`, or `archived` |
| `config` | object | Full agent configuration (persona, tools, context, etc.) |
| `stats` | object | Agent activity statistics |
| `createdAt` | string | ISO 8601 timestamp when created |
| `updatedAt` | string\|null | ISO 8601 timestamp of last update |
| `editableFields` | string[] | Fields you can update via `PATCH /identity/agents/{agentId}` |

#### Update Agent Basics

Update safe agent fields without changing role, tools, workflow, or model settings. Use the dedicated HITL endpoint for escalation routes.

**Endpoint**: `PATCH /identity/agents/{agentId}`

**Editable fields**: `name`, `status`, `avatarUrl`, `tags`, and limited `persona` fields (`tone`, `bio`, `signature`, `traits`, `avatarUrl`, `avatarEmoji`).

**Not editable via API**: `kind`, `roleId`, `identityId`, `config.tools`, `config.workflow`, `config.model`, `config.context`, `config.hitl`, `specialists`, `policies`.

**Request Body** (partial update):
```json
{
  "name": "Support Agent",
  "status": "active",
  "tags": ["support"],
  "persona": {
    "tone": "friendly",
    "bio": "Helps customers with billing questions"
  }
}
```

**Response**: Full agent object (same shape as GET), including `editableFields`.

#### Get Agent HITL Configuration

**Endpoint**: `GET /identity/agents/{agentId}/hitl`

Returns normalized escalation routes for the agent, plus the identity owner email as a fallback contact when no HITL routes are configured.

**Response**:
```json
{
  "agentId": "uuid",
  "identityId": "uuid",
  "ownerEmail": "owner@example.com",
  "routes": [
    {
      "level": 1,
      "label": "Primary escalation",
      "instructions": "Contact the inbox manager first",
      "timeoutMins": 10,
      "toRole": "inbox-manager",
      "toUserId": null,
      "channels": ["email"],
      "contactInfo": {
        "email": "ops@example.com",
        "phone": null,
        "slack": null
      }
    }
  ],
  "updatedAt": "2025-01-11T12:00:00Z"
}
```

`ownerEmail` is the identity owner's contact email (from handle claim or Clerk). Use it when `routes` is empty or a route has no `contactInfo.email`.

#### Update Agent HITL Configuration

**Endpoint**: `PATCH /identity/agents/{agentId}/hitl`

**Replace semantics for `routes`:**

| Body | Behavior |
|------|----------|
| Omit `routes` | No change; returns current config |
| `"routes": []` | Clears all routes |
| `"routes": [...]` | Replaces the full routes array |

**Allowed channels**: `email`, `sms`, `slack`, `in_app`

**Validation**: Up to 10 routes, unique levels, contact requirements per channel. Optional `label` and `instructions` per route.

See [Configuring Agents](../agents/configuring-agents.md#human-in-the-loop-hitl) for UI configuration.

---

### Conversation Endpoints

Identity-scoped endpoints for listing email threads, fetching detail, and reading messages. Email-first; chat and voice are supported only where persisted in Solo today.

#### List Conversations

**Endpoint**: `GET /conversations`

**Query Parameters**:
| Parameter | Type | Description |
|-----------|------|-------------|
| `limit` | number | Page size (1–100, default 20) |
| `offset` | number | Pagination offset |
| `status` | string | Filter by processing status |
| `channel` | string | Filter by channel (e.g. `email`) |
| `agentId` | uuid | Filter by assigned agent |
| `isRead` | boolean | Filter read/unread |
| `isArchived` | boolean | Include archived |
| `isSpam` | boolean | Include spam |
| `lastMessageAfter` | ISO date | Messages after timestamp |
| `search` | string | Subject/preview search |

**Response**:
```json
{
  "conversations": [
    {
      "id": "uuid",
      "identityId": "uuid",
      "subject": "Billing question",
      "channel": "email",
      "preview": "Hi, I have a question...",
      "status": "active",
      "isRead": false,
      "lastMessageAt": "2025-01-11T12:00:00Z"
    }
  ],
  "total": 42,
  "limit": 20,
  "offset": 0,
  "hasMore": true
}
```

#### Get Conversation

**Endpoint**: `GET /conversations/{conversationId}`

Returns conversation detail including participants, assigned agents, linked tasks, and metadata.

#### List Conversation Messages

**Endpoint**: `GET /conversations/{conversationId}/messages`

**Query Parameters**:
| Parameter | Type | Description |
|-----------|------|-------------|
| `direction` | string | `inbound` or `outbound` |
| `order` | string | `asc` (default) or `desc` |
| `limit` | number | Max messages (1–200, default 100) |
| `includeAttachments` | boolean | Include attachment metadata |

**Note**: `GET /email/thread` remains available for compatibility. Prefer `/conversations` for new integrations.

---

### Blog Endpoints

#### Create Blog Post

Create a new blog post (created as `draft` by default).

**Endpoint**: `POST /blog/posts`

**Headers**:
```
Authorization: Bearer <your-api-key>
Content-Type: application/json
```

**Request Body**:
```json
{
  "title": "Getting Started with AI Agents",
  "content": "# Introduction\n\nLearn how AI agents can help your business...",
  "excerpt": "A quick guide to setting up your first AI agent.",
  "authorName": "Jordan Martinez",
  "authorAvatarUrl": "https://example.com/avatars/jordan.png",
  "tags": ["ai", "getting-started"],
  "coverImageUrl": "https://example.com/images/cover.jpg",
  "metaDescription": "Learn how to get started with AI agents.",
  "slug": "getting-started-with-ai-agents"
}
```

**Required Fields**:
- `title` (string) - Post title
- `content` (string) - Post content in Markdown format
- `authorName` (string) - Author display name

**Optional Fields**:
- `slug` (string) - Custom URL slug (auto-generated from title if omitted)
- `excerpt` (string) - Short summary for listing cards
- `coverImageUrl` (string) - Cover image URL
- `authorAvatarUrl` (string) - Author avatar image URL
- `authorAgentId` (string) - UUID of the authoring agent
- `tags` (string[]) - Array of tag strings
- `metaDescription` (string) - SEO meta description

**Response** (201):
```json
{
  "success": true,
  "data": {
    "post": {
      "id": "post-uuid",
      "title": "Getting Started with AI Agents",
      "slug": "getting-started-with-ai-agents",
      "content": "# Introduction\n\n...",
      "excerpt": "A quick guide to setting up your first AI agent.",
      "coverImageUrl": "https://example.com/images/cover.jpg",
      "authorName": "Jordan Martinez",
      "authorAvatarUrl": "https://example.com/avatars/jordan.png",
      "status": "draft",
      "publishedAt": null,
      "tags": ["ai", "getting-started"],
      "metaDescription": "Learn how to get started with AI agents.",
      "createdAt": "2026-02-06T12:00:00Z",
      "updatedAt": "2026-02-06T12:00:00Z"
    }
  }
}
```

#### List Blog Posts

Get blog posts for your identity with optional filtering.

**Endpoint**: `GET /blog/posts`

**Headers**:
```
Authorization: Bearer <your-api-key>
```

**Query Parameters**:

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `status` | string | all | Filter: `draft`, `published`, `archived` |
| `tag` | string | - | Filter by tag |
| `search` | string | - | Search by title |
| `limit` | number | 20 | Max results (1-100) |
| `offset` | number | 0 | Pagination offset |

**Example Requests**:
```bash
# Get all posts
GET /blog/posts

# Get only published posts
GET /blog/posts?status=published

# Filter by tag
GET /blog/posts?tag=ai&limit=10
```

**Response** (200):
```json
{
  "success": true,
  "data": {
    "posts": [
      {
        "id": "post-uuid",
        "title": "Getting Started with AI Agents",
        "slug": "getting-started-with-ai-agents",
        "excerpt": "A quick guide...",
        "status": "published",
        "publishedAt": "2026-02-06T12:00:00Z",
        "tags": ["ai", "getting-started"],
        "authorName": "Jordan Martinez",
        "authorAvatarUrl": "https://..."
      }
    ],
    "total": 15,
    "limit": 20,
    "offset": 0
  }
}
```

#### Get Blog Post

Get a single blog post by ID.

**Endpoint**: `GET /blog/posts/{postId}`

**Headers**:
```
Authorization: Bearer <your-api-key>
```

**Response** (200):
```json
{
  "success": true,
  "data": {
    "post": {
      "id": "post-uuid",
      "title": "Getting Started with AI Agents",
      "slug": "getting-started-with-ai-agents",
      "content": "# Introduction\n\nFull markdown content...",
      "excerpt": "A quick guide...",
      "status": "published",
      "publishedAt": "2026-02-06T12:00:00Z",
      "tags": ["ai", "getting-started"],
      "authorName": "Jordan Martinez",
      "authorAvatarUrl": "https://...",
      "createdAt": "2026-02-06T10:00:00Z",
      "updatedAt": "2026-02-06T12:00:00Z"
    }
  }
}
```

#### Update Blog Post

Update a blog post. Supports partial updates -- only include fields you want to change.

**Endpoint**: `PATCH /blog/posts/{postId}`

**Headers**:
```
Authorization: Bearer <your-api-key>
Content-Type: application/json
```

**Request Body** (all fields optional):
```json
{
  "title": "Updated Title",
  "content": "Updated markdown content...",
  "tags": ["updated-tag"]
}
```

**Note**: If `title` is updated and no explicit `slug` is provided, the slug auto-updates from the new title.

**Response** (200):
Returns the updated post object.

#### Publish Blog Post

Publish a blog post. Sets the status to `published` and records the publication timestamp.

**Endpoint**: `POST /blog/posts/{postId}/publish`

**Headers**:
```
Authorization: Bearer <your-api-key>
```

**Response** (200):
```json
{
  "success": true,
  "data": {
    "post": {
      "id": "post-uuid",
      "status": "published",
      "publishedAt": "2026-02-06T14:30:00Z"
    }
  }
}
```

#### Unpublish Blog Post

Unpublish a blog post. Reverts the status to `draft` and removes it from the public blog.

**Endpoint**: `POST /blog/posts/{postId}/unpublish`

**Headers**:
```
Authorization: Bearer <your-api-key>
```

**Response** (200):
```json
{
  "success": true,
  "data": {
    "post": {
      "id": "post-uuid",
      "status": "draft",
      "publishedAt": null
    }
  }
}
```

#### Delete Blog Post

Permanently delete a blog post.

**Endpoint**: `DELETE /blog/posts/{postId}`

**Headers**:
```
Authorization: Bearer <your-api-key>
```

**Response** (200):
```json
{
  "success": true
}
```

**Error Responses**:

**400 Bad Request** - Invalid post ID:
```json
{
  "error": "ValidationError",
  "message": "Invalid postId format. Must be a valid UUID."
}
```

**404 Not Found** - Post not found:
```json
{
  "error": "NotFound",
  "message": "Blog post not found"
}
```

---

### Usage Tracking Endpoints

#### Track Token Usage

Record token usage from workflows.

**Endpoint**: `POST /usage/track`

**Headers**:
```
Authorization: Bearer <your-api-key>
Content-Type: application/json
```

**Request Body**:
```json
{
  "type": "token_usage",
  "identityId": "identity-id",
  "promptTokens": 100,
  "completionTokens": 50,
  "totalTokens": 150,
  "model": "gpt-4",
  "provider": "openai",
  "usageType": "workflow",
  "operation": "email_processing",
  "agentId": "agent-id",
  "conversationId": "conversation-id",
  "taskId": "task-id",
  "metadata": {
    "workflow": "inbox-manager",
    "step": "analyze_email"
  }
}
```

**Response**:
```json
{
  "success": true,
  "id": "usage-id",
  "createdAt": "2025-01-01T00:00:00Z"
}
```

#### Track Agent Execution

Record agent execution events.

**Endpoint**: `POST /usage/track`

**Headers**:
```
Authorization: Bearer <your-api-key>
Content-Type: application/json
```

**Request Body**:
```json
{
  "type": "agent_execution",
  "identityId": "identity-id",
  "agentId": "agent-id",
  "status": "completed",
  "operation": "send_email",
  "conversationId": "conversation-id",
  "taskId": "task-id",
  "messageId": "message-id",
  "result": {
    "success": true,
    "emailSent": true
  },
  "metadata": {
    "workflow": "inbox-manager",
    "duration": 1.5
  }
}
```

**Response**:
```json
{
  "success": true,
  "id": "execution-id",
  "createdAt": "2025-01-01T00:00:00Z"
}
```

#### Get Usage Statistics

Get current usage statistics.

**Endpoint**: `GET /usage/stats`

**Headers**:
```
Authorization: Bearer <your-api-key>
```

**Response**:
```json
{
  "tokenUsage": {
    "current": 50000,
    "limit": 1000000,
    "percentage": 5,
    "formatted": "50K / 1M"
  },
  "executions": {
    "current": 150,
    "limit": 300,
    "percentage": 50,
    "formatted": "150 / 300"
  },
  "plan": "free"
}
```

## Request Format

### Headers

All requests require:
- `Authorization: Bearer <your-api-key>`
- `Content-Type: application/json` (for POST/PUT requests)

### Request Body

POST and PUT requests include JSON body:
```json
{
  "field": "value"
}
```

## Response Format

### Success Response

**Status**: `200 OK` or `201 Created`

```json
{
  "success": true,
  "data": { ... }
}
```

### Error Response

**Status**: `400 Bad Request`, `401 Unauthorized`, `404 Not Found`, `500 Internal Server Error`

```json
{
  "error": "Error message",
  "details": { ... }
}
```

## Security & Access Control

API keys are scoped to your Solo Identity. Resources belonging to another identity return **404 Not Found** (not 403) to protect tenant privacy.

- **User API keys** (`sk_live_...`): Bound to one identity; cannot override `identityId` in request bodies or query parameters
- **Cross-identity access**: Returns `404` with `"Resource not found or access denied"`
- **Identity override attempts**: Returns `403 Forbidden`

Applies to agents, conversations, tasks, plans, and work orders accessed via API.

See [Authentication](./authentication.md#identity-scoping) for details.

## Error Codes

| Status Code | Meaning |
|------------|---------|
| `200` | Success |
| `201` | Created |
| `400` | Bad Request |
| `401` | Unauthorized |
| `404` | Not Found |
| `429` | Rate Limit Exceeded |
| `500` | Internal Server Error |

## Rate Limits

Rate limits are enforced per API key:

| Plan | Requests/Day |
|------|--------------|
| **Free** | 100 |
| **Solopreneur** | 1,000 |
| **Small Business** | 10,000 |
| **Enterprise** | Unlimited |

Rate limit headers included in responses:
- `X-RateLimit-Limit`: Total requests allowed
- `X-RateLimit-Remaining`: Requests remaining
- `X-RateLimit-Reset`: Unix timestamp when limit resets

## Next Steps

- Learn about [Authentication](./authentication.md)
- Explore [Webhooks](./webhooks.md)
- See [API Examples](./examples.md)

---

**Version**: 1.7.0  
**Last Updated**: July 2026

