API Reference
Referencia completa de la API de Rivelko / Complete Rivelko API reference
Base URL: https://app.rivelko.com
All request and response bodies use JSON (Content-Type: application/json) unless noted otherwise.
All authenticated endpoints accept either an active NextAuth session (cookie) or an API key (Authorization: Bearer rvk_...). See Authentication for details.
Todos los endpoints autenticados aceptan una sesion activa de NextAuth (cookie) o una API key (Authorization: Bearer rvk_...). Consulta Authentication para mas detalles.
This reference is organized API-first:
- Management API — authenticated endpoints (API key or session) for building integrations. This is the public API.
- Widget endpoints — unauthenticated endpoints used by the embeddable widget, validated by the
Originheader. - Internal endpoints — first-party dashboard endpoints that are not part of the public API.
Table of Contents
Forms
All form management endpoints require authentication (session cookie or API key).
Todos los endpoints de gestion de formularios requieren autenticacion.
List Forms
List all forms for the authenticated user. Paginated.
curl -X GET "https://app.rivelko.com/api/forms?page=1&limit=10" \
-H "Authorization: Bearer rvk_..."Query Parameters:
| Param | Type | Default | Description |
|---|---|---|---|
page | number | 1 | Page number |
limit | number | 10 | Items per page (max 100) |
search | string | - | Filter by form name (case-insensitive) |
isPublished | string | - | "true" or "false" |
Response 200 OK:
{
"forms": [
{
"id": "clx...",
"name": "Contact Form",
"isPublished": true,
"formType": "CONVERSATIONAL",
"createdAt": "2026-01-15T10:00:00.000Z",
"_count": { "conversations": 42 }
}
],
"pagination": {
"page": 1,
"limit": 10,
"total": 3,
"totalPages": 1
}
}
Create Form
Create a new form.
curl -X POST "https://app.rivelko.com/api/forms" \
-H "Authorization: Bearer rvk_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Contact Form",
"welcomeMessage": "Hello! How can we help?",
"goodbyeMessage": "Thanks for reaching out!",
"tone": "FRIENDLY",
"formType": "CONVERSATIONAL",
"language": "EN",
"allowedDomains": [
"example.com"
],
"questions": [
{
"order": 1,
"text": "What is your name?",
"type": "text",
"required": true
},
{
"order": 2,
"text": "Email",
"type": "email",
"required": true
}
]
}'Request Body:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | Yes | - | Form name (1-200 chars) |
businessDescription | string | No | null | Business description |
welcomeMessage | string | Yes | - | Welcome message (1-500 chars) |
goodbyeMessage | string | Yes | - | Goodbye message (1-500 chars) |
tone | string | No | "FRIENDLY" | FORMAL, CASUAL, or FRIENDLY |
questions | Question[] | Yes | - | Array of questions (min 1) |
style | Style | No | defaults | Widget style options |
language | string | No | "ES" | ES or EN |
formType | string | No | "CONVERSATIONAL" | CONVERSATIONAL or CLASSIC |
allowedDomains | string[] | No | [] | Domains allowed to use the widget |
Question object:
| Field | Type | Required | Description |
|---|---|---|---|
order | number | Yes | Display order |
text | string | Yes | Question text |
type | string | Yes | text, email, phone, number, select, date |
required | boolean | Yes | Whether the question is required |
options | string[] | No | Options for select type |
Style object:
| Field | Type | Default | Description |
|---|---|---|---|
primaryColor | string | "#E11D62" | Hex color for widget accent |
avatarUrl | string | null | Avatar image URL |
position | string | "bottom-right" | bottom-right or bottom-left |
bubbleIcon | string | null | Custom bubble icon |
Response 201 Created: The created form object.
Errors:
| Status | Error | Cause |
|---|---|---|
| 400 | Invalid data | Validation failure |
| 401 | Unauthorized | Not authenticated |
| 403 | Plan limit | Form limit reached for your plan |
Get Form
Get a single form by ID. Must be the form owner.
curl -X GET "https://app.rivelko.com/api/forms/:id" \
-H "Authorization: Bearer rvk_..."Response 200 OK: Full form object.
Errors:
| Status | Error | Cause |
|---|---|---|
| 401 | Unauthorized | Not authenticated |
| 404 | Form not found | Not found or not owner |
Update Form
Update a form. All fields are optional (partial update). Must be the form owner.
curl -X PUT "https://app.rivelko.com/api/forms/:id" \
-H "Authorization: Bearer rvk_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Updated Contact Form",
"isPublished": true
}'Request Body: Same fields as POST /api/forms, but all optional. Additionally:
| Field | Type | Description |
|---|---|---|
isPublished | boolean | Publish or unpublish |
Response 200 OK: Updated form object.
Delete Form
Delete a form and all its conversations. Must be the form owner.
curl -X DELETE "https://app.rivelko.com/api/forms/:id" \
-H "Authorization: Bearer rvk_..."Response 200 OK:
{ "success": true }
Generate Form with AI
Use AI to generate form questions from a business description or URL.
Genera preguntas de formulario usando IA a partir de una descripcion o URL.
curl -X POST "https://app.rivelko.com/api/forms/generate" \
-H "Authorization: Bearer rvk_..." \
-H "Content-Type: application/json" \
-d '{
"description": "A dental clinic in Madrid that does orthodontics and implants",
"language": "ES"
}'Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
description | string | Yes (if no url) | Business description |
url | string | Yes (if no description) | Website URL to analyze |
language | string | No | ES (default) or EN |
Response 200 OK: Generated form structure (name, welcomeMessage, goodbyeMessage, questions, etc.). This is not persisted -- use POST /api/forms to create it.
Errors:
| Status | Error | Cause |
|---|---|---|
| 400 | Either description or url... | Neither field provided |
| 500 | Failed to generate form | AI generation error |
Conversations
Endpoints for viewing form responses. Authenticated, paginated.
Endpoints para ver las respuestas de formularios. Autenticados y paginados.
List Conversations
List conversations across all forms owned by the user.
curl -X GET "https://app.rivelko.com/api/conversations?page=1&limit=10" \
-H "Authorization: Bearer rvk_..."Query Parameters:
| Param | Type | Default | Description |
|---|---|---|---|
page | number | 1 | Page number |
limit | number | 10 | Items per page (max 100) |
formId | string | - | Filter by form ID |
status | string | - | ACTIVE, COMPLETED, or ABANDONED |
from | string | - | Start date (ISO 8601) |
to | string | - | End date (ISO 8601) |
Response 200 OK:
{
"conversations": [
{
"id": "clx...",
"formId": "clx...",
"status": "COMPLETED",
"collectedData": { "name": "Juan", "email": "juan@example.com" },
"createdAt": "2026-04-01T14:30:00.000Z",
"form": { "id": "clx...", "name": "Contact Form", "formType": "CONVERSATIONAL" },
"_count": { "messages": 8 }
}
],
"pagination": { "page": 1, "limit": 10, "total": 42, "totalPages": 5 }
}
Get Conversation
Get a single conversation with paginated messages. Must own the form.
curl -X GET "https://app.rivelko.com/api/conversations/:id?messagesPage=1&messagesLimit=10" \
-H "Authorization: Bearer rvk_..."Query Parameters:
| Param | Type | Default | Description |
|---|---|---|---|
messagesPage | number | 1 | Messages page number |
messagesLimit | number | 10 | Messages per page (max 100) |
Response 200 OK:
{
"id": "clx...",
"formId": "clx...",
"status": "COMPLETED",
"collectedData": { "name": "Juan" },
"form": { "id": "clx...", "name": "Contact Form", "questions": [...], "formType": "CONVERSATIONAL" },
"messages": [
{ "id": "clx...", "role": "ASSISTANT", "content": "Hello! What is your name?", "createdAt": "..." },
{ "id": "clx...", "role": "VISITOR", "content": "Juan", "createdAt": "..." }
],
"messagesPagination": { "page": 1, "limit": 10, "total": 8, "totalPages": 1 }
}
Errors:
| Status | Error | Cause |
|---|---|---|
| 403 | Forbidden | Not the form owner |
| 404 | Conversation not found | Does not exist |
Webhooks
Configure webhook notifications for form events. Requires PRO or BUSINESS plan.
Configura notificaciones webhook para eventos de formularios. Requiere plan PRO o BUSINESS.
For a complete guide, see Webhook Guide.
Get Webhook
Get the webhook configuration for a form. The secret is never returned; hasSecret indicates if one is set.
curl -X GET "https://app.rivelko.com/api/forms/:id/webhook" \
-H "Authorization: Bearer rvk_..."Response 200 OK:
{
"id": "clx...",
"formId": "clx...",
"url": "https://myapp.com/webhooks/rivelko",
"events": ["conversation.completed"],
"isActive": true,
"hasSecret": true,
"createdAt": "2026-04-01T00:00:00.000Z",
"updatedAt": "2026-04-01T00:00:00.000Z"
}
Returns null if no webhook is configured.
Update Webhook
Create or update the webhook configuration for a form.
curl -X PUT "https://app.rivelko.com/api/forms/:id/webhook" \
-H "Authorization: Bearer rvk_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://myapp.com/webhooks/rivelko",
"events": [
"conversation.completed"
],
"secret": "whsec_my_secret_key"
}'Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes (on create) | Webhook endpoint URL |
events | string[] | Yes (on create) | Events to listen for |
isActive | boolean | No | Enable/disable (default: true) |
secret | string | No | HMAC-SHA256 signing secret |
Available events: conversation.completed
Response 201 Created (new) or 200 OK (update):
{
"id": "clx...",
"formId": "clx...",
"url": "https://myapp.com/webhooks/rivelko",
"events": ["conversation.completed"],
"isActive": true,
"hasSecret": true,
"createdAt": "...",
"updatedAt": "..."
}
Test Webhook
Send a test webhook to the configured URL. Useful for verifying your endpoint works.
curl -X POST "https://app.rivelko.com/api/forms/:id/webhook/test" \
-H "Authorization: Bearer rvk_..."Response 200 OK:
{
"success": true,
"statusCode": 200
}
Errors:
| Status | Error | Cause |
|---|---|---|
| 404 | No webhook configured for this form | No webhook config exists |
| 502 | Webhook delivery failed | Endpoint returned non-2xx or timed out |
Pipelines
CRM pipelines for tracking deals through customizable stages. Creating a pipeline requires the PRO or BUSINESS plan (FREE has no pipeline access). Reading, updating, and deleting an existing pipeline works regardless of the current plan.
Pipelines CRM para seguir deals a traves de etapas personalizables. Crear un pipeline requiere plan PRO o BUSINESS (FREE no tiene acceso a pipelines). Leer, actualizar y borrar un pipeline existente funciona en cualquier plan.
List Pipelines
List all pipelines owned by the authenticated user, each with its stages. Not paginated -- always returns the full list.
curl -X GET "https://app.rivelko.com/api/pipelines" \
-H "Authorization: Bearer rvk_..."Response 200 OK:
{
"pipelines": [
{
"id": "clx...",
"userId": "clx...",
"name": "Sales Pipeline",
"createdAt": "2026-04-01T00:00:00.000Z",
"updatedAt": "2026-04-01T00:00:00.000Z",
"stages": [
{ "id": "clx...", "pipelineId": "clx...", "name": "New Lead", "color": "#3b82f6", "order": 0 },
{ "id": "clx...", "pipelineId": "clx...", "name": "Contacted", "color": "#8b5cf6", "order": 1 }
]
}
]
}
Errors:
| Status | Error | Cause |
|---|---|---|
| 401 | Unauthorized | Not authenticated |
Create Pipeline
Create a new pipeline. Requires the PRO or BUSINESS plan.
Crea un pipeline nuevo. Requiere plan PRO o BUSINESS.
curl -X POST "https://app.rivelko.com/api/pipelines" \
-H "Authorization: Bearer rvk_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Sales Pipeline",
"stages": [
{
"name": "New Lead",
"color": "#3b82f6",
"order": 0
},
{
"name": "Contacted",
"color": "#8b5cf6",
"order": 1
}
]
}'Request Body:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | Yes | - | Pipeline name (1-100 chars) |
stages | Stage[] | No | see below | Custom stages (2-10 if provided) |
Stage object:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | Yes | - | Stage name (1-50 chars) |
color | string | No | "#6b7280" | Hex color (#RRGGBB) |
order | number | Yes | - | Display order (integer >= 0) |
If stages is omitted, the pipeline is created with 6 built-in default stages: New Lead, Contacted, Qualified, Proposal, Won, Lost.
Response 201 Created: The created pipeline object with its stages (same shape as a GET /api/pipelines item).
Errors:
| Status | Error | Cause |
|---|---|---|
| 400 | Invalid data | Validation failure |
| 401 | Unauthorized | Not authenticated |
| 403 | Plan limit reached: maximum 0 pipeline (code: "FEATURE_NOT_AVAILABLE") | Plan doesn't include pipelines (FREE) |
| 403 | Plan limit reached: maximum {N} pipelines (code: "PIPELINE_LIMIT_REACHED") | Plan's pipeline count reached (PRO: 3; unlimited on BUSINESS) |
Get Pipeline
Get a single pipeline with its stages. Must be the pipeline owner.
curl -X GET "https://app.rivelko.com/api/pipelines/:id" \
-H "Authorization: Bearer rvk_..."Response 200 OK: Pipeline object (same shape as a list item).
Errors:
| Status | Error | Cause |
|---|---|---|
| 401 | Unauthorized | Not authenticated |
| 404 | Pipeline with id "{id}" not found | Not found or not owner |
Update Pipeline
Update a pipeline's name and/or stages. Must be the pipeline owner. No plan check is re-applied on update -- it works on any plan as long as you already own the pipeline.
curl -X PUT "https://app.rivelko.com/api/pipelines/:id" \
-H "Authorization: Bearer rvk_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Sales Pipeline (renamed)",
"stages": [
{
"name": "New Lead",
"color": "#3b82f6",
"order": 0
},
{
"name": "Won",
"color": "#22c55e",
"order": 1
}
]
}'Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | New pipeline name (1-100 chars) |
stages | Stage[] | No | Full replacement stage list (2-10) |
Stage object:
| Field | Type | Required | Description |
|---|---|---|---|
id | string | No | Accepted by validation but ignored -- see note below |
name | string | Yes | Stage name (1-50 chars) |
color | string | No | Hex color (#RRGGBB), default "#6b7280" |
order | number | Yes | Display order (integer >= 0) |
Note: If
stagesis provided, the pipeline's entire stage list is deleted and recreated from the array you send -- it is not a partial merge, and new stage IDs are always generated (anyidyou include per stage is validated but has no effect on the update). If any existing deal still points at a stage ID that would be deleted, the request fails at the database level (theDeal.stageIdforeign key isON DELETE RESTRICT), surfacing as a generic500 Internal server error. Move or delete affected deals first (PATCH /api/deals/:id/move) before replacing stages that are in use.
Response 200 OK: Updated pipeline object with its stages.
Errors:
| Status | Error | Cause |
|---|---|---|
| 400 | Invalid data | Validation failure |
| 401 | Unauthorized | Not authenticated |
| 404 | Pipeline with id "{id}" not found | Not found or not owner |
| 500 | Internal server error | Replacing stages while a deal still references one of the removed stage IDs |
Delete Pipeline
Delete a pipeline. Must be the pipeline owner. Cascades: also deletes all of the pipeline's stages and deals.
curl -X DELETE "https://app.rivelko.com/api/pipelines/:id" \
-H "Authorization: Bearer rvk_..."Response 200 OK:
{ "success": true }
Errors:
| Status | Error | Cause |
|---|---|---|
| 401 | Unauthorized | Not authenticated |
| 404 | Pipeline with id "{id}" not found | Not found or not owner |
Deals
Deals track potential sales moving through a pipeline's stages. Every deal endpoint requires ownership of the parent pipeline; there is no separate authorization check on the deal itself.
Los deals siguen ventas potenciales a traves de las etapas de un pipeline. Todos los endpoints de deals requieren ser propietario del pipeline padre.
Note: Creating a deal does not re-check the pipeline feature flag or enforce a monthly count, even though
maxDealsPerMonthis defined per plan (PRO: 500/month, BUSINESS: unlimited) and aDEAL_LIMIT_REACHEDerror code exists in the error catalog. Neither is currently applied by this endpoint -- deal creation is effectively unlimited once you own a pipeline.
List Deals
List deals in the pipeline identified by :id. Must be the pipeline owner.
curl -X GET "https://app.rivelko.com/api/pipelines/:id/deals" \
-H "Authorization: Bearer rvk_..."Query Parameters:
| Param | Type | Default | Description |
|---|---|---|---|
stageId | string | - | Filter by stage ID |
search | string | - | Case-insensitive match on title, contact name, or contact email |
Note:
pageandlimitare accepted and validated by this endpoint's query schema, but they have no effect -- the response always contains the full, unpaginated list of deals for the pipeline. If the query string fails validation entirely, the endpoint silently falls back to the defaults instead of returning400.
Response 200 OK:
{
"deals": [
{
"id": "clx...",
"pipelineId": "clx...",
"stageId": "clx...",
"conversationId": null,
"title": "Juan Perez",
"value": 1500,
"currency": "USD",
"contactName": "Juan Perez",
"contactEmail": "juan@example.com",
"notes": null,
"createdAt": "2026-04-01T00:00:00.000Z",
"updatedAt": "2026-04-01T00:00:00.000Z",
"stage": { "id": "clx...", "pipelineId": "clx...", "name": "New Lead", "color": "#3b82f6", "order": 0 }
}
]
}
Errors:
| Status | Error | Cause |
|---|---|---|
| 401 | Unauthorized | Not authenticated |
| 404 | Pipeline not found | Pipeline does not exist or you don't own it |
Create Deal
Create a deal in the pipeline identified by :id. Must be the pipeline owner.
curl -X POST "https://app.rivelko.com/api/pipelines/:id/deals" \
-H "Authorization: Bearer rvk_..." \
-H "Content-Type: application/json" \
-d '{
"pipelineId": "YOUR_PIPELINE_ID",
"stageId": "YOUR_STAGE_ID",
"title": "Juan Perez",
"value": 1500,
"currency": "USD",
"contactName": "Juan Perez",
"contactEmail": "juan@example.com"
}'Request Body:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
pipelineId | string | Yes | - | Must be present for validation to pass, but the path :id always wins -- send the same ID in both places |
stageId | string | Yes | - | Must be a stage belonging to this pipeline |
title | string | Yes | - | Deal title (1-200 chars) |
value | number | No | null | Deal amount (>= 0) |
currency | string | No | "USD" | 3-character currency code (e.g. USD, EUR) |
contactName | string | No | null | Contact name (max 200 chars) |
contactEmail | string | No | null | Contact email (validated format) |
notes | string | No | null | Free-text notes (max 5000 chars) |
Response 201 Created: The created deal object (no nested stage; see GET /api/deals/:id for the field list).
Errors:
| Status | Error | Cause |
|---|---|---|
| 400 | Invalid data | Validation failure |
| 401 | Unauthorized | Not authenticated |
| 404 | Pipeline with id "{id}" not found | Pipeline does not exist or you don't own it |
Get Deal
Get a single deal, including its stage. Must own the parent pipeline.
curl -X GET "https://app.rivelko.com/api/deals/:id" \
-H "Authorization: Bearer rvk_..."Response 200 OK: Deal object with a nested stage (same shape as a GET /api/pipelines/:id/deals item).
Errors:
| Status | Error | Cause |
|---|---|---|
| 401 | Unauthorized | Not authenticated |
| 404 | Deal not found | Deal does not exist, or its pipeline isn't yours |
Update Deal
Update a deal. Must own the parent pipeline. All fields optional.
curl -X PUT "https://app.rivelko.com/api/deals/:id" \
-H "Authorization: Bearer rvk_..." \
-H "Content-Type: application/json" \
-d '{
"title": "Juan Perez (updated)",
"value": 2000
}'Request Body:
| Field | Type | Description |
|---|---|---|
stageId | string | Move to a different stage (should be in the same pipeline) |
title | string | Deal title (1-200 chars) |
value | number | Deal amount (>= 0) |
currency | string | 3-character currency code |
contactName | string | Contact name (max 200 chars) |
contactEmail | string | Contact email (validated format) |
notes | string | Free-text notes (max 5000 chars) |
Response 200 OK: Updated deal object.
Errors:
| Status | Error | Cause |
|---|---|---|
| 400 | Invalid data | Validation failure |
| 401 | Unauthorized | Not authenticated |
| 404 | Deal with id "{id}" not found | Deal does not exist |
| 404 | Pipeline with id "{id}" not found | Deal exists, but its pipeline isn't yours |
Delete Deal
Delete a deal. Must own the parent pipeline.
curl -X DELETE "https://app.rivelko.com/api/deals/:id" \
-H "Authorization: Bearer rvk_..."Response 200 OK:
{ "success": true }
Errors: Same as PUT /api/deals/:id (Deal ... not found / Pipeline ... not found).
Move Deal
Move a deal to a different stage. Equivalent to PUT /api/deals/:id with only stageId.
curl -X PATCH "https://app.rivelko.com/api/deals/:id/move" \
-H "Authorization: Bearer rvk_..." \
-H "Content-Type: application/json" \
-d '{
"stageId": "YOUR_STAGE_ID"
}'Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
stageId | string | Yes | Target stage ID |
Response 200 OK: Updated deal object.
Errors: Same as PUT /api/deals/:id.
Tickets
Support tickets, optionally linked to the form/conversation they originated from. Creating a ticket requires the PRO or BUSINESS plan (FREE has no ticket access). Reading, updating, and deleting an existing ticket works regardless of the current plan.
Tickets de soporte, opcionalmente vinculados al formulario/conversacion de origen. Crear un ticket requiere plan PRO o BUSINESS (FREE no tiene acceso a tickets). Leer, actualizar y borrar un ticket existente funciona en cualquier plan.
List Tickets
List tickets owned by the authenticated user. Not paginated -- always returns the full list (no page/limit params exist for this endpoint).
curl -X GET "https://app.rivelko.com/api/tickets" \
-H "Authorization: Bearer rvk_..."Query Parameters:
| Param | Type | Default | Description |
|---|---|---|---|
status | string | - | OPEN, IN_PROGRESS, RESOLVED, or CLOSED |
priority | string | - | LOW, MEDIUM, HIGH, or URGENT |
search | string | - | Case-insensitive match on subject, contact name, or contact email |
Response 200 OK:
{
"tickets": [
{
"id": "clx...",
"userId": "clx...",
"number": 1000,
"conversationId": null,
"formId": null,
"subject": "Cannot log in",
"description": "I get an error when I try to sign in.",
"priority": "HIGH",
"status": "OPEN",
"assigneeName": null,
"assigneeEmail": null,
"contactName": "Juan Perez",
"contactEmail": "juan@example.com",
"createdAt": "2026-04-01T00:00:00.000Z",
"updatedAt": "2026-04-01T00:00:00.000Z"
}
]
}
Errors:
| Status | Error | Cause |
|---|---|---|
| 400 | Invalid query | Query params fail validation |
| 401 | Unauthorized | Not authenticated |
Create Ticket
Create a ticket. Requires the PRO or BUSINESS plan.
curl -X POST "https://app.rivelko.com/api/tickets" \
-H "Authorization: Bearer rvk_..." \
-H "Content-Type: application/json" \
-d '{
"subject": "Cannot log in",
"description": "I get an error when I try to sign in.",
"priority": "HIGH",
"contactName": "Juan Perez",
"contactEmail": "juan@example.com"
}'Request Body:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
subject | string | Yes | - | Ticket subject (1-300 chars) |
description | string | No | null | Free-text description (max 5000 chars) |
priority | string | No | "MEDIUM" | LOW, MEDIUM, HIGH, or URGENT |
contactName | string | No | null | Contact name (max 200 chars) |
contactEmail | string | No | null | Contact email (validated format) |
number is assigned automatically (sequential per user, starting at 1000). status always starts as "OPEN". formId and conversationId cannot be set through this endpoint -- they're only populated internally when a ticket is auto-created from a form conversation.
Response 201 Created: The created ticket object.
Errors:
| Status | Error | Cause |
|---|---|---|
| 400 | Invalid data | Validation failure |
| 401 | Unauthorized | Not authenticated |
| 403 | Plan limit reached: maximum 0 tickets (code: "FEATURE_NOT_AVAILABLE") | Plan doesn't include tickets (FREE) |
Note:
maxTicketsPerMonthis defined per plan (PRO: 500/month, BUSINESS: unlimited) but is not currently enforced by this endpoint -- only theticketsfeature flag is checked, so ticket creation is effectively unlimited on PRO/BUSINESS.
Get Ticket
Get a single ticket. Must be the ticket owner.
curl -X GET "https://app.rivelko.com/api/tickets/:id" \
-H "Authorization: Bearer rvk_..."Response 200 OK: Ticket object.
Errors:
| Status | Error | Cause |
|---|---|---|
| 401 | Unauthorized | Not authenticated |
| 404 | Ticket with id "{id}" not found | Not found or not owner |
Update Ticket
Update a ticket. Must be the ticket owner. All fields optional.
curl -X PUT "https://app.rivelko.com/api/tickets/:id" \
-H "Authorization: Bearer rvk_..." \
-H "Content-Type: application/json" \
-d '{
"status": "RESOLVED",
"assigneeName": "Support Agent"
}'Request Body:
| Field | Type | Description |
|---|---|---|
subject | string | Ticket subject (1-300 chars) |
description | string | Free-text description (max 5000 chars) |
priority | string | LOW, MEDIUM, HIGH, or URGENT |
status | string | OPEN, IN_PROGRESS, RESOLVED, or CLOSED |
assigneeName | string | Assignee name (max 200 chars) |
assigneeEmail | string | Assignee email (validated format) |
contactName | string | Contact name (max 200 chars) |
contactEmail | string | Contact email (validated format) |
Response 200 OK: Updated ticket object.
Errors:
| Status | Error | Cause |
|---|---|---|
| 400 | Invalid data | Validation failure |
| 401 | Unauthorized | Not authenticated |
| 404 | Ticket with id "{id}" not found | Not found or not owner |
Delete Ticket
Delete a ticket. Must be the ticket owner.
curl -X DELETE "https://app.rivelko.com/api/tickets/:id" \
-H "Authorization: Bearer rvk_..."Response 200 OK:
{ "success": true }
Errors:
| Status | Error | Cause |
|---|---|---|
| 401 | Unauthorized | Not authenticated |
| 404 | Ticket with id "{id}" not found | Not found or not owner |
Account
Your profile, API key and current usage.
Get Current User
Get the authenticated user's profile. API key is returned masked (rvk_...xxxx).
curl -X GET "https://app.rivelko.com/api/users/me" \
-H "Authorization: Bearer rvk_..."Response 200 OK:
{
"id": "clx...",
"name": "Juan Perez",
"email": "juan@example.com",
"image": "https://...",
"plan": "PRO",
"apiKey": "rvk_...a1b2",
"createdAt": "2026-01-01T00:00:00.000Z"
}
Rotate API Key
Generate a new API key. Replaces the previous key. The full key is only returned once.
Genera una nueva API key. Reemplaza la anterior. La clave completa solo se muestra una vez.
curl -X POST "https://app.rivelko.com/api/users/me/api-key" \
-H "Authorization: Bearer rvk_..."Response 200 OK:
{
"apiKey": "rvk_a1b2c3d4e5f6...64_hex_chars"
}
Note: The returned key is immediately usable as
Authorization: Bearer rvk_...on any management endpoint. See Authentication.
Get Usage
Get current month's usage vs plan limits.
curl -X GET "https://app.rivelko.com/api/usage" \
-H "Authorization: Bearer rvk_..."Response 200 OK: Plan-specific usage object with current counts and limits.
Widget endpoints
These endpoints require no authentication. They are used by the embeddable widget and can be called from any client. Domain validation is enforced via the Origin or Referer header.
Estos endpoints no requieren autenticacion. Son usados por el widget embebible y validados por dominio.
Get Form Config
Fetch the configuration of a published form. Used by the widget on initialization.
curl -X GET "https://app.rivelko.com/api/forms/:id/config"Parameters:
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Form ID |
CORS: This endpoint is callable from any origin. It responds with Access-Control-Allow-Origin: * and implements OPTIONS for CORS preflight requests.
Response 200 OK:
{
"id": "clx...",
"name": "Dental Clinic Contact Form",
"welcomeMessage": "Hi! Tell me about your visit.",
"goodbyeMessage": "Thanks! We'll be in touch.",
"tone": "FRIENDLY",
"questions": [
{
"order": 1,
"text": "What is your name?",
"type": "text",
"required": true,
"options": null
}
],
"style": {
"primaryColor": "#E11D62",
"avatarUrl": null,
"position": "bottom-right",
"bubbleIcon": null
},
"language": "ES",
"formType": "CONVERSATIONAL",
"allowedDomains": ["example.com"],
"objective": "Collect leads for a dental consultation",
"showBranding": true
}
objective is the business objective used to steer the AI (may be null). showBranding indicates whether the "Powered by Rivelko" badge must be shown -- always true on the FREE plan, otherwise it follows the form's style setting.
Errors:
| Status | Error | Cause |
|---|---|---|
| 404 | Form with id "{id}" not found | Form does not exist or is not published |
| 403 | Account inactive (code: "SUBSCRIPTION_INACTIVE") | Owner's subscription is not active (paid plans) |
Send Chat Message
Send a message to a conversational form. Returns a streaming text response (chunked transfer encoding).
Envia un mensaje a un formulario conversacional. Devuelve una respuesta en streaming.
curl -X POST "https://app.rivelko.com/api/chat" \
-H "Origin: https://example.com" \
-H "Content-Type: application/json" \
-d '{
"formId": "YOUR_FORM_ID",
"message": "Hello"
}'Para continuar una conversación existente, reenvía el conversationId devuelto en la cabecera X-Conversation-Id:
{ "formId": "YOUR_FORM_ID", "conversationId": "CONV_ID", "message": "Me llamo Juan" }
Headers:
| Header | Value | Required |
|---|---|---|
| Content-Type | application/json | Yes |
| Origin | Your domain origin | Yes |
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
formId | string | Yes | Form ID |
conversationId | string | No | Conversation ID (omit for first message) |
message | string | Yes | Visitor message (1-5000 chars) |
Response 200 OK:
- Content-Type:
text/plain; charset=utf-8 - Body: Streaming text (AI assistant response, chunked)
- Header
X-Conversation-Id: The conversation ID (use for subsequent messages)
CORS Headers:
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: X-Conversation-Id
Errors:
| Status | Error | Cause |
|---|---|---|
| 400 | Invalid data | Validation failure (missing/invalid fields) |
| 400 | Classic forms do not support chat | Form type is CLASSIC |
| 403 | Domain not allowed | Origin not in form's allowedDomains |
| 404 | Form not found | Form does not exist or is not published |
| 404 | Conversation not found | Invalid conversationId for this form |
JavaScript Example (streaming):
const response = await fetch("https://app.rivelko.com/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ formId: "YOUR_FORM_ID", message: "Hello" }),
});
const conversationId = response.headers.get("X-Conversation-Id");
const reader = response.body.getReader();
const decoder = new TextDecoder();
let fullText = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
fullText += decoder.decode(value, { stream: true });
console.log(fullText); // progressive update
}
Submit Form Response
Submit a classic form with all answers at once.
Envia las respuestas de un formulario clasico.
curl -X POST "https://app.rivelko.com/api/forms/:id/submit" \
-H "Origin: https://example.com" \
-H "Content-Type: application/json" \
-d '{
"answers": {
"What is your name?": "Juan Perez",
"What is your email?": "juan@example.com"
}
}'Parameters:
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Form ID |
Headers:
| Header | Value | Required |
|---|---|---|
| Content-Type | application/json | Yes |
| Origin | Your domain origin | Yes |
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
answers | Record<string, string> | Yes | Map of question text to answer |
{
"answers": {
"What is your name?": "Juan Perez",
"What is your email?": "juan@example.com",
"What is your phone?": "+34 912 345 678"
}
}
Response 200 OK:
{
"success": true,
"conversationId": "clx..."
}
Errors:
| Status | Error | Cause |
|---|---|---|
| 400 | Invalid data | Validation failure |
| 400 | This endpoint is only for classic forms | Form type is CONVERSATIONAL |
| 400 | Missing required field: {question} | Required question not answered |
| 403 | Domain not allowed | Origin not in allowedDomains |
| 404 | Form not found | Form does not exist or is not published |
Get Widget Script
Serves the embeddable widget JavaScript bundle (~28KB).
curl -X GET "https://app.rivelko.com/api/widget"Response: application/javascript
Cached for 1 hour in production, no cache in development. You typically do not call this directly; use the <script> embed tag instead. See Widget Integration.
Internal endpoints
These endpoints are not part of the public API. They exist for the Rivelko dashboard (first-party UI) and offer no stability guarantee — do not call them from external integrations; they may change or be removed without notice. Listed here only for completeness.
Estos endpoints no forman parte de la API publica. Existen para el panel de Rivelko y no ofrecen garantia de estabilidad; no deben llamarse desde integraciones externas.
Create Checkout Session
POST /api/stripe/checkout — Creates a Stripe Checkout session and returns a checkout.stripe.com URL to redirect a signed-in user in the browser. Meaningless to a headless integration (there is no browser to redirect), so it is not part of the public API.
Open Billing Portal
POST /api/stripe/portal — Creates a Stripe Customer Portal session and returns a billing.stripe.com URL to redirect the user. First-party billing management only.
Get Dashboard Stats
GET /api/dashboard/stats — Returns overview counters shaped specifically for the dashboard UI (recentConversations, completionRate, …). For programmatic usage data use Get Usage instead.
Update Current User
PUT /api/users/me — Updates the signed-in user's display name. A settings-panel action; not part of the integration surface.