VitrinaAPI

Record documents

Tests, reports and images hanging off a record, with their retention deadline — and the access and erasure requests of Chile's Ley 21.719. Marked sensitive.

Beta
This may change at any time, with a changelog entry and a notice to recent callers — see versioning.

Download the full API pública projection: openapi.json.

Add documents to a patient record explains this resource in prose, with runnable examples.

MethodPathWhat it does
GET/clinic/documents/{id}/contentDownload one document’s bytes
POST/clinic/documents/{id}/deleteDelete a document before its retention runs out (tombstone)
GET/clinic/patients/{id}/documentsPHI documents on one patient’s ficha
POST/clinic/patients/{id}/documentsUpload a PHI document
POST/clinic/patients/{id}/eraseErasure request (Ley 21.719), bounded by the retention floor
POST/clinic/patients/{id}/exportExport one patient’s whole clinical record
GET/clinic/privacy-requestsExport and erasure requests
GET/clinic/privacy-requests/{id}/downloadDownload an export archive

GET /clinic/documents/{id}/content

Download one document’s bytes

Streams from the private bucket through this route rather than a signed URL. A purged document answers with the date it was disposed of and its reason — the record of it remains, the file does not. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes
curl https://api.vitrinadev.com/api/v1/clinic/documents/<id>/content \
  -H "Authorization: Bearer $VITRINA_KEY"

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/documents/{id}/delete

Delete a document before its retention runs out (tombstone)

A file uploaded to the wrong ficha must be correctable, so this is allowed — but it writes the SAME tombstone the retention sweep writes, with the operator’s reason instead of «retention», so the two are equally visible in an audit. A POST because the reason is required. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
reasonstringyesmín. 3, máx. 400
curl -X POST https://api.vitrinadev.com/api/v1/clinic/documents/<id>/delete \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "Cargado en la ficha equivocada"
  }'

Example response (200)

{
  "data": {
    "id": "16161616-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "clinic_patient_id": "12121212-0000-4000-8000-000000000001",
    "encounter_id": "14141414-0000-4000-8000-000000000001",
    "appointment_id": null,
    "kind": "informe",
    "title": "Informe de evaluación inicial",
    "mime_type": "image/png",
    "size_bytes": 95,
    "sensitivity": "phi",
    "uploaded_by_user_id": "11111111-0000-4000-8000-000000000001",
    "uploaded_source": "staff",
    "retention_until": "2041-09-24",
    "purged_at": "2026-09-24T18:10:33.907Z",
    "purge_reason": "Cargado en la ficha equivocada",
    "created_at": "2026-09-24T13:44:10.552Z",
    "updated_at": "2026-09-24T13:44:10.552Z"
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/patients/{id}/documents

PHI documents on one patient’s ficha

sensitivity defaults to phi because a file uploaded to a clinical record is health data until somebody says otherwise; the ficha marks those rows. Purged rows stay visible as tombstones — "we held an exam and disposed of it in 2042" is information. Requires clinic_record:read AND the per-patient PHI gate. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes
kindqueryexamen \imagen \informe \
pagequeryintegerno≥ 1, por defecto 1
limitqueryintegerno≥ 1, ≤ 200, por defecto 50
curl https://api.vitrinadev.com/api/v1/clinic/patients/<id>/documents \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "data": [],
    "total": 0,
    "page": 1,
    "limit": 50
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/patients/{id}/documents

Upload a PHI document

THE TTL SHIPS WITH THE COLLECTION (ADR 0083 §5): retention_until is stamped from the tenant’s policy for this kind at upload time, and there is no code path that stores a document without one. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
kindexamen \imagen \informe \
titlestring | null—máx. 300
contentstringyesmín. 8, máx. 28000000
mime_typestring | null—máx. 160
sensitivityphi \administrative—
encounter_idstring | null—
appointment_idstring | null—
curl -X POST https://api.vitrinadev.com/api/v1/clinic/patients/<id>/documents \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "informe",
    "title": "Informe de evaluación inicial",
    "sensitivity": "phi",
    "encounter_id": "14141414-0000-4000-8000-000000000001",
    "mime_type": "image/png",
    "content": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
  }'

Example response (201)

{
  "data": {
    "id": "16161616-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "clinic_patient_id": "12121212-0000-4000-8000-000000000001",
    "encounter_id": "14141414-0000-4000-8000-000000000001",
    "appointment_id": null,
    "kind": "informe",
    "title": "Informe de evaluación inicial",
    "mime_type": "image/png",
    "size_bytes": 95,
    "sensitivity": "phi",
    "uploaded_by_user_id": "11111111-0000-4000-8000-000000000001",
    "uploaded_source": "staff",
    "retention_until": "2041-09-24",
    "purged_at": null,
    "purge_reason": null,
    "created_at": "2026-09-24T13:44:10.552Z",
    "updated_at": "2026-09-24T13:44:10.552Z"
  }
}

Answers: 201 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/patients/{id}/erase

Erasure request (Ley 21.719), bounded by the retention floor

INSIDE the 15-year window (Ley 20.584, Decreto 41) the request is REFUSED — with the legal reason and the date it may be executed — and the refusal is recorded; a silent refusal is as bad as a wrongful deletion. OUTSIDE it, the PII is tombstoned and the clinical rows stay for their own retention. A refusal answers 201, not a 4xx: the request was accepted and lawfully answered, and the row is the answer. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
reasonstring | null—máx. 600
curl -X POST https://api.vitrinadev.com/api/v1/clinic/patients/<id>/erase \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "La paciente ejerce su derecho de supresión (Ley 21.719)"
  }'

