Next CapNext Cap

External Data API

Read-only REST API for pulling leads, conversations, tickets, visitors, and analytics into your CRM or internal systems.

Overview

The External Data API lets you pull your organization's data — leads, conversations, tickets, visitors, and analytics — into your own CRM or internal systems. Data retrieval endpoints are read-only (GET). Management endpoints (e.g. webhooks) support POST/DELETE. All endpoints are authenticated via secret API keys and scoped to your organization.

Plan requirements: Pro plan and above for REST API access. Enterprise plan for Webhooks. Free plan organizations cannot use this API.
Recently added (May 2026): Call-back requests are now part of the External Data API. Use GET /external/call-requests to list them, PATCH to update status from your CRM, and the matching call_request.* events on the Webhooks page. Any extra fields you configured on the visitor form come through as a custom_fields object — see the Call Requests section below.

Authentication

Create a Data API key from Settings → API Keys in your dashboard. Select data as the scope. The key will have a sk_live_ prefix.

Pass the key in the X-Api-Key header or as a Bearer token:

Authenticationbash
# Option 1: X-Api-Key header
curl -H "X-Api-Key: sk_live_YOUR_KEY" https://api.nextcap.ai/api/v1/external/leads
# Option 2: Authorization Bearer
curl -H "Authorization: Bearer sk_live_YOUR_KEY" https://api.nextcap.ai/api/v1/external/leads
This API is server-to-server only. Requests from browsers (with an Origin header) are blocked with 403. Never expose your sk_live_ key in frontend code.

Pagination & Filtering

All list endpoints support pagination and date filtering:

pageinteger

Page number (default: 1).

per_pageinteger

Items per page, 1–100 (default: 20).

created_afterISO 8601

Filter items created after this date.

created_beforeISO 8601

Filter items created before this date.

Response envelopejson
{
"data": [ ... ],
"meta": {
"page": 1,
"per_page": 20,
"total": 142,
"total_pages": 8
}
}

Rate Limits

Each API key has a per-minute rate limit (default: 60 rpm). Response headers include X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset. Exceeding the limit returns 429 Too Many Requests.

Leads

curl -H "X-Api-Key: sk_live_YOUR_KEY" \
"https://api.nextcap.ai/api/v1/external/leads?per_page=10&created_after=2026-01-01"
GET/external/leadsAPI Key

List all leads for your organization.

sourcestring

Filter by source: pre_chat_form, in_chat, manual, telegram.

agent_idUUID

Filter by widget/agent ID.

emailstring

Search by email (partial match).

Response — 200 OKjson
{
"data": [
{
"id": "uuid",
"email": "john@acme.com",
"name": "John Doe",
"phone": "+1-555-0100",
"company": "Acme Inc",
"custom_fields": {},
"source": "pre_chat_form",
"agent_id": "uuid",
"conversation_id": "uuid",
"visitor_id": "uuid",
"created_at": "2026-03-15T10:30:00.000Z",
"updated_at": "2026-03-15T10:30:00.000Z"
}
],
"meta": { "page": 1, "per_page": 20, "total": 42, "total_pages": 3 }
}
GET/external/leads/:idAPI Key

Get a single lead by ID.

Conversations

GET/external/conversationsAPI Key

List conversations.

statusstring

Filter: active, handed_off, ticketed, resolved, archived.

agent_idUUID

Filter by widget/agent ID.

visitor_idstring

Filter by visitor text ID.

assigned_agent_idUUID

Filter by assigned human agent.

GET/external/conversations/:idAPI Key

Get a single conversation.

GET/external/conversations/:id/messagesAPI Key

List messages in a conversation.

Response — 200 OKjson
{
"data": [
{
"id": "uuid",
"conversation_id": "uuid",
"role": "user",
"content": "How do I reset my password?",
"sources": null,
"attachments": null,
"feedback": null,
"created_at": "2026-03-15T10:30:00.000Z"
},
{
"id": "uuid",
"conversation_id": "uuid",
"role": "assistant",
"content": "You can reset your password from...",
"sources": [{ "title": "Password Reset Guide", "chunk": "To reset..." }],
"attachments": null,
"feedback": "positive",
"created_at": "2026-03-15T10:30:05.000Z"
}
],
"meta": { "page": 1, "per_page": 20, "total": 8, "total_pages": 1 }
}
Messages are returned in chronological order. The role field can be user, assistant, system, or agent (human agent messages during handoff). The sources field contains knowledge base citations. Token counts, latency, and model information are not exposed.

Tickets

GET/external/ticketsAPI Key

List support tickets.

statusstring

Filter: new, open, in_progress, waiting_customer, waiting_internal, resolved, closed, reopened.

prioritystring

Filter: low, normal, high, urgent.

department_idUUID

Filter by department.

assigned_agent_idUUID

Filter by assigned agent.

categorystring

Filter by category.

tagsstring

Comma-separated tag filter.

sourcestring

Filter by source: handoff, widget, dashboard, email, api, import, telegram, whatsapp, instagram.

