API reference
AgencyOSX API
Connect your AgencyOSX agents to external tools, automation workflows, and AI platforms. Use the REST API to list agents, send messages, and read your conversation history programmatically — or connect over MCP to Claude Desktop, Manus, and Cursor.
Quickstart#
Your first call in 60 seconds. Get a token at Settings → API Access, then send a message to an agent with POST /chat — the reply comes straight back as JSON.
Request
curl -X POST https://app.agencyosx.ai/api/v1/chat \
-H "Authorization: Bearer aosx_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"agentId": "kai",
"message": "Find me 10 dental clinics in Manchester"
}'Pass the returned chatId back on your next POST /chat to continue the same conversation. Each turn is billed to your wallet in USD.
Response
{
"response": "Here are 10 dental clinics in Manchester...",
"agent": { "id": "kai", "name": "Kai" },
"chatId": "d4c3b2a1-...",
"usage": { "estimated_cost_usd": 0.0854 },
"wallet": { "balanceUsd": 12.34 }
}Response
{
"response": "Here are 10 dental clinics in Manchester...",
"agent": { "id": "kai", "name": "Kai" },
"chatId": "d4c3b2a1-...",
"usage": { "estimated_cost_usd": 0.0854 },
"wallet": { "balanceUsd": 12.34 }
}Authentication#
Every endpoint requires a Bearer token. Generate an API token in your AgencyOSX dashboard under Settings → API Access, then include it in the Authorization header of every authenticated request.
Tokens are prefixed with aosx_. Keep them secret — treat them like passwords. You can revoke and regenerate tokens at any time from Settings → API Access.
HTTP header
Authorization: Bearer aosx_your_token_hereHTTP header
Authorization: Bearer aosx_your_token_hereBase URL#
All endpoint paths in this reference are relative to the base URL. Requests must be made over HTTPS.
Base URL
https://app.agencyosx.ai/api/v1Base URL
https://app.agencyosx.ai/api/v1Pagination#
Every list endpoint takes the same two query parameters and returns the same pagination object. pagination.hasMore is true while rows remain — keep increasing offset by limit until it is false.
Parameters
| Parameter | Required | Description | Example |
|---|---|---|---|
limitinteger · query | Optional | Rows per page (1–100). Defaults to 20; values above the cap clamp to 100. | 50 |
offsetinteger · query | Optional | Rows to skip, for pagination. Defaults to 0. | 40 |
Response fields
| Field | Meaning | Example |
|---|---|---|
total | The total number of rows that match your request across every page — the full result-set size, ignoring limit and offset. | 137 |
limit | The page size applied to this response — the most rows it can contain. Echoes your limit after defaulting to 20 and clamping to the max of 100. | 20 |
offset | How many rows were skipped before this page (zero-based). Echoes your offset; defaults to 0. With offset 40 and limit 20 this page is rows 41–60. | 40 |
hasMore | true while more rows remain beyond this page (total > offset + limit). Page forward by adding limit to offset, and stop once it is false. | true |
Paginated response
{
"clients": [ /* … up to `limit` rows … */ ],
"pagination": {
"total": 137,
"limit": 20,
"offset": 40,
"hasMore": true
}
}Paginated response
{
"clients": [ /* … up to `limit` rows … */ ],
"pagination": {
"total": 137,
"limit": 20,
"offset": 40,
"hasMore": true
}
}Sorting#
List endpoints accept sort and order. Each endpoint defines which fields it can sort by and a sensible default; the allowed fields are listed on each endpoint below. An unknown sort field or a bad order returns 400.
Parameters
| Parameter | Required | Description | Example |
|---|---|---|---|
sortstring · query | Optional | Field to sort by. Each endpoint lists its allowed fields; an unknown field returns 400. | created |
orderstring · query | Optional | Sort direction: "asc" or "desc". Each endpoint sets its own default. | desc |
Examples
# Sort by a field, descending
?sort=created&order=desc
# Ascending
?sort=name&order=ascExamples
# Sort by a field, descending
?sort=created&order=desc
# Ascending
?sort=name&order=ascDate ranges#
Endpoints that filter by time accept the parameters below — use either a shortcut period or an explicit startDate/endDate range, not both (a range may not exceed 366 days). On GET /account/usage a range is always applied (default 30d); on the endpoints that support it (conversations, workflow runs, receipts) it is an optional created filter.
Every date you send is interpreted in UTC, and every timestamp in a response is ISO 8601 in UTC with a trailing Z (e.g. 2026-06-28T10:00:00Z). Two input formats are accepted:
- Date only —
YYYY-MM-DD(e.g.2026-06-01), read as that day at00:00:00 UTC. A date-onlyendDatecovers the whole day, through23:59:59.999 UTC. - Full timestamp — any ISO 8601 / RFC 3339 value (e.g.
2026-06-01T09:30:00Z). To pin to a local day boundary, include an offset such as2026-06-01T00:00:00+01:00— it's converted to UTC for you.
Ranges are half-open: a row matches when created_at ≥ startDate and < endDate.
Parameters
| Parameter | Required | Description | Example |
|---|---|---|---|
periodstring · query | Optional | Shortcut window ending now (see the values below). Mutually exclusive with startDate/endDate. | 7d |
startDatestring · query | Optional | Inclusive lower bound. A date-only YYYY-MM-DD is read as 00:00 UTC; or pass a full ISO 8601 timestamp. Use with endDate for an explicit range. | 2026-06-01 |
endDatestring · query | Optional | Exclusive upper bound — but a date-only YYYY-MM-DD includes that whole UTC day. Full ISO 8601 timestamps allowed. Defaults to now. Range capped at 366 days. | 2026-06-30 |
period values
| Value | Meaning | Example |
|---|---|---|
today | From 00:00 UTC today until now. | ?period=today |
24h | The last 24 hours (rolling). | ?period=24h |
7d | The last 7 days. | ?period=7d |
30d | The last 30 days (default when nothing is supplied). | ?period=30d |
90d | The last 90 days. | ?period=90d |
Examples
# Shortcut period
?period=7d
# Explicit range (inclusive of both days)
?startDate=2026-06-01&endDate=2026-06-30
# Open-ended (start → now)
?startDate=2026-06-01Examples
# Shortcut period
?period=7d
# Explicit range (inclusive of both days)
?startDate=2026-06-01&endDate=2026-06-30
# Open-ended (start → now)
?startDate=2026-06-01Agents
The specialist AgencyOSX agents you can message. Browse them here, then use an agent's id with POST /chat.
/agentsAuth requiredList agents#
List every available agent — id, name, title, description, and expertise topics. The id is the agent's name lowercased (e.g. "kai"); use it anywhere an agentId is required. Paginated and sortable.
Parameters
| Parameter | Required | Description | Example |
|---|---|---|---|
limitinteger · query | Optional | Rows per page (1–100). Defaults to 20; values above the cap clamp to 100. | 50 |
offsetinteger · query | Optional | Rows to skip, for pagination. Defaults to 0. | 40 |
sortstring · query | Optional | Field to sort by — defaults to name. Options:
| title |
orderstring · query | Optional | Sort direction: "asc" or "desc". Defaults to asc. | desc |
Request
curl "https://app.agencyosx.ai/api/v1/agents?limit=50&sort=name&order=asc" \
-H "Authorization: Bearer aosx_your_token_here"Response
{
"agents": [
{
"id": "kai",
"name": "Kai",
"title": "Lead Generator",
"description": "I help you find and qualify ideal prospects...",
"expertiseTopics": [
"Lead list building",
"ICP definition",
"LinkedIn prospecting"
]
}
],
"pagination": { "total": 16, "limit": 20, "offset": 0, "hasMore": false }
}Request
curl "https://app.agencyosx.ai/api/v1/agents?limit=50&sort=name&order=asc" \
-H "Authorization: Bearer aosx_your_token_here"Response
{
"agents": [
{
"id": "kai",
"name": "Kai",
"title": "Lead Generator",
"description": "I help you find and qualify ideal prospects...",
"expertiseTopics": [
"Lead list building",
"ICP definition",
"LinkedIn prospecting"
]
}
],
"pagination": { "total": 16, "limit": 20, "offset": 0, "hasMore": false }
}/agents/:idAuth requiredRetrieve an agent#
Get details for a single agent. The response is a flat object (not wrapped).
Parameters
| Parameter | Required | Description | Example |
|---|---|---|---|
idstring | Optional | Agent identifier — the agent's name, lowercased (e.g. "kai"). The legacy slug ("lead-generator") is also accepted. | kai |
Request
curl https://app.agencyosx.ai/api/v1/agents/kai \
-H "Authorization: Bearer aosx_your_token_here"Response
{
"id": "kai",
"name": "Kai",
"title": "Lead Generator",
"description": "I help you find and qualify ideal prospects...",
"expertiseTopics": [
"Lead list building",
"ICP definition",
"LinkedIn prospecting"
]
}Request
curl https://app.agencyosx.ai/api/v1/agents/kai \
-H "Authorization: Bearer aosx_your_token_here"Response
{
"id": "kai",
"name": "Kai",
"title": "Lead Generator",
"description": "I help you find and qualify ideal prospects...",
"expertiseTopics": [
"Lead list building",
"ICP definition",
"LinkedIn prospecting"
]
}Chat
Send messages to an agent and read your conversation history. Each turn is billed to your wallet and saved to a conversation you can continue.
/chatAuth requiredSend a message#
Send a message to an agent and get the agent's reply as JSON. The turn is saved to a conversation and returned as chatId — pass that chatId back to continue the same thread (the server keeps the history).
Parameters
| Parameter | Required | Description | Example |
|---|---|---|---|
agentIdstring | Required | The agent to message — its public id, the agent's lowercased name (e.g. kai). Get the list from GET /agents; an unknown id returns 404. | kai |
messagestring | Required | The user's message for this turn. Up to 50,000 characters — longer messages return 400. | Find 20 marketing agencies in London |
chatIdstring | Optional | Continue an existing conversation — pass the chatId from a prior response or from GET /chat. Must belong to you and to the same agent (a mismatched agent returns 400). Omit to start a new conversation; when set, the stored history is used automatically. | d4c3b2a1-6f5e-4b8a-0d9c-2f1e4a3b5c6d |
Response fields
| Field | Meaning | Example |
|---|---|---|
response | stringThe agent's full reply for this turn, as plain text. | — |
agent | objectThe agent that answered — id (its public id) and name. | — |
chatId | stringThe conversation this turn was saved to. Pass it back as chatId on the next request to continue the same thread. | — |
usage.estimated_cost_usd | numberThe amount, in USD, charged to your wallet for this turn (OSX LLM, retail). No token counts are returned. | — |
wallet.balanceUsd | numberYour remaining wallet balance in USD after this turn was charged. | — |
Request
curl -X POST https://app.agencyosx.ai/api/v1/chat \
-H "Authorization: Bearer aosx_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"agentId": "kai",
"message": "Find me 10 dental clinics in Manchester with no social media presence"
}
// Continue that conversation on the next turn:
{
"agentId": "kai",
"chatId": "d4c3b2a1-...",
"message": "Now draft a cold email for the first three."
}'Response
{
"response": "Here are 10 dental clinics in Manchester...",
"agent": { "id": "kai", "name": "Kai" },
"chatId": "d4c3b2a1-...",
"usage": { "estimated_cost_usd": 0.0854 },
"wallet": { "balanceUsd": 12.34 }
}Request
curl -X POST https://app.agencyosx.ai/api/v1/chat \
-H "Authorization: Bearer aosx_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"agentId": "kai",
"message": "Find me 10 dental clinics in Manchester with no social media presence"
}
// Continue that conversation on the next turn:
{
"agentId": "kai",
"chatId": "d4c3b2a1-...",
"message": "Now draft a cold email for the first three."
}'Response
{
"response": "Here are 10 dental clinics in Manchester...",
"agent": { "id": "kai", "name": "Kai" },
"chatId": "d4c3b2a1-...",
"usage": { "estimated_cost_usd": 0.0854 },
"wallet": { "balanceUsd": 12.34 }
}/chatAuth requiredList conversations#
List your past conversations — both those held in the AgencyOSX app and those created via POST /chat — most recently active first. Paginated; filter by agent with agentId, and by created time with the date range.
Parameters
| Parameter | Required | Description | Example |
|---|---|---|---|
limitinteger · query | Optional | Rows per page (1–100). Defaults to 20; values above the cap clamp to 100. | 50 |
offsetinteger · query | Optional | Rows to skip, for pagination. Defaults to 0. | 40 |
agentIdstring · query | Optional | Only conversations with this agent. The name lowercased (e.g. "kai") or the legacy slug. | kai |
periodstring · query | Optional | Shortcut window ending now (see the values below). Mutually exclusive with startDate/endDate. | 7d |
startDatestring · query | Optional | Inclusive lower bound. A date-only YYYY-MM-DD is read as 00:00 UTC; or pass a full ISO 8601 timestamp. Use with endDate for an explicit range. | 2026-06-01 |
endDatestring · query | Optional | Exclusive upper bound — but a date-only YYYY-MM-DD includes that whole UTC day. Full ISO 8601 timestamps allowed. Defaults to now. Range capped at 366 days. | 2026-06-30 |
sortstring · query | Optional | Field to sort by — defaults to updated. Options:
| created |
orderstring · query | Optional | Sort direction: "asc" or "desc". Defaults to desc. | asc |
Response fields
| Field | Meaning | Example |
|---|---|---|
type | stringWhat the conversation is about — agent (a general conversation with an agent) or client (scoped to a specific client in your workspace). | — |
clientId | string · nullableThe client the conversation is scoped to when type is client; otherwise null. Resolve it with GET /clients. | — |
agentId | stringThe agent on the conversation — its public id (the agent's name lowercased, e.g. kai). | — |
title | stringA short title for the conversation, derived from its first message. | — |
Request
curl "https://app.agencyosx.ai/api/v1/chat?agentId=kai&limit=50&sort=created&order=desc" \
-H "Authorization: Bearer aosx_your_token_here"Response
{
"conversations": [
{
"id": "d4c3b2a1-...",
"type": "agent",
"clientId": null,
"agentId": "kai",
"title": "Manchester dental clinics",
"createdAt": "2026-06-28T10:00:00Z",
"updatedAt": "2026-06-28T10:14:00Z"
},
{
"id": "e5f6a7b8-...",
"type": "client",
"clientId": "b1f2c3d4-...",
"agentId": "mia",
"title": "Acme Dental — ad review",
"createdAt": "2026-06-27T09:00:00Z",
"updatedAt": "2026-06-27T09:20:00Z"
}
],
"pagination": { "total": 2, "limit": 20, "offset": 0, "hasMore": false }
}Request
curl "https://app.agencyosx.ai/api/v1/chat?agentId=kai&limit=50&sort=created&order=desc" \
-H "Authorization: Bearer aosx_your_token_here"Response
{
"conversations": [
{
"id": "d4c3b2a1-...",
"type": "agent",
"clientId": null,
"agentId": "kai",
"title": "Manchester dental clinics",
"createdAt": "2026-06-28T10:00:00Z",
"updatedAt": "2026-06-28T10:14:00Z"
},
{
"id": "e5f6a7b8-...",
"type": "client",
"clientId": "b1f2c3d4-...",
"agentId": "mia",
"title": "Acme Dental — ad review",
"createdAt": "2026-06-27T09:00:00Z",
"updatedAt": "2026-06-27T09:20:00Z"
}
],
"pagination": { "total": 2, "limit": 20, "offset": 0, "hasMore": false }
}/chat/:chatIdAuth requiredGet a conversation#
Retrieve one conversation and its messages, oldest first. Messages are paginated. Only conversations owned by your account are returned; anything else is a 404.
Parameters
| Parameter | Required | Description | Example |
|---|---|---|---|
chatIdstring | Optional | The conversation id (a UUID) from GET /chat. | d4c3b2a1-6f5e-4b8a-0d9c-2f1e4a3b5c6d |
limitinteger · query | Optional | Messages per page (1–100). Defaults to 50. | 50 |
offsetinteger · query | Optional | Messages to skip, for pagination. Defaults to 0. | 50 |
Response fields
| Field | Meaning | Example |
|---|---|---|
messages[].id | stringThe AgencyOSX message id — a UUID generated by AgencyOSX, not a model/provider message id. Stable, and unique within the conversation. | — |
messages[].role | stringWho sent the message — user (you) or agent (the AgencyOSX agent). | — |
Request
curl https://app.agencyosx.ai/api/v1/chat/d4c3b2a1-6f5e-4b8a-0d9c-2f1e4a3b5c6d \
-H "Authorization: Bearer aosx_your_token_here"Response
{
"conversation": {
"id": "d4c3b2a1-...",
"type": "agent",
"clientId": null,
"agentId": "kai",
"title": "Manchester dental clinics",
"createdAt": "2026-06-28T10:00:00Z",
"updatedAt": "2026-06-28T10:14:00Z"
},
"messages": [
{
"id": "1a2b3c4d-...",
"role": "user",
"content": "Find me 10 dental clinics in Manchester",
"createdAt": "2026-06-28T10:00:00Z"
},
{
"id": "2b3c4d5e-...",
"role": "agent",
"content": "Here are 10 dental clinics in Manchester...",
"createdAt": "2026-06-28T10:00:09Z"
}
],
"pagination": { "total": 2, "limit": 50, "offset": 0, "hasMore": false }
}Request
curl https://app.agencyosx.ai/api/v1/chat/d4c3b2a1-6f5e-4b8a-0d9c-2f1e4a3b5c6d \
-H "Authorization: Bearer aosx_your_token_here"Response
{
"conversation": {
"id": "d4c3b2a1-...",
"type": "agent",
"clientId": null,
"agentId": "kai",
"title": "Manchester dental clinics",
"createdAt": "2026-06-28T10:00:00Z",
"updatedAt": "2026-06-28T10:14:00Z"
},
"messages": [
{
"id": "1a2b3c4d-...",
"role": "user",
"content": "Find me 10 dental clinics in Manchester",
"createdAt": "2026-06-28T10:00:00Z"
},
{
"id": "2b3c4d5e-...",
"role": "agent",
"content": "Here are 10 dental clinics in Manchester...",
"createdAt": "2026-06-28T10:00:09Z"
}
],
"pagination": { "total": 2, "limit": 50, "offset": 0, "hasMore": false }
}Clients
The clients in your workspace. List them for context, or create one to organise an agency account's work.
/clientsAuth requiredList clients#
List the clients in your workspace, oldest first. Paginated and sortable.
Parameters
| Parameter | Required | Description | Example |
|---|---|---|---|
limitinteger · query | Optional | Rows per page (1–100). Defaults to 20; values above the cap clamp to 100. | 50 |
offsetinteger · query | Optional | Rows to skip, for pagination. Defaults to 0. | 40 |
sortstring · query | Optional | Field to sort by — defaults to created. Options:
| name |
orderstring · query | Optional | Sort direction: "asc" or "desc". Defaults to asc. | desc |
Request
curl "https://app.agencyosx.ai/api/v1/clients?limit=50&sort=name" \
-H "Authorization: Bearer aosx_your_token_here"Response
{
"clients": [
{
"id": "b1f2c3d4-...",
"name": "Acme Dental",
"slug": "acme-dental",
"industry": "Healthcare",
"website": "https://acmedental.com",
"brandColor": "#0ea5e9"
}
],
"pagination": { "total": 1, "limit": 20, "offset": 0, "hasMore": false }
}Request
curl "https://app.agencyosx.ai/api/v1/clients?limit=50&sort=name" \
-H "Authorization: Bearer aosx_your_token_here"Response
{
"clients": [
{
"id": "b1f2c3d4-...",
"name": "Acme Dental",
"slug": "acme-dental",
"industry": "Healthcare",
"website": "https://acmedental.com",
"brandColor": "#0ea5e9"
}
],
"pagination": { "total": 1, "limit": 20, "offset": 0, "hasMore": false }
}/clientsAuth requiredCreate a client#
Create a client in your primary workspace. Only a name is required; industry, website, and a brand colour are optional — the same fields as the app's Add client form. A URL-safe slug is generated automatically.
Parameters
| Parameter | Required | Description | Example |
|---|---|---|---|
namestring | Required | The client's display name (1–120 characters). | — |
industrystring | Optional | e.g. "Healthcare", "E-commerce". | — |
websitestring | Optional | The client's website. | — |
brandColorstring | Optional | The client's brand colour as a hex value (e.g. #0ea5e9). A non-hex value returns 400. | — |
Request
curl -X POST https://app.agencyosx.ai/api/v1/clients \
-H "Authorization: Bearer aosx_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Dental",
"industry": "Healthcare",
"website": "https://acmedental.com",
"brandColor": "#0ea5e9"
}'Response
// Optional fields you didn't set come back as null.
{
"client": {
"id": "b1f2c3d4-...",
"name": "Acme Dental",
"slug": "acme-dental",
"industry": "Healthcare",
"website": "https://acmedental.com",
"brandColor": "#0ea5e9"
}
}Request
curl -X POST https://app.agencyosx.ai/api/v1/clients \
-H "Authorization: Bearer aosx_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Dental",
"industry": "Healthcare",
"website": "https://acmedental.com",
"brandColor": "#0ea5e9"
}'Response
// Optional fields you didn't set come back as null.
{
"client": {
"id": "b1f2c3d4-...",
"name": "Acme Dental",
"slug": "acme-dental",
"industry": "Healthcare",
"website": "https://acmedental.com",
"brandColor": "#0ea5e9"
}
}Pipeline
Sales opportunities linked to shared Contacts. The public API supports intake through POST /pipeline.
/pipelineAuth requiredAdd a person to Pipeline#
Accept one person from a form, webhook, or integration. Creates or links their shared Contact and adds a Pipeline opportunity. Provide a name (full_name or first_name/last_name), email, or phone; company alone is not sufficient.
Notes
- Requires a workspace member, admin, or owner token. The token selects the workspace. Send a JSON object of at most 2,000,000 bytes.
- Send the person directly as the JSON body, without a people array. source_name defaults to "API".
- Returns 201 when a visible opportunity is created, otherwise 200. An external_id replay skips the opportunity instead of updating it. A contact with an existing visible opportunity also counts as skipped; its current stage is preserved. Tags may still be merged into the contact.
- Contacts are matched within the same client scope by normalized email or phone. Ambiguous matches create a separate contact marked for review. To explicitly choose a contact, use POST /contacts with contact_id and addToPipeline instead.
Parameters
| Parameter | Required | Description | Example |
|---|---|---|---|
full_namestring · body | Optional | Contact name, up to 320 characters. If omitted, first_name and last_name are joined. | Alex Morgan |
first_namestring · body | Optional | First name, up to 320 characters. | Alex |
last_namestring · body | Optional | Last name, up to 320 characters. | Morgan |
emailstring · body | Optional | Valid email address, up to 320 characters. Trimmed and lowercased. | alex@example.com |
phonestring · body | Optional | Phone number, up to 80 characters. Include the country code for consistent matching. | +14155550123 |
companystring · body | Optional | Business name, up to 320 characters. | Acme Dental |
tagsstring[] | string · body | Optional | Up to 30 tags, each at most 40 characters. An array or a comma-, semicolon-, or pipe-separated string is accepted. Tags are trimmed and deduplicated case-insensitively and merged with the contact's existing tags. The merged set may not exceed 30; otherwise the request fails with 422 and nothing is saved. | ["Dental"] |
source_namestring · body | Optional | Source label, up to 320 characters. Keep it unchanged when retrying with external_id. | Website form |
external_idstring · body | Optional | Stable source record ID, up to 320 characters. Recommended for retries. Scoped to the token workspace, clientId, and source_name; keep all of these unchanged on replay. | website-form-123 |
clientIdstring · body | Optional | UUID of an active client in the token workspace. Omit for agency-level intake. | — |
stagestring · body | Optional | Initial opportunity stage: "new_lead" (default), "contacted", "qualified", "proposal_sent", "won", or "lost". Display labels such as "Proposal sent" are also accepted, case-insensitively. | qualified |
Response fields
| Field | Meaning | Example |
|---|---|---|
created | integerNew visible opportunities: 0 or 1. | — |
skipped | integerPeople that did not create a new visible opportunity: 0 or 1. | — |
people[].id | stringNew Pipeline opportunity UUID, not a Contact UUID. The array is empty when skipped; existing opportunity IDs are not returned. | — |
Request
curl -X POST https://app.agencyosx.ai/api/v1/pipeline \
-H "Authorization: Bearer aosx_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"full_name": "Alex Morgan",
"email": "alex@example.com",
"company": "Acme Dental",
"stage": "qualified",
"tags": ["Dental"],
"source_name": "Website form",
"external_id": "website-form-123"
}'Response
{
"created": 1,
"skipped": 0,
"people": [{ "id": "4e91cb12-a650-4b0d-9872-bc713d408a65" }]
}Request
curl -X POST https://app.agencyosx.ai/api/v1/pipeline \
-H "Authorization: Bearer aosx_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"full_name": "Alex Morgan",
"email": "alex@example.com",
"company": "Acme Dental",
"stage": "qualified",
"tags": ["Dental"],
"source_name": "Website form",
"external_id": "website-form-123"
}'Response
{
"created": 1,
"skipped": 0,
"people": [{ "id": "4e91cb12-a650-4b0d-9872-bc713d408a65" }]
}Contacts
Shared CRM relationships, including people already in Pipeline. The public API supports intake through POST /contacts.
/contactsAuth requiredCreate or link a contact#
Save one shared CRM contact, optionally adding them to Pipeline. Provide a name (full_name or first_name/last_name), email, or phone, unless you supply contact_id. Send fields directly in the JSON body, without a people array.
Notes
- Requires a workspace member, admin, or owner token. The token selects the workspace. Send a JSON object of at most 2,000,000 bytes.
- Returns 201 for a new contact, or 200 when linked to an existing contact, even if a new opportunity is added. Without source_name, the retry scope uses "api". Use external_id for retry identity; requestId does not control API deduplication.
- A unique, compatible email or phone match reuses the contact and fills missing profile values. An external_id replay reuses the saved contact without updating its profile. Tags are merged, and addToPipeline can add an opportunity if none is visible. Existing opportunity stages are preserved.
- Ambiguous or archived matches return 409 with error and conflict: { row, candidates }. row is 1 for this endpoint; each candidate includes id, full_name, email, company, and archived. Retry with contact_id to choose an active contact, or force_new: true to create a separate contact. Restore an archived contact in the app before selecting it.
Parameters
| Parameter | Required | Description | Example |
|---|---|---|---|
full_namestring · body | Optional | Contact name, up to 320 characters. If omitted, first_name and last_name are joined. | Alex Morgan |
first_namestring · body | Optional | First name, up to 320 characters. | Alex |
last_namestring · body | Optional | Last name, up to 320 characters. | Morgan |
emailstring · body | Optional | Valid email address, up to 320 characters. Trimmed and lowercased. | alex@example.com |
phonestring · body | Optional | Phone number, up to 80 characters. Include the country code for consistent matching. | +14155550123 |
companystring · body | Optional | Business name, up to 320 characters. | Acme Dental |
tagsstring[] | string · body | Optional | Up to 30 tags, each at most 40 characters. An array or a comma-, semicolon-, or pipe-separated string is accepted. Tags are trimmed and deduplicated case-insensitively and merged with the contact's existing tags. The merged set may not exceed 30; otherwise the request fails with 422 and nothing is saved. | ["Dental"] |
source_namestring · body | Optional | Source label, up to 320 characters. Keep it unchanged when retrying with external_id. | Website form |
external_idstring · body | Optional | Stable source record ID, up to 320 characters. Recommended for retries. Scoped to the token workspace, clientId, and source_name; keep all of these unchanged on replay. | website-form-123 |
clientIdstring · body | Optional | UUID of an active client in the token workspace. Omit for agency-level intake. | — |
contact_idstring · body | Optional | UUID of an existing, unarchived contact in the same workspace and clientId scope. Selects the contact without overwriting its profile. | — |
force_newboolean · body | Optional | Create a separate contact despite email/phone matches. Defaults to false. Does not override a saved external_id replay or an explicit contact_id. | false |
addToPipelineboolean · body | Optional | Add a Pipeline opportunity if this contact has no visible opportunity. Defaults to false. | true |
stagestring · body | Optional | Initial opportunity stage: "new_lead" (default), "contacted", "qualified", "proposal_sent", "won", or "lost". Display labels such as "Proposal sent" are also accepted, case-insensitively. Used only when addToPipeline creates an opportunity. | qualified |
Response fields
| Field | Meaning | Example |
|---|---|---|
created | integerNew contacts: 0 or 1. This does not count opportunities. | — |
linked | integerExisting contacts reused, including external_id replays: 0 or 1. | — |
contactIds | string[]The created or linked Contact UUID. Opportunity IDs are not included. | — |
Request
curl -X POST https://app.agencyosx.ai/api/v1/contacts \
-H "Authorization: Bearer aosx_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"full_name": "Alex Morgan",
"email": "alex@example.com",
"company": "Acme Dental",
"source_name": "Website form",
"external_id": "website-form-123",
"addToPipeline": true,
"stage": "qualified"
}'Response
{
"created": 1,
"linked": 0,
"contactIds": ["83f9b461-2a15-4d89-b736-5c9180fae264"]
}Request
curl -X POST https://app.agencyosx.ai/api/v1/contacts \
-H "Authorization: Bearer aosx_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"full_name": "Alex Morgan",
"email": "alex@example.com",
"company": "Acme Dental",
"source_name": "Website form",
"external_id": "website-form-123",
"addToPipeline": true,
"stage": "qualified"
}'Response
{
"created": 1,
"linked": 0,
"contactIds": ["83f9b461-2a15-4d89-b736-5c9180fae264"]
}Leads
Prospects collected before they become Contacts or Pipeline opportunities. The public API supports intake through POST /scraped-leads.
/scraped-leadsAuth requiredAdd leads#
Import prospects into Leads without creating Contacts or Pipeline opportunities. Send one lead directly in the JSON body, or a people array containing 1 to 2,000 leads. Each lead needs at least a company, full_name, email, or phone.
Notes
- Requires a workspace member, admin, or owner token. The token selects the workspace. Send a JSON object of at most 2,000,000 bytes.
- Agency workspace only: omit clientId. This endpoint saves supplied lead data; it does not start a scraping job. Contacts and Pipeline promotion happen separately in the app.
- Keep requestId and the people array order unchanged when retrying the same request. Deduplication uses requestId plus the row position within the workspace, not email, phone, or external_id. Replays skip existing rows without updating their data. Use a new UUID for a new import.
- Returns 201 if any leads are created, otherwise 200. The same requestId reuses the batchId. Text fields in each lead accept up to 320 characters, except phone (80) and custom_fields (limits below).
- For batches, put the lead fields below inside each people object; requestId and batchName stay at the top level. For a single lead, put all fields at the top level and omit people.
Parameters
| Parameter | Required | Description | Example |
|---|---|---|---|
requestIdstring · body | Required | UUID identifying this import. Reuse it for retries of the same payload. | e78a610f-3462-4eb0-a97c-2c97a83b16a4 |
batchNamestring · body | Optional | Nonblank batch name, at most 120 characters. Defaults to "API import". Replays retain the original name. | October prospects |
peopleobject[] · body | Optional | Batch of 1 to 2,000 lead objects, subject to the total body size limit. Omit to submit a single lead directly. | — |
companystring · body | Optional | Business name. | Acme Dental |
full_namestring · body | Optional | Contact name. | Alex Morgan |
emailstring · body | Optional | Valid email address. Trimmed and lowercased. | alex@example.com |
phonestring · body | Optional | Phone number, up to 80 characters. | +14155550123 |
websitestring · body | Optional | HTTP or HTTPS website with no embedded credentials. A bare domain is normalized to HTTPS. | https://example.com |
locationstring · body | Optional | Full address. If omitted, assembled from the structured address fields. | — |
address_line1string · body | Optional | Street address. | — |
address_line2string · body | Optional | Suite, unit, or other address details. | — |
citystring · body | Optional | City or town. | — |
regionstring · body | Optional | State, province, or region. | — |
postal_codestring · body | Optional | Postal or ZIP code. | — |
countrystring · body | Optional | Country name or code. | — |
source_namestring · body | Optional | Per-lead source label. Defaults to "API import". | Partner directory |
latitudenumber | string · body | Optional | Finite latitude from -90 to 90. Numeric strings are accepted. | 37.7749 |
longitudenumber | string · body | Optional | Finite longitude from -180 to 180. Numeric strings are accepted. | -122.4194 |
custom_fieldsobject · body | Optional | Up to 100 additional string values. Keys may contain at most 120 characters and values at most 4,000. Blank values are dropped. | {"industry":"Dental"} |
Response fields
| Field | Meaning | Example |
|---|---|---|
created | integerNumber of newly saved leads. | — |
skipped | integerNumber of rows already saved under this requestId and row position. | — |
ids | string[]UUIDs of newly created leads only. Empty on a full replay. | — |
batchId | stringBatch UUID, also returned on retries and single-lead API imports. | — |
Request
curl -X POST https://app.agencyosx.ai/api/v1/scraped-leads \
-H "Authorization: Bearer aosx_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"requestId": "e78a610f-3462-4eb0-a97c-2c97a83b16a4",
"batchName": "October prospects",
"people": [
{
"company": "Acme Dental",
"full_name": "Alex Morgan",
"email": "alex@example.com",
"website": "https://example.com",
"city": "San Francisco",
"source_name": "Partner directory",
"custom_fields": { "industry": "Dental" }
}
]
}'Response
{
"created": 1,
"skipped": 0,
"ids": ["6c218e75-b942-4f70-a503-19d6e8b127cf"],
"batchId": "f14c9d28-672a-4d51-b830-7e6a0529c143"
}Request
curl -X POST https://app.agencyosx.ai/api/v1/scraped-leads \
-H "Authorization: Bearer aosx_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"requestId": "e78a610f-3462-4eb0-a97c-2c97a83b16a4",
"batchName": "October prospects",
"people": [
{
"company": "Acme Dental",
"full_name": "Alex Morgan",
"email": "alex@example.com",
"website": "https://example.com",
"city": "San Francisco",
"source_name": "Partner directory",
"custom_fields": { "industry": "Dental" }
}
]
}'Response
{
"created": 1,
"skipped": 0,
"ids": ["6c218e75-b942-4f70-a503-19d6e8b127cf"],
"batchId": "f14c9d28-672a-4d51-b830-7e6a0529c143"
}Workflows
Multi-step agent automations. List your workflows and inspect their run history. Build and enable them in the app.
/workflowsAuth requiredList workflows#
List your workflows, most recently updated first. Paginated and sortable.
Parameters
| Parameter | Required | Description | Example |
|---|---|---|---|
limitinteger · query | Optional | Rows per page (1–100). Defaults to 20; values above the cap clamp to 100. | 50 |
offsetinteger · query | Optional | Rows to skip, for pagination. Defaults to 0. | 40 |
sortstring · query | Optional | Field to sort by — defaults to updated. Options:
| created |
orderstring · query | Optional | Sort direction: "asc" or "desc". Defaults to desc. | asc |
Response fields
| Field | Meaning | Example |
|---|---|---|
isActive | booleanWhether the workflow is enabled. Workflows are built, configured, and enabled in the app. | — |
Request
curl "https://app.agencyosx.ai/api/v1/workflows?limit=50&sort=updated&order=desc" \
-H "Authorization: Bearer aosx_your_token_here"Response
{
"workflows": [
{
"id": "7c3e9f10-...",
"name": "Weekly client report",
"isActive": false,
"createdAt": "2026-06-28T10:00:00Z",
"updatedAt": "2026-06-28T10:00:00Z"
}
],
"pagination": { "total": 1, "limit": 20, "offset": 0, "hasMore": false }
}Request
curl "https://app.agencyosx.ai/api/v1/workflows?limit=50&sort=updated&order=desc" \
-H "Authorization: Bearer aosx_your_token_here"Response
{
"workflows": [
{
"id": "7c3e9f10-...",
"name": "Weekly client report",
"isActive": false,
"createdAt": "2026-06-28T10:00:00Z",
"updatedAt": "2026-06-28T10:00:00Z"
}
],
"pagination": { "total": 1, "limit": 20, "offset": 0, "hasMore": false }
}/workflows/:workflowId/runsAuth requiredList workflow runs#
List the run history for one of your workflows, newest first. Each run reports its status, step progress, and the amount charged to your wallet. Paginated; startDate/endDate filter by when each run was created.
Parameters
| Parameter | Required | Description | Example |
|---|---|---|---|
workflowIdstring · path | Required | The workflow's id (from GET /workflows). | 7c3e9f10-2a4b-4c6d-8e0f-1a2b3c4d5e6f |
limitinteger · query | Optional | Rows per page (1–100). Defaults to 20; values above the cap clamp to 100. | 50 |
offsetinteger · query | Optional | Rows to skip, for pagination. Defaults to 0. | 40 |
periodstring · query | Optional | Shortcut window ending now (see the values below). Mutually exclusive with startDate/endDate. | 7d |
startDatestring · query | Optional | Inclusive lower bound. A date-only YYYY-MM-DD is read as 00:00 UTC; or pass a full ISO 8601 timestamp. Use with endDate for an explicit range. | 2026-06-01 |
endDatestring · query | Optional | Exclusive upper bound — but a date-only YYYY-MM-DD includes that whole UTC day. Full ISO 8601 timestamps allowed. Defaults to now. Range capped at 366 days. | 2026-06-30 |
sortstring · query | Optional | Field to sort by — defaults to created. Options:
| started |
orderstring · query | Optional | Sort direction: "asc" or "desc". Defaults to desc. | asc |
Request
curl https://app.agencyosx.ai/api/v1/workflows/7c3e9f10-2a4b-4c6d-8e0f-1a2b3c4d5e6f/runs \
-H "Authorization: Bearer aosx_your_token_here"Response
{
"workflowId": "7c3e9f10-...",
"workflowName": "Weekly client report",
"runs": [
{
"id": "a1b2c3d4-...",
"workflowId": "7c3e9f10-...",
"status": "completed",
"triggerSource": "manual",
"inputData": { "client": "Acme Dental" },
"currentStep": 3,
"totalSteps": 3,
"chargedUsd": 0.0124,
"error": null,
"startedAt": "2026-06-28T10:05:01Z",
"completedAt": "2026-06-28T10:05:48Z",
"createdAt": "2026-06-28T10:05:00Z"
}
],
"pagination": { "total": 1, "limit": 20, "offset": 0, "hasMore": false }
}Request
curl https://app.agencyosx.ai/api/v1/workflows/7c3e9f10-2a4b-4c6d-8e0f-1a2b3c4d5e6f/runs \
-H "Authorization: Bearer aosx_your_token_here"Response
{
"workflowId": "7c3e9f10-...",
"workflowName": "Weekly client report",
"runs": [
{
"id": "a1b2c3d4-...",
"workflowId": "7c3e9f10-...",
"status": "completed",
"triggerSource": "manual",
"inputData": { "client": "Acme Dental" },
"currentStep": 3,
"totalSteps": 3,
"chargedUsd": 0.0124,
"error": null,
"startedAt": "2026-06-28T10:05:01Z",
"completedAt": "2026-06-28T10:05:48Z",
"createdAt": "2026-06-28T10:05:00Z"
}
],
"pagination": { "total": 1, "limit": 20, "offset": 0, "hasMore": false }
}/workflows/:workflowId/runs/:runIdAuth requiredRetrieve a workflow run#
Get a single run together with the result of every step — the agent that ran, the prompt it received, its response, any files it produced, and the amount charged.
Parameters
| Parameter | Required | Description | Example |
|---|---|---|---|
workflowIdstring · path | Required | The workflow's id. | — |
runIdstring · path | Required | The run's id (from GET /workflows/:workflowId/runs). | — |
Response fields
| Field | Meaning | Example |
|---|---|---|
run.status | stringThe run's overall state — completed, running, failed, or queued. | — |
run.triggerSource | stringWhat started the run — manual, webhook, or schedule. | — |
run.chargedUsd | numberTotal amount, in USD, charged to your wallet for the whole run (the sum of every step's chargedUsd). | — |
run.currentStep / run.totalSteps | integerStep progress — how many of the run's steps have completed, out of the total. | — |
steps[] | arrayOne entry per step, in order. Each carries the agentId that ran, the promptSent, its response, any files produced, its own chargedUsd, and its status. | — |
Request
curl https://app.agencyosx.ai/api/v1/workflows/7c3e9f10-2a4b-4c6d-8e0f-1a2b3c4d5e6f/runs/a1b2c3d4-5e6f-4a8b-9c0d-1e2f3a4b5c6d \
-H "Authorization: Bearer aosx_your_token_here"Response
{
"run": {
"id": "a1b2c3d4-...",
"workflowId": "7c3e9f10-...",
"status": "completed",
"triggerSource": "manual",
"inputData": { "client": "Acme Dental" },
"currentStep": 3,
"totalSteps": 3,
"chargedUsd": 0.0124,
"error": null,
"startedAt": "2026-06-28T10:05:01Z",
"completedAt": "2026-06-28T10:05:48Z",
"createdAt": "2026-06-28T10:05:00Z"
},
"steps": [
{
"id": "0f1e2d3c-...",
"stepIndex": 0,
"nodeId": "agent-1",
"agentId": "kai",
"promptSent": "Summarise this week's ad performance for Acme Dental",
"response": "This week Acme Dental's campaigns...",
"files": [],
"chargedUsd": 0.0061,
"status": "completed",
"error": null,
"startedAt": "2026-06-28T10:05:01Z",
"completedAt": "2026-06-28T10:05:22Z"
}
]
}Request
curl https://app.agencyosx.ai/api/v1/workflows/7c3e9f10-2a4b-4c6d-8e0f-1a2b3c4d5e6f/runs/a1b2c3d4-5e6f-4a8b-9c0d-1e2f3a4b5c6d \
-H "Authorization: Bearer aosx_your_token_here"Response
{
"run": {
"id": "a1b2c3d4-...",
"workflowId": "7c3e9f10-...",
"status": "completed",
"triggerSource": "manual",
"inputData": { "client": "Acme Dental" },
"currentStep": 3,
"totalSteps": 3,
"chargedUsd": 0.0124,
"error": null,
"startedAt": "2026-06-28T10:05:01Z",
"completedAt": "2026-06-28T10:05:48Z",
"createdAt": "2026-06-28T10:05:00Z"
},
"steps": [
{
"id": "0f1e2d3c-...",
"stepIndex": 0,
"nodeId": "agent-1",
"agentId": "kai",
"promptSent": "Summarise this week's ad performance for Acme Dental",
"response": "This week Acme Dental's campaigns...",
"files": [],
"chargedUsd": 0.0061,
"status": "completed",
"error": null,
"startedAt": "2026-06-28T10:05:01Z",
"completedAt": "2026-06-28T10:05:22Z"
}
]
}Account
Your account snapshot — wallet balance and auto-top-up — plus your usage and payment receipts over a window. All money figures are in USD.
/accountAuth requiredRetrieve your account#
Get a snapshot of the token-holder's account — id, email, wallet balance, and auto-top-up status. All money figures are in dollars. For usage, call GET /account/usage; for payment receipts, GET /account/receipts.
Response fields
| Field | Meaning | Example |
|---|---|---|
wallet.balanceUsd | numberYour current Account Credits balance, in USD. Spent before pay-as-you-go. | — |
wallet.payAsYouGo | objectPay-as-you-go status — enabled (a card is on file), owedUsd (usage accrued since your credits ran out) and the creditCapUsd at which your card is charged. | — |
Request
curl https://app.agencyosx.ai/api/v1/account \
-H "Authorization: Bearer aosx_your_token_here"Response
{
"account": {
"id": "8f7e6d5c-...",
"email": "owner@youragency.com"
},
"wallet": {
"balanceUsd": 12.34,
"payAsYouGo": { "enabled": true, "owedUsd": 0, "creditCapUsd": 25 }
}
}Request
curl https://app.agencyosx.ai/api/v1/account \
-H "Authorization: Bearer aosx_your_token_here"Response
{
"account": {
"id": "8f7e6d5c-...",
"email": "owner@youragency.com"
},
"wallet": {
"balanceUsd": 12.34,
"payAsYouGo": { "enabled": true, "owedUsd": 0, "creditCapUsd": 25 }
}
}/account/usageAuth requiredGet usage#
Your retail spend over a window — the total charged to your wallet, broken down by agent and per day.
Notes
- Pick the window with a
periodshortcut (todayfor current usage;7d/30d/90d) or an explicitstartDate/endDaterange. - Defaults to the last 30 days when nothing is supplied.
- All cost figures are the amount charged to your wallet, in USD (retail) — never token counts.
Parameters
| Parameter | Required | Description | Example |
|---|---|---|---|
periodstring · query | Optional | Shortcut window ending now (see the values below). Mutually exclusive with startDate/endDate. | 7d |
startDatestring · query | Optional | Inclusive lower bound. A date-only YYYY-MM-DD is read as 00:00 UTC; or pass a full ISO 8601 timestamp. Use with endDate for an explicit range. | 2026-06-01 |
endDatestring · query | Optional | Exclusive upper bound — but a date-only YYYY-MM-DD includes that whole UTC day. Full ISO 8601 timestamps allowed. Defaults to now. Range capped at 366 days. | 2026-06-30 |
Response fields
| Field | Meaning | Example |
|---|---|---|
period | stringEchoes the window you requested — one of today, 24h, 7d, 30d, 90d, or custom for an explicit startDate/endDate range. | — |
totalCostUsd | numberThe total amount charged to your wallet over the window, in USD (retail). Equals the sum of every agent's costUsd. | — |
byAgent | objectRetail spend keyed by agent — each agent's public id (e.g. kai) mapped to its costUsd for the window. Spend that can't be attributed to an agent appears under unknown. | — |
dailyBreakdown | arrayOne row per UTC day with spend in the window — newest first — each with date and costUsd. | — |
Request
curl "https://app.agencyosx.ai/api/v1/account/usage?period=30d" \
-H "Authorization: Bearer aosx_your_token_here"Response
{
"period": "30d",
"startDate": "2026-05-30T00:00:00.000Z",
"endDate": "2026-06-29T12:00:00.000Z",
"currency": "usd",
"totalCostUsd": 4.82,
"byAgent": {
"kai": { "costUsd": 3.10 },
"finn": { "costUsd": 1.72 }
},
"dailyBreakdown": [
{ "date": "2026-06-29", "costUsd": 0.21 },
{ "date": "2026-06-28", "costUsd": 0.64 }
]
}Request
curl "https://app.agencyosx.ai/api/v1/account/usage?period=30d" \
-H "Authorization: Bearer aosx_your_token_here"Response
{
"period": "30d",
"startDate": "2026-05-30T00:00:00.000Z",
"endDate": "2026-06-29T12:00:00.000Z",
"currency": "usd",
"totalCostUsd": 4.82,
"byAgent": {
"kai": { "costUsd": 3.10 },
"finn": { "costUsd": 1.72 }
},
"dailyBreakdown": [
{ "date": "2026-06-29", "costUsd": 0.21 },
{ "date": "2026-06-28", "costUsd": 0.64 }
]
}/account/receiptsAuth requiredList receipts#
List your wallet payments and refunds — every top-up (manual and automatic) plus any refund — newest first. Paginated; filter by date with the standard range. Usage charges aren't receipts; for those, see GET /account/usage.
Parameters
| Parameter | Required | Description | Example |
|---|---|---|---|
limitinteger · query | Optional | Rows per page (1–100). Defaults to 20; values above the cap clamp to 100. | 50 |
offsetinteger · query | Optional | Rows to skip, for pagination. Defaults to 0. | 40 |
periodstring · query | Optional | Shortcut window ending now (see the values below). Mutually exclusive with startDate/endDate. | 7d |
startDatestring · query | Optional | Inclusive lower bound. A date-only YYYY-MM-DD is read as 00:00 UTC; or pass a full ISO 8601 timestamp. Use with endDate for an explicit range. | 2026-06-01 |
endDatestring · query | Optional | Exclusive upper bound — but a date-only YYYY-MM-DD includes that whole UTC day. Full ISO 8601 timestamps allowed. Defaults to now. Range capped at 366 days. | 2026-06-30 |
Response fields
| Field | Meaning | Example |
|---|---|---|
type | stringWhat the entry is — topup (you topped up), auto_topup (a saved card was charged automatically), refund, or reversal. | — |
amountUsd | numberThe payment amount in USD, always positive — type indicates whether it was money in or a refund. | — |
receiptUrl | string · nullableA link to the Stripe receipt for the payment, or null when none is available (e.g. for some refunds). | — |
Request
curl "https://app.agencyosx.ai/api/v1/account/receipts?period=90d" \
-H "Authorization: Bearer aosx_your_token_here"Response
{
"receipts": [
{
"id": "c4d5e6f7-...",
"date": "2026-06-28T09:30:00Z",
"type": "topup",
"amountUsd": 25,
"status": "succeeded",
"receiptUrl": "https://pay.stripe.com/receipts/..."
}
],
"pagination": { "total": 1, "limit": 20, "offset": 0, "hasMore": false }
}Request
curl "https://app.agencyosx.ai/api/v1/account/receipts?period=90d" \
-H "Authorization: Bearer aosx_your_token_here"Response
{
"receipts": [
{
"id": "c4d5e6f7-...",
"date": "2026-06-28T09:30:00Z",
"type": "topup",
"amountUsd": 25,
"status": "succeeded",
"receiptUrl": "https://pay.stripe.com/receipts/..."
}
],
"pagination": { "total": 1, "limit": 20, "offset": 0, "hasMore": false }
}MCP integration#
AgencyOSX exposes a remote Model Context Protocol (MCP) server, so you can connect your agents directly to Claude Code, Claude Desktop, Manus, Cursor, or any MCP-compatible client. No local server required — your client connects to the hosted endpoint.
Every method — initialize, tools/list, and tools/call — requires your Bearer token. MCP clients send it on every request, so set it once in your client config. Protocol errors are returned as JSON-RPC error objects.
Claude Code (CLI)
The fastest path. Grab a token from Settings → API Access, then run one command:
# Add AgencyOSX to Claude Code (one command)
claude mcp add --transport http agencyosx https://app.agencyosx.ai/api/mcp \
--header "Authorization: Bearer aosx_your_token_here"
# Verify it connected (should show "agencyosx" with 2 tools)
claude mcp list
# Share it with your team via a committed .mcp.json instead:
claude mcp add --transport http agencyosx --scope project \
https://app.agencyosx.ai/api/mcp \
--header "Authorization: Bearer aosx_your_token_here"Confirm with claude mcp list or /mcp inside Claude Code — you'll see agencyosx with its tools. MCP tool calls must return within ~60s, so keep individual requests focused.
Claude Desktop
Add the following to your claude_desktop_config.json:
{
"mcpServers": {
"agencyosx": {
"url": "https://app.agencyosx.ai/api/mcp",
"headers": {
"Authorization": "Bearer aosx_your_token_here"
}
}
}
}Manus
Manus supports remote MCP servers natively. Point it at the same endpoint:
Server URL: https://app.agencyosx.ai/api/mcp
Authorization: Bearer aosx_your_token_hereAvailable MCP tools
list_agentsList all available AgencyOSX agents with their expertise areas.
ask_agentChat with an AgencyOSX specialist agent and get the response back as text.
Code examples#
A complete script — list agents, chat, and read your conversation history — in your language of choice.
# List all agents
curl https://app.agencyosx.ai/api/v1/agents \
-H "Authorization: Bearer aosx_your_token_here"
# Chat with an agent
curl -X POST https://app.agencyosx.ai/api/v1/chat \
-H "Authorization: Bearer aosx_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"agentId": "kai",
"message": "Find me 10 dental clinics in Manchester"
}'
# List your past conversations with an agent
curl "https://app.agencyosx.ai/api/v1/chat?agentId=kai" \
-H "Authorization: Bearer aosx_your_token_here"
# Read one conversation's full history
curl https://app.agencyosx.ai/api/v1/chat/d4c3b2a1-6f5e-4b8a-0d9c-2f1e4a3b5c6d \
-H "Authorization: Bearer aosx_your_token_here"Rate limits#
Rate limits are applied per account — shared across all of your API tokens, so adding tokens does not raise your limit. When a limit is exceeded, the API returns 429 Too Many Requests. Wait the stated window before retrying.
| Endpoint | Limit | Window |
|---|---|---|
POST /chat | 60 requests | Per minute |
GET /chat, GET /chat/:chatId | 120 requests | Per minute |
POST /api/mcp (tools/call) | 60 requests | Per minute |
GET /agents, GET /agents/:id | 120 requests | Per minute |
GET /clients, GET /workflows | 120 requests | Per minute |
POST /clients | 30 requests | Per minute |
POST /pipeline | 60 requests | Per minute |
POST /contacts | 60 requests | Per minute |
POST /scraped-leads | 60 requests | Per minute |
GET /workflows/:id/runs (+ /:runId) | 120 requests | Per minute |
GET /account, GET /account/usage, GET /account/receipts | 120 requests | Per minute |
Billable endpoints (e.g. POST /chat) run on OSX LLM and are charged to your AgencyOSX wallet in USD. Check your balance and spend with GET /account and GET /account/usage.
Example 429
{
"error": "Rate limit exceeded. Maximum 60 requests per minute."
}Example 429
{
"error": "Rate limit exceeded. Maximum 60 requests per minute."
}Error handling#
Error responses are a flat JSON object with an error field. The HTTP status code carries the semantic meaning.
| Status | Meaning |
|---|---|
400 | Bad request - missing/invalid parameters, or an input over the size limit |
401 | Unauthorized - missing, invalid, revoked, or expired API token |
402 | Payment required - wallet balance too low; add funds in Settings → Usage |
403 | Forbidden - account suspended, workspace inaccessible, token workspace mismatch, or insufficient write access |
404 | Not found - the requested resource or user profile does not exist or is inaccessible; Pipeline intake also returns this for an unavailable client |
409 | Contact conflict - choose a candidate with contact_id or create a separate contact with force_new; details are in conflict |
413 | Payload too large - Pipeline, Contacts, and Leads intake bodies must not exceed 2,000,000 bytes |
422 | Invalid intake - correct the person, lead, stage, client, or requestId described in error; a tag merge that would leave a contact with more than 30 tags is also rejected |
429 | Rate limited - the per-minute request limit or the daily safety cap was reached; wait and retry |
500 | Internal error - contact support if persistent |
502 | Upstream error - the underlying model provider failed; retry shortly |
503 | Service unavailable - the model service is temporarily unavailable; retry shortly |
Error response
{
"error": "The provided API token is invalid or has been revoked."
}Error response
{
"error": "The provided API token is invalid or has been revoked."
}