Saltar al contenido

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

Listar solicitudes
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.

Crear una solicitud a nombre de un miembro
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" }  }'
201
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.

Un agente que reintenta sin duplicar respuestas
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.

429
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.

401
{  "error": {    "code": "invalid_api_token",    "message": "Token de API inválido"  }}
HTTPCódigos
400invalid_request
401invalid_api_token
404not_found
409board_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
410member_erased
422validation_error (con details.issues), invalid_parent_comment, invalid_changelog_author, invalid_conversation_actor, invalid_conversation_assignee, invalid_attachment, invalid_asset
429rate_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í que attachment_urls, addRequestAttachments, addCommentAttachments, POST /assets y POST /assets/import responden 422 invalid_attachment o invalid_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étodoRutaQué hace
GET/boardslistBoardsLista los tableros de feedback del espacio, en su orden.
POST/boardscreateBoardCrea un tablero de feedback. 409 board_name_conflict si ya existe uno con ese nombre.
GET/boards/:board_idgetBoardObtiene un tablero.
PATCH/boards/:board_idupdateBoardEdita nombre, color, descripción o visibilidad (public o private) de un tablero.
DELETE/boards/:board_iddeleteBoardBorra un tablero vacío. 409 board_not_empty si todavía tiene solicitudes.

Solicitudes

MétodoRutaQué hace
GET/requests/tagslistRequestTagsLista las etiquetas de solicitudes del espacio.
POST/requests/tagscreateRequestTagCrea una etiqueta de solicitudes. 409 tag_name_conflict si ya existe.
DELETE/requests/tags/:tag_iddeleteRequestTagBorra una etiqueta de solicitudes y la quita de todas las solicitudes.
GET/requestslistRequestsLista solicitudes con filtros y orden. sort: newest, oldest, top, trending o status_updated.
POST/requestscreateRequestCrea una solicitud a nombre de un miembro (author { id } o { external_id }). Acepta Idempotency-Key.
GET/requests/:request_idgetRequestObtiene una solicitud por id o por número.
PATCH/requests/:request_idupdateRequestEdita título o cuerpo de una solicitud (Jotly también acepta hidden).
DELETE/requests/:request_iddeleteRequestBorra una solicitud con sus votos y comentarios.
GET/requests/:request_id/similarlistSimilarRequestsSolicitudes parecidas a otra (o a un texto en query), para detectar duplicados. similarity va de 0 a 1.
PATCH/requests/:request_id/statusupdateRequestStatusCambia el estado y avisa por correo a autor, votantes y seguidores. actor opcional: quién lo cambia (alguien del equipo).
PATCH/requests/:request_id/boardmoveRequestBoardMueve una solicitud a otro tablero.
POST/requests/:request_id/mergemergeRequestFusiona una solicitud duplicada en la canónica (into_request_id). Votos y comentarios pasan a la canónica.
GET/requests/:request_id/voteslistRequestVotesLista quién votó una solicitud, del voto más nuevo al más viejo.
POST/requests/:request_id/votesaddRequestVoteVota una solicitud en nombre de un miembro (por ejemplo, tras una llamada de ventas).
DELETE/requests/:request_id/votesdeleteRequestVoteQuita el voto de un miembro (voter_id o voter_external_id).
GET/requests/:request_id/subscriptionslistRequestSubscriptionsLista quién sigue una solicitud (recibe sus avisos).
POST/requests/:request_id/subscriptionsaddRequestSubscriptionSuscribe a un miembro a los avisos de una solicitud.
DELETE/requests/:request_id/subscriptionsdeleteRequestSubscriptionQuita la suscripción de un miembro (subscriber_id o subscriber_external_id).
GET/requests/:request_id/tagslistRequestTagAssignmentsLista las etiquetas de una solicitud.
POST/requests/:request_id/tagsaddRequestTagsAgrega etiquetas a una solicitud sin quitar las que ya tiene.
DELETE/requests/:request_id/tags/:tag_iddeleteRequestTagAssignmentQuita una etiqueta de una solicitud.
PUT/requests/:request_id/tagssetRequestTagsReemplaza todas las etiquetas de una solicitud (Jotly).
GET/requests/:request_id/commentslistRequestCommentsLista los comentarios de una solicitud en hilos de un nivel (replies), con los fijados primero. Incluye notas internas.
POST/requests/:request_id/commentscreateRequestCommentComenta 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_idgetCommentObtiene un comentario.
PATCH/requests/:request_id/comments/:comment_idupdateCommentEdita el texto de un comentario.
DELETE/requests/:request_id/comments/:comment_iddeleteCommentBorra un comentario y sus respuestas.
GET/requests/:request_id/comments/:comment_id/voteslistCommentVotesLista quién votó un comentario.
POST/requests/:request_id/comments/:comment_id/votesaddCommentVoteVota un comentario en nombre de un miembro.
DELETE/requests/:request_id/comments/:comment_id/votesdeleteCommentVoteQuita el voto de un miembro en un comentario (voter_id o voter_external_id).
PUT/requests/:request_id/comments/:comment_id/pinpinCommentFija un comentario arriba del hilo.
DELETE/requests/:request_id/comments/:comment_id/pinunpinCommentDesfija un comentario.
GET/requests/:request_id/attachmentslistRequestAttachmentsLista las imágenes adjuntas de una solicitud (URLs firmadas que caducan en una hora).
POST/requests/:request_id/attachmentsaddRequestAttachmentsAdjunta imágenes a una solicitud a partir de URLs (máximo 4 por solicitud).
DELETE/requests/:request_id/attachments/:attachment_iddeleteRequestAttachmentQuita un adjunto de una solicitud.
GET/requests/:request_id/comments/:comment_id/attachmentslistCommentAttachmentsLista las imágenes adjuntas de un comentario.
POST/requests/:request_id/comments/:comment_id/attachmentsaddCommentAttachmentsAdjunta imágenes a un comentario a partir de URLs (máximo 4 por comentario).
DELETE/requests/:request_id/comments/:comment_id/attachments/:attachment_iddeleteCommentAttachmentQuita un adjunto de un comentario.

