API Endpoints Reference
Complete reference for all MailTrixy REST API endpoints. All endpoints require authentication via Bearer token and are prefixed with /api/v1. See the API Overview for authentication, rate limiting, and response format details.
Contacts
Scope required: contacts
List Contacts
| Method | GET |
| URL | /api/v1/contacts |
| Description | Retrieve a paginated list of contacts. Supports filtering by tag, group, search query, and date range. |
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
| page | integer | Page number (default: 1) |
| per_page | integer | Items per page, max 100 (default: 25) |
| search | string | Search by name, email, or company |
| tag | string | Filter by tag name |
| group_id | integer | Filter by contact group ID |
Response: 200 OK
GET /api/v1/contacts?search=acme&per_page=2
{
"success": true,
"data": [
{
"id": 42,
"name": "Jane Smith",
"email": "jane@acme.com",
"company": "Acme Corp",
"phone": "+1-555-0123",
"tags": [{"id": 1, "name": "lead"}],
"created_at": "2026-03-20T08:30:00Z",
"updated_at": "2026-03-25T10:00:00Z"
},
{
"id": 43,
"name": "Bob Johnson",
"email": "bob@acme.com",
"company": "Acme Corp",
"phone": null,
"tags": [],
"created_at": "2026-03-21T09:15:00Z",
"updated_at": "2026-03-21T09:15:00Z"
}
],
"meta": {
"current_page": 1,
"per_page": 2,
"total": 5,
"last_page": 3
}
}
Get Single Contact
| Method | GET |
| URL | /api/v1/contacts/{id} |
| Description | Retrieve a single contact with all details including custom fields, tags, and groups. |
Response: 200 OK
GET /api/v1/contacts/42
{
"success": true,
"data": {
"id": 42,
"name": "Jane Smith",
"email": "jane@acme.com",
"company": "Acme Corp",
"phone": "+1-555-0123",
"tags": [{"id": 1, "name": "lead"}, {"id": 2, "name": "enterprise"}],
"groups": [{"id": 5, "name": "Newsletter Subscribers"}],
"custom_fields": {
"department": "Engineering",
"deal_value": 50000
},
"conversations_count": 8,
"last_contacted_at": "2026-03-24T14:20:00Z",
"created_at": "2026-03-20T08:30:00Z",
"updated_at": "2026-03-25T10:00:00Z"
}
}
Error: 404 Not Found if the contact does not exist.
Create Contact
| Method | POST |
| URL | /api/v1/contacts |
| Description | Create a new contact. The email must be unique within the workspace. |
Request Body:
{
"name": "Jane Smith",
"email": "jane@acme.com",
"company": "Acme Corp",
"phone": "+1-555-0123",
"tags": ["lead", "enterprise"],
"group_ids": [5, 12],
"custom_fields": {
"department": "Engineering",
"deal_value": 50000
}
}
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Full name of the contact |
| string | Yes | Unique email address | |
| company | string | No | Company or organization name |
| phone | string | No | Phone number with country code |
| tags | array | No | Array of tag names (created if they don't exist) |
| group_ids | array | No | Array of contact group IDs to add the contact to |
| custom_fields | object | No | Key-value pairs for custom field data |
Response: 201 Created
{
"success": true,
"data": {
"id": 42,
"name": "Jane Smith",
"email": "jane@acme.com",
"company": "Acme Corp",
"phone": "+1-555-0123",
"tags": [{"id": 1, "name": "lead"}, {"id": 2, "name": "enterprise"}],
"groups": [{"id": 5, "name": "Newsletter Subscribers"}, {"id": 12, "name": "VIP Clients"}],
"custom_fields": {"department": "Engineering", "deal_value": 50000},
"created_at": "2026-03-25T10:00:00Z",
"updated_at": "2026-03-25T10:00:00Z"
}
}
Errors:
| Status | Condition |
|---|---|
| 422 | Validation error (missing required fields, duplicate email) |
| 401 | Missing or invalid API token |
Update Contact
| Method | PUT |
| URL | /api/v1/contacts/{id} |
| Description | Update an existing contact. Only the provided fields are updated (partial update). |
Request Body:
PUT /api/v1/contacts/42
{
"company": "Acme Industries",
"phone": "+1-555-9999",
"custom_fields": {
"deal_value": 75000
}
}
Response: 200 OK -- Returns the full updated contact object (same structure as GET).
Errors: 404 if not found, 422 if validation fails.
Delete Contact
| Method | DELETE |
| URL | /api/v1/contacts/{id} |
| Description | Permanently delete a contact and all associated data. This action cannot be undone. |
Response: 200 OK
{
"success": true,
"data": {
"message": "Contact deleted successfully."
}
}
Errors: 404 if the contact does not exist.
Conversations
Scope required: conversations
List Conversations
| Method | GET |
| URL | /api/v1/conversations |
| Description | List conversations with optional filtering by status, assignee, contact, and label. |
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
| status | string | Filter by status: open, closed, snoozed, all |
| assignee_id | integer | Filter by assigned team member ID |
| contact_id | integer | Filter by contact ID |
| label | string | Filter by conversation label |
Response: 200 OK
{
"success": true,
"data": [
{
"id": 101,
"subject": "Billing question",
"status": "open",
"contact": {"id": 42, "name": "Jane Smith", "email": "jane@acme.com"},
"assignee": {"id": 3, "name": "Support Agent"},
"labels": ["billing", "priority"],
"messages_count": 5,
"last_message_at": "2026-03-25T14:30:00Z",
"created_at": "2026-03-24T09:00:00Z"
}
],
"meta": {
"current_page": 1,
"per_page": 25,
"total": 28,
"last_page": 2
}
}
Reply to Conversation
| Method | POST |
| URL | /api/v1/conversations/{id}/reply |
| Description | Send a reply message in an existing conversation. The email is sent to the contact. |
Request Body:
POST /api/v1/conversations/101/reply
{
"body_html": "<p>Thanks for reaching out! Your invoice has been updated.</p>",
"cc": ["manager@acme.com"],
"bcc": [],
"attachments": []
}
| Field | Type | Required | Description |
|---|---|---|---|
| body_html | string | Yes | HTML body of the reply message |
| cc | array | No | CC email addresses |
| bcc | array | No | BCC email addresses |
Response: 201 Created
{
"success": true,
"data": {
"id": 580,
"conversation_id": 101,
"direction": "outbound",
"body_html": "<p>Thanks for reaching out! Your invoice has been updated.</p>",
"sender": {"id": 3, "name": "Support Agent"},
"created_at": "2026-03-25T15:00:00Z"
}
}
Campaigns
Scope required: campaigns
List Campaigns
| Method | GET |
| URL | /api/v1/campaigns |
| Description | List all campaigns with status filters. Returns campaign metadata and delivery statistics. |
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
| status | string | draft, scheduled, sending, sent, paused |
Response: 200 OK
{
"success": true,
"data": [
{
"id": 15,
"name": "March Newsletter",
"subject": "What's new in March 2026",
"status": "sent",
"from_email": "newsletter@yourcompany.com",
"from_name": "Your Company",
"recipients_count": 1200,
"stats": {
"delivered": 1180,
"opened": 384,
"clicked": 98,
"bounced": 20,
"unsubscribed": 3
},
"sent_at": "2026-03-20T09:00:00Z",
"created_at": "2026-03-18T14:00:00Z"
}
],
"meta": {
"current_page": 1,
"per_page": 25,
"total": 12,
"last_page": 1
}
}
Create Campaign
| Method | POST |
| URL | /api/v1/campaigns |
| Description | Create a new email campaign. The campaign is created in draft status by default. Use schedule_at to schedule or POST to /campaigns/{id}/send to send immediately. |
Request Body:
{
"name": "April Product Update",
"subject": "Exciting new features in April",
"from_email": "newsletter@yourcompany.com",
"from_name": "Your Company",
"body_html": "<h1>April Update</h1><p>Hello {{contact.name}}, check out what's new...</p>",
"recipient_groups": [1, 3],
"email_account_id": 5,
"schedule_at": "2026-04-01T09:00:00Z"
}
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Internal campaign name |
| subject | string | Yes | Email subject line (supports merge tags) |
| from_email | string | Yes | Sender email address (must be verified) |
| from_name | string | Yes | Sender display name |
| body_html | string | Yes | HTML content of the email |
| recipient_groups | array | Yes | Array of contact group IDs to send to |
| email_account_id | integer | Yes | ID of the connected email account to send from |
| schedule_at | string | No | ISO 8601 datetime to schedule sending (omit for draft) |
Response: 201 Created
{
"success": true,
"data": {
"id": 16,
"name": "April Product Update",
"subject": "Exciting new features in April",
"status": "scheduled",
"recipients_count": 850,
"schedule_at": "2026-04-01T09:00:00Z",
"created_at": "2026-03-25T16:00:00Z"
}
}
Tags
Scope required: contacts
List Tags
| Method | GET |
| URL | /api/v1/tags |
| Description | List all tags in the workspace with contact counts. |
Response: 200 OK
{
"success": true,
"data": [
{"id": 1, "name": "lead", "color": "#3B82F6", "contacts_count": 45},
{"id": 2, "name": "enterprise", "color": "#8B5CF6", "contacts_count": 12},
{"id": 3, "name": "churned", "color": "#EF4444", "contacts_count": 8}
]
}
Create Tag
| Method | POST |
| URL | /api/v1/tags |
Request Body:
{
"name": "vip",
"color": "#F59E0B"
}
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Unique tag name (lowercase, no spaces) |
| color | string | No | Hex color code (default: auto-assigned) |
Response: 201 Created
{
"success": true,
"data": {
"id": 4,
"name": "vip",
"color": "#F59E0B",
"contacts_count": 0
}
}
Update Tag
| Method | PUT |
| URL | /api/v1/tags/{id} |
Request Body:
{
"name": "vip-client",
"color": "#10B981"
}
Response: 200 OK -- Returns the updated tag object.
Delete Tag
| Method | DELETE |
| URL | /api/v1/tags/{id} |
| Description | Delete a tag. The tag is removed from all contacts that had it. Contacts themselves are not deleted. |
Response: 200 OK
{
"success": true,
"data": {
"message": "Tag deleted successfully."
}
}
Contact Groups
Scope required: contacts
List Groups
| Method | GET |
| URL | /api/v1/contact-groups |
| Description | List all contact groups with member counts. |
Response: 200 OK
{
"success": true,
"data": [
{"id": 1, "name": "Newsletter Subscribers", "description": "Opted-in newsletter list", "members_count": 520, "created_at": "2026-01-10T08:00:00Z"},
{"id": 2, "name": "Trial Users", "description": "Users on free trial", "members_count": 85, "created_at": "2026-02-15T12:00:00Z"}
]
}
Create Group
| Method | POST |
| URL | /api/v1/contact-groups |
Request Body:
{
"name": "Webinar Attendees",
"description": "Contacts who registered for the Q2 webinar"
}
Response: 201 Created
{
"success": true,
"data": {
"id": 3,
"name": "Webinar Attendees",
"description": "Contacts who registered for the Q2 webinar",
"members_count": 0,
"created_at": "2026-03-25T16:30:00Z"
}
}
Update Group
| Method | PUT |
| URL | /api/v1/contact-groups/{id} |
Request Body:
{
"name": "Q2 Webinar Attendees",
"description": "Updated description"
}
Response: 200 OK -- Returns the updated group object.
Delete Group
| Method | DELETE |
| URL | /api/v1/contact-groups/{id} |
| Description | Delete a contact group. Members are removed from the group but not deleted. |
Response: 200 OK
{
"success": true,
"data": {
"message": "Contact group deleted successfully."
}
}
Manage Group Members
| Method | Endpoint | Description |
|---|---|---|
| GET | /contact-groups/{id}/members | List all contacts in a group (paginated) |
| POST | /contact-groups/{id}/members | Add contacts to the group |
| DELETE | /contact-groups/{id}/members | Remove contacts from the group |
Add Members -- Request Body:
POST /api/v1/contact-groups/3/members
{
"contact_ids": [42, 43, 44]
}
Response: 200 OK
{
"success": true,
"data": {
"added": 3,
"already_members": 0,
"members_count": 3
}
}
Remove Members -- Request Body:
DELETE /api/v1/contact-groups/3/members
{
"contact_ids": [44]
}
Response: 200 OK
{
"success": true,
"data": {
"removed": 1,
"members_count": 2
}
}
Deals
Scope required: deals
List Deals
| Method | GET |
| URL | /api/v1/deals |
| Description | List all deals with optional filtering by stage, assignee, and contact. |
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
| stage | string | lead, qualified, proposal, negotiation, won, lost |
| assignee_id | integer | Filter by assigned team member |
| contact_id | integer | Filter by associated contact |
Response: 200 OK
{
"success": true,
"data": [
{
"id": 10,
"title": "Acme Corp Enterprise Plan",
"value": 75000,
"currency": "USD",
"stage": "proposal",
"contact": {"id": 42, "name": "Jane Smith", "email": "jane@acme.com"},
"assignee": {"id": 3, "name": "Sales Rep"},
"expected_close_date": "2026-04-15",
"created_at": "2026-03-10T11:00:00Z",
"updated_at": "2026-03-24T16:00:00Z"
}
],
"meta": {
"current_page": 1,
"per_page": 25,
"total": 18,
"last_page": 1
}
}
Create Deal
| Method | POST |
| URL | /api/v1/deals |
Request Body:
{
"title": "Acme Corp Enterprise Plan",
"value": 75000,
"currency": "USD",
"stage": "lead",
"contact_id": 42,
"assignee_id": 3,
"expected_close_date": "2026-04-15",
"notes": "Initial discussion about enterprise needs"
}
| Field | Type | Required | Description |
|---|---|---|---|
| title | string | Yes | Name/title of the deal |
| value | number | Yes | Monetary value of the deal |
| currency | string | No | ISO 4217 currency code (default: workspace currency) |
| stage | string | No | Pipeline stage (default: lead) |
| contact_id | integer | Yes | Associated contact ID |
| assignee_id | integer | No | Team member to assign the deal to |
| expected_close_date | string | No | Expected close date (YYYY-MM-DD) |
| notes | string | No | Free-text notes about the deal |
Response: 201 Created -- Returns the full deal object.
Update Deal
| Method | PUT |
| URL | /api/v1/deals/{id} |
Request Body: Same fields as create (all optional for partial update).
PUT /api/v1/deals/10
{
"stage": "negotiation",
"value": 80000
}
Response: 200 OK -- Returns the full updated deal object.
Delete Deal
| Method | DELETE |
| URL | /api/v1/deals/{id} |
Response: 200 OK
{
"success": true,
"data": {
"message": "Deal deleted successfully."
}
}
Webhooks (Zapier Compatible)
MailTrixy can send real-time webhook notifications to external services when events occur. Webhooks use a Zapier-compatible JSON format, making them work seamlessly with Zapier, Make (Integromat), n8n, and custom integrations.
Scope required: webhooks
List Webhooks
| Method | GET |
| URL | /api/v1/webhooks |
Response: 200 OK
{
"success": true,
"data": [
{
"id": 1,
"url": "https://hooks.zapier.com/hooks/catch/123456/abcdef/",
"events": ["contact.created", "contact.updated", "deal.stage_changed"],
"is_active": true,
"created_at": "2026-03-20T10:00:00Z"
}
]
}
Create Webhook
| Method | POST |
| URL | /api/v1/webhooks |
Request Body:
{
"url": "https://hooks.zapier.com/hooks/catch/123456/abcdef/",
"events": [
"contact.created",
"contact.updated",
"contact.deleted",
"conversation.created",
"conversation.replied",
"conversation.closed",
"deal.created",
"deal.stage_changed",
"deal.won",
"deal.lost",
"campaign.sent",
"campaign.completed"
]
}
| Field | Type | Required | Description |
|---|---|---|---|
| url | string | Yes | HTTPS URL to receive webhook POST requests |
| events | array | Yes | Array of event types to subscribe to |
Response: 201 Created
{
"success": true,
"data": {
"id": 2,
"url": "https://hooks.zapier.com/hooks/catch/123456/abcdef/",
"events": ["contact.created", "contact.updated", "..."],
"secret": "whsec_abc123def456",
"is_active": true,
"created_at": "2026-03-25T17:00:00Z"
}
}
The secret is returned only at creation time. Use it to verify webhook signatures via the X-MailTrixy-Signature header.
Delete Webhook
| Method | DELETE |
| URL | /api/v1/webhooks/{id} |
Response: 200 OK
{
"success": true,
"data": {
"message": "Webhook deleted successfully."
}
}
Webhook Payload Format
When an event occurs, MailTrixy sends a POST request to your webhook URL with the following Zapier-compatible JSON payload:
// Headers
POST https://hooks.zapier.com/hooks/catch/123456/abcdef/
Content-Type: application/json
X-MailTrixy-Signature: sha256=abc123...
X-MailTrixy-Event: contact.created
// Body
{
"event": "contact.created",
"timestamp": "2026-03-25T18:00:00Z",
"workspace_id": 1,
"data": {
"id": 50,
"name": "New Contact",
"email": "new@example.com",
"company": "Example Inc",
"tags": ["lead"],
"created_at": "2026-03-25T18:00:00Z"
}
}
Available Events
| Event | Triggered When |
|---|---|
| contact.created | A new contact is added |
| contact.updated | A contact's details are modified |
| contact.deleted | A contact is permanently deleted |
| conversation.created | A new conversation is started |
| conversation.replied | A reply is sent in a conversation |
| conversation.closed | A conversation is resolved/closed |
| deal.created | A new deal is created |
| deal.stage_changed | A deal moves to a different pipeline stage |
| deal.won | A deal is marked as won |
| deal.lost | A deal is marked as lost |
| campaign.sent | A campaign begins sending |
| campaign.completed | A campaign has finished sending to all recipients |
Status Codes Summary
| Code | Meaning |
|---|---|
| 200 | OK -- Request succeeded (GET, PUT, DELETE) |
| 201 | Created -- Resource created successfully (POST) |
| 400 | Bad Request -- Malformed request body |
| 401 | Unauthenticated -- Missing or invalid Bearer token |
| 403 | Forbidden -- Token lacks required scope |
| 404 | Not Found -- Resource does not exist |
| 422 | Validation Error -- Request validation failed (see error details) |
| 429 | Rate Limited -- Too many requests, retry after cooldown |
| 500 | Server Error -- Internal server error |