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 |
| 429 | Too many requests (code: "RATE_LIMITED") | Over 30 requests/minute from one IP — retry after Retry-After seconds |
| 429 | Capacity reached (code: "CONVERSATION_LIMIT_REACHED") | Form owner's monthly conversation allowance is used up |
| 429 | Message limit (code: "MESSAGE_LIMIT_REACHED") | The conversation hit its per-conversation message cap |
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 |
| 429 | Too many submissions (code: "RATE_LIMITED") | Over 10 submissions/minute from one IP — retry after Retry-After seconds |
| 429 | Capacity reached (code: "CONVERSATION_LIMIT_REACHED") | Form owner's monthly conversation allowance is used up |
Request Upload URL
Request a short-lived presigned URL to upload a file for a file-type question on a classic form. Requires the instance to have S3-compatible storage configured (RIVELKO_S3_*) and the form owner's plan to include file uploads.
Solicita una URL prefirmada de corta duración para subir un archivo en una pregunta de tipo file de un formulario clásico.
curl -X POST "https://app.rivelko.com/api/forms/:id/upload-url" \
-H "Origin: https://example.com" \
-H "Content-Type: application/json" \
-d '{
"questionText": "Attach your ID document",
"fileName": "passport.pdf",
"contentType": "application/pdf",
"sizeBytes": 245678
}'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 |
|---|---|---|---|
questionText | string | Yes | Text of the file-type question this upload answers |
fileName | string | Yes | Original file name (max 300 chars) |
contentType | string | Yes | MIME type of the file (max 200 chars) |
sizeBytes | number | Yes | Declared file size in bytes (integer >= 1) |
Response 200 OK:
{
"uploadId": "clx...",
"url": "https://<account-id>.r2.cloudflarestorage.com/<bucket>/forms/.../passport.pdf?X-Amz-..."
}
url is a presigned PUT URL valid for 10 minutes -- upload the raw file bytes there directly, setting the Content-Type header to the same value sent in the request body. Then, when calling Submit Form Response, answer the question with the marker string rivelko:file:{uploadId} in place of the file itself; the server re-verifies the uploaded object (type and actual size) before accepting the submission.
Errors:
| Status | Error | Cause |
|---|---|---|
| 400 | Invalid data | Validation failure (missing/invalid fields) |
| 400 | Unknown file question | questionText doesn't match a file-type question on this form |
| 400 | File type not allowed for this question | Extension/MIME doesn't match the question's configured file category |
| 400 | File too large (max {N}MB) | sizeBytes exceeds the plan's per-file size cap |
| 403 | Domain not allowed | Origin not in the form's allowedDomains |
| 403 | File uploads are not available (code: "FEATURE_NOT_AVAILABLE") | Instance has no RIVELKO_S3_* storage configured (feature disabled) |
| 403 | File uploads are not available on this plan (code: "FEATURE_NOT_AVAILABLE") | Form owner's plan doesn't include file uploads (FREE) |
| 403 | Plan limit reached: maximum {N} storage MB (code: "STORAGE_LIMIT_REACHED") | Owner's total stored bytes would exceed the plan's storage cap |
| 404 | Form not found | Form does not exist, is not published, or is not a CLASSIC form |
| 429 | Too many uploads (code: "RATE_LIMITED") | Over 10 requests/minute from one IP — retry after Retry-After seconds |
Get Widget Script
Serves the embeddable widget JavaScript bundle (~11KB gzipped).
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.
Download Uploaded File
GET /api/uploads/:id/download — Authenticates the caller (session or API key) and 302-redirects to a 60-second presigned URL for the underlying file object. Read-then-check like every cross-tenant lookup in this codebase: a foreign or nonexistent upload id returns the identical 404 Upload with id "{id}" not found — ownership can't be probed by testing ids.