Kintara API

Last updated November 6, 2026

Read calls, transcripts, contacts, appointments and leads, create contacts and leads, and get signed webhooks when something happens. Agencies use one key for all their sub-accounts. The machine-readable OpenAPI 3.1 description is at /api/v1/openapi.json; import it into Postman, Insomnia or a code generator.

Quick start

Create a key in Settings → Developers (or, for an agency, on the agency portal’s API page), pick its scopes and copy it: it is shown only once. Then:

curl https://kintara-j2gv.vercel.app/api/v1/me \
  -H "Authorization: Bearer $KINTARA_API_KEY"

All requests and responses are JSON over HTTPS. Base URL: https://kintara-j2gv.vercel.app/api/v1. Times are ISO 8601 in UTC, phone numbers E.164, money in cents.

Authentication and scopes

Send the key as a bearer token: Authorization: Bearer <key>. A missing, invalid or revoked key gets 401 with a WWW-Authenticate: Bearer header; a key without the scope an endpoint needs gets 403 insufficient_scope. Keys can be revoked at any time from the same page.

ScopeAllows
organizations:readView sub-accounts
calls:readRead calls, transcripts and summaries
contacts:readRead contacts
contacts:writeCreate and update contacts
appointments:readRead appointments
leads:readRead leads
leads:writeCreate and update leads
webhooks:manageManage webhook endpoints

Sub-accounts (agency keys)

A key made inside a sub-account always acts on that organization. An agency key can act on any of the agency’s sub-accounts: name the one you mean with the Kintara-Organization header or the organization_id query parameter. List them with GET /organizations. Endpoints marked “Organization required” answer 400 organization_required when an agency key names none, and 403 for an organization outside the agency.

curl "https://kintara-j2gv.vercel.app/api/v1/calls?limit=10" \
  -H "Authorization: Bearer $KINTARA_API_KEY" \
  -H "Kintara-Organization: 0b6f2c1e-7d3a-4e8b-9c5f-1a2b3c4d5e6f"

Rate limits

Requests are counted per one-minute window: 600 per minute shared by an agency’s keys (the agency’s plan can raise it) and 120 per minute for a sub-account’s own keys. Every response carries:

  • X-RateLimit-Limit: requests allowed in the window
  • X-RateLimit-Remaining: requests left in it
  • X-RateLimit-Reset: Unix time (seconds) the window resets

Over the limit you get 429 rate_limited with a Retry-After header in seconds; wait that long before retrying. GET /me shows your current limit.

Pagination

List endpoints return the newest first, 25 per page by default. Set limit (1 to 100) and pass the previous page’s next_cursor as cursor until has_more is false.

{
  "data": [ ... ],
  "has_more": true,
  "next_cursor": "eyJ0IjoiMjAyNi0xMS0wNlQxNTowNDowNS4wMDBaIiwiaSI6Ii4uLiJ9"
}

Versioning

The major version is part of the URL (/api/v1) and every response carries a Kintara-Api-Version header. Additive changes (new fields, endpoints, optional parameters and webhook event types) ship in place, so ignore fields you do not know. Breaking changes only ship as a new major version.

  • A deprecated version answers with Deprecation, Sunset and Link headers and stays available for at least 180 days after it is deprecated.
  • GET /api/versions lists every version and its status: /api/versions.
  • An unknown version (for example /api/v2/calls) returns 404 unsupported_api_version.
  • Every change is announced in the changelog.

Errors

Errors use the HTTP status and a JSON body with a stable code:

{
  "error": {
    "code": "invalid_parameter",
    "message": "limit must be between 1 and 100.",
    "details": { "param": "limit" }
  }
}
  • 400 invalid_request, invalid_parameter, organization_required
  • 401 unauthorized: missing, invalid or revoked key
  • 403 forbidden, insufficient_scope
  • 404 not_found: no such record in this organization
  • 429 rate_limited; 500 server_error (safe to retry)

Endpoints

Paths are relative to https://kintara-j2gv.vercel.app. Endpoints marked with an organization also take Kintara-Organization / organization_id.

