{
  "openapi": "3.1.0",
  "info": {
    "title": "Lexvia — API de integración PJUD",
    "version": "1.0.0",
    "description": "Endpoints que usa la extensión de navegador de Lexvia para sincronizar las causas de un estudio con el portal del Poder Judicial de Chile.\n\n**No es una API pública abierta**: requiere un token emitido por Lexvia. El resto del producto se usa a través de la aplicación web, con cuenta.\n\n## Versionado\n\nCada respuesta declara el contrato con el que fue servida en la cabecera `X-Lexvia-Api-Version` (hoy `1`). Conviene leerla: el consumidor de esta API es una extensión instalada en el equipo del usuario, que **no se actualiza cuando desplegamos**.\n\n## Deprecación\n\nUn cambio incompatible sube la versión y la anterior queda disponible al menos **90 días**. Durante ese período sus respuestas incluyen las cabeceras `Deprecation` y `Sunset` (RFC 8594) con la fecha de retiro. Los cambios compatibles —campos nuevos en una respuesta, parámetros opcionales nuevos— no suben la versión, así que un cliente no debe romperse por recibir un campo que no esperaba.\n\n## Límite de tasa\n\nLas respuestas traen las cabeceras `RateLimit-Limit`, `RateLimit-Remaining` y `RateLimit-Reset`. Conviene mirar `Remaining` y bajar el ritmo antes de llegar a cero: un 429 trae además `Retry-After`, en segundos.",
    "contact": {
      "name": "Lexvia",
      "email": "hola@lexvia.cl",
      "url": "https://www.lexvia.cl/contacto"
    }
  },
  "servers": [
    {
      "url": "https://www.lexvia.cl",
      "description": "Producción"
    }
  ],
  "tags": [
    {
      "name": "PJUD",
      "description": "Sincronización de causas con el portal del Poder Judicial de Chile."
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Token emitido por Lexvia. Hay dos alcances: el token de infraestructura, que ve toda la cola, y el token de un estudio, cuyo alcance se deriva del propio token y no se puede ampliar por parámetro."
      }
    },
    "schemas": {
      "CasoPjud": {
        "type": "object",
        "required": [
          "id",
          "rit",
          "courtName",
          "courtId",
          "procedureType",
          "libro"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identificador de la causa en Lexvia."
          },
          "rit": {
            "type": "string",
            "description": "Rol interno del tribunal.",
            "example": "C-282-2026"
          },
          "courtName": {
            "type": "string",
            "nullable": true,
            "description": "Nombre del tribunal.",
            "example": "1º Juzgado Civil de Santiago"
          },
          "courtId": {
            "type": "string",
            "nullable": true,
            "description": "Identificador del tribunal en el portal del Poder Judicial."
          },
          "procedureType": {
            "type": "string",
            "nullable": true,
            "description": "Tipo de procedimiento.",
            "example": "ordinario"
          },
          "libro": {
            "type": "string",
            "nullable": true,
            "description": "Libro del Poder Judicial para recursos de Corte (\"Protección\" o \"Amparo\"). Es null en primera instancia."
          },
          "lastSyncedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Última sincronización correcta de esta causa."
          },
          "workspaceId": {
            "type": "string",
            "format": "uuid",
            "description": "Estudio dueño de la causa. Solo lo devuelve /pending con token de infraestructura."
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Motivo del fallo, en texto plano."
          }
        }
      }
    },
    "parameters": {
      "VersionDeApi": {
        "name": "X-Lexvia-Api-Version",
        "in": "header",
        "required": false,
        "description": "Versión del contrato contra la que se quiere hablar. Hoy la única es `1`. Ver la política de deprecación en la descripción de la API.",
        "schema": {
          "type": "string",
          "enum": [
            "1"
          ],
          "default": "1"
        }
      }
    }
  },
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/api/pjud/pending": {
      "get": {
        "operationId": "listarCausasPendientes",
        "tags": [
          "PJUD"
        ],
        "summary": "Reclamar causas por sincronizar",
        "description": "Devuelve las causas que corresponde sincronizar, de la menos sincronizada a la más reciente, y las reclama de forma atómica con un lease de 10 minutos: dos pollers en paralelo nunca reciben las mismas causas. El lease se libera al ingerir, o expira solo si el proceso muere.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Cuántas causas devolver.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "workspaceId",
            "in": "query",
            "required": false,
            "description": "Filtra a un solo estudio. Se ignora con token de estudio: ahí el alcance sale del token.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Causas reclamadas.",
            "headers": {
              "X-Lexvia-Api-Version": {
                "description": "Versión del contrato con el que se sirvió esta respuesta. Ver la sección de versionado en la descripción de la API.",
                "schema": {
                  "type": "string",
                  "example": "1"
                }
              },
              "RateLimit-Limit": {
                "description": "Peticiones permitidas en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Peticiones que quedan en la ventana. Bajar el ritmo antes de que llegue a cero evita el 429.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicie.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "cases"
                  ],
                  "properties": {
                    "cases": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CasoPjud"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Falta el token, es inválido o fue revocado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Se superó el límite de tasa. Reintentar después de los segundos que indica `Retry-After`.",
            "headers": {
              "X-Lexvia-Api-Version": {
                "description": "Versión del contrato con el que se sirvió esta respuesta. Ver la sección de versionado en la descripción de la API.",
                "schema": {
                  "type": "string",
                  "example": "1"
                }
              },
              "RateLimit-Limit": {
                "description": "Peticiones permitidas en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Peticiones que quedan en la ventana. Bajar el ritmo antes de que llegue a cero evita el 429.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicie.",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Segundos que hay que esperar antes de reintentar.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error al consultar la cola.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/pjud/reserved": {
      "get": {
        "operationId": "listarCausasReservadas",
        "tags": [
          "PJUD"
        ],
        "summary": "Causas reservadas del estudio",
        "description": "Causas que no aparecen en la Consulta Unificada pública y que solo se pueden leer desde la sesión del abogado en su propio navegador. A diferencia de /api/pjud/pending no usa lease, porque no hay dos procesos compitiendo: es un solo navegador mirando sus causas.\n\n**Solo acepta token de estudio.** El token de infraestructura se rechaza con 403 a propósito: aceptarlo devolvería las causas reservadas de todos los estudios.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Cuántas causas devolver.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Causas reservadas del estudio dueño del token.",
            "headers": {
              "X-Lexvia-Api-Version": {
                "description": "Versión del contrato con el que se sirvió esta respuesta. Ver la sección de versionado en la descripción de la API.",
                "schema": {
                  "type": "string",
                  "example": "1"
                }
              },
              "RateLimit-Limit": {
                "description": "Peticiones permitidas en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Peticiones que quedan en la ventana. Bajar el ritmo antes de que llegue a cero evita el 429.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicie.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "cases"
                  ],
                  "properties": {
                    "cases": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CasoPjud"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Falta el token, es inválido o fue revocado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Se usó el token de infraestructura, que no tiene estudio asociado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Se superó el límite de tasa. Reintentar después de los segundos que indica `Retry-After`.",
            "headers": {
              "X-Lexvia-Api-Version": {
                "description": "Versión del contrato con el que se sirvió esta respuesta. Ver la sección de versionado en la descripción de la API.",
                "schema": {
                  "type": "string",
                  "example": "1"
                }
              },
              "RateLimit-Limit": {
                "description": "Peticiones permitidas en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Peticiones que quedan en la ventana. Bajar el ritmo antes de que llegue a cero evita el 429.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicie.",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Segundos que hay que esperar antes de reintentar.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error al consultar las causas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/pjud/ingest": {
      "post": {
        "operationId": "ingerirMovimientosPjud",
        "tags": [
          "PJUD"
        ],
        "summary": "Ingerir los datos leídos del Poder Judicial",
        "description": "Recibe lo que la extensión extrajo de una causa y lo escribe con el mismo canal de ingesta que usa la sincronización programada: deduplica movimientos, actualiza los hitos procesales y libera el lease que dejó /api/pjud/pending.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "rit"
                ],
                "properties": {
                  "rit": {
                    "type": "string",
                    "description": "Rol interno del tribunal.",
                    "example": "C-282-2026"
                  },
                  "caratulado": {
                    "type": "string",
                    "description": "Carátula."
                  },
                  "tribunal": {
                    "type": "string",
                    "description": "Tribunal."
                  },
                  "filingDate": {
                    "type": "string",
                    "description": "Fecha de ingreso, en formato DD/MM/AAAA.",
                    "example": "19/01/2026"
                  },
                  "procedimiento": {
                    "type": "string"
                  },
                  "estadoProcesal": {
                    "type": "string"
                  },
                  "etapa": {
                    "type": "string"
                  },
                  "ubicacion": {
                    "type": "string"
                  },
                  "movements": {
                    "type": "array",
                    "description": "Movimientos leídos de la causa.",
                    "items": {
                      "type": "object",
                      "properties": {
                        "folio": {
                          "type": "string"
                        },
                        "etapa": {
                          "type": "string"
                        },
                        "tramite": {
                          "type": "string"
                        },
                        "descripcion": {
                          "type": "string"
                        },
                        "fecha": {
                          "type": "string",
                          "example": "19/01/2026"
                        },
                        "foja": {
                          "type": "string"
                        },
                        "documentUrl": {
                          "type": "string",
                          "nullable": true
                        }
                      }
                    }
                  },
                  "parties": {
                    "type": "array",
                    "description": "Partes de la causa.",
                    "items": {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "demandante",
                            "demandado",
                            "tercero"
                          ]
                        },
                        "name": {
                          "type": "string"
                        },
                        "rut": {
                          "type": "string",
                          "nullable": true
                        }
                      }
                    }
                  },
                  "workspaceId": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Acota la escritura a un estudio."
                  },
                  "caseId": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Acota la escritura a una causa concreta."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ingesta realizada.",
            "headers": {
              "X-Lexvia-Api-Version": {
                "description": "Versión del contrato con el que se sirvió esta respuesta. Ver la sección de versionado en la descripción de la API.",
                "schema": {
                  "type": "string",
                  "example": "1"
                }
              },
              "RateLimit-Limit": {
                "description": "Peticiones permitidas en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Peticiones que quedan en la ventana. Bajar el ritmo antes de que llegue a cero evita el 429.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicie.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ingested",
                    "errors"
                  ],
                  "properties": {
                    "ingested": {
                      "type": "integer",
                      "description": "Movimientos nuevos escritos."
                    },
                    "errors": {
                      "type": "array",
                      "description": "Causas que fallaron. Una ingesta puede tener éxito parcial: revisar siempre este arreglo.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "caseId": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "rit": {
                            "type": "string"
                          },
                          "error": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "El cuerpo no es JSON válido o falta el campo rit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Falta el token, es inválido o fue revocado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No hay ninguna causa activa con ese RIT y la sincronización habilitada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Se superó el límite de tasa. Reintentar después de los segundos que indica `Retry-After`.",
            "headers": {
              "X-Lexvia-Api-Version": {
                "description": "Versión del contrato con el que se sirvió esta respuesta. Ver la sección de versionado en la descripción de la API.",
                "schema": {
                  "type": "string",
                  "example": "1"
                }
              },
              "RateLimit-Limit": {
                "description": "Peticiones permitidas en la ventana actual.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Peticiones que quedan en la ventana. Bajar el ritmo antes de que llegue a cero evita el 429.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Segundos hasta que la ventana se reinicie.",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Segundos que hay que esperar antes de reintentar.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error al escribir la ingesta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  }
}