Example response (201)

{
  "data": {
    "id": "17171717-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "clinic_patient_id": "12121212-0000-4000-8000-000000000001",
    "kind": "export",
    "status": "completed",
    "requested_by_user_id": "11111111-0000-4000-8000-000000000001",
    "requested_reason": "La paciente ejerce su derecho de acceso (Ley 21.719)",
    "started_at": "2026-09-24T18:20:04.001Z",
    "completed_at": "2026-09-24T18:20:07.430Z",
    "size_bytes": 148223,
    "token_expires_at": "2026-10-01T18:20:07.430Z",
    "result": {
      "documents": 3,
      "photos": 1,
      "notes": 6,
      "consents": 2
    },
    "retention_until": "2026-10-01",
    "refusal_reason": null,
    "error": null,
    "created_at": "2026-09-24T18:20:04.001Z",
    "updated_at": "2026-09-24T18:20:07.430Z"
  }
}

Answers: 201 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/patients/{id}/export

Export one patient’s whole clinical record

Assembles the ficha, its fichas filled, consents, clinical images and documents — with the documents’ own bytes inline — into one JSON archive, stores it, and returns a download path with a 7-day token. JSON and not a bundle of PDFs because Ley 21.719’s portability right is about the DATA; a rendered PDF is a picture of the data. Recorded as export in the PHI access log. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
reasonstring | null—máx. 600
curl -X POST https://api.vitrinadev.com/api/v1/clinic/patients/<id>/export \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "La paciente ejerce su derecho de acceso (Ley 21.719)"
  }'

Example response (201)

{
  "data": {
    "id": "17171717-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "clinic_patient_id": "12121212-0000-4000-8000-000000000001",
    "kind": "export",
    "status": "completed",
    "requested_by_user_id": "11111111-0000-4000-8000-000000000001",
    "requested_reason": "La paciente ejerce su derecho de acceso (Ley 21.719)",
    "started_at": "2026-09-24T18:20:04.001Z",
    "completed_at": "2026-09-24T18:20:07.430Z",
    "size_bytes": 148223,
    "token_expires_at": "2026-10-01T18:20:07.430Z",
    "result": {
      "documents": 3,
      "photos": 1,
      "notes": 6,
      "consents": 2
    },
    "retention_until": "2026-10-01",
    "refusal_reason": null,
    "error": null,
    "created_at": "2026-09-24T18:20:04.001Z",
    "updated_at": "2026-09-24T18:20:07.430Z"
  }
}

Answers: 201 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/privacy-requests

Export and erasure requests

The durable record of who asked for what, when, and what came back — including a refused erasure and the date its window closes. Requires clinic_record:read.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
clinic_patient_idqueryuuidno
kindqueryexport \erasureno
limitqueryintegerno≥ 1, ≤ 500, por defecto 100
curl https://api.vitrinadev.com/api/v1/clinic/privacy-requests \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "data": [
      {
        "id": "1fa19e08-0000-4000-8000-000000000001",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
        "kind": "export",
        "status": "failed",
        "requested_by_user_id": "11111111-0000-4000-8000-000000000001",
        "requested_reason": null,
        "started_at": "2026-09-22T22:21:18.312Z",
        "completed_at": "2026-09-22T22:21:18.499Z",
        "storage_key": null,
        "size_bytes": null,
        "token_expires_at": null,
        "result": {},
        "retention_until": null,
        "refusal_reason": null,
        "error": "getaddrinfo ENOTFOUND minio",
        "created_at": "2026-09-22T22:21:18.308Z",
        "updated_at": "2026-09-22T22:21:18.498Z"
      }
    ],
    "total": 1
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/privacy-requests/{id}/download

Download an export archive

The token AND the staff gate are both checked: the token stops a stale link working forever, the gate stops the wrong staff member opening it. The archive is deleted when its token expires — PHI in a bucket must not outlive its link. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes
tokenquerystringyesmín. 10, máx. 4096
curl https://api.vitrinadev.com/api/v1/clinic/privacy-requests/<id>/download \
  -H "Authorization: Bearer $VITRINA_KEY"

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

On this page