Archivos

MétodoRutaQué hace
POST/assetscreateAssetSube una imagen (multipart: file, name). Sin almacenamiento de archivos configurado responde invalid_asset.
POST/assets/importimportAssetImporta 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_idgetAssetObtiene un asset del espacio.

Novedades

MétodoRutaQué hace
GET/changelogs/tagslistChangelogTagsLista las etiquetas del changelog (Nueva función, Mejora, Corrección...).
POST/changelogs/tagscreateChangelogTagCrea una etiqueta del changelog. 409 tag_name_conflict si ya existe.
DELETE/changelogs/tags/:tag_iddeleteChangelogTagBorra una etiqueta del changelog y la quita de todas las entradas.
GET/changelogslistChangelogsLista entradas del changelog. status: draft, scheduled o published.
POST/changelogscreateChangelogCrea 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_idgetChangelogObtiene una entrada del changelog con etiquetas, autores y solicitudes vinculadas.
PATCH/changelogs/:changelog_idupdateChangelogEdita una entrada; status publica, programa (con publish_at) o regresa a borrador.
DELETE/changelogs/:changelog_iddeleteChangelogBorra una entrada del changelog.
GET/changelogs/:changelog_id/tagslistChangelogTagAssignmentsLista las etiquetas de una entrada del changelog.
POST/changelogs/:changelog_id/tagsaddChangelogTagsAgrega etiquetas a una entrada sin quitar las que ya tiene.
DELETE/changelogs/:changelog_id/tags/:tag_iddeleteChangelogTagAssignmentQuita una etiqueta de una entrada.
GET/changelogs/:changelog_id/authorslistChangelogAuthorsLista los autores de una entrada.
POST/changelogs/:changelog_id/authorsaddChangelogAuthorsAgrega autores a una entrada. Tienen que ser del equipo (invalid_changelog_author si no).
DELETE/changelogs/:changelog_id/authors/:member_iddeleteChangelogAuthorQuita a un autor de una entrada.
GET/changelogs/:changelog_id/requestslistChangelogRequestsLista las solicitudes que cierra una entrada.
POST/changelogs/:changelog_id/requestsaddChangelogRequestsVincula solicitudes (por id o número) a una entrada; sus votantes ven que se cumplió.
DELETE/changelogs/:changelog_id/requests/:request_iddeleteChangelogRequestDesvincula una solicitud de una entrada.

Miembros

MétodoRutaQué hace
GET/memberslistMembersLista miembros (quienes votan, publican o chatean). type: user, admin u owner. company_external_id filtra por empresa.
GET/members/lookuplookupMemberBusca un miembro por external_id o por correo.
POST/members/syncsyncMemberCrea 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_idgetMemberObtiene un miembro. 410 member_erased si sus datos se borraron.
PATCH/members/:member_idupdateMemberEdita un miembro: external_id, correo, nombre, avatar o traits (se fusionan; un trait en null se borra).
DELETE/members/:member_iddeleteMemberBorra 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étodoRutaQué hace
POST/companies/syncsyncCompanyCrea o actualiza una empresa por external_id. name es obligatorio al crearla; los traits se fusionan.
GET/companieslistCompaniesLista empresas. Filtra por search (nombre o dominio), external_id o domain.
GET/companies/lookuplookupCompanyBusca una empresa por external_id.
GET/companies/:company_idgetCompanyObtiene una empresa.
PATCH/companies/:company_idupdateCompanyEdita una empresa. 409 company_identity_conflict si el external_id nuevo ya es de otra.
GET/companies/:company_id/memberslistCompanyMembersLista los miembros de una empresa.
POST/companies/:company_id/membersaddCompanyMemberLiga un miembro ({ id } o { external_id }) a una empresa.
DELETE/companies/:company_id/members/:member_iddeleteCompanyMemberDesliga a un miembro de una empresa.

Conversaciones

MétodoRutaQué hace
GET/conversationslistConversationsLista 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_idgetConversationObtiene una conversación. Sus mensajes están en listConversationTimeline.
GET/conversations/:conversation_id/timelinelistConversationTimelineLí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/repliescreateConversationReplyResponde 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/notescreateConversationNoteAgrega una nota interna (el cliente no la ve). Acepta Idempotency-Key.
PATCH/conversations/:conversation_id/statusupdateConversationStatusResuelve o reabre una conversación. 409 conversation_status_conflict si está archivada o en spam.
PATCH/conversations/:conversation_id/assigneeupdateConversationAssigneeAsigna 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