Response — 200 OKjson
{
"data": [
{
"id": "uuid",
"ticket_number": 3461,
"subject": "Cannot access my account",
"status": "new",
"priority": "normal",
"department_id": "uuid",
"department": {
"id": "uuid",
"name": "Technical Support",
"name_translations": { "fa": "پشتیبانی فنی", "fr": "Support technique" },
"description_translations": { "fa": "...", "fr": "..." },
"supported_languages": ["en", "fa", "fr"],
"color": "#6366f1",
"is_default": false
},
"assigned_agent_id": null,
"agent_id": "uuid",
"agent_name": "Otet Bot .IR",
"agent_display_name": null,
"source": "api",
"external_id": null,
"external_source": null,
"origin_url": "https://acme-fr.com/billing",
"origin_domain": "acme-fr.com",
"visitor_email": "user@example.com",
"detected_language": "fa",
"metadata": { "api_key_id": "...", "identity_verified": false, "originUrl": "https://acme-fr.com/billing", "originDomain": "acme-fr.com" },
"created_at": "2026-05-09T10:30:00.000Z",
"updated_at": "2026-05-09T10:30:00.000Z"
}
],
"meta": { "page": 1, "per_page": 20, "total": 42, "total_pages": 3 }
}
Three fields people keep confusing. source is the intake channel (api, widget, email, telegram). external_source is import provenance only — set to "zendesk"/"freshdesk"/etc. when a ticket was migrated from another helpdesk, and intentionally null for tickets created via this API or the widget. Don't use it for routing or per-site labelling. origin_domain is the hostname the visitor was on — that's what you want when one widget runs across multiple sites.

Identifying which bot a ticket came from: use agent_name (the operational name shown in your dashboard).

Localising the department label: read department.name_translations[locale] and fall back to department.name. The map only contains locales you have translated in the dashboard. department is null when the ticket has no department.

Full field reference: see the Ticket response fields section in the Tickets API doc — every field documented with its type and meaning.
GET/external/tickets/:idAPI Key

Get a single ticket with full details.

GET/external/tickets/:id/eventsAPI Key

Get the ticket timeline (status changes, assignments, replies).

Internal notes added by agents are never included in this response.

Call Requests

GET/external/call-requestsAPI Key

List call-back requests captured by your widgets.

statusstring

Filter: new, contacted, scheduled, completed, cancelled.

channelstring

Filter: widget, telegram, api.

Response — 200 OKjson
{
"data": [
{
"id": "uuid",
"org_id": "uuid",
"agent_id": "uuid",
"conversation_id": "uuid",
"visitor_id": "uuid",
"lead_id": "uuid",
"name": "Jane Doe",
"email": "jane@example.com",
"phone": "+15551234567",
"notes": null,
"preferred_time": null,
"subject": "Question about pricing",
"preferred_date": "2026-05-20",
"preferred_time_window": "09:00-09:30",
"timezone": "Europe/Berlin",
"status": "new",
"assigned_user_id": null,
"triggered_by": "visitor",
"channel": "widget",
"visitor_language": "en",
"source_url": "https://acme.com/pricing",
"source_domain": "acme.com",
"custom_fields": {
"best_language": "English",
"topic": "Enterprise plan"
},
"created_at": "2026-05-14T12:34:00.000Z",
"updated_at": "2026-05-14T12:34:00.000Z",
"completed_at": null
}
],
"meta": { "page": 1, "per_page": 20, "total": 12, "total_pages": 1 }
}
Custom fields. custom_fields echoes whatever extra fields the organization configured on the visitor form (e.g. Topic, Best language to talk in) — keys match the field keys set in the dashboard. null when the visitor submitted no answers.

Date & time format. preferred_date is YYYY-MM-DD. preferred_time_window is a 24-hour HH:MM-HH:MM range in the visitor's local timezone (separate timezone field, IANA).
GET/external/call-requests/:idAPI Key

Get a single call request with full details.

PATCH/external/call-requests/:idAPI Key

Update workflow state — status, assignment, or notes — from your CRM.

Request body (all fields optional)json
{
"status": "contacted",
"assigned_user_id": "uuid-of-team-member",
"notes": "Left voicemail; will retry tomorrow."
}
Status changes that move the row to completed also fire the call_request.completed webhook event.

Visitors

GET/external/visitorsAPI Key

List tracked visitors.

countrystring

Filter by ISO country code (e.g., US, DE).

has_emailboolean

Filter by whether the visitor has a captured email.

GET/external/visitors/:idAPI Key

Get a single visitor profile.

GET/external/visitors/:id/sessionsAPI Key

List visit sessions (UTM data, referrer, page views).

GET/external/visitors/:id/profileAPI Key

Get the AI-generated visitor profile (sentiment, topics, interests).

Departments

GET/external/departmentsAPI Key

List all active departments.

Response — 200 OKjson
{
"data": [
{
"id": "uuid",
"name": "Technical Support",
"description": "Help with technical issues",
"name_translations": { "fa": "پشتیبانی فنی", "fr": "Support technique" },
"description_translations": { "fa": "...", "fr": "..." },
"supported_languages": ["en", "fa", "fr"],
"slug": "technical-support",
"parent_id": null,
"is_default": false,
"color": "#6366f1",
"settings": { "autoAssign": false, "businessHoursOnly": false },
"created_at": "2026-03-15T10:30:00.000Z"
}
]
}
name_translations and description_translations are flat maps keyed by ISO 639-1 locale code (e.g. "fa", "fr"). Only locales you have translated in the dashboard appear; fall back to the top-level name / description when a key is missing. supported_languages is an empty array when the department accepts all languages.
GET/external/departments/:idAPI Key

Get a single department.

Analytics

GET/external/analytics/overviewAPI Key

Aggregated metrics for a date range.

start_dateYYYY-MM-DDrequired

Start of the date range.

end_dateYYYY-MM-DDrequired

End of the date range.

GET/external/analytics/dailyAPI Key

Daily breakdown of metrics for the date range.

start_dateYYYY-MM-DDrequired

Start of the date range.

end_dateYYYY-MM-DDrequired

End of the date range.