markus API
programmatic access to your campaigns, contacts, conversations, and more.
base URL
https://markusai.dev/api/v1the markus API uses REST conventions with JSON request and response bodies. all endpoints require authentication via API key.
responses follow a consistent shape: { success: true, data: ... } for success and { success: false, error: { code, message } } for errors.
MCP server
the markus MCP server lets AI agents (Claude Code, Cursor, etc.) manage campaigns, contacts, conversations, messages, and more via the Model Context Protocol.
install
npx -y @jclvsh/markus-mcpClaude Code
{
"mcpServers": {
"markus": {
"command": "npx",
"args": [
"-y",
"@jclvsh/markus-mcp"
],
"env": {
"MARKUS_API_KEY": "mk_live_YOUR_API_KEY"
}
}
}
}add to .claude/settings.json
Cursor
{
"mcpServers": {
"markus": {
"command": "npx",
"args": [
"-y",
"@jclvsh/markus-mcp"
],
"env": {
"MARKUS_API_KEY": "mk_live_YOUR_API_KEY"
}
}
}
}add to .cursor/mcp.json
available tools
65 tools across 14 categories:
| category | tools | description |
|---|---|---|
| authentication | 1 | verify your API key and view its metadata. |
| API keys | 3 | programmatically manage API keys. |
| campaigns | 11 | create and manage outreach campaigns. |
| contacts | 8 | manage contacts and import leads. |
| conversations | 5 | manage conversations across all channels. |
| messages | 6 | manage message approval workflow and sending. |
| AI | 5 | AI-powered content generation and analysis. |
| discovery | 4 | search for and discover new contacts. |
| analytics | 5 | campaign performance metrics and reporting. |
| billing | 1 | view billing plans and subscription information. |
| channels | 3 | manage connected accounts and multi-channel messaging. |
| notifications | 4 | manage user notifications. |
| webhooks | 7 | manage webhook endpoints for receiving real-time event notifications. |
| agent credits | 2 | credit-based payments for AI agents using x402 (USDC on Base) or MPP (Stripe). 1 credit = 1 campaign (30 contacts, 300 messages, 50 AI generations). no subscription required. |
recommended scopes
for most AI agent use cases: campaigns:read, campaigns:write, contacts:read, contacts:write, messages:read, messages:write, ai:read, ai:write
authentication
all API requests must include your API key in the Authorization header.
header format
Authorization: Bearer mk_live_YOUR_API_KEYexample request
curl -X GET "https://markusai.dev/api/v1/campaigns" \
-H "Authorization: Bearer mk_live_YOUR_API_KEY"JavaScript
const response = await fetch("https://markusai.dev/api/v1/campaigns", {
headers: {
"Authorization": "Bearer mk_live_YOUR_API_KEY",
"Content-Type": "application/json",
},
});
const { success, data } = await response.json();Python
import requests
response = requests.get(
"https://markusai.dev/api/v1/campaigns",
headers={"Authorization": "Bearer mk_live_YOUR_API_KEY"},
)
data = response.json()scopes
API keys can be scoped to limit access. use * for full access, or specify individual scopes like campaigns:read.
| scope | description |
|---|---|
| campaigns:read | list and view campaigns |
| campaigns:write | create, update, delete campaigns |
| contacts:read | list and view contacts |
| contacts:write | create, update, delete contacts |
| conversations:read | list and view conversations |
| conversations:write | create and update conversations |
| messages:read | list pending messages |
| messages:write | approve, reject, retry messages |
| ai:read | view AI usage and suggestions |
| ai:write | generate AI content and analysis |
| discovery:read | view discovery results |
| discovery:write | run discovery searches |
| analytics:read | view analytics data |
| billing:read | view billing information |
| notifications:read | view notifications |
| notifications:write | update notifications |
| channels:read | view connected accounts |
| channels:write | manage channel connections |
error responses
authentication errors return HTTP 401 or 403:
{
"success": false,
"error": {
"code": "AUTH_001",
"message": "invalid or missing API key",
"requestId": "req_abc123"
}
}rate limits
API requests are rate-limited per API key. exceeding limits returns HTTP 429.
limits by scope
| scope | limit | window | description |
|---|---|---|---|
| global (per key) | 120 | 1 minute | overall API request limit |
| burst (per key) | 20 | 1 second | burst request protection |
| campaigns:write | 30 | 1 minute | campaign mutations |
| contacts:write | 60 | 1 minute | contact mutations |
| ai:write | 20 | 1 minute | AI generation requests |
| discovery:write | 5 | 1 hour | discovery searches |
response headers
rate limit information is included in response headers:
| header | description |
|---|---|
| X-RateLimit-Limit | maximum requests allowed in the window |
| X-RateLimit-Remaining | requests remaining in current window |
| X-RateLimit-Reset | UTC epoch seconds when the window resets |
| Retry-After | seconds to wait before retrying (only on 429) |
429 response example
{
"success": false,
"error": {
"code": "RATE_001",
"message": "too many requests. please try again in 45 seconds.",
"requestId": "req_abc123"
}
}authentication
verify your API key and view its metadata.
get information about the currently authenticated API key
API keys
programmatically manage API keys.
list all API keys for your account
create a new API key. the raw key is only returned once.
revoke an API key. this action is irreversible.
campaigns
create and manage outreach campaigns.
list all campaigns
create a new campaign
get a specific campaign with full details
update a campaign
soft-delete a campaign (sets status to 'deleted')
list contacts in a campaign
get campaign usage statistics
trigger AI product analysis for a campaign
list target profiles for a campaign
create a target profile for a campaign
delete a target profile
contacts
manage contacts and import leads.
list all contacts
create a single contact
get a specific contact
update a contact
soft-delete a contact (sets deleted_at timestamp)
bulk import contacts (up to 500 per request)
get contact usage for the current billing period
get conversations for a specific contact
conversations
manage conversations across all channels.
list all conversations
get a conversation with details
get messages for a conversation
mark a conversation as read
get engagement metrics across conversations
messages
manage message approval workflow and sending.
list messages pending approval
approve a pending message for sending
reject a pending message
retry a failed message
submit feedback to revise a message with AI
bulk approve or reject messages
AI
AI-powered content generation and analysis.
generate AI content (email subject + body)
generate campaign-wide content strategy
analyze a response for sentiment and suggested actions
get AI generation usage for the current billing period
get AI-suggested responses for a conversation
discovery
search for and discover new contacts.
run a contact discovery search
run discovery search using campaign target profiles
get the status of a discovery job
list available discovery data sources
analytics
campaign performance metrics and reporting.
get overall analytics overview
get analytics for a specific campaign
get performance metrics over time
get average response time metrics
get contact type distribution
billing
view billing plans and subscription information.
get available billing plans and their features
channels
manage connected accounts and multi-channel messaging.
list connected social accounts
check which channels are available for messaging
send a message through a specific channel
notifications
manage user notifications.
list notifications
update a notification (e.g. mark as read)
delete a notification
mark all notifications as read
webhooks
manage webhook endpoints for receiving real-time event notifications.
list all webhook endpoints
create a new webhook endpoint
get a specific webhook endpoint
update a webhook endpoint
delete a webhook endpoint
send a test event to a webhook endpoint
list recent webhook deliveries
agent credits
credit-based payments for AI agents using x402 (USDC on Base) or MPP (Stripe). 1 credit = 1 campaign (30 contacts, 300 messages, 50 AI generations). no subscription required.
check the agent's current credit balance
purchase 5 credits with x402 or MPP payment
webhook events
receive real-time HTTP POST notifications when events happen in your account.
how webhooks work
1. register a webhook endpoint URL via the API or settings page.
2. select which events you want to receive (or subscribe to all with *).
3. when an event occurs, we send an HTTP POST to your endpoint with the event payload.
4. respond with a 2xx status code within 10 seconds.
payload format
all webhook payloads follow this structure:
{
"id": "evt_uuid",
"type": "campaign.created",
"created_at": "2024-01-15T10:30:00Z",
"data": {
"id": "uuid",
"name": "Q1 outreach",
"status": "draft"
}
}signature verification
every webhook request includes an X-Webhook-Signature header. verify it using HMAC-SHA256 with your webhook signing secret.
import crypto from "crypto";
function verifyWebhookSignature(payload: string, signature: string, secret: string): boolean {
const expected = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// In your webhook handler:
const payload = await request.text();
const signature = request.headers.get("X-Webhook-Signature");
if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
return new Response("invalid signature", { status: 401 });
}
const event = JSON.parse(payload);
// Process event...Python verification
import hmac
import hashlib
def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)event types
campaign
| event | description |
|---|---|
campaign.created | a new campaign was created |
campaign.updated | a campaign was updated |
campaign.deleted | a campaign was soft-deleted |
contact
| event | description |
|---|---|
contact.created | a new contact was created or imported |
contact.updated | a contact was updated |
contact.deleted | a contact was soft-deleted |
conversation
| event | description |
|---|---|
conversation.created | a new conversation was started |
conversation.reply_received | a reply was received in a conversation |
message
| event | description |
|---|---|
message.sent | a message was sent |
message.delivered | a message was confirmed delivered |
message.opened | a message was opened by the recipient |
message.bounced | a message bounced |
message.approved | a pending message was approved |
message.rejected | a pending message was rejected |
discovery
| event | description |
|---|---|
discovery.completed | a discovery search completed successfully |
discovery.failed | a discovery search failed |
delivery policy
webhook delivery is attempted once. your endpoint should respond with a 2xx status code within 10 seconds. automatic retries with exponential backoff are coming soon.
error codes
all errors return { success: false, error: { code, message } }
authentication
| code | HTTP | description |
|---|---|---|
| AUTH_001 | 401 | unauthorized — invalid or missing API key |
| AUTH_002 | 403 | forbidden — insufficient permissions |
| AUTH_003 | 401 | session expired |
API keys
| code | HTTP | description |
|---|---|---|
| APIKEY_001 | 401 | invalid or missing API key |
| APIKEY_002 | 401 | API key expired |
| APIKEY_003 | 401 | API key revoked |
| APIKEY_004 | 403 | scope not granted for this operation |
| APIKEY_005 | 429 | API key creation limit reached |
campaigns
| code | HTTP | description |
|---|---|---|
| CAMP_001 | 404 | campaign not found |
| CAMP_002 | 500 | campaign creation failed |
| CAMP_003 | 500 | campaign update failed |
| CAMP_005 | 409 | campaign already active |
| CAMP_007 | 400 | campaign has no contacts |
| CAMP_008 | 400 | invalid campaign status transition |
| CAMP_009 | 429 | campaign limit reached for billing period |
contacts
| code | HTTP | description |
|---|---|---|
| CONT_001 | 404 | contact not found |
| CONT_002 | 409 | duplicate contact email |
| CONT_003 | 400 | invalid email address |
| CONT_007 | 500 | bulk import failed |
| CONT_009 | 429 | contact limit reached for billing period |
conversations
| code | HTTP | description |
|---|---|---|
| CONV_001 | 404 | conversation not found |
| CONV_002 | 500 | conversation update failed |
| CONV_003 | 500 | message send failed |
messages
| code | HTTP | description |
|---|---|---|
| MSG_001 | 500 | message send failed |
| MSG_004 | 409 | message already approved |
| MSG_005 | 404 | message not found |
| MSG_015 | 429 | message limit reached for billing period |
AI
| code | HTTP | description |
|---|---|---|
| AI_001 | 503 | AI service unavailable |
| AI_002 | 500 | AI generation failed |
| AI_003 | 500 | AI analysis failed |
| AI_004 | 429 | AI rate limit exceeded |
| AI_005 | 400 | AI content filtered |
discovery
| code | HTTP | description |
|---|---|---|
| DISC_001 | 500 | discovery job failed |
| DISC_002 | 404 | discovery job not found |
| DISC_006 | 429 | discovery rate limited |
| DISC_007 | 400 | invalid search query |
| DISC_008 | 429 | discovery quota exceeded |
webhooks
| code | HTTP | description |
|---|---|---|
| HOOK_001 | 400 | invalid webhook signature |
| HOOK_003 | 400 | invalid webhook payload |
rate limiting
| code | HTTP | description |
|---|---|---|
| RATE_001 | 429 | too many requests |
| RATE_002 | 429 | API rate limit exceeded |
server
| code | HTTP | description |
|---|---|---|
| SERVER_001 | 500 | internal server error |
| SERVER_002 | 400 | request validation failed |
| SERVER_003 | 404 | resource not found |
| SERVER_004 | 405 | method not allowed |
| SERVER_005 | 400 | invalid input |
| SERVER_006 | 500 | database error |
| SERVER_008 | 504 | request timeout |