# markus — full documentation for AI agents markus is the ai marketer. it runs multi-channel outreach campaigns — create campaigns with AI-generated personalized content, discover leads via Google Maps and social platforms, manage contacts, automate email and social messaging, and track conversations across email, Instagram, Twitter, and LinkedIn. website: https://markusai.dev docs: https://markusai.dev/docs llms.txt: https://markusai.dev/llms.txt openapi: https://markusai.dev/openapi.json ## what markus is - an AI-powered outreach automation platform for email, Instagram DMs, Twitter DMs, and LinkedIn messages - a lead discovery engine that finds contacts via Google Maps, web scraping, and social platforms - a campaign management system with objective-driven workflows (venue booking, product promotion, custom outreach) - a unified inbox that aggregates conversations across all channels with real-time updates - a developer platform with a REST API (80+ endpoints), MCP server, webhooks, and agent payment protocols (x402, MPP) ## what markus is not - not a general-purpose CRM (focused on outreach automation, not deal pipelines) - not an email client or ESP (uses SendGrid for delivery) - not a social media scheduler (sends direct messages, not public posts) ## key features - multi-channel outreach (email, Instagram, Twitter, LinkedIn) - AI content generation and personalization via Claude - automated contact discovery via Google Maps and social platforms - objective-driven campaigns (venue booking, product promotion, custom) - AI-analyzed target profiles for product promotion campaigns - message approval workflow with AI feedback - unified inbox across all channels with real-time updates - real-time analytics and engagement tracking - developer REST API with scoped API keys and webhooks - MCP server for AI agent integration - agent payment protocols: x402 (USDC on Base) and MPP (Stripe) ## how it works ### campaign creation flow 1. choose objective type: venue_dj (book DJ gigs), venue_host (host events), product_promotion (AI-analyzed marketing), custom (your own criteria) 2. for product_promotion: enter product name, URL, and description. AI analyzes the product and generates target profiles with job titles, industries, pain points, buying signals, and recommended channels 3. discover contacts: search Google Maps by location/business type, scrape social platforms, or import CSV 4. AI drafts personalized messages for each contact using Claude, considering campaign objective, contact data, and product analysis 5. review and approve drafts in the inbox. markus sends approved messages across channels (email via SendGrid, DMs via platform APIs) 6. track responses in the unified inbox. AI can help draft follow-up responses ### multi-channel orchestration the orchestrator picks the best channel per contact based on available handles: - if contact has email → email via SendGrid - if contact has Instagram handle + connected account → Instagram DM - if contact has Twitter handle + connected account → Twitter DM - if contact has LinkedIn profile + connected account → LinkedIn message humanized delays between messages prevent anti-detection triggers ### delivery tracking - email: SendGrid webhooks track sent, delivered, opened, clicked, bounced, spam reports - Twitter: Account Activity API webhooks for inbound DMs - Instagram: Messaging webhooks for inbound DMs and read receipts - all channels feed into unified conversation view with forward-only status progression ## agent payment protocols AI agents can use markus without an API key via credits purchased with x402 (crypto) or MPP (fiat). 1 credit = 1 campaign with 30 contacts, 300 messages, and 50 AI generations included. ### how it works 1. create campaigns: `POST /api/v1/campaigns` — x402 agents pay per-campaign ($2.25) or use credits. MPP agents use credits. 2. create campaigns — each campaign costs 1 credit 3. use discovery, AI generation, and messaging for free (bundled with the campaign) 4. check balance: `GET /api/v1/agent/credits` 5. credits never expire. refunded if campaign creation fails. ### pricing two options: - x402 per-campaign: $2.25. send X-PAYMENT header with POST /campaigns. - credit pack (MPP only): 5 credits for $12.00 via POST /agent/credits. credits are tried first. if balance is 0 and a payment header is present, per-campaign settlement happens inline. x402 is cheaper because there are no payment processing fees. ### x402 (crypto) - network: eip155:8453 (Base mainnet) - asset: USDC (0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913) - header: X-PAYMENT (base64 encoded payment proof) - facilitator: Coinbase Developer Platform - used only for credit purchases (not per-operation) ### MPP (fiat via Stripe) - header: Authorization: Payment - challenge: WWW-Authenticate: Payment (in 402 response) - method: stripe charge (card payment methods) - used only for credit purchases (not per-operation) ### 402 response format (insufficient credits) ```json { "error": { "code": "PAY_005", "message": "insufficient credits: 1 credit required, balance is 0. x402: pay $2.25 per-campaign, or buy a pack at POST /api/v1/agent/credits" }, "credits": { "required": 1, "balance": 0 }, "per_campaign": { "x402_price": "2.25" }, "pack": { "purchase_url": "/api/v1/agent/credits", "id": "pack_5", "credits": 5, "mpp_price": "12.00" } } ``` to pay per-campaign, resend the same POST /campaigns request with a payment header at the per-campaign price. ### agent-accessible endpoints - POST /api/v1/agent/credits — buy 5 credits via MPP ($12.00). x402 agents use per-campaign pricing instead. - GET /api/v1/agent/credits — check balance - POST /api/v1/campaigns — 1 credit per campaign - POST /api/v1/discovery/search — free (bundled) - POST /api/v1/ai/generate — free (bundled) - POST /api/v1/messages/send — free (bundled) ## quick start 1. create a campaign — choose objective type (venue booking, product promotion, or custom outreach) 2. add target profiles — define who you want to reach (AI generates profiles for product promotion) 3. discover contacts — search Google Maps, social platforms, or import a CSV 4. review AI drafts — markus generates personalized messages for each contact using Claude 5. activate — approve drafts and markus sends them across your chosen channels ## pricing ### subscription plans limits are per campaign. each campaign includes its own allocation of contacts, messages, and AI generations. | plan | price | campaigns/period | contacts/campaign | messages/campaign | AI/campaign | |------|-------|-----------------|-------------------|------------------|-------------| | starter | $12/month | 5 | 30 | 300 | 50 | | pro | $25/month | 15 | 30 | 200 | 35 | | premium | $50/month | 25 | 30 | 200 | 40 | ### agent pay-per-use (x402/MPP) no subscription required. buy credits via x402 or MPP. 1 credit = 1 campaign (30 contacts, 300 messages, 50 AI generations included). 5 credits for $12.00 (MPP only). x402 agents pay per-campaign ($2.25) at POST /campaigns. ## FAQ ### getting started Q: how do I create my first campaign? A: navigate to the campaigns page and click 'create campaign'. choose your objective type (venue booking, product promotion, or custom outreach), fill in the details, discover contacts, and activate. markus drafts personalized messages for each contact using AI. Q: how does contact discovery work? A: markus uses Google Maps, web scraping, and social media to find leads based on your criteria. specify your target location, business type, and filters, and markus finds relevant contacts with emails and social handles for your campaigns. Q: how do I connect my social accounts? A: go to settings and click 'connected accounts'. you can link Twitter, Instagram, and LinkedIn via OAuth. once connected, markus can send DMs and track conversations across all channels. ### campaigns Q: what are objective types? A: campaigns are driven by objectives: 'book DJ gigs' and 'host events' for venue outreach, 'promote a product' for AI-analyzed product marketing with auto-generated target profiles, and 'custom outreach' for your own criteria. the objective determines the wizard flow and discovery strategy. Q: how does AI message generation work? A: when you activate a campaign, markus uses Claude to draft personalized emails for each contact. it considers the contact's business, your campaign objective, and any product analysis to write relevant outreach. you review and approve drafts before they're sent. Q: can I customize email templates? A: yes. use variables like {{business_name}} and {{contact_name}} to personalize. you can also let the AI generate templates based on your campaign objective and target audience. ### contacts & discovery Q: how do I import existing contacts? A: go to contacts and click 'import'. upload a CSV with columns for business name, email, phone, website, Instagram, and address. download the template first to get the correct format. Q: what data sources does discovery use? A: markus discovers contacts via Google Maps (location-based business search), social media platforms (Instagram, Twitter, LinkedIn), and web scraping. each source returns different data — Maps gives addresses and phone numbers, social gives handles and follower counts. ### channels & messaging Q: what channels does markus support? A: email (via SendGrid), Instagram DMs, Twitter DMs, and LinkedIn messages. each channel has its own adapter for formatting, rate limits, and delivery tracking. the orchestrator picks the best channel per contact based on available handles. Q: what happens when someone replies? A: replies are tracked in the unified inbox. email replies come through SendGrid inbound webhooks, Twitter and Instagram DMs come through platform webhooks. AI can help draft responses, which you review before sending. Q: what is the email sending limit? A: message limits are per campaign, not global. starter: 300 messages per campaign, pro: 200, premium: 200. this protects sender reputation and deliverability. the number of campaigns you can create per billing period determines total capacity. ### billing Q: what plans are available? A: markus offers starter, pro, and premium plans with monthly and yearly billing. each plan includes limits for campaigns, contacts, emails, and AI generations per billing period. see the pricing page for current rates. Q: how do I upgrade or cancel? A: go to settings > billing to manage your subscription. you can upgrade, downgrade, or cancel anytime. cancellations take effect at the end of your current billing period. ### API & integrations Q: does markus have an API? A: yes. markus has a REST API at /api/v1 with 80+ endpoints covering campaigns, contacts, conversations, messages, discovery, AI, analytics, billing, and webhooks. authenticate with API keys (mk_live_...) or agent payment protocols (x402, MPP). Q: how do I get an API key? A: go to settings > API keys and click 'generate key'. choose the scopes you need (campaigns:read, contacts:write, etc.) and set an expiration. the raw key is only shown once — save it securely. Q: what are x402 and MPP? A: x402 and MPP are agent payment protocols that let AI agents use the markus API without an API key. x402 agents pay per-campaign ($2.25). MPP agents buy credit packs (5 for $12.00). each campaign includes 30 contacts, 300 messages, and 50 AI generations. credits never expire and are refunded if campaign creation fails. Q: how do I track campaign performance via API? A: the analytics endpoints provide open rates, click rates, response rates, and conversions. filter by date range and campaign. webhook subscriptions can push events to your server in real-time. --- # API documentation ## base URL ``` https://markusai.dev/api/v1 ``` ## authentication three authentication methods supported: 1. API key: `Authorization: Bearer mk_live_...` 2. x402 wallet: `X-PAYMENT: ` 3. MPP credential: `Authorization: Payment ` create API keys at https://markusai.dev/settings under the API section. agent payment protocols (x402/MPP) require no API key — see agent payment protocols section above. ## scopes API keys support scoped permissions. assign only the scopes your integration needs. | scope | description | |-------|-------------| | campaigns:read | view campaigns, target profiles, and campaign usage | | campaigns:write | create, update, delete campaigns and target profiles | | contacts:read | view contacts, contact usage, and contact conversations | | contacts:write | create, update, delete, and bulk import contacts | | conversations:read | view conversations, messages, and engagement metrics | | conversations:write | mark conversations as read | | messages:read | view pending messages | | messages:write | approve, reject, retry, provide feedback, and bulk manage messages | | ai:read | view AI usage and suggestions | | ai:write | generate AI content and analyze responses | | discovery:read | view discovery jobs and data sources | | discovery:write | run discovery searches | | analytics:read | view analytics, performance metrics, and reports | | billing:read | view billing plans and subscription information | | channels:read | view connected accounts and channel availability | | channels:write | send messages through channels | | notifications:read | view notifications | | notifications:write | update, delete, and mark notifications as read | ## endpoints ### authentication verify your API key and view its metadata. #### GET /api/v1/keys/me get information about the currently authenticated API key **response (200):** ```json { "success": true, "data": { "id": "uuid", "name": "production", "key_prefix": "mk_live_a1b2c3d4", "scopes": ["*"], "last_used_at": "2024-01-15T10:30:00Z", "is_active": true } } ``` ### API keys programmatically manage API keys. #### GET /api/v1/keys list all API keys for your account **query parameters:** | name | type | required | description | |------|------|----------|-------------| | page | integer | no | page number (default: 1) | | page_size | integer | no | items per page (default: 50) | **response (200):** ```json { "success": true, "data": [ { "id": "uuid", "name": "production", "key_prefix": "mk_live_a1b2c3d4", "scopes": ["*"], "is_active": true, "last_used_at": "2024-01-15T10:30:00Z" } ], "meta": { "pagination": { "page": 1, "page_size": 50, "total": 1, "total_pages": 1 } } } ``` #### POST /api/v1/keys create a new API key. the raw key is only returned once. **request body (JSON):** | name | type | required | description | |------|------|----------|-------------| | name | string | yes | key name (1-100 characters) | | scopes | string[] | no | permission scopes (e.g. ["campaigns:read", "contacts:write"]). defaults to ["*"] for full access | | expires_at | ISO 8601 | no | optional expiration date | **response (200):** ```json { "success": true, "data": { "id": "uuid", "name": "staging", "key_prefix": "mk_live_e5f6g7h8", "key": "mk_live_e5f6g7h8a9b0c1d2e3f4g5h6i7j8k9l0m1n2o3p4", "scopes": ["campaigns:read", "contacts:read"], "is_active": true } } ``` **notes:** - the raw key value is only returned on creation — store it securely #### DELETE /api/v1/keys/:id revoke an API key. this action is irreversible. **response (200):** ```json { "success": true, "data": null } ``` **errors:** - 404 APIKEY_001: API key not found ### campaigns create and manage outreach campaigns. #### GET /api/v1/campaigns list all campaigns **scope:** `campaigns:read` **query parameters:** | name | type | required | description | |------|------|----------|-------------| | page | integer | no | page number (default: 1) | | page_size | integer | no | items per page (default: 50) | | status | string | no | filter by status (draft, active, paused, completed) | **response (200):** ```json { "success": true, "data": [ { "id": "uuid", "name": "Q1 outreach", "objective_type": "product_promotion", "status": "active", "total_contacts": 150, "contacts_reached": 42, "responses_received": 8, "created_at": "2024-01-15T10:30:00Z" } ], "meta": { "pagination": { "page": 1, "page_size": 50, "total": 1, "total_pages": 1 } } } ``` #### POST /api/v1/campaigns create a new campaign **scope:** `campaigns:write` **request body (JSON):** | name | type | required | description | |------|------|----------|-------------| | name | string | yes | campaign name | | objective_type | "venue_dj" | "venue_host" | "product_promotion" | "custom" | yes | campaign objective type | | product_name | string | no | product name (for product_promotion objective) | | product_url | url | no | product URL for AI analysis | | product_description | string | no | product description for AI context | **response (200):** ```json { "success": true, "data": { "id": "uuid", "name": "spring outreach", "objective_type": "product_promotion", "status": "draft", "created_at": "2024-01-15T10:30:00Z" } } ``` **errors:** - 402 PAY_005: insufficient credits (1 credit required) — purchase credits at POST /api/v1/agent/credits - 429 CAMP_009: campaign limit reached for billing period **notes:** - agents: 1 credit per campaign. buy credits at POST /api/v1/agent/credits. #### GET /api/v1/campaigns/:id get a specific campaign with full details **scope:** `campaigns:read` **errors:** - 404 CAMP_001: campaign not found #### PATCH /api/v1/campaigns/:id update a campaign **scope:** `campaigns:write` **request body (JSON):** | name | type | required | description | |------|------|----------|-------------| | name | string | no | campaign name | | status | "draft" | "active" | "paused" | "completed" | no | campaign status | | product_name | string | no | product name | | product_url | url | no | product URL | **errors:** - 404 CAMP_001: campaign not found - 400 CAMP_008: invalid status transition #### DELETE /api/v1/campaigns/:id soft-delete a campaign (sets status to 'deleted') **scope:** `campaigns:write` **response (200):** ```json { "success": true, "data": null } ``` **errors:** - 404 CAMP_001: campaign not found #### GET /api/v1/campaigns/:id/contacts list contacts in a campaign **scope:** `campaigns:read` **query parameters:** | name | type | required | description | |------|------|----------|-------------| | page | integer | no | page number (default: 1) | | page_size | integer | no | items per page (default: 50) | | status | string | no | filter by contact status | **errors:** - 404 CAMP_001: campaign not found #### GET /api/v1/campaigns/:id/usage get campaign usage statistics **scope:** `campaigns:read` **response (200):** ```json { "success": true, "data": { "total_contacts": 150, "contacts_reached": 42, "responses_received": 8, "emails_sent": 42, "emails_opened": 28, "emails_clicked": 12 } } ``` **errors:** - 404 CAMP_001: campaign not found #### POST /api/v1/campaigns/:id/analyze trigger AI product analysis for a campaign **scope:** `campaigns:write` **errors:** - 404 CAMP_001: campaign not found - 429 AI_004: AI rate limit exceeded **notes:** - analyzes the campaign's product URL/description and generates target profiles - returns immediately — analysis runs asynchronously #### GET /api/v1/campaigns/:id/target-profiles list target profiles for a campaign **scope:** `campaigns:read` **response (200):** ```json { "success": true, "data": [ { "id": "uuid", "campaign_id": "uuid", "name": "SaaS founders", "profile_type": "b2b_professional", "fit_score": 0.85, "job_titles": ["CEO", "CTO", "founder"], "industries": ["technology", "SaaS"], "discovery_keywords": ["saas founder", "tech startup"], "recommended_channels": ["email", "linkedin"] } ] } ``` **errors:** - 404 CAMP_001: campaign not found #### POST /api/v1/campaigns/:id/target-profiles create a target profile for a campaign **scope:** `campaigns:write` **request body (JSON):** | name | type | required | description | |------|------|----------|-------------| | name | string | yes | profile name | | profile_type | "local_business" | "content_creator" | "influencer" | "b2b_professional" | "ecommerce" | "saas_company" | "agency" | "freelancer" | "other" | yes | target profile type | | job_titles | string[] | no | target job titles | | industries | string[] | no | target industries | | discovery_keywords | string[] | no | keywords for contact discovery | | recommended_channels | string[] | no | recommended outreach channels | **errors:** - 404 CAMP_001: campaign not found #### DELETE /api/v1/campaigns/:id/target-profiles/:profileId delete a target profile **scope:** `campaigns:write` **response (200):** ```json { "success": true, "data": null } ``` **errors:** - 404 CAMP_001: campaign or profile not found ### contacts manage contacts and import leads. #### GET /api/v1/contacts list all contacts **scope:** `contacts:read` **query parameters:** | name | type | required | description | |------|------|----------|-------------| | page | integer | no | page number (default: 1) | | page_size | integer | no | items per page (default: 50) | | search | string | no | search by name or email | **response (200):** ```json { "success": true, "data": [ { "id": "uuid", "email": "jane@example.com", "first_name": "Jane", "last_name": "Smith", "company_name": "Acme Inc", "tags": ["lead", "enterprise"] } ], "meta": { "pagination": { "page": 1, "page_size": 50, "total": 1, "total_pages": 1 } } } ``` #### POST /api/v1/contacts create a single contact **scope:** `contacts:write` **request body (JSON):** | name | type | required | description | |------|------|----------|-------------| | email | string | yes | contact email | | first_name | string | no | first name | | last_name | string | no | last name | | company_name | string | no | company name | | tags | string[] | no | tags for categorization | | campaign_id | uuid | no | campaign to associate with | **errors:** - 409 CONT_002: duplicate contact email - 429 CONT_009: contact limit reached for billing period #### GET /api/v1/contacts/:id get a specific contact **scope:** `contacts:read` **errors:** - 404 CONT_001: contact not found #### PATCH /api/v1/contacts/:id update a contact **scope:** `contacts:write` **request body (JSON):** | name | type | required | description | |------|------|----------|-------------| | first_name | string | no | first name | | last_name | string | no | last name | | company_name | string | no | company name | | tags | string[] | no | tags | **errors:** - 404 CONT_001: contact not found #### DELETE /api/v1/contacts/:id soft-delete a contact (sets deleted_at timestamp) **scope:** `contacts:write` **response (200):** ```json { "success": true, "data": null } ``` **errors:** - 404 CONT_001: contact not found #### POST /api/v1/contacts/bulk bulk import contacts (up to 500 per request) **scope:** `contacts:write` **request body (JSON):** | name | type | required | description | |------|------|----------|-------------| | contacts | array | yes | array of contact objects (email required, first_name/last_name/company_name optional) | | campaign_id | uuid | no | campaign to associate contacts with | **response (200):** ```json { "success": true, "data": { "imported": 48, "skipped": 2, "errors": [] } } ``` **errors:** - 429 CONT_009: contact limit reached for billing period **notes:** - duplicates are skipped by email. max 500 contacts per request. #### GET /api/v1/contacts/usage get contact usage for the current billing period **scope:** `contacts:read` **response (200):** ```json { "success": true, "data": { "used": 150, "limit": 1000, "remaining": 850, "billing_period_start": "2024-01-01T00:00:00Z", "billing_period_end": "2024-02-01T00:00:00Z" } } ``` #### GET /api/v1/contacts/:id/conversations get conversations for a specific contact **scope:** `contacts:read` **errors:** - 404 CONT_001: contact not found ### conversations manage conversations across all channels. #### GET /api/v1/conversations list all conversations **scope:** `conversations:read` **query parameters:** | name | type | required | description | |------|------|----------|-------------| | page | integer | no | page number (default: 1) | | page_size | integer | no | items per page (default: 50) | | status | string | no | filter by status (active, completed, failed, human_review) | | campaign_id | uuid | no | filter by campaign | #### GET /api/v1/conversations/:id get a conversation with details **scope:** `conversations:read` **errors:** - 404 CONV_001: conversation not found #### GET /api/v1/conversations/:id/messages get messages for a conversation **scope:** `conversations:read` **errors:** - 404 CONV_001: conversation not found #### POST /api/v1/conversations/:id/read mark a conversation as read **scope:** `conversations:write` **response (200):** ```json { "success": true, "data": null } ``` **errors:** - 404 CONV_001: conversation not found #### GET /api/v1/conversations/engagement get engagement metrics across conversations **scope:** `conversations:read` **response (200):** ```json { "success": true, "data": { "total_conversations": 250, "active_conversations": 42, "response_rate": 0.28, "avg_response_time_hours": 4.5 } } ``` ### messages manage message approval workflow and sending. #### GET /api/v1/messages/pending list messages pending approval **scope:** `messages:read` **query parameters:** | name | type | required | description | |------|------|----------|-------------| | page | integer | no | page number (default: 1) | | page_size | integer | no | items per page (default: 50) | | campaign_id | uuid | no | filter by campaign | #### POST /api/v1/messages/:id/approve approve a pending message for sending **scope:** `messages:write` **errors:** - 404 MSG_005: message not found - 409 MSG_004: message already approved #### POST /api/v1/messages/:id/reject reject a pending message **scope:** `messages:write` **request body (JSON):** | name | type | required | description | |------|------|----------|-------------| | reason | string | no | rejection reason | **errors:** - 404 MSG_005: message not found #### POST /api/v1/messages/:id/retry retry a failed message **scope:** `messages:write` **errors:** - 404 MSG_005: message not found #### POST /api/v1/messages/:id/feedback submit feedback to revise a message with AI **scope:** `messages:write` **request body (JSON):** | name | type | required | description | |------|------|----------|-------------| | feedback | string | yes | revision instructions (e.g. 'make it more casual and shorter') | **errors:** - 404 MSG_005: message not found - 429 AI_004: AI rate limit exceeded #### POST /api/v1/messages/bulk bulk approve or reject messages **scope:** `messages:write` **request body (JSON):** | name | type | required | description | |------|------|----------|-------------| | action | "approve" | "reject" | yes | action to perform | | ids | uuid[] | yes | message queue item IDs | **response (200):** ```json { "success": true, "data": { "processed": 5, "failed": 0 } } ``` ### AI AI-powered content generation and analysis. #### POST /api/v1/ai/generate generate AI content (email subject + body) **scope:** `ai:write` **request body (JSON):** | name | type | required | description | |------|------|----------|-------------| | contact_id | uuid | yes | target contact ID | | conversation_id | uuid | yes | conversation ID | | generation_type | "initial_outreach" | "follow_up" | "reply" | yes | type of content to generate | | context | object | no | additional context for generation | **errors:** - 429 AI_004: AI rate limit exceeded - 500 AI_002: AI generation failed **notes:** - free for agents (bundled with campaign credit). #### POST /api/v1/ai/generate/campaign generate campaign-wide content strategy **scope:** `ai:write` **request body (JSON):** | name | type | required | description | |------|------|----------|-------------| | campaign_id | uuid | yes | campaign ID | | content_type | "email_template" | "outreach_strategy" | yes | type of content to generate | **errors:** - 404 CAMP_001: campaign not found - 429 AI_004: AI rate limit exceeded #### POST /api/v1/ai/analyze analyze a response for sentiment and suggested actions **scope:** `ai:write` **request body (JSON):** | name | type | required | description | |------|------|----------|-------------| | conversation_id | uuid | yes | conversation ID | | message_content | string | yes | message text to analyze | **response (200):** ```json { "success": true, "data": { "sentiment": "positive", "intent": "interested", "suggested_actions": ["schedule_meeting", "send_more_info"], "confidence": 0.92 } } ``` **errors:** - 429 AI_004: AI rate limit exceeded #### GET /api/v1/ai/usage get AI generation usage for the current billing period **scope:** `ai:read` **response (200):** ```json { "success": true, "data": { "used": 45, "limit": 500, "remaining": 455, "billing_period_start": "2024-01-01T00:00:00Z", "billing_period_end": "2024-02-01T00:00:00Z" } } ``` #### GET /api/v1/ai/suggestions get AI-suggested responses for a conversation **scope:** `ai:read` **query parameters:** | name | type | required | description | |------|------|----------|-------------| | conversation_id | uuid | yes | conversation ID | **response (200):** ```json { "success": true, "data": { "suggestions": [ { "type": "reply", "content": "thanks for your interest! ...", "confidence": 0.88 }, { "type": "follow_up", "content": "just wanted to check in...", "confidence": 0.72 } ] } } ``` ### discovery search for and discover new contacts. #### POST /api/v1/discovery/search run a contact discovery search **scope:** `discovery:write` **request body (JSON):** | name | type | required | description | |------|------|----------|-------------| | query | string | yes | search query | | source | "google_maps" | "instagram" | "twitter" | "linkedin" | yes | discovery data source | | max_results | integer | no | maximum results to return (default: 50) | | campaign_id | uuid | no | campaign to associate results with | **response (200):** ```json { "success": true, "data": { "job_id": "uuid", "status": "processing" } } ``` **errors:** - 429 DISC_008: discovery quota exceeded - 429 DISC_006: discovery rate limited **notes:** - discovery runs asynchronously. use the job status endpoint to check progress. - free for agents (bundled with campaign credit). #### POST /api/v1/discovery/search/campaign run discovery search using campaign target profiles **scope:** `discovery:write` **request body (JSON):** | name | type | required | description | |------|------|----------|-------------| | campaign_id | uuid | yes | campaign ID | | target_profile_id | uuid | no | specific target profile to use (defaults to all) | **errors:** - 404 CAMP_001: campaign not found - 429 DISC_008: discovery quota exceeded #### GET /api/v1/discovery/jobs/:id get the status of a discovery job **scope:** `discovery:read` **response (200):** ```json { "success": true, "data": { "id": "uuid", "status": "completed", "contacts_found": 42, "source": "google_maps", "completed_at": "2024-01-15T10:35:00Z" } } ``` **errors:** - 404 DISC_002: discovery job not found #### GET /api/v1/discovery/sources list available discovery data sources **scope:** `discovery:read` **response (200):** ```json { "success": true, "data": [ { "id": "google_maps", "name": "Google Maps", "available": true }, { "id": "instagram", "name": "Instagram", "available": true }, { "id": "twitter", "name": "Twitter", "available": true }, { "id": "linkedin", "name": "LinkedIn", "available": false } ] } ``` ### analytics campaign performance metrics and reporting. #### GET /api/v1/analytics/overview get overall analytics overview **scope:** `analytics:read` **response (200):** ```json { "success": true, "data": { "total_campaigns": 12, "active_campaigns": 3, "total_contacts": 1250, "total_conversations": 450, "overall_response_rate": 0.36, "emails_sent_this_period": 230 } } ``` #### GET /api/v1/analytics/campaigns/:id get analytics for a specific campaign **scope:** `analytics:read` **response (200):** ```json { "success": true, "data": { "campaign_id": "uuid", "contacts_total": 150, "contacts_reached": 42, "emails_sent": 42, "emails_opened": 28, "emails_clicked": 12, "responses_received": 8, "response_rate": 0.19, "open_rate": 0.67, "click_rate": 0.29 } } ``` **errors:** - 404 CAMP_001: campaign not found #### GET /api/v1/analytics/performance get performance metrics over time **scope:** `analytics:read` **query parameters:** | name | type | required | description | |------|------|----------|-------------| | period | "7d" | "30d" | "90d" | no | time period (default: 30d) | #### GET /api/v1/analytics/response-time get average response time metrics **scope:** `analytics:read` **query parameters:** | name | type | required | description | |------|------|----------|-------------| | campaign_id | uuid | no | filter by campaign | #### GET /api/v1/analytics/contact-types get contact type distribution **scope:** `analytics:read` ### billing view billing plans and subscription information. #### GET /api/v1/billing/plans get available billing plans and their features **scope:** `billing:read` **response (200):** ```json { "success": true, "data": [ { "id": "free", "name": "free", "price_monthly": 0, "limits": { "campaigns": 2, "contacts": 100, "emails_per_month": 50, "ai_generations": 20, "discovery_searches": 5 } }, { "id": "starter", "name": "starter", "price_monthly": 29, "limits": { "campaigns": 10, "contacts": 1000, "emails_per_month": 500, "ai_generations": 200, "discovery_searches": 25 } } ] } ``` ### channels manage connected accounts and multi-channel messaging. #### GET /api/v1/channels/accounts list connected social accounts **scope:** `channels:read` **response (200):** ```json { "success": true, "data": [ { "id": "uuid", "platform": "twitter", "account_name": "@myaccount", "status": "active", "connected_at": "2024-01-15T10:30:00Z" } ] } ``` #### GET /api/v1/channels/availability check which channels are available for messaging **scope:** `channels:read` **response (200):** ```json { "success": true, "data": { "email": { "available": true, "configured": true }, "twitter": { "available": true, "configured": true }, "instagram": { "available": true, "configured": false }, "linkedin": { "available": true, "configured": false } } } ``` #### POST /api/v1/channels/send send a message through a specific channel **scope:** `channels:write` **request body (JSON):** | name | type | required | description | |------|------|----------|-------------| | channel | "email" | "twitter" | "instagram" | "linkedin" | yes | outreach channel | | conversation_id | uuid | yes | conversation ID | | content | string | yes | message content | **errors:** - 404 CONV_001: conversation not found - 429 MSG_015: message limit reached **notes:** - free for agents (bundled with campaign credit). ### notifications manage user notifications. #### GET /api/v1/notifications list notifications **scope:** `notifications:read` **query parameters:** | name | type | required | description | |------|------|----------|-------------| | page | integer | no | page number (default: 1) | | page_size | integer | no | items per page (default: 50) | #### PATCH /api/v1/notifications/:id update a notification (e.g. mark as read) **scope:** `notifications:write` **request body (JSON):** | name | type | required | description | |------|------|----------|-------------| | read | boolean | no | mark as read/unread | **errors:** - 404 NOTIF_001: notification not found #### DELETE /api/v1/notifications/:id delete a notification **scope:** `notifications:write` **response (200):** ```json { "success": true, "data": null } ``` **errors:** - 404 NOTIF_001: notification not found #### POST /api/v1/notifications/read-all mark all notifications as read **scope:** `notifications:write` **response (200):** ```json { "success": true, "data": { "updated": 12 } } ``` ### webhooks manage webhook endpoints for receiving real-time event notifications. #### GET /api/v1/webhooks list all webhook endpoints **query parameters:** | name | type | required | description | |------|------|----------|-------------| | page | integer | no | page number (default: 1) | | page_size | integer | no | items per page (default: 50) | #### POST /api/v1/webhooks create a new webhook endpoint **request body (JSON):** | name | type | required | description | |------|------|----------|-------------| | url | url | yes | webhook endpoint URL (HTTPS) | | events | string[] | yes | event types to subscribe to (e.g. ["campaign.created", "contact.created"]). use ["*"] for all events | | description | string | no | optional description | **response (200):** ```json { "success": true, "data": { "id": "uuid", "url": "https://example.com/webhooks/markus", "secret": "whsec_abc123...", "events": ["campaign.created", "contact.created"], "is_active": true } } ``` **notes:** - the signing secret is only returned on creation — store it securely #### GET /api/v1/webhooks/:id get a specific webhook endpoint **errors:** - 404 HOOK_001: webhook not found #### PATCH /api/v1/webhooks/:id update a webhook endpoint **request body (JSON):** | name | type | required | description | |------|------|----------|-------------| | url | url | no | webhook endpoint URL | | events | string[] | no | event types | | is_active | boolean | no | enable/disable | **errors:** - 404 HOOK_001: webhook not found #### DELETE /api/v1/webhooks/:id delete a webhook endpoint **response (200):** ```json { "success": true, "data": null } ``` **errors:** - 404 HOOK_001: webhook not found #### POST /api/v1/webhooks/:id/test send a test event to a webhook endpoint **response (200):** ```json { "success": true, "data": { "delivered": true, "response_status": 200, "response_time_ms": 145 } } ``` **errors:** - 404 HOOK_001: webhook not found #### GET /api/v1/webhooks/:id/deliveries list recent webhook deliveries **query parameters:** | name | type | required | description | |------|------|----------|-------------| | page | integer | no | page number (default: 1) | | page_size | integer | no | items per page (default: 50) | **errors:** - 404 HOOK_001: webhook not found ### 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. #### GET /api/v1/agent/credits check the agent's current credit balance **response (200):** ```json { "success": true, "data": { "balance": 7, "identity_id": "uuid" } } ``` **notes:** - requires x402 or MPP identity header (no payment needed for balance check). - returns null balance for API key users (they don't use credits). #### POST /api/v1/agent/credits purchase 5 credits with x402 or MPP payment **response (201):** ```json { "success": true, "data": { "balance": 5, "credits_added": 5, "pack": "pack_5" } } ``` **errors:** - 402 PAY_001: payment required — $12.00 (MPP) for 5 credits. x402 agents: use per-campaign pricing at POST /campaigns. - 500 PAY_007: purchase failed after payment (contact support) **notes:** - credit packs: 5 credits for $12.00 (MPP only). x402 agents pay per-campaign ($2.25) at POST /campaigns. - x402 agents can pay per-campaign ($2.25) by sending X-PAYMENT header with POST /campaigns. MPP agents must use packs. - credits never expire. send X-PAYMENT (x402) or Authorization: Payment (MPP) header. ## MCP server the markus MCP server lets AI agents manage campaigns, contacts, conversations, messages, and more. ```bash npx -y @jclvsh/markus-mcp ``` set the MARKUS_API_KEY environment variable to your API key. recommended scopes: campaigns:read, campaigns:write, contacts:read, contacts:write, messages:read, messages:write, ai:read, ai:write. ## OpenAPI spec OpenAPI 3.1 spec available at: https://markusai.dev/openapi.json ## 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 |