API REST
Todo lo que haces en el panel, por HTTP y en JSON. La API y el servidor MCP comparten el mismo registro de 84 operaciones y las mismas llaves. Disponible en Starter y Pro.
Autenticación
Crea una llave en Ajustes → API. Se muestra una sola vez: guárdala en tu gestor de secretos. Mándala en cada petición con la cabecera Authorization: Bearer pk_live_….
Dirección base: https://jotly.si/api/v1
curl "https://jotly.si/api/v1/requests?statuses=planned,in_progress&sort=top&limit=10" \ -H "Authorization: Bearer pk_live_TU_LLAVE"Cada llave tiene acceso completo a su espacio. Crea una por integración o agente para poder revocarla sin afectar a las demás.
Crear y responder
Los parámetros de ruta van en la URL; en GET y DELETE el resto va en la query, y en POST, PATCH y PUT en un cuerpo JSON. Cada recurso viene envuelto en su nombre ({ request }, { board }, { member }). Al crear la respuesta es 201; las relaciones (votar, suscribir, etiquetar) responden { ok: true } y los borrados 204 sin cuerpo.
curl -X POST "https://jotly.si/api/v1/requests" \ -H "Authorization: Bearer pk_live_TU_LLAVE" \ -H "Content-Type: application/json" \ -d '{ "board_id": "brd_123", "title": "Exportación automática cada mes", "body": "Que las facturas lleguen solas al contador.", "author": { "external_id": "u_812" } }'HTTP/1.1 201 CreatedX-Request-Id: req_8f2k1x0c9q7m3v5b6n4z { "request": { "id": "post_4j8w2q", "number": 42, "title": "Exportación automática cada mes", "status": "pending", "board": { "id": "brd_123", "name": "Funciones", "type": "public" }, "author": { "id": "cus_9x1", "external_id": "u_812", "type": "user", "first_name": "Lucía" }, "tags": [], "attachments": [], "vote_count": 1, "comment_count": 0 }}Los miembros (author, voter, subscriber, actor, member) se referencian con { id } o { external_id } (tu id de usuario). Jotly también acepta { email }; con external_id o email el miembro se crea si no existe. El actor de una respuesta o de un cambio de estado tiene que ser alguien del equipo; sin él actúa quien creó la llave.
Paginación
Todas las listas responden { <recurso>: [...], next_cursor }. Pide la siguiente página con cursor igual al next_cursor de la anterior; cuando llega null ya no hay más. limit va de 1 a 100 (10 en solicitudes y novedades, 20 en conversaciones y miembros, 50 en votos y en la línea de tiempo). El cursor es opaco: no lo armes a mano.
Idempotencia
POST /requests, POST /changelogs, POST /assets/import y las respuestas y notas de conversación aceptan la cabecera Idempotency-Key. Durante 24 horas, repetir la petición con la misma llave y el mismo cuerpo devuelve la respuesta original (con la cabecera Idempotent-Replayed: true) en vez de crear otra vez.
curl -X POST "https://jotly.si/api/v1/conversations/cnv_123/replies" \ -H "Authorization: Bearer pk_live_TU_LLAVE" \ -H "Idempotency-Key: corrida-2026-10-19-cnv_123" \ -H "Content-Type: application/json" \ -d '{ "body": "Listo, ya quedó.", "actor": { "external_id": "agente-soporte" } }'- La misma llave con otro cuerpo responde
409 idempotency_key_reused. - Si la primera petición sigue en curso,
409 idempotency_in_progress: reintenta en unos segundos. - Si la petición falla, la llave se libera y puedes reintentar con ella.
Límites de tasa
Cada llave tiene un límite por minuto que comparten la API REST y el servidor MCP (120 por omisión). Al pasarlo, la respuesta es 429 con la cabecera Retry-After y los segundos de espera en details.retry_after_seconds.
HTTP/1.1 429 Too Many RequestsRetry-After: 23 { "error": { "code": "rate_limited", "message": "Demasiadas peticiones: el límite es 120 por minuto por llave", "details": { "retry_after_seconds": 23 } }}Errores
Los errores responden con el código HTTP correspondiente y { error: { code, message, details? } }. Toda respuesta, también los errores, trae la cabecera X-Request-Id: inclúyela si nos escribes a soporte.
{ "error": { "code": "invalid_api_token", "message": "Token de API inválido" }}| HTTP | Códigos |
|---|---|
| 400 | invalid_request |
| 401 | invalid_api_token |
| 404 | not_found |
| 409 | board_name_conflict, board_not_empty, tag_name_conflict, member_email_conflict, member_identity_conflict, member_not_erasable, member_erased, company_identity_conflict, conversation_status_conflict, idempotency_key_reused, idempotency_in_progress |
| 410 | member_erased |
| 422 | validation_error (con details.issues), invalid_parent_comment, invalid_changelog_author, invalid_conversation_actor, invalid_conversation_assignee, invalid_attachment, invalid_asset |
| 429 | rate_limited |
Miembros y empresas
POST /members/sync crea o actualiza un miembro por external_id; sus traits se fusionan (un rasgo en null se borra) y companies crea o actualiza sus empresas y lo liga a ellas. El identify del widget acepta el mismo companies. DELETE /members/:member_id borra sus datos personales y conserva sus solicitudes, comentarios y votos; después, leerlo o borrarlo otra vez responde 410 member_erased.
Adjuntos y archivos: Jotly todavía no tiene almacenamiento de archivos, así queattachment_urls,addRequestAttachments,addCommentAttachments,POST /assetsyPOST /assets/importresponden422 invalid_attachmentoinvalid_asset. Listar y quitar adjuntos sí funciona.
Operaciones
El detalle de cada parámetro está en la especificación OpenAPI. Puedes importarla en Postman o Insomnia, o generar un cliente con ella.
Tableros
| Método | Ruta | Qué hace |
|---|---|---|
| GET | /boards | listBoardsLista los tableros de feedback del espacio, en su orden. |
| POST | /boards | createBoardCrea un tablero de feedback. 409 board_name_conflict si ya existe uno con ese nombre. |
| GET | /boards/:board_id | getBoardObtiene un tablero. |
| PATCH | /boards/:board_id | updateBoardEdita nombre, color, descripción o visibilidad (public o private) de un tablero. |
| DELETE | /boards/:board_id | deleteBoardBorra un tablero vacío. 409 board_not_empty si todavía tiene solicitudes. |
Solicitudes
| Método | Ruta | Qué hace |
|---|---|---|
| GET | /requests/tags | listRequestTagsLista las etiquetas de solicitudes del espacio. |
| POST | /requests/tags | createRequestTagCrea una etiqueta de solicitudes. 409 tag_name_conflict si ya existe. |
| DELETE | /requests/tags/:tag_id | deleteRequestTagBorra una etiqueta de solicitudes y la quita de todas las solicitudes. |
| GET | /requests | listRequestsLista solicitudes con filtros y orden. sort: newest, oldest, top, trending o status_updated. |
| POST | /requests | createRequestCrea una solicitud a nombre de un miembro (author { id } o { external_id }). Acepta Idempotency-Key. |
| GET | /requests/:request_id | getRequestObtiene una solicitud por id o por número. |
| PATCH | /requests/:request_id | updateRequestEdita título o cuerpo de una solicitud (Jotly también acepta hidden). |
| DELETE | /requests/:request_id | deleteRequestBorra una solicitud con sus votos y comentarios. |
| GET | /requests/:request_id/similar | listSimilarRequestsSolicitudes parecidas a otra (o a un texto en query), para detectar duplicados. similarity va de 0 a 1. |
| PATCH | /requests/:request_id/status | updateRequestStatusCambia el estado y avisa por correo a autor, votantes y seguidores. actor opcional: quién lo cambia (alguien del equipo). |
| PATCH | /requests/:request_id/board | moveRequestBoardMueve una solicitud a otro tablero. |
| POST | /requests/:request_id/merge | mergeRequestFusiona una solicitud duplicada en la canónica (into_request_id). Votos y comentarios pasan a la canónica. |
| GET | /requests/:request_id/votes | listRequestVotesLista quién votó una solicitud, del voto más nuevo al más viejo. |
| POST | /requests/:request_id/votes | addRequestVoteVota una solicitud en nombre de un miembro (por ejemplo, tras una llamada de ventas). |
| DELETE | /requests/:request_id/votes | deleteRequestVoteQuita el voto de un miembro (voter_id o voter_external_id). |
| GET | /requests/:request_id/subscriptions | listRequestSubscriptionsLista quién sigue una solicitud (recibe sus avisos). |
| POST | /requests/:request_id/subscriptions | addRequestSubscriptionSuscribe a un miembro a los avisos de una solicitud. |
| DELETE | /requests/:request_id/subscriptions | deleteRequestSubscriptionQuita la suscripción de un miembro (subscriber_id o subscriber_external_id). |
| GET | /requests/:request_id/tags | listRequestTagAssignmentsLista las etiquetas de una solicitud. |
| POST | /requests/:request_id/tags | addRequestTagsAgrega etiquetas a una solicitud sin quitar las que ya tiene. |
| DELETE | /requests/:request_id/tags/:tag_id | deleteRequestTagAssignmentQuita una etiqueta de una solicitud. |
| PUT | /requests/:request_id/tags | setRequestTagsReemplaza todas las etiquetas de una solicitud (Jotly). |
| GET | /requests/:request_id/comments | listRequestCommentsLista los comentarios de una solicitud en hilos de un nivel (replies), con los fijados primero. Incluye notas internas. |
| POST | /requests/:request_id/comments | createRequestCommentComenta una solicitud a nombre de un miembro (author). Sin author comenta el equipo. visibility internal = nota que el cliente no ve. Una respuesta a otra respuesta queda en el mismo hilo. |
| GET | /requests/:request_id/comments/:comment_id | getCommentObtiene un comentario. |
| PATCH | /requests/:request_id/comments/:comment_id | updateCommentEdita el texto de un comentario. |
| DELETE | /requests/:request_id/comments/:comment_id | deleteCommentBorra un comentario y sus respuestas. |
| GET | /requests/:request_id/comments/:comment_id/votes | listCommentVotesLista quién votó un comentario. |
| POST | /requests/:request_id/comments/:comment_id/votes | addCommentVoteVota un comentario en nombre de un miembro. |
| DELETE | /requests/:request_id/comments/:comment_id/votes | deleteCommentVoteQuita el voto de un miembro en un comentario (voter_id o voter_external_id). |
| PUT | /requests/:request_id/comments/:comment_id/pin | pinCommentFija un comentario arriba del hilo. |
| DELETE | /requests/:request_id/comments/:comment_id/pin | unpinCommentDesfija un comentario. |
| GET | /requests/:request_id/attachments | listRequestAttachmentsLista las imágenes adjuntas de una solicitud (URLs firmadas que caducan en una hora). |
| POST | /requests/:request_id/attachments | addRequestAttachmentsAdjunta imágenes a una solicitud a partir de URLs (máximo 4 por solicitud). |
| DELETE | /requests/:request_id/attachments/:attachment_id | deleteRequestAttachmentQuita un adjunto de una solicitud. |
| GET | /requests/:request_id/comments/:comment_id/attachments | listCommentAttachmentsLista las imágenes adjuntas de un comentario. |
| POST | /requests/:request_id/comments/:comment_id/attachments | addCommentAttachmentsAdjunta imágenes a un comentario a partir de URLs (máximo 4 por comentario). |
| DELETE | /requests/:request_id/comments/:comment_id/attachments/:attachment_id | deleteCommentAttachmentQuita un adjunto de un comentario. |
Archivos
| Método | Ruta | Qué hace |
|---|---|---|
| POST | /assets | createAssetSube una imagen (multipart: file, name). Sin almacenamiento de archivos configurado responde invalid_asset. |
| POST | /assets/import | importAssetImporta una imagen desde una URL HTTPS pública (hasta 3 redirecciones, 10 MiB, PNG, JPEG, GIF o WebP). Devuelve el asset con su URL permanente. |
| GET | /assets/:asset_id | getAssetObtiene un asset del espacio. |
Novedades
| Método | Ruta | Qué hace |
|---|---|---|
| GET | /changelogs/tags | listChangelogTagsLista las etiquetas del changelog (Nueva función, Mejora, Corrección...). |
| POST | /changelogs/tags | createChangelogTagCrea una etiqueta del changelog. 409 tag_name_conflict si ya existe. |
| DELETE | /changelogs/tags/:tag_id | deleteChangelogTagBorra una etiqueta del changelog y la quita de todas las entradas. |
| GET | /changelogs | listChangelogsLista entradas del changelog. status: draft, scheduled o published. |
| POST | /changelogs | createChangelogCrea una entrada del changelog. Queda en borrador salvo status=published (o publish_at futuro para programarla). Sin authors firma quien creó la llave. Acepta Idempotency-Key. |
| GET | /changelogs/:changelog_id | getChangelogObtiene una entrada del changelog con etiquetas, autores y solicitudes vinculadas. |
| PATCH | /changelogs/:changelog_id | updateChangelogEdita una entrada; status publica, programa (con publish_at) o regresa a borrador. |
| DELETE | /changelogs/:changelog_id | deleteChangelogBorra una entrada del changelog. |
| GET | /changelogs/:changelog_id/tags | listChangelogTagAssignmentsLista las etiquetas de una entrada del changelog. |
| POST | /changelogs/:changelog_id/tags | addChangelogTagsAgrega etiquetas a una entrada sin quitar las que ya tiene. |
| DELETE | /changelogs/:changelog_id/tags/:tag_id | deleteChangelogTagAssignmentQuita una etiqueta de una entrada. |
| GET | /changelogs/:changelog_id/authors | listChangelogAuthorsLista los autores de una entrada. |
| POST | /changelogs/:changelog_id/authors | addChangelogAuthorsAgrega autores a una entrada. Tienen que ser del equipo (invalid_changelog_author si no). |
| DELETE | /changelogs/:changelog_id/authors/:member_id | deleteChangelogAuthorQuita a un autor de una entrada. |
| GET | /changelogs/:changelog_id/requests | listChangelogRequestsLista las solicitudes que cierra una entrada. |
| POST | /changelogs/:changelog_id/requests | addChangelogRequestsVincula solicitudes (por id o número) a una entrada; sus votantes ven que se cumplió. |
| DELETE | /changelogs/:changelog_id/requests/:request_id | deleteChangelogRequestDesvincula una solicitud de una entrada. |
Miembros
| Método | Ruta | Qué hace |
|---|---|---|
| GET | /members | listMembersLista miembros (quienes votan, publican o chatean). type: user, admin u owner. company_external_id filtra por empresa. |
| GET | /members/lookup | lookupMemberBusca un miembro por external_id o por correo. |
| POST | /members/sync | syncMemberCrea o actualiza un miembro por external_id (tu id de usuario). Los traits se fusionan. companies[] crea o actualiza sus empresas y lo liga a ellas. |
| GET | /members/:member_id | getMemberObtiene un miembro. 410 member_erased si sus datos se borraron. |
| PATCH | /members/:member_id | updateMemberEdita un miembro: external_id, correo, nombre, avatar o traits (se fusionan; un trait en null se borra). |
| DELETE | /members/:member_id | deleteMemberBorra los datos personales de un miembro (correo, nombre, avatar, external_id, traits y empresas). Sus solicitudes, comentarios y votos se quedan. 410 si ya se borró; 409 member_not_erasable si es del equipo. |
Empresas
| Método | Ruta | Qué hace |
|---|---|---|
| POST | /companies/sync | syncCompanyCrea o actualiza una empresa por external_id. name es obligatorio al crearla; los traits se fusionan. |
| GET | /companies | listCompaniesLista empresas. Filtra por search (nombre o dominio), external_id o domain. |
| GET | /companies/lookup | lookupCompanyBusca una empresa por external_id. |
| GET | /companies/:company_id | getCompanyObtiene una empresa. |
| PATCH | /companies/:company_id | updateCompanyEdita una empresa. 409 company_identity_conflict si el external_id nuevo ya es de otra. |
| GET | /companies/:company_id/members | listCompanyMembersLista los miembros de una empresa. |
| POST | /companies/:company_id/members | addCompanyMemberLiga un miembro ({ id } o { external_id }) a una empresa. |
| DELETE | /companies/:company_id/members/:member_id | deleteCompanyMemberDesliga a un miembro de una empresa. |
Conversaciones
| Método | Ruta | Qué hace |
|---|---|---|
| GET | /conversations | listConversationsLista conversaciones. inbox_state: needs_team (esperan al equipo), ai_handling (las atiende la IA) o waiting_on_customer. Para agentes programados: updated_since + inbox_state=needs_team. |
| GET | /conversations/:conversation_id | getConversationObtiene una conversación. Sus mensajes están en listConversationTimeline. |
| GET | /conversations/:conversation_id/timeline | listConversationTimelineLínea de tiempo de una conversación, de lo más viejo a lo más nuevo. types: message, note, ai_handoff, resolved, reopened, assigned, unassigned. |
| POST | /conversations/:conversation_id/replies | createConversationReplyResponde al cliente como alguien del equipo (actor; sin actor, quien creó la llave). La IA se retira de la conversación; reabre si estaba resuelta. Acepta Idempotency-Key. |
| POST | /conversations/:conversation_id/notes | createConversationNoteAgrega una nota interna (el cliente no la ve). Acepta Idempotency-Key. |
| PATCH | /conversations/:conversation_id/status | updateConversationStatusResuelve o reabre una conversación. 409 conversation_status_conflict si está archivada o en spam. |
| PATCH | /conversations/:conversation_id/assignee | updateConversationAssigneeAsigna la conversación a alguien del equipo (assignee { id } o { external_id }, o assignee_email) o la desasigna con null. |
Ver también Servidor MCP