# OutreachAgent API Reference (LLM Context) ## Overview OutreachAgent is an API-first email infrastructure platform for teams building AI agents. Bring your own agent runtime. OutreachAgent handles inboxes, delivery, workflows, scheduling, webhooks, and observability. Optional intelligence helpers can add semantic search and structured extraction when configured, but they are not required. Base URL: https://api.outreachagent.dev/v1 Auth: Bearer token in Authorization header. API keys are prefixed with "rm_". Rate limit: 100 requests/minute per organization. Pagination: All list endpoints accept ?limit= (max 100, default 20) and ?offset= (default 0). Response shape for lists: { items: T[], total: number, limit: number, offset: number } Idempotency: POST /v1/messages/send and POST /v1/enrollments accept Idempotency-Key header. Errors: { error: { code: string, message: string } } with appropriate HTTP status. Retryable status codes: 408, 429, 500, 502, 503, 504. ## Resource Hierarchy Organization > Pod > Inbox > Thread > Message > Attachment Organizations are the tenant boundary. Pods provide regional/logical isolation. Inboxes are email identities. Threads group related messages. Messages are individual emails. ## Data Model Organization { id, name, slug, plan: "free"|"pro", createdAt } ApiKey { id, name, maskedKey, lastUsedAt, createdAt } Pod { id, name, region, createdAt } Inbox { id, address, displayName (shown as sender name in recipient inboxes and notifications), status: "active"|"warming"|"disabled", podId, createdAt, updatedAt } Domain { id, name, status: "pending"|"verifying"|"verified"|"failed", dkimStatus: "missing"|"pending"|"verified", spfStatus: "missing"|"pending"|"verified", createdAt, dnsRecords?: [{ type, host, value, priority? }] } Message { id, inboxId, threadId, direction: "inbound"|"outbound", subject, preview, from, to: string[], status: "queued"|"sent"|"delivered"|"received"|"bounced", createdAt, attachments: Attachment[] } Thread { id, inboxId, subject, participants: string[], lastMessageAt, messageCount } Attachment { id, fileName, contentType, size, url } Contact { id, email, fullName, attributes: Record, segmentIds: string[], lastSeenAt, createdAt } Template { id, name, subject, status: "active"|"draft"|"paused"|"failed"|"archived", version, updatedAt } TemplatePreview { subject, html, missingVariables: string[] } WorkflowDefinition { id, name, version, status: "active"|"draft"|"paused"|"failed"|"archived", trigger: "manual"|"api"|"event"|"scheduled", nodes: WorkflowNode[], exitCriteria?: ExitCriterion[], optOutMode?: "link"|"reply"|"header_only", schedule?: { cronExpression: string, contactFilter?: { segmentId?: string } }, pausedNodeIds?: string[], createdAt, updatedAt } WorkflowNode { id, type: "delay"|"send_email"|"send_webhook"|"wait_for_event"|"branch"|"exit"|"goto"|"ab_test", label, nextNodeId, delayAmount?, delayUnit?: "minutes"|"hours"|"days", delayMinutes?, templateId?, webhookUrl?, webhookBody?, webhookAuthHeader?, waitForEvent?, timeoutMinutes?, timeoutNextNodeId?, branches?: [{ condition: { field, operator: "equals"|"contains"|"exists"|"not_equals"|"is_true"|"is_false", value? }, nextNodeId }], targetNodeId?: string, maxIterations?: number, variants?: AbTestVariant[], webhookResponseBranches?: [{ condition: { field, operator, value? }, nextNodeId }] } AbTestVariant { id, templateId, weight } ExitCriterion { trigger: "reply"|"bounce"|"unsubscribe"|"manual"|"custom_event", eventName?, replyClassifications?: string[] } Enrollment { id, workflowId, contactId, status: "active"|"completed"|"paused"|"failed"|"exited", currentNodeId, exitReason?, variantId?, createdAt } WorkflowAnalytics { workflowId, totalEnrollments, activeEnrollments, completedEnrollments, exitedEnrollments, failedEnrollments, emailsSent, emailsBounced, completionRate, exitRate } BulkEnrollmentResult { contactId, enrollmentId?, status: "enrolled"|"skipped"|"failed", reason? } SendLimit { id, organizationId, inboxId?, dailyLimit, createdAt } WorkflowTemplate { id, name, description, category, trigger, nodes, exitCriteria?, optOutMode? } ExecutionLog { id, enrollmentId, nodeId, status: "scheduled"|"running"|"completed"|"failed"|"skipped", message, createdAt } WebhookEndpoint { id, url, status: "healthy"|"degraded"|"failing", subscribedEvents: string[], errorRate, createdAt } WebhookDelivery { id, endpointId, eventName, status: "delivered"|"retrying"|"failed", attemptCount, lastAttemptAt } WebhookEvent { id, name, createdAt, resourceId, resourceType } UsageSummary { totalSent, totalDelivered, totalComplained, deliveryRate, bounceRate, complaintRate, rejectionRate } Subscription { id, organizationId, plan, status, stripeCustomerId?, stripeSubscriptionId?, currentPeriodEnd, createdAt, updatedAt } UsageCounter { id, metric, count, windowStart } Invitation { id, organizationId, email, role: "owner"|"admin"|"member", accepted, createdAt } ## Complete Route Catalog (63 routes) GET /v1/me GET /v1/api-keys POST /v1/api-keys body: { name: string } DELETE /v1/api-keys/:apiKeyId GET /v1/pods POST /v1/pods body: { name: string, region: string } GET /v1/pods/:podId DELETE /v1/pods/:podId GET /v1/inboxes POST /v1/inboxes body: { address: string, displayName: string (sender name recipients see), podId: string } GET /v1/inboxes/:inboxId PATCH /v1/inboxes/:inboxId body: { displayName?: string, status?: string } DELETE /v1/inboxes/:inboxId GET /v1/messages POST /v1/messages/send body: { inboxId: string, subject: string, body: string, html?: string, from: string, to: string[] } (sender name comes from inbox displayName) GET /v1/messages/:messageId GET /v1/threads GET /v1/threads/:threadId GET /v1/domains POST /v1/domains body: { name: string } GET /v1/domains/:domainId DELETE /v1/domains/:domainId GET /v1/contacts POST /v1/contacts body: { email: string, fullName: string, attributes?: Record, segmentIds?: string[] } GET /v1/contacts/:contactId PATCH /v1/contacts/:contactId body: { fullName?: string, email?: string, attributes?: Record, segmentIds?: string[] } DELETE /v1/contacts/:contactId GET /v1/templates POST /v1/templates body: { name: string, subject: string, html?: string } GET /v1/templates/:templateId PATCH /v1/templates/:templateId body: { name?: string, subject?: string, html?: string } DELETE /v1/templates/:templateId POST /v1/templates/:templateId/preview body: { variables: Record } GET /v1/workflows POST /v1/workflows body: { name: string, trigger: "manual"|"api"|"event", nodes: WorkflowNode[], exitCriteria?: ExitCriterion[], optOutMode?: "link"|"reply"|"header_only" } GET /v1/workflows/:workflowId PATCH /v1/workflows/:workflowId body: { name?: string, trigger?: string, nodes?: WorkflowNode[], exitCriteria?: ExitCriterion[], optOutMode?: "link"|"reply"|"header_only" } DELETE /v1/workflows/:workflowId POST /v1/workflows/:workflowId/publish POST /v1/workflows/:workflowId/pause POST /v1/workflows/:workflowId/resume GET /v1/workflows/:workflowId/analytics POST /v1/workflows/:workflowId/simulate body: { contactId?: string, event?: object, webhookResponsesByNodeId?: object, forcedVariantId?: string, seed?: number } POST /v1/workflows/:workflowId/test-send body: { nodeId: string, to: string, contactId?: string, variables?: Record } POST /v1/workflows/:workflowId/preview body: { contactId: string, variables?: Record } POST /v1/workflows/:workflowId/nodes/:nodeId/pause POST /v1/workflows/:workflowId/nodes/:nodeId/resume GET /v1/workflow-templates POST /v1/workflows/from-template body: { templateId: string, name?: string } GET /v1/send-limits PUT /v1/send-limits body: { inboxId?: string, dailyLimit: number } GET /v1/enrollments POST /v1/enrollments body: { workflowId: string, contactId: string, variables?: Record } POST /v1/enrollments/bulk body: { workflowId: string, contactIds: string[] } GET /v1/enrollments/:enrollmentId GET /v1/enrollments/:enrollmentId/logs GET /v1/webhooks/endpoints POST /v1/webhooks/endpoints body: { url: string, subscribedEvents: string[] } GET /v1/webhooks/endpoints/:endpointId PATCH /v1/webhooks/endpoints/:endpointId body: { url?: string, subscribedEvents?: string[] } DELETE /v1/webhooks/endpoints/:endpointId GET /v1/webhooks/deliveries GET /v1/events GET /v1/metrics/summary GET /v1/metrics/timeseries GET /v1/organizations GET /v1/organizations/:organizationId/invitations POST /v1/organizations/:organizationId/invitations body: { email: string, role: "owner"|"admin"|"member" } GET /v1/organizations/:organizationId/memberships GET /v1/billing/subscription GET /v1/billing/usage-counters POST /v1/billing/checkout-session body: { tier: "pro" } POST /v1/billing/portal-session ## Webhook Events message.received - Inbound email received message.delivered - Outbound email delivered message.bounced - Outbound email bounced message.complained - Recipient marked as spam enrollment.completed - Workflow enrollment completed enrollment.failed - Workflow enrollment failed enrollment.exited - Workflow enrollment exited via exit criteria ## TypeScript SDK Quick Reference Install: npm install @outreachagent/sdk-ts Import: import { OutreachAgentClient } from "@outreachagent/sdk-ts"; Init: const client = new OutreachAgentClient("https://api.outreachagent.dev", "rm_live_..."); SDK methods (all return Promises): client.listOrganizations() -> Organization[] client.listApiKeys(params?) -> PaginatedResponse client.createApiKey(name) -> ApiKey client.revokeApiKey(apiKeyId) -> { ok: true } client.listPods(params?) -> PaginatedResponse client.getPod(podId) -> Pod client.createPod({ name, region }) -> Pod client.deletePod(podId) -> { ok: true } client.listInboxes(params?) -> PaginatedResponse client.getInbox(inboxId) -> Inbox client.createInbox({ address, displayName, podId }) -> Inbox client.updateInbox(inboxId, { displayName?, status? }) -> Inbox client.deleteInbox(inboxId) -> { ok: true } client.listMessages(params?) -> PaginatedResponse client.getMessage(messageId) -> Message client.sendMessage({ inboxId, subject, body, html?, from, to }) -> Message client.listThreads(params?) -> PaginatedResponse client.getThread(threadId) -> Thread client.listDomains(params?) -> PaginatedResponse client.getDomain(domainId) -> Domain client.createDomain(name) -> Domain client.deleteDomain(domainId) -> { ok: true } client.listContacts(params?) -> PaginatedResponse client.getContact(contactId) -> Contact client.createContact({ email, fullName, attributes?, segmentIds? }) -> Contact client.updateContact(contactId, { fullName?, email?, attributes?, segmentIds? }) -> Contact client.deleteContact(contactId) -> { ok: true } client.listTemplates(params?) -> PaginatedResponse