{"openapi":"3.0.3","info":{"title":"AI-Tasker API","version":"0.2.0","description":"MicroSaaS API-first de gestión de tareas por agente IA. Auth triple: Clerk JWT (dashboard), header x-api-key o Authorization: Bearer atkr_… (integraciones y LLMs vía MCP). Modelo nexo-común: los agentes externos traen su propio LLM; los endpoints /api/ai/* (LLM server-side Gemini/DeepSeek) están DESHABILITADOS por defecto y devuelven 503 AI_SERVER_DISABLED salvo AI_ENABLED=true. v2: jerarquía Workspace → Project → Sprint/Task, códigos de tarea automáticos (KEY-N por proyecto), actores {userId, channel}, relaciones tipadas TaskLink (blocks/related) y ficheros externos por URL.\n"},"servers":[{"url":"https://api.ai-tasker.theboomer.dev"}],"components":{"securitySchemes":{"clerkJwt":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Sesión Clerk del dashboard"},"apiKeyHeader":{"type":"apiKey","in":"header","name":"x-api-key"},"apiKeyBearer":{"type":"http","scheme":"bearer","bearerFormat":"atkr_","description":"API key de integración/LLM (prefijo atkr_)"}},"schemas":{"Actor":{"type":"object","description":"autoría de una acción (creador, asignado, comentarista, dueño de un link)","required":["userId"],"properties":{"userId":{"type":"string","description":"clerkId o id lógico del agente"},"channel":{"type":"string","enum":["human","cli","llm","agent"],"default":"human","description":"JWT→human; API key→su channel declarado"}}},"AcceptanceCriterion":{"type":"object","properties":{"text":{"type":"string"},"checked":{"type":"boolean","default":false}}},"FileRef":{"type":"object","description":"referencia a un fichero externo (no se suben binarios)","required":["kind","url"],"properties":{"kind":{"type":"string","enum":["gdrive","aws","minio","url"]},"url":{"type":"string"},"label":{"type":"string"}}},"Project":{"type":"object","properties":{"_id":{"type":"string"},"workspaceId":{"type":"string"},"parent":{"type":"string","nullable":true,"description":"proyecto padre (anidado, recursivo); null = raíz. El slug del hijo lleva el del padre como prefijo"},"name":{"type":"string"},"slug":{"type":"string","description":"único por workspace; usado en la URL"},"key":{"type":"string","pattern":"^[A-Z2-9]{2,8}$","description":"prefijo de códigos de tarea (sin I/O/0/1)"},"description":{"type":"string"},"repoUrl":{"type":"string","nullable":true},"branch":{"type":"string","nullable":true},"version":{"type":"string","nullable":true,"description":"versión del proyecto (package.json)"},"status":{"type":"string","enum":["active","archived"]},"taskCounter":{"type":"number"},"createdAt":{"type":"string","format":"date-time"}}},"TaskLink":{"type":"object","properties":{"_id":{"type":"string"},"from":{"type":"string"},"to":{"type":"string"},"type":{"type":"string","enum":["blocks","related"]},"projectId":{"type":"string"},"createdBy":{"$ref":"#/components/schemas/Actor"}}},"Task":{"type":"object","properties":{"_id":{"type":"string"},"code":{"type":"string","description":"\"KEY-N\" asignado por el servidor, único por proyecto"},"title":{"type":"string"},"description":{"type":"string"},"body":{"type":"string","description":"cuerpo extendido en markdown"},"status":{"type":"string","enum":["backlog","todo","in_progress","review","done"]},"priority":{"type":"string","enum":["urgent","high","normal","low"]},"tags":{"type":"array","items":{"type":"string"}},"assignees":{"type":"array","items":{"$ref":"#/components/schemas/Actor"}},"creator":{"$ref":"#/components/schemas/Actor"},"startDate":{"type":"string","format":"date-time","nullable":true},"endDate":{"type":"string","format":"date-time","nullable":true},"dueDateCustom":{"type":"string","nullable":true,"description":"fecha en lenguaje natural"},"estimatedMinutes":{"type":"number","nullable":true,"description":"duración estimada en minutos (UI HH:mm)"},"actualHours":{"type":"number","nullable":true},"goal":{"type":"string","nullable":true},"acceptanceCriteria":{"type":"array","items":{"$ref":"#/components/schemas/AcceptanceCriterion"}},"files":{"type":"array","items":{"$ref":"#/components/schemas/FileRef"}},"repoUrl":{"type":"string","nullable":true},"branch":{"type":"string","nullable":true},"version":{"type":"string","nullable":true},"projectVersion":{"type":"string","nullable":true,"description":"snapshot de Project.version al crear"},"position":{"type":"number"},"workspaceId":{"type":"string"},"projectId":{"type":"string"},"parent":{"type":"string","nullable":true},"children":{"type":"array","items":{"type":"string"}},"sprintId":{"type":"string","nullable":true},"blocks":{"type":"array","items":{"type":"string"},"description":"solo en GET detalle: tareas que esta tarea bloquea"},"blockedBy":{"type":"array","items":{"type":"string"},"description":"solo en GET detalle: bloqueos pendientes sobre esta tarea"},"related":{"type":"array","items":{"type":"string"},"description":"solo en GET detalle"},"comments":{"type":"array","items":{"type":"object","properties":{"text":{"type":"string"},"author":{"$ref":"#/components/schemas/Actor"},"createdAt":{"type":"string","format":"date-time"}}}},"createdBy":{"type":"string"},"deleted":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"TaskAttachment":{"type":"object","required":["_id","filename","mimeType","size","sha256","createdBy","createdAt","canDelete"],"properties":{"_id":{"type":"string"},"filename":{"type":"string","maxLength":255},"mimeType":{"type":"string","enum":["image/jpeg","image/png","image/webp","application/pdf"]},"size":{"type":"integer","minimum":1,"maximum":10485760},"sha256":{"type":"string","pattern":"^[0-9a-f]{64}$"},"createdBy":{"$ref":"#/components/schemas/Actor"},"createdAt":{"type":"string","format":"date-time"},"canDelete":{"type":"boolean"}}},"AttachmentDownloadLink":{"type":"object","required":["url","expiresAt"],"properties":{"url":{"type":"string","description":"relative one-use bearer-capability URL; expires after five minutes"},"expiresAt":{"type":"string","format":"date-time"}}},"ActivityActor":{"type":"object","required":["userId"],"properties":{"userId":{"type":"string","description":"identidad humana propietaria de la acción"},"apiKeyId":{"type":"string","nullable":true,"description":"id de la API key cuando la llamada es de agente"},"channel":{"type":"string","enum":["human","cli","llm","agent"]}}},"TaskActivity":{"type":"object","required":["taskId","projectId","workspaceId","kind","actor","createdAt"],"properties":{"_id":{"type":"string"},"taskId":{"type":"string"},"projectId":{"type":"string"},"workspaceId":{"type":"string"},"kind":{"type":"string","enum":["created","updated","moved","assigned","delegated","completed","commented","comment_updated","comment_removed","linked","unlinked","archived"]},"actor":{"$ref":"#/components/schemas/ActivityActor"},"changes":{"type":"array","items":{"type":"object","required":["field"],"properties":{"field":{"type":"string"},"from":{},"to":{}}}},"correlationId":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"}}},"ActivityPage":{"type":"object","required":["activity","hasMore","nextCursor"],"properties":{"activity":{"type":"array","items":{"$ref":"#/components/schemas/TaskActivity"}},"hasMore":{"type":"boolean"},"nextCursor":{"type":"string","nullable":true}}},"ProjectActivity":{"type":"object","required":["projectId","workspaceId","kind","actor","createdAt"],"properties":{"_id":{"type":"string"},"projectId":{"type":"string"},"workspaceId":{"type":"string"},"kind":{"type":"string","enum":["created","updated","archived","restored","deleted"]},"actor":{"$ref":"#/components/schemas/ActivityActor"},"changes":{"type":"array","items":{"type":"object","required":["field"],"properties":{"field":{"type":"string"},"from":{},"to":{}}}},"correlationId":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"}}},"ProjectActivityPage":{"type":"object","required":["activity","hasMore","nextCursor"],"properties":{"activity":{"type":"array","items":{"$ref":"#/components/schemas/ProjectActivity"}},"hasMore":{"type":"boolean"},"nextCursor":{"type":"string","nullable":true}}},"ProjectNote":{"type":"object","required":["_id","workspaceId","projectId","title","content","author","updatedBy","version","createdAt","updatedAt"],"properties":{"_id":{"type":"string"},"workspaceId":{"type":"string"},"projectId":{"type":"string"},"title":{"type":"string","maxLength":160},"content":{"type":"string","maxLength":20000,"description":"Markdown; treated as untrusted data"},"author":{"$ref":"#/components/schemas/ActivityActor"},"updatedBy":{"$ref":"#/components/schemas/ActivityActor"},"version":{"type":"integer","minimum":1},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"ProjectNoteSummary":{"type":"object","required":["_id","title","excerpt","author","updatedAt","version"],"properties":{"_id":{"type":"string"},"title":{"type":"string","maxLength":160},"excerpt":{"type":"string","maxLength":180},"author":{"$ref":"#/components/schemas/ActivityActor"},"updatedAt":{"type":"string","format":"date-time"},"version":{"type":"integer","minimum":1}}},"ProjectNotePage":{"type":"object","required":["notes","hasMore","nextCursor"],"properties":{"notes":{"type":"array","items":{"$ref":"#/components/schemas/ProjectNoteSummary"}},"hasMore":{"type":"boolean"},"nextCursor":{"type":"string","nullable":true}}},"AgentServiceInfo":{"type":"object","required":["id","version","name","endpoints","auth","onboarding","free","hierarchy"],"properties":{"id":{"type":"string","enum":["ai-tasker"]},"version":{"type":"integer","enum":[1]},"name":{"type":"string"},"description":{"type":"string"},"endpoints":{"type":"object","required":["apiBaseUrl","mcp","registration","serviceInfo","plans","usage","dashboard","keyRotation","documentation"],"properties":{"apiBaseUrl":{"type":"string","format":"uri"},"mcp":{"type":"object","properties":{"transport":{"type":"string"},"url":{"type":"string","format":"uri"}}},"registration":{"type":"object","properties":{"path":{"type":"string"},"url":{"type":"string","format":"uri"},"method":{"type":"string"},"authentication":{"type":"string"},"idempotencyHeader":{"type":"string"},"idempotencyAvailable":{"type":"boolean"}}},"serviceInfo":{"type":"object","properties":{"path":{"type":"string"},"url":{"type":"string","format":"uri"},"method":{"type":"string"}}},"plans":{"type":"object","properties":{"path":{"type":"string"},"url":{"type":"string","format":"uri"},"method":{"type":"string"}}},"usage":{"type":"object","properties":{"path":{"type":"string"},"method":{"type":"string"},"authentication":{"type":"string"}}},"dashboard":{"type":"object","properties":{"url":{"type":"string","format":"uri"}}},"keyRotation":{"type":"object","required":["request","preview","confirm","direct"],"properties":{"request":{"type":"object","properties":{"path":{"type":"string"},"method":{"type":"string"},"authentication":{"type":"string"},"lifetimeSeconds":{"type":"integer"}}},"preview":{"type":"object","properties":{"path":{"type":"string"},"method":{"type":"string"},"authentication":{"type":"string"}}},"confirm":{"type":"object","properties":{"path":{"type":"string"},"method":{"type":"string"},"authentication":{"type":"string"}}},"direct":{"type":"object","properties":{"path":{"type":"string"},"method":{"type":"string"},"authentication":{"type":"string"},"idempotencyHeader":{"type":"string"}}}}},"documentation":{"type":"object","properties":{"url":{"type":"string","format":"uri"}}}}},"auth":{"type":"object","properties":{"human":{"type":"string"},"agent":{"type":"string"}}},"onboarding":{"type":"object","required":["mode","humanLinkRequired","linkToken","preview","claim","paidUpgradeRequiresHumanAction"],"properties":{"mode":{"type":"string"},"humanLinkRequired":{"type":"boolean"},"linkToken":{"type":"object","properties":{"path":{"type":"string"},"method":{"type":"string"},"lifetimeSeconds":{"type":"integer"}}},"preview":{"type":"object","properties":{"path":{"type":"string"},"method":{"type":"string"},"authentication":{"type":"string"}}},"claim":{"type":"object","properties":{"path":{"type":"string"},"method":{"type":"string"},"authentication":{"type":"string"}}},"paidUpgradeRequiresHumanAction":{"type":"boolean"}}},"free":{"type":"object","properties":{"tasks":{"type":"integer"},"requests":{"type":"integer"},"expiresDays":{"type":"integer"}}},"hierarchy":{"type":"array","items":{"type":"string"}}}},"AgentLinkPreviewRequest":{"type":"object","required":["token"],"properties":{"token":{"type":"string","minLength":32,"maxLength":256}}},"AgentLinkPreview":{"type":"object","required":["agentId","workspaceId","workspaceName","memberRole","expiresAt","paidUpgradeRequiresSeparateHumanAction"],"properties":{"agentId":{"type":"string"},"workspaceId":{"type":"string"},"workspaceName":{"type":"string"},"memberRole":{"type":"string","enum":["admin"]},"expiresAt":{"type":"string","format":"date-time"},"paidUpgradeRequiresSeparateHumanAction":{"type":"boolean","enum":[true]}}},"AgentLinkClaim":{"type":"object","required":["linked","agentId","workspaceId","message"],"properties":{"linked":{"type":"boolean","enum":[true]},"agentId":{"type":"string"},"workspaceId":{"type":"string"},"message":{"type":"string"}}},"AgentKeyRotationTokenRequest":{"type":"object","required":["token"],"properties":{"token":{"type":"string","minLength":32,"maxLength":256}}},"AgentKeyRotationPreview":{"type":"object","required":["agentId","workspaceId","workspaceName","expiresAt","requestedBy"],"properties":{"agentId":{"type":"string"},"workspaceId":{"type":"string"},"workspaceName":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"},"requestedBy":{"type":"string","enum":["human","cli","llm","agent"]}}},"AgentKeyRotationResult":{"type":"object","required":["apiKey","key","agentId","workspaceId"],"properties":{"apiKey":{"type":"string","format":"password","description":"Replacement key, returned once; plaintext is never persisted."},"key":{"type":"object","required":["id","prefix"],"properties":{"id":{"type":"string"},"prefix":{"type":"string"}}},"agentId":{"type":"string"},"workspaceId":{"type":"string"}}},"AgentKeyRotationDirectResult":{"type":"object","required":["apiKey","key"],"properties":{"apiKey":{"type":"string","format":"password","description":"Replacement key, returned once; plaintext is never persisted."},"key":{"type":"object","required":["id","prefix"],"properties":{"id":{"type":"string"},"prefix":{"type":"string"}}}}},"Sprint":{"type":"object","properties":{"_id":{"type":"string"},"name":{"type":"string"},"workspaceId":{"type":"string"},"projectId":{"type":"string","nullable":true},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"status":{"type":"string","enum":["planning","active","completed","cancelled"]},"tasks":{"type":"array","items":{"type":"string"}},"velocity":{"type":"number"},"capacity":{"type":"number","nullable":true},"goals":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"}}},"Workspace":{"type":"object","properties":{"_id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string","description":"URL identifier (/w/:wsSlug), assigned on create + backfilled by migration"},"description":{"type":"string"},"ownerId":{"type":"string"},"members":{"type":"array","items":{"type":"object","properties":{"clerkId":{"type":"string"},"role":{"type":"string","enum":["owner","admin","member"]},"joinedAt":{"type":"string","format":"date-time"}}}},"plan":{"type":"string","enum":["free","pro","business","enterprise"]},"archived":{"type":"boolean"}}},"WorkspaceWalletSummary":{"type":"object","required":["workspaceId","enabled","balanceCents","reservedCents","perOperationLimitCents","periodLimitCents","period","periodKey","periodSpentCents","periodReservedCents"],"properties":{"workspaceId":{"type":"string"},"enabled":{"type":"boolean"},"balanceCents":{"type":"integer","minimum":0,"description":"saldo disponible en céntimos EUR"},"reservedCents":{"type":"integer","minimum":0,"description":"saldo retenido por operaciones pendientes"},"perOperationLimitCents":{"type":"integer","minimum":0},"periodLimitCents":{"type":"integer","minimum":0,"description":"tope del mes calendario UTC"},"period":{"type":"string","enum":["calendar_month_utc"]},"periodKey":{"type":"string","nullable":true,"pattern":"^\\d{4}-\\d{2}$"},"periodSpentCents":{"type":"integer","minimum":0},"periodReservedCents":{"type":"integer","minimum":0}}},"WalletTopupSession":{"type":"object","required":["intentId","status","amountCents","currency","sessionId","url","reused"],"properties":{"intentId":{"type":"string","description":"id de la intención durable de funding"},"status":{"type":"string","enum":["pending","completed","failed"]},"amountCents":{"type":"integer","minimum":500,"maximum":10000},"currency":{"type":"string","enum":["eur"]},"sessionId":{"type":"string","nullable":true,"description":"id de la sesión de Checkout; null si aún no se abrió"},"url":{"type":"string","nullable":true,"description":"URL de Checkout para redirigir tras click explícito"},"reused":{"type":"boolean","description":"true cuando la Idempotency-Key relee una intención existente"}}},"ApiKey":{"type":"object","properties":{"_id":{"type":"string"},"name":{"type":"string"},"prefix":{"type":"string","description":"Solo los primeros caracteres; el secreto se muestra una vez al crear"},"workspaceId":{"type":"string"},"channel":{"type":"string","enum":["human","cli","llm","agent"],"default":"agent","description":"canal que se atribuye a las acciones hechas con esta key"},"scopes":{"type":"array","items":{"type":"string","enum":["tasks:read","tasks:write","sprints:read","sprints:write","ai:generate"]}},"lastUsedAt":{"type":"string","format":"date-time","nullable":true},"revokedAt":{"type":"string","format":"date-time","nullable":true}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}}},"InsufficientTokens":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"required":{"type":"number"},"available":{"type":"number"},"action":{"type":"string"}}}]},"WorkspaceLifecycle":{"type":"object","description":"Estado y plazos del ciclo de vida 7+7 de un workspace provisional de agente (política §4). Los workspaces reclamados quedan fuera de archivo y purga.","properties":{"provisional":{"type":"boolean"},"state":{"type":"string","enum":["active","archived","purging"]},"claimed":{"type":"boolean"},"activityDeadlineAt":{"type":"string","format":"date-time","description":"creación/última actividad calificadora + 7 días; solo para provisionales activos sin claim"},"archiveAfterDaysOfInactivity":{"type":"integer","enum":[7]},"recoveryWindowDays":{"type":"integer","enum":[7]},"archivedAt":{"type":"string","format":"date-time"},"recoveryEndsAt":{"type":"string","format":"date-time"},"purgeAt":{"type":"string","format":"date-time"},"warning":{"$ref":"#/components/schemas/LifecycleWarning"}}},"LifecycleWarning":{"type":"object","nullable":true,"description":"Advertencia estructurada en las 48 horas previas a cada vencimiento. Se expone en el cuerpo y como cabecera X-Workspace-Lifecycle-Warning (JSON) en cada interacción válida del agente.","properties":{"code":{"type":"string","enum":["ARCHIVE_IMMINENT","PURGE_IMMINENT"]},"deadlineAt":{"type":"string","format":"date-time"},"hoursRemaining":{"type":"number"},"recoveryWindowDays":{"type":"integer"},"message":{"type":"string"}}}},"responses":{"Unauthorized":{"description":"no autenticado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"acceso denegado (incluye CLERK_SESSION_REQUIRED y workspace fuera del scope de la API key)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"no existe o está borrado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"BadRequest":{"description":"input inválido","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"security":[{"clerkJwt":[]},{"apiKeyHeader":[]},{"apiKeyBearer":[]}],"paths":{"/health":{"get":{"summary":"Health check (público)","security":[],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"timestamp":{"type":"string","format":"date-time"}}}}}}}}},"/.well-known/ai-tasker.json":{"get":{"summary":"Contrato público de descubribilidad para agentes","security":[],"responses":{"200":{"description":"metadata pública y límites de onboarding","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentServiceInfo"}}}}}}},"/service-info":{"get":{"summary":"Alias público del contrato de descubribilidad","security":[],"responses":{"200":{"description":"metadata pública y límites de onboarding","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentServiceInfo"}}}}}}},"/agent/service-info":{"get":{"summary":"Contrato público de onboarding autónomo","security":[],"responses":{"200":{"description":"metadata pública y límites de onboarding","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentServiceInfo"}}}}}}},"/agent/register":{"post":{"summary":"Registrar agente autónomo con workspace y API key provisionales","description":"Sin Idempotency-Key no se puede recuperar la API key si se pierde la respuesta. Con la cabecera, el servidor exige AGENT_CREDENTIAL_ENCRYPTION_KEY y devuelve la misma credencial durante reintentos del mismo payload y huella.","security":[],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":16,"maxLength":160},"description":"Clave opaca, aleatoria y de al menos 16 caracteres para reintentar el alta sin duplicar workspace ni API key."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":80}}}}}},"responses":{"200":{"description":"reintento idempotente; devuelve la misma API key","content":{"application/json":{"schema":{"type":"object","required":["agentId","workspaceId","apiKey","resumed","quotas","usage","expiresAt"],"properties":{"agentId":{"type":"string"},"workspaceId":{"type":"string"},"apiKey":{"type":"string","description":"Credencial bearer secreta; persistir de forma segura."},"resumed":{"type":"boolean","enum":[true]},"quotas":{"type":"object"},"usage":{"type":"object"},"expiresAt":{"type":"string","format":"date-time","nullable":true},"lifecycle":{"$ref":"#/components/schemas/WorkspaceLifecycle"}}}}}},"201":{"description":"agente registrado; guardar la API key en un almacén seguro","content":{"application/json":{"schema":{"type":"object","required":["agentId","workspaceId","apiKey","quotas","usage","expiresAt"],"properties":{"agentId":{"type":"string"},"workspaceId":{"type":"string"},"apiKey":{"type":"string","description":"Credencial bearer secreta; solo se devuelve al registrar o reintentar con la misma clave idempotente."},"quotas":{"type":"object","properties":{"tasks":{"type":"integer"},"requests":{"type":"integer"}}},"usage":{"type":"object","properties":{"tasks":{"type":"integer"},"requests":{"type":"integer"}}},"expiresAt":{"type":"string","format":"date-time","nullable":true,"description":"Siempre null en altas nuevas: el TTL fijo de 30 días fue sustituido por el ciclo de vida 7+7 (lifecycle)."},"lifecycle":{"$ref":"#/components/schemas/WorkspaceLifecycle"},"next":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"409":{"description":"la clave idempotente ya se usó con otro payload/huella o no permite recuperar una credencial legacy","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"límite de cinco altas por huella en una ventana móvil de una hora","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"falta la clave de cifrado para el modo idempotente o no se puede recuperar la credencial","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/agent/link-token":{"post":{"summary":"Emitir una URL de vinculación humana de un solo uso","description":"Requiere una API key de agente activa. El token dura 15 minutos y solo se conserva su hash.","security":[{"apiKeyBearer":[]}],"responses":{"200":{"description":"URL de revisión y confirmación Clerk","content":{"application/json":{"schema":{"type":"object","required":["authorizationUrl","expiresInSeconds","workspaceId","next"],"properties":{"authorizationUrl":{"type":"string","format":"uri"},"expiresInSeconds":{"type":"integer","enum":[900]},"workspaceId":{"type":"string"},"next":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/agent/key-rotation-token":{"post":{"operationId":"requestAgentKeyRotation","summary":"Emitir una URL temporal para solicitar la rotación de una API key de agente","description":"La clave actual sigue válida hasta la confirmación Clerk. El token de solicitud dura 15 minutos y solo se conserva su hash.","security":[{"apiKeyBearer":[]},{"apiKeyHeader":[]}],"responses":{"200":{"description":"URL de confirmación humana; la clave no cambia todavía","content":{"application/json":{"schema":{"type":"object","required":["authorizationUrl","expiresInSeconds","workspaceId","next"],"properties":{"authorizationUrl":{"type":"string","format":"uri"},"expiresInSeconds":{"type":"integer","enum":[900]},"workspaceId":{"type":"string"},"next":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"410":{"description":"workspace archivado o no disponible","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"error interno al crear la solicitud","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/agent-links/preview":{"post":{"summary":"Revisar los datos del agente y workspace sin consumir el token","security":[{"clerkJwt":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentLinkPreviewRequest"}}}},"responses":{"200":{"description":"Datos mínimos para consentimiento humano explícito","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentLinkPreview"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"410":{"description":"token expirado/usado o destino no disponible","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/agent-links/claim":{"post":{"summary":"Confirmar el vínculo humano y añadir el usuario al workspace como admin","description":"No crea pagos ni amplía el plan; cualquier upgrade requiere una acción humana independiente.","security":[{"clerkJwt":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentLinkPreviewRequest"}}}},"responses":{"200":{"description":"vínculo confirmado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentLinkClaim"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"410":{"description":"token expirado/usado o destino no disponible","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/agent-key-rotations/preview":{"post":{"operationId":"previewAgentKeyRotation","summary":"Revisar la solicitud de rotación sin consumir el token","security":[{"clerkJwt":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentKeyRotationTokenRequest"}}}},"responses":{"200":{"description":"Datos del agente y workspace; no incluye la clave","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentKeyRotationPreview"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"410":{"description":"token expirado/usado o destino no disponible","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"error interno al revisar la solicitud","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/agent-key-rotations/confirm":{"post":{"operationId":"confirmAgentKeyRotation","summary":"Confirmar con Clerk y mostrar la nueva API key una sola vez","description":"Solo owner/admin; la clave anterior se invalida al confirmar. La nueva clave nunca se persiste en texto plano.","security":[{"clerkJwt":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentKeyRotationTokenRequest"}}}},"responses":{"200":{"description":"Rotación confirmada; copiar y entregar apiKey al agente","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentKeyRotationResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"la clave o identidad cambió; solicitar una nueva rotación","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"token expirado/usado o destino no disponible","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"error interno al completar la rotación","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/agent-key-rotations/{apiKeyId}/rotate":{"parameters":[{"name":"apiKeyId","in":"path","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":16,"maxLength":160}}],"post":{"operationId":"rotateAgentApiKey","summary":"Rotación iniciada directamente por un owner/admin","description":"Requiere Clerk y confirmación explícita en el dashboard. La nueva API key solo se devuelve en esta respuesta.","security":[{"clerkJwt":[]}],"responses":{"200":{"description":"Clave nueva; se muestra una sola vez","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentKeyRotationDirectResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"la clave cambió o se repitió la misma clave de idempotencia","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"workspace archivado o no disponible","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"error interno al rotar la clave","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/workspaces":{"get":{"summary":"Listar workspaces (las API keys solo ven el suyo)","responses":{"200":{"description":"lista","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Workspace"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"summary":"Crear workspace (Clerk)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"description":{"type":"string"}}}}}},"responses":{"201":{"description":"creado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Workspace"}}}},"400":{"$ref":"#/components/responses/BadRequest"}}}},"/api/workspaces/{idOrSlug}/activity":{"parameters":[{"name":"idOrSlug","in":"path","required":true,"description":"workspace id o slug","schema":{"type":"string"}},{"name":"since","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"until","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"kind","in":"query","schema":{"type":"string","enum":["created","updated","moved","assigned","delegated","completed","commented","comment_updated","comment_removed","linked","unlinked","archived"]}},{"name":"channel","in":"query","schema":{"type":"string","enum":["human","cli","llm","agent"]}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":500,"default":100}},{"name":"cursor","in":"query","schema":{"type":"string"}}],"get":{"summary":"Leer actividad del workspace con filtros y cursor; requiere pertenencia al workspace","responses":{"200":{"description":"página de actividad, newest first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivityPage"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/api/workspaces/{idOrSlug}/activity/export":{"parameters":[{"name":"idOrSlug","in":"path","required":true,"description":"workspace id o slug","schema":{"type":"string"}}],"post":{"summary":"Crear export ZIP del feed (owner/admin con Clerk); enlace de un solo uso de 15 minutos","description":"Genera manifest.json + events.jsonl UTF-8 con los eventos del workspace en el\nperiodo indicado. Secretos redactados, emails de miembros enmascarados, sin\ncontenido de tarjetas ni bytes de adjuntos. El token crudo solo aparece una vez\nen la respuesta; en auditoría queda su hash.\n","security":[{"clerkJwt":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"since":{"type":"string","format":"date-time"},"until":{"type":"string","format":"date-time"}}}}}},"responses":{"201":{"description":"export creado; la URL de descarga consume el token de un solo uso","content":{"application/json":{"schema":{"type":"object","required":["exportId","schemaVersion","downloadUrl","expiresAt","counts"],"properties":{"exportId":{"type":"string"},"schemaVersion":{"type":"string"},"downloadUrl":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"},"truncated":{"type":"boolean"},"counts":{"type":"object","required":["task","project","total"],"properties":{"task":{"type":"integer","minimum":0},"project":{"type":"integer","minimum":0},"total":{"type":"integer","minimum":0}}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/api/workspaces/{idOrSlug}/activity/export/{exportId}/download":{"parameters":[{"name":"idOrSlug","in":"path","required":true,"description":"workspace id o slug","schema":{"type":"string"}},{"name":"exportId","in":"path","required":true,"schema":{"type":"string"}},{"name":"token","in":"query","required":true,"description":"token de un solo uso","schema":{"type":"string"}}],"get":{"summary":"Descargar el ZIP one-shot (owner/admin con Clerk); un replay devuelve 410","security":[{"clerkJwt":[]}],"responses":{"200":{"description":"ZIP con manifest.json y events.jsonl","content":{"application/zip":{"schema":{"type":"string","format":"binary"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"export inexistente","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"enlace ya consumido o expirado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/workspaces/activity-retention/purge":{"post":{"summary":"Barrido manual de retención 30d del feed; disabled-by-default","description":"Solo borra con ACTIVITY_RETENTION_ENABLED === 'true'; sin el flag informa\n{enabled:false} y no borra nada. Idempotente y observable; nunca toca registros\ncontables de Stripe/tokens/wallet.\n","security":[{"clerkJwt":[]}],"responses":{"200":{"description":"resultado de la purga idempotente","content":{"application/json":{"schema":{"type":"object","required":["enabled","deleted"],"properties":{"enabled":{"type":"boolean"},"cutoff":{"type":"string","format":"date-time","nullable":true},"deleted":{"type":"object","properties":{"taskActivity":{"type":"integer","minimum":0},"projectActivity":{"type":"integer","minimum":0},"activityExport":{"type":"integer","minimum":0}}}}}}}},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/api/workspaces/{idOrSlug}/wallet":{"parameters":[{"name":"idOrSlug","in":"path","required":true,"description":"workspace id o slug","schema":{"type":"string"}}],"get":{"summary":"Consultar saldo y límites del monedero (owner/admin con Clerk)","description":"Solo lectura; no crea una cartera. Nunca devuelve eventos, reservas ni referencias de pago.","security":[{"clerkJwt":[]}],"responses":{"200":{"description":"estado actual; el periodo de gasto es el mes calendario UTC","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceWalletSummary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"description":"fallo inesperado al leer el monedero","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"summary":"Reemplazar opt-in y topes del monedero (owner/admin con Clerk)","description":"No modifica el saldo. Si enabled=true, ambos topes deben ser positivos y el tope por operación no puede superar el mensual UTC.","security":[{"clerkJwt":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["enabled","perOperationLimitCents","periodLimitCents"],"properties":{"enabled":{"type":"boolean"},"perOperationLimitCents":{"type":"integer","minimum":0},"periodLimitCents":{"type":"integer","minimum":0}}}}}},"responses":{"200":{"description":"configuración actualizada; el saldo no se altera","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceWalletSummary"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"description":"fallo inesperado al actualizar el monedero","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"no se pudo completar la actualización atómica; se puede reintentar","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/workspaces/{idOrSlug}/wallet/topup":{"parameters":[{"name":"idOrSlug","in":"path","required":true,"description":"workspace id o slug","schema":{"type":"string"}}],"post":{"summary":"Compra única de saldo del monedero (owner/admin con Clerk)","description":"Crea una intención durable de funding y abre Stripe Checkout (mode payment, EUR fijo).\nMVP: importes enteros de 500–10.000 céntimos (5–100 €). Moneda y URLs de retorno\nsiempre las fija el entorno; nunca el cliente. Requiere cabecera Idempotency-Key\n(16–160): la misma clave con el mismo importe relee la misma intención (200) y con\nimporte distinto responde 409. Desactivado salvo flag explícito `WALLET_TOPUP_ENABLED=true`\n(403 `WALLET_TOPUP_DISABLED` si está apagado). El saldo se acredita únicamente cuando\nel webhook verificado (`/api/tokens/webhook`) confirma la sesión pagada contra la\nintención persistida; la compra no activa `wallet.enabled` ni cambia topes.\n","security":[{"clerkJwt":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":16,"maxLength":160}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["amountCents"],"properties":{"amountCents":{"type":"integer","minimum":500,"maximum":10000}}}}}},"responses":{"200":{"description":"intención existente releída (misma Idempotency-Key y payload)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WalletTopupSession"}}}},"201":{"description":"intención creada y sesión de Checkout abierta","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WalletTopupSession"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"sesión Clerk requerida o flag WALLET_TOPUP_ENABLED apagado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"misma Idempotency-Key con importe/owner distintos; no se crea nada","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"no se pudo abrir la sesión de Checkout; la intención queda pendiente y se puede reintentar con la misma clave","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Stripe no configurado (PAYMENTS_NOT_CONFIGURED)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/workspaces/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"Detalle de workspace + taskCount","responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Workspace"},{"type":"object","properties":{"taskCount":{"type":"number"}}}]}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"put":{"summary":"Actualizar name/description (owner, whitelisted)","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"}}}}}},"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Workspace"}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"summary":"Archivar workspace (owner, soft)","responses":{"200":{"description":"archivado"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/workspaces/{id}/permanent":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"delete":{"summary":"Eliminar permanentemente un workspace archivado (solo owner con sesión Clerk)","description":"Requiere que el workspace esté archivado y confirmName coincida exactamente con su nombre. Purga tareas, proyectos, sprints, comentarios, actividad, tokens, adjuntos y blobs/chunks GridFS; revoca API keys, retira el workspace de claves compartidas y desactiva webhooks/asientos. Conserva los libros Operation/Transaction y el historial BillingSeat. La purga no es transaccional; si falla, el workspace permanece archivado y se puede reintentar.\n","security":[{"clerkJwt":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["confirmName"],"properties":{"confirmName":{"type":"string","description":"Debe coincidir exactamente con el nombre del workspace"}}}}}},"responses":{"200":{"description":"workspace y contenido operativo purgados","content":{"application/json":{"schema":{"type":"object","required":["message","attachmentsRemoved","gridFsFilesRemoved"],"properties":{"message":{"type":"string"},"attachmentsRemoved":{"type":"integer"},"gridFsFilesRemoved":{"type":"integer"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"el workspace debe archivarse antes","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"fallo durante la purga; workspace queda archivado para reintentar","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/projects":{"get":{"summary":"Listar proyectos del workspace (archivados solo con includeArchived=true)","parameters":[{"name":"workspaceId","in":"query","required":true,"schema":{"type":"string"}},{"name":"includeArchived","in":"query","required":false,"schema":{"type":"string","enum":["true"]}}],"responses":{"200":{"description":"lista","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Project"}}}}},"403":{"$ref":"#/components/responses/Forbidden"}}},"post":{"summary":"Crear proyecto (owner/admin del workspace). slug se deriva del nombre (y del slug del padre si es subproyecto) y se desambigua con sufijo -N","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["workspaceId","name","key"],"properties":{"workspaceId":{"type":"string"},"parentId":{"type":"string","nullable":true,"description":"subproyecto de otro proyecto del mismo workspace; si no existe → 400"},"name":{"type":"string"},"key":{"type":"string","pattern":"^[A-Z2-9]{2,8}$"},"description":{"type":"string"},"repoUrl":{"type":"string"},"branch":{"type":"string"},"version":{"type":"string"}}}}}},"responses":{"201":{"description":"creado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"400":{"description":"name/key inválidos, key ya usada en el workspace o parentId inválido","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/api/projects/{idOrSlug}":{"parameters":[{"name":"idOrSlug","in":"path","required":true,"schema":{"type":"string"}},{"name":"workspaceId","in":"query","required":false,"description":"necesario si se resuelve por slug","schema":{"type":"string"}}],"get":{"summary":"Detalle de proyecto por id o slug","responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"put":{"summary":"Actualizar name/key/description/repoUrl/branch/version/status/parent (owner/admin)","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"key":{"type":"string","pattern":"^[A-Z2-9]{2,8}$"},"parentId":{"type":"string","nullable":true,"description":"reubicar bajo otro proyecto del workspace (null = raíz); recalcula el slug; si el padre es el propio proyecto o un descendiente → 400"},"description":{"type":"string"},"repoUrl":{"type":"string"},"branch":{"type":"string"},"version":{"type":"string"},"status":{"type":"string","enum":["active","archived"]}}}}}},"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"summary":"Eliminar proyecto solo si no tiene tareas activas ni subproyectos (owner/admin); desancla sus sprints","responses":{"200":{"description":"borrado"},"400":{"description":"el proyecto aún tiene tareas, proyectos hijos o notas","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/projects/{idOrSlug}/export":{"get":{"summary":"Descargar export versionado del proyecto y sus descendientes","description":"Incluye tareas y relaciones internas del subárbol en el mismo workspace. No es import ni sincronización; las referencias URL se limpian y las claves/tokens reconocibles se redactan. Comentarios y notas se omiten por defecto; includeComments=true e includeNotes=true los habilitan por separado. Las notas se limitan a 100 y 5 MiB UTF-8 de texto agregado; un exceso devuelve 413.","parameters":[{"name":"idOrSlug","in":"path","required":true,"schema":{"type":"string"}},{"name":"workspaceId","in":"query","required":false,"description":"necesario al resolver por slug","schema":{"type":"string"}},{"name":"includeComments","in":"query","required":false,"schema":{"type":"boolean","default":false}},{"name":"includeNotes","in":"query","required":false,"schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"bundle JSON con README.md, manifest.yaml y ficheros YAML/Markdown; tareas eliminadas incluidas como datos de backup","headers":{"Content-Disposition":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","required":["format","manifest","files"],"properties":{"format":{"type":"string","enum":["ai-tasker-project-export"]},"manifest":{"type":"object","required":["version","exportedAt","rootProjectId","projectCount","taskCount","linkCount","commentsIncluded","notesIncluded","noteCount"],"properties":{"version":{"type":"integer","enum":[1]},"exportedAt":{"type":"string","format":"date-time"},"workspaceId":{"type":"string"},"rootProjectId":{"type":"string"},"projectCount":{"type":"integer"},"taskCount":{"type":"integer"},"deletedTaskCount":{"type":"integer"},"linkCount":{"type":"integer"},"externalLinksOmitted":{"type":"integer"},"commentsIncluded":{"type":"boolean"},"notesIncluded":{"type":"boolean"},"noteCount":{"type":"integer","maximum":100},"files":{"type":"array","items":{"type":"string"}}}},"files":{"type":"object","additionalProperties":{"type":"string"}}}}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"413":{"description":"el export solicitado supera el límite de notas","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"error interno al preparar el export","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/projects/{idOrSlug}/archive":{"parameters":[{"name":"idOrSlug","in":"path","required":true,"schema":{"type":"string"}},{"name":"workspaceId","in":"query","required":false,"schema":{"type":"string"}}],"post":{"summary":"Archivar proyecto (owner/admin); oculta del listado por defecto","responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/projects/{idOrSlug}/activity":{"get":{"summary":"Leer historial paginado de un proyecto (solo lectura)","description":"Requiere membresía del workspace. Para un proyecto borrado, idOrSlug debe ser su ObjectId y workspaceId debe identificar el workspace accesible. Los diffs de texto libre se redactan antes de persistir.","parameters":[{"name":"idOrSlug","in":"path","required":true,"schema":{"type":"string"}},{"name":"workspaceId","in":"query","required":false,"description":"necesario para slug lookup o historial de un proyecto borrado","schema":{"type":"string"}},{"name":"since","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"until","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"actorId","in":"query","schema":{"type":"string","maxLength":128}},{"name":"channel","in":"query","schema":{"type":"string","enum":["human","cli","llm","agent"]}},{"name":"kind","in":"query","schema":{"type":"string","enum":["created","updated","archived","restored","deleted"]}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":500,"default":100}},{"name":"cursor","in":"query","schema":{"type":"string","maxLength":512}}],"responses":{"200":{"description":"actividad más reciente primero; cursor estable por createdAt y _id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectActivityPage"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/projects/{idOrSlug}/notes":{"get":{"summary":"Buscar notas Markdown de un proyecto (solo resúmenes)","description":"Búsqueda literal case-insensitive acotada al proyecto/workspace; no acepta regex. El contenido completo requiere GET de una nota seleccionada.","parameters":[{"name":"idOrSlug","in":"path","required":true,"schema":{"type":"string"}},{"name":"workspaceId","in":"query","required":false,"description":"necesario para resolver por slug","schema":{"type":"string"}},{"name":"q","in":"query","required":false,"schema":{"type":"string","maxLength":100}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":30}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":512}}],"responses":{"200":{"description":"página de resúmenes, sin contenido completo","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectNotePage"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"summary":"Crear una nota Markdown compartida en el proyecto","description":"Actor y canal siempre se derivan de la credencial; se ignoran campos de autoría enviados por el cliente. Las API keys deben enviar Idempotency-Key (16–160 caracteres); el scope de replay incluye workspace, API key y payload. El mismo key/scope/payload devuelve la nota y no vuelve a consumir cuota ni wallet. Tras el cupo incluido, esta operación no-IA debita 0,01 EUR solo si owner/admin habilitó el wallet y sus topes admiten el gasto; una reserva wallet pendiente tiene prioridad al reintentar aunque se renueve la cuota incluida. El periodo de gasto es el `periodKey` registrado al reservar. Los fallos ambiguos se reintentan con la misma clave.","parameters":[{"name":"idOrSlug","in":"path","required":true,"schema":{"type":"string"}},{"name":"workspaceId","in":"query","required":false,"description":"necesario para resolver por slug","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"obligatorio para API key; no se aplica a sesiones Clerk","schema":{"type":"string","minLength":16,"maxLength":160}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title"],"properties":{"title":{"type":"string","minLength":1,"maxLength":160},"content":{"type":"string","maxLength":20000,"description":"Markdown no confiable"}}}}}},"responses":{"200":{"description":"replay idempotente; devuelve la nota existente","content":{"application/json":{"schema":{"type":"object","required":["note"],"properties":{"note":{"$ref":"#/components/schemas/ProjectNote"}}}}}},"201":{"description":"nota creada","content":{"application/json":{"schema":{"type":"object","required":["note"],"properties":{"note":{"$ref":"#/components/schemas/ProjectNote"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"402":{"description":"cupo incluido agotado y wallet deshabilitado, sin saldo o fuera de topes; no se crea la nota","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"la clave ya se usó con otro scope/payload o el resultado confirmado ya no está disponible","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"límite diario de requests autenticadas con API key agotado; no se crea la nota","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"ledger wallet temporalmente no disponible; reintentar con la misma Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/projects/{idOrSlug}/notes/{noteId}":{"parameters":[{"name":"idOrSlug","in":"path","required":true,"schema":{"type":"string"}},{"name":"noteId","in":"path","required":true,"schema":{"type":"string"}},{"name":"workspaceId","in":"query","required":false,"description":"necesario para resolver por slug","schema":{"type":"string"}}],"get":{"summary":"Leer el contenido completo de una nota seleccionada","description":"El contenido Markdown se considera dato no confiable. Requiere acceso al workspace y al proyecto exactos.","responses":{"200":{"description":"nota","content":{"application/json":{"schema":{"type":"object","required":["note"],"properties":{"note":{"$ref":"#/components/schemas/ProjectNote"}}}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"summary":"Actualizar nota con control de versión","description":"Solo autor original o Clerk owner/admin; version debe coincidir y se incrementa atómicamente.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["version"],"properties":{"version":{"type":"integer","minimum":1},"title":{"type":"string","minLength":1,"maxLength":160},"content":{"type":"string","maxLength":20000}}}}}},"responses":{"200":{"description":"nota actualizada","content":{"application/json":{"schema":{"type":"object","required":["note"],"properties":{"note":{"$ref":"#/components/schemas/ProjectNote"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"versión obsoleta (NOTE_VERSION_CONFLICT)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Eliminar nota con control de versión","description":"Solo autor original o Clerk owner/admin; la eliminación es permanente.","parameters":[{"name":"version","in":"query","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"nota eliminada","content":{"application/json":{"schema":{"type":"object","required":["deleted","noteId"],"properties":{"deleted":{"type":"boolean","enum":[true]},"noteId":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"versión obsoleta (NOTE_VERSION_CONFLICT)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/tasks":{"get":{"summary":"Listar tareas (scope: projectId o workspaceId); sprintId filtra por pertenencia real a Sprint.tasks","parameters":[{"name":"projectId","in":"query","required":false,"schema":{"type":"string"}},{"name":"workspaceId","in":"query","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","schema":{"type":"string","enum":["backlog","todo","in_progress","review","done"]}},{"name":"priority","in":"query","schema":{"type":"string","enum":["urgent","high","normal","low"]}},{"name":"tag","in":"query","schema":{"type":"string"}},{"name":"assignee","in":"query","required":false,"description":"userId del actor, o \"me\" = quien llama (clerkId con sesión de dashboard, id de la apikey para un agente)","schema":{"type":"string"}},{"name":"q","in":"query","required":false,"description":"texto: coincide con título (contains, i) o con el código exacto tipo ATK-12; limita a 50 para el autocomplete","schema":{"type":"string"}},{"name":"sprintId","in":"query","required":false,"description":"id de sprint","o \"none\"/\"backlog\" para tareas sin sprint":null,"schema":{"type":"string"}}],"responses":{"200":{"description":"lista","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Task"}}}}},"400":{"description":"falta projectId/workspaceId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"summary":"Crear tarea (manual = gratuita). Asigna code \"KEY-N\" atómicamente y snapshot-ea project.version","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Requerida para agentes (API key): AGENT_MUTATION con replay idempotente. 201 crea, 200 replay del mismo payload, 409 clave reutilizada con payload/ámbito distinto","schema":{"type":"string","minLength":16,"maxLength":160}}],"description":"Con API key consume 1 operación semántica por workspace/mes UTC (100 en Free, contador atómico; también vía MCP). Agotado el cupo incluido, se reserva 0,01 € del wallet solo con opt-in y topes del owner/admin; sin opt-in la respuesta es estable 402 y no se ejecuta nada. Los humanos (Clerk) no consumen cuota ni necesitan la cabecera.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["projectId","title"],"properties":{"projectId":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"body":{"type":"string"},"parent":{"type":"string","nullable":true},"priority":{"type":"string","enum":["urgent","high","normal","low"]},"status":{"type":"string","enum":["backlog","todo","in_progress","review","done"]},"tags":{"type":"array","items":{"type":"string"}},"assignees":{"type":"array","description":"cada elemento es un id de actor o {userId, channel}; la literal \"me\" se resuelve con la credencial de quien llama (clerkId, o id de la apikey para un agente)","items":{"oneOf":[{"type":"string","description":"legacy clerkId → Actor channel human"},{"$ref":"#/components/schemas/Actor"}]}},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"dueDateCustom":{"type":"string"},"estimatedMinutes":{"type":"number"},"goal":{"type":"string"},"acceptanceCriteria":{"type":"array","items":{"$ref":"#/components/schemas/AcceptanceCriterion"}},"files":{"type":"array","items":{"$ref":"#/components/schemas/FileRef"}},"repoUrl":{"type":"string"},"branch":{"type":"string"},"version":{"type":"string"},"sprintId":{"type":"string","nullable":true}}}}}},"responses":{"201":{"description":"creada","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Task"}}}},"400":{"description":"title/projectId requeridos, depth excedido, parent (mismo proyecto) o sprint (otro workspace) inválidos, o assignees mal formados","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"proyecto inexistente","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"colisión de code (reintentar)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"cuota de tareas free del agente agotada","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/tasks/search":{"get":{"summary":"Search tasks with bounded typed AND filters and stable cursor pagination","parameters":[{"name":"projectId","in":"query","required":false,"schema":{"type":"string"}},{"name":"workspaceId","in":"query","required":false,"schema":{"type":"string"}},{"name":"q","in":"query","required":false,"description":"Literal title substring, exact task code, or ObjectId; maximum 120 characters","schema":{"type":"string","maxLength":120}},{"name":"filters","in":"query","required":false,"description":"JSON array of at most 8 {field,operator,value} clauses; maximum 4096 UTF-8 bytes; clauses are combined with AND. Fields: status/priority eq or in; tag contains (exact tag); title contains (literal, case-insensitive); assignee eq (\"me\" resolves from the authenticated credential); startDate/endDate/createdAt/updatedAt before or after (ISO date/instant); estimatedMinutes eq/gt/lt.","schema":{"type":"string","maxLength":4096}},{"name":"sort","in":"query","required":false,"description":"Allowlisted stable sort field; ties are broken by task id","schema":{"type":"string","enum":["position","title","priority","createdAt","updatedAt","startDate","endDate","estimatedMinutes"],"default":"position"}},{"name":"order","in":"query","required":false,"schema":{"type":"string","enum":["asc","desc"],"default":"asc"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor bound to the same scope, filters, q, sort, order and limit; maximum 2048 characters","schema":{"type":"string","maxLength":2048}}],"responses":{"200":{"description":"Stable page of tasks; cursor is null when no further page exists","content":{"application/json":{"schema":{"type":"object","required":["tasks","hasMore","nextCursor","limit","sort","order"],"properties":{"tasks":{"type":"array","items":{"$ref":"#/components/schemas/Task"}},"hasMore":{"type":"boolean"},"nextCursor":{"type":"string","nullable":true},"limit":{"type":"integer","minimum":1,"maximum":100},"sort":{"type":"string","enum":["position","title","priority","createdAt","updatedAt","startDate","endDate","estimatedMinutes"]},"order":{"type":"string","enum":["asc","desc"]}}}}}},"400":{"description":"Invalid scope, filter field/operator/value, sort, order, limit or cursor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/tasks/{idOrCode}":{"parameters":[{"name":"idOrCode","in":"path","required":true,"description":"ObjectId o code (KEY-N)","schema":{"type":"string"}},{"name":"projectId","in":"query","required":false,"description":"necesario si se resuelve por code","schema":{"type":"string"}}],"get":{"summary":"Detalle de tarea (excluye borradas) + vistas blocks/blockedBy/related de TaskLink","responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Task"}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"put":{"summary":"Actualizar campos whitelisted (title, description, body, status, priority, tags, assignees, startDate, endDate, dueDateCustom, estimatedMinutes, actualHours, goal, acceptanceCriteria, files, repoUrl, branch, version, sprintId) con validación de tipos; sprintId puede ser de cualquier proyecto del mismo workspace; code/projectId/creator inmutables","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Requerida para agentes: AGENT_MUTATION; reapplicar el mismo parche converge y cobra una sola vez, 409 con payload distinto","schema":{"type":"string","minLength":16,"maxLength":160}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"status":{"type":"string","enum":["backlog","todo","in_progress","review","done"]},"priority":{"type":"string","enum":["urgent","high","normal","low"]}}}}}},"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Task"}}}},"400":{"$ref":"#/components/responses/BadRequest"}}},"delete":{"summary":"Soft delete (owner/admin vía $elemMatch); elimina sus TaskLink","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Requerida para agentes: AGENT_MUTATION de archivo; el replay devuelve el mismo resultado sin recargar","schema":{"type":"string","minLength":16,"maxLength":160}}],"responses":{"200":{"description":"archivada"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/tasks/{idOrCode}/attachments":{"parameters":[{"name":"idOrCode","in":"path","required":true,"description":"ObjectId o código KEY-N","schema":{"type":"string"}},{"name":"projectId","in":"query","required":false,"description":"requerido al resolver por código","schema":{"type":"string"}}],"get":{"summary":"Listar metadatos de adjuntos; requiere membresía en el workspace","responses":{"200":{"description":"metadatos, sin incluir bytes","content":{"application/json":{"schema":{"type":"object","required":["attachments"],"properties":{"attachments":{"type":"array","items":{"$ref":"#/components/schemas/TaskAttachment"}}}}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"summary":"Subir un adjunto privado (scanner debe informar clean antes de habilitarlo)","description":"Un fichero por solicitud. Solo JPEG, PNG, WebP y PDF; máximo 10 MiB por fichero, 10 por tarea y 250 MiB por workspace. El servidor valida firma, extensión y MIME y falla cerrado ante error del scanner.","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary"}}}}}},"responses":{"201":{"description":"adjunto escaneado y disponible","content":{"application/json":{"schema":{"type":"object","required":["attachment"],"properties":{"attachment":{"$ref":"#/components/schemas/TaskAttachment"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"límite de 10 adjuntos por tarea alcanzado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"límite de tamaño o cuota superado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"415":{"description":"formato no permitido o firma/MIME/extensión no coinciden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"malware detectado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"scanner no disponible; no se entrega el fichero","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/tasks/{idOrCode}/attachments/{attachmentId}":{"parameters":[{"name":"idOrCode","in":"path","required":true,"description":"ObjectId o código KEY-N","schema":{"type":"string"}},{"name":"attachmentId","in":"path","required":true,"schema":{"type":"string"}},{"name":"projectId","in":"query","required":false,"description":"requerido al resolver por código","schema":{"type":"string"}}],"get":{"summary":"Descargar un adjunto limpio con autorización de workspace","responses":{"200":{"description":"bytes servidos como attachment; nunca inline","headers":{"Content-Disposition":{"schema":{"type":"string"},"description":"attachment filename"},"X-Content-Type-Options":{"schema":{"type":"string"},"description":"nosniff"},"Cache-Control":{"schema":{"type":"string"},"description":"private, no-store"}},"content":{"image/jpeg":{"schema":{"type":"string","format":"binary"}},"image/png":{"schema":{"type":"string","format":"binary"}},"image/webp":{"schema":{"type":"string","format":"binary"}},"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"summary":"Borrar permanentemente un adjunto (autor, owner o admin)","responses":{"200":{"description":"GridFS bytes, metadatos, tokens de descarga y cuota eliminados/actualizados","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"attachmentId":{"type":"string"}}}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/tasks/{idOrCode}/attachments/{attachmentId}/download-link":{"parameters":[{"name":"idOrCode","in":"path","required":true,"schema":{"type":"string"}},{"name":"attachmentId","in":"path","required":true,"schema":{"type":"string"}},{"name":"projectId","in":"query","required":false,"description":"requerido al resolver por código","schema":{"type":"string"}}],"post":{"summary":"Emitir un enlace temporal de un solo uso para un adjunto limpio","description":"El enlace es una capacidad bearer; compartirlo solo con el destinatario previsto. Expira a los cinco minutos y se consume al descargar.","responses":{"201":{"description":"ruta de descarga de un solo uso y expiración","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttachmentDownloadLink"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/attachments/download/{token}":{"get":{"summary":"Descargar con token de un solo uso","description":"Público por diseño; el token aleatorio es una capacidad bearer efímera de un solo uso. Nunca almacenar ni compartir el token fuera del destinatario previsto.","security":[],"parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","minLength":32,"maxLength":128}}],"responses":{"200":{"description":"fichero descargado como attachment; token consumido","headers":{"Content-Disposition":{"schema":{"type":"string"}},"X-Content-Type-Options":{"schema":{"type":"string"},"example":"nosniff"},"Cache-Control":{"schema":{"type":"string"},"example":"private, no-store"},"Referrer-Policy":{"schema":{"type":"string"},"example":"no-referrer"}},"content":{"image/jpeg":{"schema":{"type":"string","format":"binary"}},"image/png":{"schema":{"type":"string","format":"binary"}},"image/webp":{"schema":{"type":"string","format":"binary"}},"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"404":{"description":"token ausente","inválido":null,"caducado o ya consumido":null}}}},"/api/tasks/{idOrCode}/activity":{"parameters":[{"name":"idOrCode","in":"path","required":true,"description":"ObjectId o code (KEY-N)","schema":{"type":"string"}},{"name":"projectId","in":"query","required":false,"description":"necesario si se resuelve por code","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":500,"default":100}},{"name":"cursor","in":"query","schema":{"type":"string"}}],"get":{"summary":"Leer historial paginado de una tarea","responses":{"200":{"description":"página de actividad, newest first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivityPage"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/tasks/{id}/move":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"patch":{"summary":"Mover (posición/estado/padre) con sincronización children y checks de ciclo y profundidad","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Requerida para agentes: AGENT_MUTATION; posiciones absolutas hacen el replay convergente y el cobro único","schema":{"type":"string","minLength":16,"maxLength":160}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"newPosition":{"type":"number"},"newStatus":{"type":"string","enum":["backlog","todo","in_progress","review","done"]},"newParent":{"type":"string","nullable":true}}}}}},"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Task"}}}},"400":{"description":"ciclo / profundidad / parent inválido","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/tasks/{id}/links":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"summary":"Crear relación tipada (body { to, type, projectId? }); blocks rechaza ciclos, related permite ambos sentidos; duplicado → 409","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["to","type"],"properties":{"to":{"type":"string"},"type":{"type":"string","enum":["blocks","related"]},"projectId":{"type":"string","description":"solo necesario si id es un code"}}}}}},"responses":{"201":{"description":"link creado"},"400":{"description":"self-link, tipo inválido, tarea ajena al workspace o ciclo de bloqueos","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"el link ya existe","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Quitar la relación { to, type } en cualquiera de los dos sentidos","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["to","type"],"properties":{"to":{"type":"string"},"type":{"type":"string","enum":["blocks","related"]},"projectId":{"type":"string"}}}}}},"responses":{"200":{"description":"link eliminado"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/tasks/{id}/comments":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"projectId","in":"query","schema":{"type":"string"}}],"get":{"summary":"Listar comentarios por createdAt/_id descendente con cursor","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"cursor","in":"query","schema":{"type":"string"}},{"name":"since","in":"query","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"{ comments, hasMore, nextCursor }"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"summary":"Agregar comentario (autor/canal derivados de autenticación; Markdown seguro, máx. 10000 caracteres)","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Requerida para agentes: AGENT_MUTATION con dedupe por índice único {workspaceId, hash}; replay devuelve el mismo comentario sin doble cobro","schema":{"type":"string","minLength":16,"maxLength":160}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"text":{"type":"string","minLength":1,"maxLength":10000},"projectId":{"type":"string"}}}}}},"responses":{"201":{"description":"{ comment }"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/tasks/{id}/comments/{commentId}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"commentId","in":"path","required":true,"schema":{"type":"string"}}],"patch":{"summary":"Editar comentario (solo el autor)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"text":{"type":"string","maxLength":10000},"projectId":{"type":"string"}}}}}},"responses":{"200":{"description":"{ comment }"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"summary":"Borrado lógico del comentario (solo el autor)","parameters":[{"name":"projectId","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"comentario borrado lógicamente"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/sprints":{"get":{"summary":"Listar sprints del workspace (ordenados por startDate; filtro opcional por proyecto)","parameters":[{"name":"workspaceId","in":"query","required":true,"schema":{"type":"string"}},{"name":"projectId","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"lista","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Sprint"}}}}},"403":{"$ref":"#/components/responses/Forbidden"}}},"post":{"summary":"Crear sprint (projectId recomendado; si solo hay workspaceId se usa el primer proyecto del workspace; endDate ≥ startDate)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","startDate","endDate"],"properties":{"projectId":{"type":"string"},"workspaceId":{"type":"string"},"name":{"type":"string"},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"capacity":{"type":"number"},"goals":{"type":"string"}}}}}},"responses":{"201":{"description":"creado (status planning)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Sprint"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/api/sprints/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"Detalle de sprint","responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Sprint"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"put":{"summary":"Actualizar name/fechas/status/capacity/goals","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"status":{"type":"string","enum":["planning","active","completed","cancelled"]},"capacity":{"type":"number","nullable":true},"goals":{"type":"string"}}}}}},"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Sprint"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"summary":"Eliminar sprint (owner/admin); desasigna sus tareas → backlog","responses":{"200":{"description":"borrado"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/sprints/{id}/tasks":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"summary":"Agregar tareas al sprint (body { taskIds: [...] }), sincroniza Task.sprintId","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["taskIds"],"properties":{"taskIds":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"sprint actualizado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Sprint"}}}},"400":{"description":"taskIds inválidos o de otro workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"summary":"Quitar tareas del sprint (body { taskIds: [...] }) → vuelven al backlog","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["taskIds"],"properties":{"taskIds":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"sprint actualizado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Sprint"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/apikeys":{"get":{"summary":"Listar API keys (solo Clerk)","responses":{"200":{"description":"lista (sin secretos)","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"}}}}},"403":{"$ref":"#/components/responses/Forbidden"}}},"post":{"summary":"Crear API key (el secreto se devuelve una única vez)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["workspaceId","name"],"properties":{"workspaceId":{"type":"string"},"name":{"type":"string"},"channel":{"type":"string","enum":["human","cli","llm","agent"],"default":"agent"},"scopes":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"201":{"description":"creada","content":{"application/json":{"schema":{"type":"object","properties":{"apiKey":{"type":"string","description":"atkr_… — mostrar una vez y no persistir"},"key":{"$ref":"#/components/schemas/ApiKey"}}}}}},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/api/apikeys/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"delete":{"summary":"Revocar API key","responses":{"200":{"description":"revocada"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/ai/generate-tasks":{"post":{"summary":"Generar tareas en lenguaje natural (Gemini). Coste 25 tokens o allowance free (100/mes)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["prompt","workspaceId"],"properties":{"prompt":{"type":"string"},"workspaceId":{"type":"string"},"context":{"type":"object","properties":{"includeExisting":{"type":"boolean"}}}}}}}},"responses":{"200":{"description":"tareas generadas (no persistidas)","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"tasks":{"type":"array","items":{"type":"object"}},"confidence":{"type":"number"}}}}}},"402":{"description":"tokens insuficientes","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InsufficientTokens"}}}},"502":{"description":"proveedor IA no disponible"},"503":{"description":"IA server-side deshabilitada (AI_SERVER_DISABLED) salvo AI_ENABLED=true; o proveedor no configurado"}}}},"/api/ai/sprint-optimize":{"post":{"summary":"Optimizar sprint. Coste 50 tokens; valida membresía del workspace","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["workspaceId","tasks"],"properties":{"workspaceId":{"type":"string"},"tasks":{"type":"array","items":{"type":"string"}},"duration":{"type":"number"},"teamVelocity":{"type":"number"}}}}}},"responses":{"200":{"description":"plan"},"402":{"description":"tokens insuficientes"},"404":{"description":"workspace no accesible"}}}},"/api/ai/task-breakdown":{"post":{"summary":"Descomponer tarea en subtareas. Coste 25 tokens","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["taskId"],"properties":{"taskId":{"type":"string"},"detailLevel":{"type":"string","enum":["brief","medium","detailed"]}}}}}},"responses":{"200":{"description":"subtareas"},"400":{"description":"taskId inválido"},"403":{"description":"acceso denegado"},"404":{"description":"tarea no encontrada"}}}},"/api/decide":{"post":{"summary":"Decision tipada (choice/noul/score con probabilidades) via Laya (embebido, $0) o Jev (OpenRouter). Coste 5 tokens. Modelo nexo: sin generacion de texto — la prosa sigue en el agente. Gate independiente: DECISION_ENGINE_ENABLED=true.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["state","questions"],"properties":{"engine":{"type":"string","enum":["auto","laya","jev"],"default":"auto"},"state":{"description":"texto o objeto con el estado sobre el que decidir","oneOf":[{"type":"string"},{"type":"object"}]},"questions":{"type":"object","description":"hasta 10 preguntas tipadas { type: choice|noul|score, instructions, criteria }","additionalProperties":{"type":"object","required":["type","instructions"],"properties":{"type":{"type":"string","enum":["choice","noul","score"]},"instructions":{"type":"string"},"criteria":{"description":"choice = objeto opcion→descripcion; score = array ordenado de niveles","oneOf":[{"type":"object"},{"type":"array","items":{"type":"string"}}]}}}}}}}}},"responses":{"200":{"description":"answers tipadas { choice, confidence, probabilities } por pregunta + engine usado","content":{"application/json":{"schema":{"type":"object","properties":{"engine":{"type":"string","enum":["laya","jev"]},"answers":{"type":"object"},"usage":{"type":"object","description":"presente solo en Jev (cost","input_tokens)":null}}}}}},"400":{"description":"request inválido (INVALID_DECISION_REQUEST con details)"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"tokens insuficientes","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InsufficientTokens"}}}},"502":{"description":"proveedor Jev error"},"503":{"description":"DECISION_ENGINE_DISABLED o DECISION_ENGINE_UNAVAILABLE (sin motores configurados; con refund)"}}}},"/api/decide/stats":{"get":{"summary":"Métricas in-process del decision engine (latencia media, errores, fallbacks, coste Jev)","responses":{"200":{"description":"métricas por motor"}}}},"/api/me":{"get":{"summary":"Perfil + saldo de tokens del usuario autenticado","responses":{"200":{"description":"user profile {clerkId, tier, tokens, referralCode, referralDiscount}"},"401":{"$ref":"#/components/responses/Unauthorized"}}},"put":{"summary":"Actualizar perfil del usuario autenticado (nombre, bio, avatarColor)","security":[{"clerkJwt":[]}],"responses":{"200":{"description":"usuario actualizado"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/credits/packs":{"get":{"summary":"Catalogo de packs de creditos (compra directa, Stripe one-time)","security":[{"clerkJwt":[]},{"apiKeyHeader":[]},{"apiKeyBearer":[]}],"responses":{"200":{"description":"catalogo de packs","content":{"application/json":{"schema":{"type":"object","properties":{"packs":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","enum":["small","medium","large","enterprise"]},"name":{"type":"string"},"priceEur":{"type":"number","description":"precio real del pack en euros"},"credits":{"type":"integer","description":"creditos que otorga el pack"}}}}}}}}}}}},"/api/credits/balance":{"get":{"summary":"Saldo de creditos del usuario autenticado","security":[{"clerkJwt":[]},{"apiKeyHeader":[]},{"apiKeyBearer":[]}],"responses":{"200":{"description":"saldo y estado de afiliacion/auto-refill","content":{"application/json":{"schema":{"type":"object","properties":{"clerkId":{"type":"string"},"referralCode":{"type":"string","nullable":true,"description":"codigo de afiliacion del usuario (REF-XXXXXXXX)"},"balance":{"type":"integer"},"lifetimeEarned":{"type":"integer"},"tier":{"type":"string"},"autoRefill":{"type":"object","properties":{"active":{"type":"boolean"},"month":{"type":"integer"}}}}}}}},"404":{"description":"usuario no encontrado"}}}},"/api/credits/checkout":{"post":{"summary":"Crear sesion de checkout de Stripe para comprar un pack de creditos","description":"Crea o reutiliza el customer de Stripe del usuario (con su email) y devuelve la sesion de checkout. Si el customer tiene un cupon o codigo de promocion colgado, se aplica a la sesion, de modo que el total puede quedar en 0 EUR.\n","security":[{"clerkJwt":[]},{"apiKeyHeader":[]},{"apiKeyBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["packId"],"properties":{"packId":{"type":"string","enum":["small","medium","large","enterprise"]}}}}}},"responses":{"200":{"description":"sesion de checkout creada","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","description":"url de pago de Stripe"},"sessionId":{"type":"string"}}}}}},"400":{"description":"UNKNOWN_PACK / INVALID_PACK_AMOUNT"},"404":{"description":"USER_NOT_FOUND"},"503":{"description":"PAYMENTS_NOT_CONFIGURED"}}}},"/api/credits/autorefill":{"get":{"summary":"Estado del auto-refill mensual","security":[{"clerkJwt":[]},{"apiKeyHeader":[]},{"apiKeyBearer":[]}],"responses":{"200":{"description":"estado del auto-refill (activo","mes":null,"creditos del mes)":null},"404":{"description":"USER_NOT_FOUND"}}}},"/api/credits/autorefill/activate":{"post":{"summary":"Activar la suscripcion mensual de auto-refill","description":"Suscripcion Stripe que recarga creditos cada mes con un crecimiento del 20 % por mes hasta el mes 12.\n","security":[{"clerkJwt":[]},{"apiKeyHeader":[]},{"apiKeyBearer":[]}],"responses":{"200":{"description":"suscripcion creada o ya activa"},"402":{"description":"BILLING_UNPAID"},"503":{"description":"PAYMENTS_NOT_CONFIGURED"}}}},"/api/credits/autorefill/cancel":{"post":{"summary":"Cancelar el auto-refill (vuelve al mes 1)","security":[{"clerkJwt":[]},{"apiKeyHeader":[]},{"apiKeyBearer":[]}],"responses":{"200":{"description":"auto-refill cancelado"},"404":{"description":"USER_NOT_FOUND"},"503":{"description":"PAYMENTS_NOT_CONFIGURED"}}}},"/api/credits/spend":{"post":{"summary":"Debitar creditos de la cuenta autenticada","description":"Usado por agentes via x402 para liquidar llamadas aisladas. Es idempotente por `idempotencyKey`: repetir la misma clave no vuelve a debitar.\n","security":[{"clerkJwt":[]},{"apiKeyHeader":[]},{"apiKeyBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount","operation","idempotencyKey"],"properties":{"amount":{"type":"integer","minimum":1},"operation":{"type":"string","description":"identificador de la operacion (p. ej. \"task:create\")"},"idempotencyKey":{"type":"string"},"metadata":{"type":"object","additionalProperties":true}}}}}},"responses":{"200":{"description":"debito aplicado","content":{"application/json":{"schema":{"type":"object","properties":{"reused":{"type":"boolean","description":"true si la clave ya se habia procesado"},"balance":{"type":"integer","description":"saldo restante"}}}}}},"400":{"description":"INVALID_SPEND_REQUEST"},"409":{"description":"INSUFFICIENT_CREDITS"}}}},"/api/credits/history":{"get":{"summary":"Historial de transacciones de creditos","security":[{"clerkJwt":[]},{"apiKeyHeader":[]},{"apiKeyBearer":[]}],"parameters":[{"in":"query","name":"limit","required":false,"schema":{"type":"integer","minimum":1,"maximum":250,"default":100}}],"responses":{"200":{"description":"lista de transacciones (type incluye referral_bonus y purchase)"}}}},"/api/credits/referral":{"post":{"summary":"Aplicar un codigo de afiliado","description":"REGALA 500 creditos a AMBAS cuentas (quien aplica y quien emitio el codigo). No es un descuento en factura: los creditos se suman al saldo de ambas y pueden usarse incluso entre agentes y LLMs.\n","security":[{"clerkJwt":[]},{"apiKeyHeader":[]},{"apiKeyBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string","description":"codigo del referidor (REF-XXXXXXXX, se acepta en minusculas)"}}}}}},"responses":{"200":{"description":"creditos otorgados","content":{"application/json":{"schema":{"type":"object","properties":{"awarded":{"type":"integer","description":"creditos otorgados a quien aplica"},"referrerAwarded":{"type":"integer","description":"creditos otorgados al referidor"},"balance":{"type":"integer"}}}}}},"400":{"description":"MISSING_REFERRAL_CODE / INVALID_REFERRAL_CODE / SELF_REFERRAL"},"404":{"description":"USER_NOT_FOUND"}}}},"/api/credits/webhook":{"post":{"summary":"Webhook de Stripe (packs y auto-refill)","description":"Firma verificada con la cabecera `stripe-signature`; el body llega en crudo para poder verificar la firma. Es la unica ruta bajo /api/credits que usa el parser raw.\n","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"evento de Stripe sin procesar"}}}},"responses":{"200":{"description":"evento recibido y aplicado"},"400":{"description":"firma invalida o INVALID_PACK_METADATA"},"503":{"description":"PAYMENTS_NOT_CONFIGURED / webhook sin configurar"}}}},"/api/referrals":{"get":{"summary":"Estado de referidos (código, verificados, créditos por referido)","description":"Resumen de la afiliación vigente, que es de CRÉDITOS: cada referido que aplica el código suma 500 créditos al referido y 500 al referidor. No hay umbral ni recompensa que reclamar, así que no se exponen `required`, `canClaim` ni `claimed` (campos del descuento de factura retirado).","security":[{"clerkJwt":[]}],"responses":{"200":{"description":"resumen de referidos"},"403":{"description":"CLERK_SESSION_REQUIRED"}}}},"/api/referrals/code":{"post":{"summary":"Generar (idempotente) mi código de referido","security":[{"clerkJwt":[]}],"responses":{"200":{"description":"{ code }"}}}},"/api/referrals/verify":{"post":{"summary":"RETIRADO (410 Gone) — la verificación con descuento ya no existe","description":"El descuento de factura por referidos está retirado: la afiliación vigente es de créditos (500 al referido + 500 al referidor) y se aplica con `POST /api/credits/referral`. La ruta se conserva y responde 410 Gone con un código de error explícito para que el cliente no la reintente a ciegas.","security":[{"clerkJwt":[]}],"deprecated":true,"responses":{"410":{"description":"REFERRAL_VERIFY_RETIRED — usar POST /api/credits/referral"}}}},"/api/referrals/claim":{"post":{"summary":"RETIRADO (410 Gone) — el 50 % de descuento por referidos ya no existe","description":"Este endpoint entregaba un 50 % de descuento sobre el siguiente pack. Ese modelo ya no existe: los referidos regalan créditos (500 + 500) y el precio de los packs es siempre el real.","security":[{"clerkJwt":[]}],"deprecated":true,"responses":{"410":{"description":"REFERRAL_DISCOUNT_RETIRED — usar POST /api/credits/referral"}}}},"/api/tokens/packs":{"get":{"summary":"Catálogo de packs — el precio final es siempre el precio real","description":"Catálogo legacy. `finalPrice` es igual a `price`: el descuento de factura por referidos está retirado. El catálogo vigente es `GET /api/credits/packs`.","responses":{"200":{"description":"packs con precio real (sin referralDiscount)"}}}},"/api/tokens/balance":{"get":{"summary":"Saldo, tier, descuentos y uso de IA","responses":{"200":{"description":"balance"}}}},"/api/tokens/checkout":{"post":{"summary":"Crear Stripe Checkout Session para un pack (precio real, sin descuento de referido)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["packId"],"properties":{"packId":{"type":"string","enum":["boost","pro","team","enterprise"]}}}}}},"responses":{"200":{"description":"{ url } redirigir al comprador"},"400":{"description":"pack desconocido"},"503":{"description":"PAYMENTS_NOT_CONFIGURED"}}}},"/api/tokens/webhook":{"post":{"summary":"Webhook de Stripe (verifica firma con STRIPE_WEBHOOK_SECRET); acredita tokens idempotente por sesión de pago y sincroniza suscripción de asientos (checkout, subscription.updated/deleted, invoice.paid/payment_failed)","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"Stripe event (raw body)"}}}},"responses":{"200":{"description":"{ received: true }"},"400":{"description":"firma inválida"}}}},"/api/billing/summary":{"get":{"summary":"Resumen de facturación por asiento (miembros, agentes, workspaces extra; estado de suscripción y estimación mensual)","responses":{"200":{"description":"seats, billableTotal, unitAmount, monthlyEstimate, status, unpaid, hasSubscription, needsCheckout, configured"},"401":{"description":"requiere sesión Clerk"}}}},"/api/billing/checkout":{"post":{"summary":"Crear Stripe Checkout (suscripción, 2 €/unidad facturable); reutiliza suscripción activa si existe","responses":{"200":{"description":"{ url } o { hasSubscription: true }"},"400":{"description":"NOTHING_TO_BILL (0 unidades facturables)"},"401":{"description":"requiere sesión Clerk"},"503":{"description":"PAYMENTS_NOT_CONFIGURED"}}}},"/api/plans":{"get":{"summary":"Catálogo público de planes (free/pro/team/business), tabla de operaciones y aliases legacy de tier","security":[],"responses":{"200":{"description":"plans[], operations[], legacyTierAliases{}"}}}},"/api/leads/self-hosted":{"post":{"summary":"Lead Self-hosted/Lifetime (§6). Público; sólo persiste pending_review, no otorga privilegios ni envía email todavía","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["company","email","agents","reason"],"properties":{"company":{"type":"string","maxLength":200},"email":{"type":"string","description":"email corporativo"},"agents":{"type":"integer","minimum":1},"airGapped":{"type":"boolean","default":false},"monthlyVolume":{"type":"string","maxLength":120},"region":{"type":"string","maxLength":100},"reason":{"type":"string","maxLength":2000}}}}}},"responses":{"201":{"description":"{ id, status: pending_review, message }"},"400":{"description":"INVALID_LEAD + details[]"}}}},"/api/usage":{"get":{"summary":"Uso y límites restantes del periodo (humano: sus workspaces · API key: sólo el vinculado) sobre el ledger de operaciones coordinadas","parameters":[{"in":"query","name":"period","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}$"},"description":"periodo YYYY-MM (default: mes UTC actual)"}],"responses":{"200":{"description":"{ workspaces: [{ workspaceId, plan, quota: { agent, hosted_ai } (limit/used/remaining), byAgent[] }], period }"},"400":{"description":"period con formato inválido"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/usage/{idOrSlug}":{"get":{"summary":"Uso de un workspace por id o slug; 404 si el llamador no pertenece (o la key no está vinculada)","parameters":[{"in":"path","name":"idOrSlug","required":true,"schema":{"type":"string"}},{"in":"query","name":"period","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}$"}}],"responses":{"200":{"description":"snapshot + name/slug"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Workspace not found (incluye ajenos: no se distingue 403/404)"}}}},"/docs":{"get":{"summary":"Documentación interactiva (Scalar)","security":[],"responses":{"200":{"description":"HTML"}}}},"/agent/lifecycle/status":{"get":{"summary":"Estado del ciclo de vida 7+7 del workspace provisional del agente","description":"Consulta de estado y plazos (archivo, ventana de recuperación, purga) con la advertencia estructurada si se está dentro de las 48 horas previas a un vencimiento.","security":[{"apiKeyBearer":[]}],"responses":{"200":{"description":"vista de ciclo de vida y advertencia (null si no aplica)","content":{"application/json":{"schema":{"type":"object","properties":{"lifecycle":{"$ref":"#/components/schemas/WorkspaceLifecycle"},"warning":{"$ref":"#/components/schemas/LifecycleWarning"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/agent/lifecycle/recovery-token":{"post":{"summary":"Única operación de agente permitida con el workspace archivado","description":"Requiere API key vigente y workspace provisional archivado (ciclo 7+7). Emite un token de recuperación de un solo uso (15 minutos, solo hash en DB) que una persona debe confirmar por Clerk vía /api/agent-links/preview + /claim antes de recoveryEndsAt. Rate limit de 3 tokens por workspace y hora. No da acceso de lectura ni modifica datos del workspace.","security":[{"apiKeyBearer":[]}],"responses":{"201":{"description":"token emitido","content":{"application/json":{"schema":{"type":"object","required":["recoveryToken","recoveryUrl","expiresInSeconds","workspaceId","lifecycle"],"properties":{"recoveryToken":{"type":"string","description":"Secreto de un solo uso; entregarlo a la persona que confirmará el claim."},"recoveryUrl":{"type":"string","format":"uri"},"expiresInSeconds":{"type":"integer","enum":[900]},"workspaceId":{"type":"string"},"agentId":{"type":"string"},"lifecycle":{"$ref":"#/components/schemas/WorkspaceLifecycle"},"warning":{"$ref":"#/components/schemas/LifecycleWarning"},"next":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"AGENT_IDENTITY_NOT_FOUND","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"WORKSPACE_NOT_ARCHIVED — el workspace no está archivado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"RECOVERY_TOKEN_RATE_LIMITED — máximo 3 tokens por workspace y hora","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/workspaces/{id}/operations/reconcile":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"summary":"Reconciliación MANUAL de operaciones ambiguas (pendings antiguos) del workspace. Deshabilitada por defecto: exige OPERATIONS_RECOVERY_ENABLED=true y owner/admin Clerk; sin scheduler. La reversión de un pending incierto NUNCA libera cupo (el efecto pudo ocurrir) ni reembolsa: el consumo queda atribuido y observable en /api/usage","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"olderThanMs":{"type":"integer","minimum":0,"description":"revertir pendings creados hace más de este tiempo (default 300000; 0 = todos)"}}}}}},"responses":{"200":{"description":"{ workspaceId, checked, reverted }"},"403":{"description":"OPERATIONS_RECOVERY_DISABLED — flag de entorno apagado (estado por defecto)"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/workspaces/lifecycle/sweep":{"post":{"summary":"Barrido manual del ciclo 7+7 (operativo, con gate Clerk)","description":"Archiva workspaces provisionales sin claim con >7 días sin actividad calificadora y purga los archivados cuya ventana de recuperación terminó sin claim. Ejecución manual explícita; el servidor no lo programa. Reutiliza la guardia atómica claim-vs-purge; workspaces reclamados jamás se tocan.","security":[{"clerkJwt":[]}],"responses":{"200":{"description":"conteos del barrido","content":{"application/json":{"schema":{"type":"object","properties":{"archived":{"type":"integer"},"purged":{"type":"array","items":{"type":"string"}},"purgedCount":{"type":"integer"},"failures":{"type":"array","items":{"type":"object","properties":{"workspaceId":{"type":"string"},"error":{"type":"string"}}}}}}}}},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/":{"get":{"summary":"Info pública de la API (id, nombre, endpoints)","security":[],"responses":{"200":{"description":"service-info embebido"}}}},"/api/openapi.json":{"get":{"summary":"Este documento OpenAPI (JSON generado del YAML embebido)","security":[],"responses":{"200":{"description":"documento OpenAPI 3.0.3"}}}},"/api/workspaces/{idOrSlug}/team":{"get":{"summary":"Equipo del workspace (miembros humanos con nombre/email) y agentes (API keys activas)","security":[{"clerkJwt":[]},{"apiKeyHeader":[]},{"apiKeyBearer":[]}],"responses":{"200":{"description":"members[], agents[]"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/api/workspaces/{id}/members":{"put":{"summary":"Cambiar rol de un miembro (owner/admin)","security":[{"clerkJwt":[]}],"responses":{"200":{"description":"membresía actualizada"},"403":{"$ref":"#/components/responses/Forbidden"}}},"delete":{"summary":"Eliminar un miembro del workspace (owner/admin)","security":[{"clerkJwt":[]}],"responses":{"200":{"description":"deleted: true"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/agent/preview":{"post":{"summary":"(alias /agent) Vista previa de un link-token antes del claim","security":[{"clerkJwt":[]}],"responses":{"200":{"description":"workspace y permisos que otorgaría el token"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/agent/claim":{"post":{"summary":"(alias /agent) Reclamar un link-token con sesión Clerk","security":[{"clerkJwt":[]}],"responses":{"200":{"description":"workspace reclamado"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/agent-links/link-token":{"post":{"summary":"(alias /api/agent-links) Emitir link-token para vincular un agente a un workspace (API key)","security":[{"apiKeyHeader":[]},{"apiKeyBearer":[]}],"responses":{"200":{"description":"{ linkToken, expiresAt } (TTL 15 min, un solo uso)"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/sprints/{id}/plan":{"get":{"summary":"Vista previa determinista del plan del sprint (candidatos de backlog, orden estable urgent→high→normal→low + fecha + _id; capacity opaca, sin comparación con minutos)","security":[{"clerkJwt":[]},{"apiKeyHeader":[]},{"apiKeyBearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"projectId","in":"query","schema":{"type":"string"}},{"name":"workspaceId","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"sprint, candidates[], warnings[], totals"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/apikeys/{id}/workspaces":{"post":{"summary":"Conceder a la API key acceso a un workspace adicional","security":[{"clerkJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"key actualizada"},"404":{"description":"API key not found"}}}},"/api/apikeys/{id}/workspaces/{workspaceId}":{"delete":{"summary":"Revocar el acceso de la API key a un workspace adicional","security":[{"clerkJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"key actualizada"},"404":{"description":"API key not found"}}}},"/api/apikeys/{id}/usage":{"get":{"summary":"Uso histórico de la API key (eventos y buckets por hora/día)","security":[{"clerkJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"hours","in":"query","schema":{"type":"integer","minimum":1,"maximum":720,"default":168}}],"responses":{"200":{"description":"key, range, total, buckets[], events[]"},"404":{"description":"API key not found"}}}},"/api/invites":{"post":{"summary":"Crear invitación por email a un workspace (rol member/admin; congela por factura impagada)","security":[{"clerkJwt":[]}],"responses":{"201":{"description":"invitación creada"},"400":{"description":"email/rol inválido"},"402":{"description":"BILLING_UNPAID"},"403":{"$ref":"#/components/responses/Forbidden"}}},"get":{"summary":"Listar invitaciones del workspace","security":[{"clerkJwt":[]}],"responses":{"200":{"description":"invites[]"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/api/invites/{id}":{"delete":{"summary":"Revocar una invitación pendiente","security":[{"clerkJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"deleted: true"},"404":{"description":"Invite not found"}}}},"/api/invites/lookup":{"get":{"summary":"Resolver un código de invitación (público; devuelve nombre/slug/rol/email)","security":[],"parameters":[{"name":"code","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"workspaceName, workspaceSlug, role, email"},"404":{"description":"NOT_FOUND"},"410":{"description":"EXPIRED"}}}},"/api/invites/accept":{"post":{"summary":"Aceptar una invitación (el email debe coincidir con la cuenta autenticada)","security":[{"clerkJwt":[]}],"responses":{"200":{"description":"workspace (idempotente si ya aceptada por el mismo usuario)"},"403":{"description":"EMAIL_MISMATCH"},"409":{"description":"ALREADY_ACCEPTED"},"410":{"description":"EXPIRED"}}}},"/api/webhooks":{"get":{"summary":"Listar webhooks del usuario","security":[{"clerkJwt":[]}],"responses":{"200":{"description":"webhooks[]"}}},"post":{"summary":"Crear webhook (URL + eventos)","security":[{"clerkJwt":[]}],"responses":{"201":{"description":"webhook creado"}}}},"/api/webhooks/{id}":{"put":{"summary":"Actualizar webhook propio","security":[{"clerkJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"webhook actualizado"},"404":{"description":"Webhook not found"}}},"delete":{"summary":"Eliminar webhook propio","security":[{"clerkJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"deleted: true"},"404":{"description":"Webhook not found"}}}},"/api/webhooks/{id}/test":{"post":{"summary":"Disparar evento de prueba webhook.test","security":[{"clerkJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"resultado del delivery"},"404":{"description":"Webhook not found"}}}},"/api/webhooks/{id}/deliveries":{"get":{"summary":"Últimos 100 deliveries del webhook","security":[{"clerkJwt":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"deliveries[]"},"404":{"description":"Webhook not found"}}}},"/api/actors":{"get":{"summary":"Resolver actores (humanos y agentes) de un workspace","security":[{"clerkJwt":[]},{"apiKeyHeader":[]},{"apiKeyBearer":[]}],"parameters":[{"name":"workspaceId","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"actors[]"},"400":{"description":"workspaceId required"}}}},"/api/x402/settlements":{"post":{"summary":"Registrar settlement x402 de un pago pilot (requiere API key; valida network/tool/scheme/paymentId)","security":[{"apiKeyHeader":[]},{"apiKeyBearer":[]}],"responses":{"200":{"description":"settlement registrado"},"400":{"description":"X402_* (unsupported/invalid)"},"403":{"description":"X402_API_KEY_REQUIRED"}}}},"/api/tokens/history":{"get":{"summary":"Historial de transacciones de tokens del usuario autenticado","security":[{"clerkJwt":[]}],"responses":{"200":{"description":"transactions[]"},"401":{"description":"requiere sesión Clerk"}}}}}}