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):

ParameterTypeDescription
messageIdstringMessage UUID - returns contextualFromAddress if the message was sent to a whitelisted handle
conversationIdstringConversation 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 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:

{
  "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

{
  "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:

ParameterTypeDescription
limitnumberMax results (default: 50, max: 100)
offsetnumberPagination offset
tagstringFilter by tag
query or qstringSearch company/notes
emailstringFilter 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. 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:

ParameterTypeDescription
query or qstringWildcard search on name, kind, and roleId. Supports * (any characters) and ? (single character).
statusstringFilter by status: active, paused, or archived
kindstringFilter by kind (exact match or wildcard pattern)
limitnumberMax results to return (default: 100, max: 100)
offsetnumberNumber 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:

FieldTypeDescription
idstringUnique agent identifier (UUID)
namestringAgent display name
kindstringAgent type/category (e.g., inbox-manager, assistant)
roleIdstringUnique role ID used for email routing (kebab-case)
emailstringAgent's email address for direct routing
statusstringAgent status: active, paused, or archived
avatarUrlstringURL to agent's avatar image
activeTasksnumberCurrent number of active tasks
lastActiveAtstring|nullISO 8601 timestamp of last activity
createdAtstringISO 8601 timestamp when created
updatedAtstring|nullISO 8601 timestamp of last update
countnumberNumber of agents in current response
totalnumberTotal number of agents matching filters
paginationobjectPagination metadata (limit, offset, hasMore)
filtersobjectApplied 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:

FieldTypeDescription
idstringUnique agent identifier (UUID)
namestringAgent display name
kindstringAgent type/category
roleIdstringUnique role ID for email routing
emailstringAgent's email address for direct routing
statusstringAgent status: active, paused, or archived
configobjectFull agent configuration (persona, tools, context, etc.)
statsobjectAgent activity statistics
createdAtstringISO 8601 timestamp when created
updatedAtstring|nullISO 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 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):

{
  "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:

ParameterTypeDefaultDescription
statusstringallFilter: draft, published, archived
tagstring-Filter by tag
searchstring-Search by title
limitnumber20Max results (1-100)
offsetnumber0Pagination 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 CodeMeaning
200Success
201Created
400Bad Request
401Unauthorized
404Not Found
429Rate Limit Exceeded
500Internal Server Error

Rate Limits

Rate limits are enforced per API key:

PlanRequests/Day
Free100
Solopreneur1,000
Small Business10,000
EnterpriseUnlimited

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


Version: 1.6.0
Last Updated: February 2026