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:
{ "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 UUIDhandle(string) - Unique handleaddress(string | null) - SoloMail addressscope(string) - Identity scope:"identity"or"business"displayName(string | null) - Display name for the identityprofile(object | null) - Profile information (see Profile Structure below)whitelistedAddresses(array | null) - Whitelisted handles assigned to this identity that can be used as "from" addressescontextualFromAddress(string | null) - Only present whenmessageIdorconversationIdis provided and the conversation originated from a whitelisted handle. Use this as the "from" address for replies.createdAt(string) - ISO 8601 timestampupdatedAt(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 identityabout(string, optional) - Detailed description (max 1000 characters)website(string, optional) - Website URLservices(array, optional) - Array of services/products offered:id(string) - Service UUIDname(string) - Service namedescription(string, optional) - Service descriptionprice(number) - Price amountcurrency(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:
{ "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 nameprofile(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
{ "profile": { "tagline": "New tagline here" } }
Example: Add a Service
{ "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:
{ "error": "Invalid request body", "message": "Request body must be a JSON object" }
404 Not Found - Identity not found:
{ "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:
{ "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:
{ "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. Createsexternal_contactif 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:
# 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:
{ "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:
{ "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 |
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:
{ "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 titlecontent(string) - Post content in Markdown formatauthorName(string) - Author display name
Optional Fields:
slug(string) - Custom URL slug (auto-generated from title if omitted)excerpt(string) - Short summary for listing cardscoverImageUrl(string) - Cover image URLauthorAvatarUrl(string) - Author avatar image URLauthorAgentId(string) - UUID of the authoring agenttags(string[]) - Array of tag stringsmetaDescription(string) - SEO meta description
Response (201):
{ "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:
# 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):
{ "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):
{ "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):
{ "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):
{ "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):
{ "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):
{ "success": true }
Error Responses:
400 Bad Request - Invalid post ID:
{ "error": "ValidationError", "message": "Invalid postId format. Must be a valid UUID." }
404 Not Found - Post not found:
{ "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:
{ "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:
{ "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:
{ "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:
{ "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:
{ "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:
{ "field": "value" }
Response Format
Success Response
Status: 200 OK or 201 Created
{ "success": true, "data": { ... } }
Error Response
Status: 400 Bad Request, 401 Unauthorized, 404 Not Found, 500 Internal Server Error
{ "error": "Error message", "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 allowedX-RateLimit-Remaining: Requests remainingX-RateLimit-Reset: Unix timestamp when limit resets
Next Steps
- Learn about Authentication
- Explore Webhooks
- See API Examples
Version: 1.6.0
Last Updated: February 2026