The public REST API is included on the Agency Pro plan. Run the CRM, reply across every inbox, manage the calendar, send quotes and invoices, text from the workspace number, read forms and tables, work the chatbot queue, act on Agent Ivan's suggestions, publish social posts, post to communities, provision sub-accounts, and subscribe to webhooks — for your agency workspace or any of your client sub-accounts.
Create API keys in Sub-accounts → API & webhooks. Every request must include the key as a bearer token. Keys are scoped — grant only the scopes an integration needs — and can be revoked at any time.
curl https://agentivan.ai/api/v1/contacts \
-H "Authorization: Bearer aiv_..."By default requests operate on your agency workspace. To act on a client sub-account, pass its id in the X-Tenant-Id header (find ids via GET /api/v1/sub-accounts):
curl https://agentivan.ai/api/v1/contacts \
-H "Authorization: Bearer aiv_..." \
-H "X-Tenant-Id: 6f6a7f2e-..."Rate limit: 120 requests/minute per key (429 with a Retry-After header when exceeded). Errors return { "error": { "code", "message" } }.
GET/api/v1/contacts
q (search name/email/phone), stage_id, limit, offset.POST/api/v1/contacts
name, email, phone, message, stage_id, assigned_to, source (at least one of name/email/phone).GET/api/v1/contacts/:id
PATCH/api/v1/contacts/:id
lead.updated, plus lead.stage_changed when the stage moves.DELETE/api/v1/contacts/:id
GET/api/v1/stages
stage_id.GET/api/v1/contacts/:id/activities
POST/api/v1/contacts/:id/notes
body.POST/api/v1/contacts/:id/assign
assigned_to (member id, or null to unassign).Scope: contacts.
GET/api/v1/conversations?channel=sms|social|email
GET/api/v1/conversations/:id?channel=sms|social|email
POST/api/v1/conversations/reply
kind (email | sms | social), id (thread/row id from the list endpoint), body. SMS replies debit the workspace's phone usage wallet.POST/api/v1/conversations/sms
to or contact_id, and body. Returns 402 when the usage balance is too low.Scope: conversations.
GET/api/v1/booking-types
GET/api/v1/booking-types/:id/slots
GET/api/v1/bookings
from, to (ISO), status.POST/api/v1/bookings
booking_type_id, name, email, starts_at (ISO), phone, fields. Validated against live availability; 409 when the slot is taken.GET/api/v1/bookings/:id
POST/api/v1/bookings/:id/cancel
reason (optional). Drops the calendar event and emails the attendee and the owner.POST/api/v1/bookings/:id/reschedule
starts_at (ISO). Keeps the duration, moves the calendar event, re-sends the invite.Scope: bookings.
GET/api/v1/quotes
status, lead_id.GET/api/v1/quotes/:id
POST/api/v1/quotes
client (contact_id or email plus optional name/phone/address), line_items (name, quantity, unit_price, …), title, discount, tax, valid_until, collect_signature. Totals and the quote number are computed for you.POST/api/v1/quotes/:id/send
POST/api/v1/quotes/:id/convert
invoice_id.GET/api/v1/invoices
status, lead_id.GET/api/v1/invoices/:id
POST/api/v1/invoices
client and line_items shape as quotes, plus due_date, payment_terms and allow_online_payment.POST/api/v1/invoices/:id/send
Scope: invoices.
GET/api/v1/phone/numbers
POST/api/v1/phone/sms
to or contact_id, and body. Matches (or creates) the contact and lands in the same thread the Unibox shows.Scope: phone.
GET/api/v1/forms
GET/api/v1/forms/:id/submissions
partial=true to include abandoned saves.Scope: forms. Every completed submission also fires the form.submitted webhook.
GET/api/v1/tables
GET/api/v1/tables/:id/rows
q, limit, offset.POST/api/v1/tables/:id/rows
fields (keys may be column ids, names or slugs) and an optional dedupe_key — posting the same key twice updates instead of duplicating. Row automations fire exactly as they do for a form entry.Scope: tables.
GET/api/v1/chatbot/conversations
status, source.GET/api/v1/chatbot/conversations/:id
GET/api/v1/chatbot/tickets
status.POST/api/v1/chatbot/tickets/:id/reply
body. Takes the conversation over from the bot and reopens it if it was closed.Scope: chatbot.
GET/api/v1/ivan/suggestions
status (defaults to suggested; pass all for everything), action_type.POST/api/v1/ivan/suggestions/:id/approve
POST/api/v1/ivan/suggestions/:id/dismiss
Scope: ivan.
GET/api/v1/communities
GET/api/v1/communities/messages?channel_id=
POST/api/v1/communities/messages
channel_id, body. Posts as the community owner.Scope: communities.
GET/api/v1/sub-accounts
POST/api/v1/sub-accounts
name, template_tenant_id (clone structure from one of your template workspaces). Quota-gated by your plan.GET/api/v1/sub-accounts/:id
DELETE/api/v1/sub-accounts/:id
Scope: sub_accounts.
GET/api/v1/usage
Scope: usage.
Register HTTPS endpoints (in the dashboard or via the API) to receive events for your agency and all of its sub-accounts. Deliveries are signed and retried with exponential backoff for up to 8 attempts.
GET/api/v1/webhooks
POST/api/v1/webhooks
url (https), events (omit for all). Returns the signing secret once.DELETE/api/v1/webhooks/:id
Events: lead.created, lead.updated, lead.stage_changed, message.inbound, booking.created, booking.cancelled, booking.rescheduled, quote.sent, quote.approved, invoice.sent, invoice.paid, form.submitted, ivan.suggestion.created, sub_account.created, sub_account.archived.
Verify deliveries by recomputing the HMAC over the raw request body:
// X-Agency-Event: lead.created
// X-Agency-Signature: sha256=<hex>
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(secret: string, rawBody: string, signature: string): boolean {
const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
return timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}Payload shape: { "event", "tenant_id", "agency_tenant_id", "created_at", "data" }
Scope (for managing endpoints via API): webhooks.
Questions or need a higher rate limit? Email hello@agentivan.ai.
Social posting
GET/api/v1/social/posts
group_id,limit.POST/api/v1/social/posts
group_id,content,image_url,scheduled_at(ISO; publishes automatically when due) orpublish: true(publish immediately). The content fans out to every account connected to the group.Scope:
social.