Account

The API key itself.

get/api/v1/me

The API key: its owner, scopes and rate limit

Scope: any valid keyOrganization optionalReturns 200

Organizations

Sub-accounts (an agency's clients).

get/api/v1/organizations

List sub-accounts

An agency key lists its sub-accounts; a sub-account key gets its own organization.

Scope: organizations:readNo organizationReturns 200

ParameterInTypeDescription
limitqueryintegerPage size, 1 to 100 (default 25).
cursorquerystring`next_cursor` from the previous page.
get/api/v1/organizations/{id}

Get a sub-account

Scope: organizations:readNo organizationReturns 200

ParameterInTypeDescription
idrequiredpathstring (uuid)The organization id.

Calls

Inbound and outbound phone calls, with transcripts.

get/api/v1/calls

List calls

Scope: calls:readOrganization required (agency keys name it)Returns 200

ParameterInTypeDescription
limitqueryintegerPage size, 1 to 100 (default 25).
cursorquerystring`next_cursor` from the previous page.
sincequerystring (date-time)Only calls created at or after this time (ISO 8601).
untilquerystring (date-time)Only calls created before this time (ISO 8601).
statusquerystringOnly calls with this status; comma-separate several.One of: queued, ringing, in_progress, completed, busy, no_answer, failed, canceled, voicemail
get/api/v1/calls/{id}

Get a call with its transcript and extracted data

Scope: calls:readOrganization required (agency keys name it)Returns 200

ParameterInTypeDescription
idrequiredpathstring (uuid)The call id.

Contacts

People and businesses in the CRM.

get/api/v1/contacts

List contacts

Scope: contacts:readOrganization required (agency keys name it)Returns 200

ParameterInTypeDescription
limitqueryintegerPage size, 1 to 100 (default 25).
cursorquerystring`next_cursor` from the previous page.
qquerystringSearch name, phone, email or company.
phonequerystringExact phone match (normalized to E.164).
emailquerystring (email)Exact email match.
post/api/v1/contacts

Create a contact (or match an existing one)

Returns 201 with a new contact, or 200 with the existing contact that has the same phone or email.

Scope: contacts:writeOrganization required (agency keys name it)Returns 200 / 201

JSON body

A new contact needs at least a name, phone or email.

FieldTypeNotes
first_namestring
last_namestring
company_namestring
phonestringPhone number, ideally E.164 (+15551234567).
emailstring (email)
tagsstring[]
notesstring
sms_opt_outboolean
email_opt_outboolean
call_opt_outboolean
get/api/v1/contacts/{id}

Get a contact

Scope: contacts:readOrganization required (agency keys name it)Returns 200

ParameterInTypeDescription
idrequiredpathstring (uuid)The contact id.
patch/api/v1/contacts/{id}

Update a contact

Only the fields sent change; send null to clear a field.

Scope: contacts:writeOrganization required (agency keys name it)Returns 200

ParameterInTypeDescription
idrequiredpathstring (uuid)The contact id.

JSON body

A new contact needs at least a name, phone or email.

FieldTypeNotes
first_namestring
last_namestring
company_namestring
phonestringPhone number, ideally E.164 (+15551234567).
emailstring (email)
tagsstring[]
notesstring
sms_opt_outboolean
email_opt_outboolean
call_opt_outboolean

Appointments

Booked appointments.

get/api/v1/appointments

List appointments

Scope: appointments:readOrganization required (agency keys name it)Returns 200

ParameterInTypeDescription
limitqueryintegerPage size, 1 to 100 (default 25).
cursorquerystring`next_cursor` from the previous page.
sincequerystring (date-time)Only appointments starting at or after this time (ISO 8601).
untilquerystring (date-time)Only appointments starting before this time (ISO 8601).
statusquerystringOnly appointments with this status; comma-separate several.One of: scheduled, confirmed, completed, canceled, no_show, rescheduled

Leads

Leads and their pipeline status.

get/api/v1/leads

List leads

Scope: leads:readOrganization required (agency keys name it)Returns 200

ParameterInTypeDescription
limitqueryintegerPage size, 1 to 100 (default 25).
cursorquerystring`next_cursor` from the previous page.
statusquerystringOnly leads with this status; comma-separate several.One of: new, contacted, qualified, appointment_booked, won, lost, unresponsive
sourcequerystringOnly leads with this source; comma-separate several.One of: inbound_call, web_form, meta_lead_ad, sms, email, referral, import, campaign, manual, other
sincequerystring (date-time)Only leads created at or after this time (ISO 8601).
untilquerystring (date-time)Only leads created before this time (ISO 8601).
post/api/v1/leads

Create a lead

Matches or creates the contact, then runs the same follow-ups as a lead made in the app: activity, lead.created event, nurture workflows and CRM sync.

Scope: leads:writeOrganization required (agency keys name it)Returns 201

JSON body

The contact comes from `contact` or from the top-level first_name / last_name / company_name / phone / email; it is matched to an existing contact by phone or email. A lead needs a contact name, phone or email.

FieldTypeNotes
sourcestringDefault manualOne of: inbound_call, web_form, meta_lead_ad, sms, email, referral, import, campaign, manual, other
source_detailstring
service_intereststring
estimated_value_centsinteger
notesstring
utmobject
contactobject
first_namestring
last_namestring
company_namestring
phonestring
emailstring (email)

Webhooks

Signed event delivery, REST hooks for Zapier and Make.

get/api/v1/webhooks

List webhook endpoints

The organization's endpoints, or the agency's own when an agency key names no organization.

Scope: webhooks:manageOrganization optionalReturns 200

post/api/v1/webhooks

Subscribe a URL to events

Also the Zapier / Make REST hook subscribe call. The response carries the signing secret, shown only once.

Scope: webhooks:manageOrganization optionalReturns 201

JSON body

`url` or `target_url` is required (https). Zapier / Make send `target_url` + `event_type`.

FieldTypeNotes
urlstring (uri)
target_urlstring (uri)Alias of url (REST hooks).
event_typesstring[]See event types
event_typestringOne event type (REST hooks).See event types
descriptionstring
sourcestringDefault apiOne of: api, zapier, make
get/api/v1/webhooks/{id}

Get a webhook endpoint

Scope: webhooks:manageOrganization optionalReturns 200

ParameterInTypeDescription
idrequiredpathstring (uuid)The webhook endpoint id.
delete/api/v1/webhooks/{id}

Unsubscribe (delete a webhook endpoint)

Also the Zapier / Make REST hook unsubscribe call.

Scope: webhooks:manageOrganization optionalReturns 200

ParameterInTypeDescription
idrequiredpathstring (uuid)The webhook endpoint id.
get/api/v1/events

Recent events in the webhook payload shape

The polling and sample endpoint for Zapier / Make. With `sample=1` and no real events yet it returns realistic sample payloads.

Scope: webhooks:manageOrganization required (agency keys name it)Returns 200

ParameterInTypeDescription
limitqueryintegerPage size, 1 to 100 (default 25).
cursorquerystring`next_cursor` from the previous page.
typequerystringOnly events with this type; comma-separate several.One of: call.completed, lead.created, appointment.booked, appointment.cancelled, message.received, integration.status_changed, integration.records.booking.confirmed, integration.records.booking.failed, integration.records.appointment.cancelled, integration.records.appointment.completed, integration.records.appointment.no_show, integration.records.patient.updated, intake.completed
samplequerystring1 or true: return sample payloads when there are no real events yet.One of: 1, true
get/api/v1/event-types

Event types a webhook can subscribe to

Scope: any valid keyNo organizationReturns 200

Webhooks

Subscribe an https URL with POST /webhooks (or in Settings → Developers). Kintara then POSTs each matching event to it as JSON:

{
  "id": "8f0c5a8e-2b7e-4f6a-9d0e-3f1c2b4a5d6e",
  "type": "call.completed",
  "created_at": "2026-11-06T15:04:05.000Z",
  "organization_id": "0b6f2c1e-7d3a-4e8b-9c5f-1a2b3c4d5e6f",
  "data": {
    "call_id": "c1a2b3d4-e5f6-4789-8abc-def012345678",
    "status": "completed",
    "duration_seconds": 184
  }
}

Every delivery carries these headers:

  • X-Kintara-Signature: v1= + hex HMAC-SHA256 of "<timestamp>.<raw body>", keyed with the endpoint’s secret
  • X-Kintara-Timestamp: Unix time (seconds) of signing; reject anything older than a few minutes
  • X-Kintara-Event: the event type
  • X-Kintara-Delivery: the delivery id

Answer with any 2xx within 10 seconds. Other answers and timeouts are retried with growing delays, up to 8 attempts; delivery is at-least-once, so de-duplicate on the payload’s id, which stays the same across retries. Answering 410 Gone disables the endpoint, and an endpoint is disabled automatically after 20 failed deliveries in a row (re-enable it in Settings → Developers).

Verifying the signature (Node.js)

import crypto from "node:crypto";
import express from "express";

const app = express();

// Keep the raw body: the signature covers the exact bytes we sent.
app.post("/kintara/webhooks", express.raw({ type: "application/json" }), (req, res) => {
  const secret = process.env.KINTARA_WEBHOOK_SECRET; // from POST /api/v1/webhooks
  const timestamp = req.get("X-Kintara-Timestamp") ?? "";
  const signature = req.get("X-Kintara-Signature") ?? "";
  const body = req.body.toString("utf8");

  const expected =
    "v1=" + crypto.createHmac("sha256", secret).update(`${timestamp}.${body}`).digest("hex");
  const valid =
    signature.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
  const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300; // 5 minutes

  if (!valid || !fresh) return res.status(400).send("Invalid signature");

  const event = JSON.parse(body); // { id, type, created_at, organization_id, data }
  // Deliveries are at-least-once: skip event.id values you have already handled.
  res.sendStatus(200); // answer quickly, do slow work in the background
});

Event types

EventWhen
call.completedA phone call (inbound or outbound) has ended.
lead.createdA new lead was captured.
appointment.bookedAn appointment was booked.
appointment.cancelledAn appointment was cancelled.
message.receivedA text or other message was received.
integration.status_changedAn integration connection changed status (for example needs reconnecting).
integration.records.booking.confirmedA booking in the connected practice system was confirmed.
integration.records.booking.failedA booking in the connected practice system failed.
integration.records.appointment.cancelledAn appointment was cancelled in the connected practice system.
integration.records.appointment.completedAn appointment was completed in the connected practice system.
integration.records.appointment.no_showA patient did not show up, per the connected practice system.
integration.records.patient.updatedA patient was updated in the connected practice system.
intake.completedA patient completed an intake form.

Leave event_types empty to receive every event. GET /event-types returns this list.

Zapier and Make (REST hooks)

The webhook endpoints follow the REST hooks pattern, so a Zapier or Make app can use instant triggers with an API key (scope webhooks:manage):

  • Subscribe: POST /api/v1/webhooks with { "target_url": "...", "event_type": "lead.created", "source": "zapier" }. Store the returned id.
  • Unsubscribe: DELETE /api/v1/webhooks/{id} when the Zap or scenario is turned off.
  • Sample data: GET /api/v1/events?type=lead.created&sample=1 returns recent events in the exact webhook payload shape, or realistic samples when the account has none yet. It also works as a polling trigger.
  • Cleanup: if the hook URL answers 410 Gone, Kintara disables the subscription and stops sending.
curl -X POST https://kintara-j2gv.vercel.app/api/v1/webhooks \
  -H "Authorization: Bearer $KINTARA_API_KEY" \
  -H "Kintara-Organization: 0b6f2c1e-7d3a-4e8b-9c5f-1a2b3c4d5e6f" \
  -H "Content-Type: application/json" \
  -d '{"target_url":"https://hooks.zapier.com/hooks/standard/123/abc","event_type":"lead.created","source":"zapier"}'

Questions or a missing endpoint? Write to privacy@kintaragroup.com.