Templates and retention
The texts and the questions before anybody signs or answers them, and how many years each kind of document is kept. They name no patient, and are therefore not sensitive.
Download the full API pública projection: openapi.json.
Sign and revoke consents explains this resource in prose, with runnable examples.
| Method | Path | What it does |
|---|---|---|
GET | /clinic/consent-templates | Consent templates |
POST | /clinic/consent-templates | Create a consent template (with its first draft version) |
GET | /clinic/consent-templates/{id} | One consent template with its versions, live and draft |
PATCH | /clinic/consent-templates/{id} | Rename, re-scope or change the allowed signature methods |
POST | /clinic/consent-templates/{id}/draft | Save the draft text and its clauses |
POST | /clinic/consent-templates/{id}/publish | Publish the draft version |
POST | /clinic/consent-templates/{id}/retire | Retire a consent template |
POST | /clinic/consent-templates/{id}/versions | Start version n+1 from the live one |
GET | /clinic/form-templates | Form templates (the configurable ficha) |
POST | /clinic/form-templates | Create a form template (with its first draft version) |
GET | /clinic/form-templates/{id} | One template with every version, the live one and the draft |
PATCH | /clinic/form-templates/{id} | Rename or re-scope a template |
POST | /clinic/form-templates/{id}/draft | Save the questions of the draft version |
POST | /clinic/form-templates/{id}/publish | Publish the draft version |
POST | /clinic/form-templates/{id}/retire | Retire a template |
POST | /clinic/form-templates/{id}/versions | Start version n+1 from the live one |
GET | /clinic/retention | Retention policy per document kind |
PUT | /clinic/retention | Set how many years one kind of document is kept |
GET /clinic/consent-templates
Consent templates
Requires clinic:read. Templates name no patient, so they write no PHI access event.
Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
status | query | draft \ | published \ | retired |
curl https://api.vitrinadev.com/api/v1/clinic/consent-templates \
-H "Authorization: Bearer $VITRINA_KEY"Example response (200)
{
"data": {
"data": [
{
"id": "3226ecbe-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"name": "Consentimiento de ortodoncia",
"description": "Tratamiento de ortodoncia fija, riesgos y alternativas",
"specialty_id": null,
"status": "draft",
"signature_methods": [
"tablet",
"public_link"
],
"current_version": null,
"published_at": null,
"retired_at": null,
"created_by_user_id": null,
"created_at": "2026-09-22T22:21:17.641Z",
"updated_at": "2026-09-22T22:21:17.641Z"
}
],
"total": 1
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/consent-templates
Create a consent template (with its first draft version)
signature_methods is per template: a consent a clinic insists on taking in the box must not be signable from a link. Requires clinic_admin:write.
Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.
Body
| Field | Type | Required | Constraints |
|---|---|---|---|
name | string | yes | mín. 1, máx. 160 |
description | string | null | — | máx. 600 |
specialty_id | string | null | — | |
signature_methods | tablet \ | public_link \ | whatsapp_otp \ |
body_html | string | — | máx. 200000 |
allows_photos | boolean | — | |
allows_marketing_use | boolean | — |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/consent-templates \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Consentimiento de ortodoncia",
"description": "Tratamiento de ortodoncia fija, riesgos y alternativas",
"signature_methods": [
"tablet",
"public_link"
],
"body_html": "<p>Declaro haber sido informada del tratamiento propuesto…</p>",
"allows_photos": true
}'Example response (201)
{
"data": {
"template": {
"id": "3226ecbe-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"name": "Consentimiento de ortodoncia",
"description": "Tratamiento de ortodoncia fija, riesgos y alternativas",
"specialty_id": null,
"status": "draft",
"signature_methods": [
"tablet",
"public_link"
],
"current_version": null,
"published_at": null,
"retired_at": null,
"created_by_user_id": null,
"created_at": "2026-09-22T22:21:17.641Z",
"updated_at": "2026-09-22T22:21:17.641Z"
},
"version": {
"id": "c692273d-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"template_id": "3226ecbe-0000-4000-8000-000000000001",
"version": 1,
"body_html": "<p>Declaro haber sido informada del tratamiento propuesto…</p>",
"allows_photos": true,
"allows_marketing_use": false,
"status": "draft",
"change_note": null,
"published_at": null,
"published_by_user_id": null,
"created_by_user_id": null,
"created_at": "2026-09-22T22:21:17.641Z",
"updated_at": "2026-09-22T22:21:17.641Z"
}
}
}Answers: 201 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/consent-templates/{id}
One consent template with its versions, live and draft
live is what a new consent would pin; allows_photos / allows_marketing_use say what the TEXT covers (the patient’s own answers live on the signed consent). Requires clinic:read.
Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
id | path | uuid | yes |
curl https://api.vitrinadev.com/api/v1/clinic/consent-templates/<id> \
-H "Authorization: Bearer $VITRINA_KEY"Example response (200)
{
"data": {
"template": {
"id": "3226ecbe-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"name": "Consentimiento de ortodoncia",
"description": "Tratamiento de ortodoncia fija, riesgos y alternativas",
"specialty_id": null,
"status": "draft",
"signature_methods": [
"tablet",
"public_link"
],
"current_version": null,
"published_at": null,
"retired_at": null,
"created_by_user_id": null,
"created_at": "2026-09-22T22:21:17.641Z",
"updated_at": "2026-09-22T22:21:17.641Z"
},
"versions": [
{
"id": "c692273d-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"template_id": "3226ecbe-0000-4000-8000-000000000001",
"version": 1,
"body_html": "<p>Declaro haber sido informada del tratamiento propuesto…</p>",
"allows_photos": true,
"allows_marketing_use": false,
"status": "draft",
"change_note": null,
"published_at": null,
"published_by_user_id": null,
"created_by_user_id": null,
"created_at": "2026-09-22T22:21:17.641Z",
"updated_at": "2026-09-22T22:21:17.641Z"
}
],
"live": null,
"draft": {
"id": "c692273d-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"template_id": "3226ecbe-0000-4000-8000-000000000001",
"version": 1,
"body_html": "<p>Declaro haber sido informada del tratamiento propuesto…</p>",
"allows_photos": true,
"allows_marketing_use": false,
"status": "draft",
"change_note": null,
"published_at": null,
"published_by_user_id": null,
"created_by_user_id": null,
"created_at": "2026-09-22T22:21:17.641Z",
"updated_at": "2026-09-22T22:21:17.641Z"
}
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
PATCH /clinic/consent-templates/{id}
Rename, re-scope or change the allowed signature methods
Identity only — never the text. Requires clinic_admin:write.
Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
id | path | uuid | yes |
Body
| Field | Type | Required | Constraints |
|---|---|---|---|
name | string | — | mín. 1, máx. 160 |
description | string | null | — | máx. 600 |
specialty_id | string | null | — | |
signature_methods | tablet \ | public_link \ | whatsapp_otp \ |
curl -X PATCH https://api.vitrinadev.com/api/v1/clinic/consent-templates/<id> \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"description": "Ortodoncia fija: riesgos, alternativas y cuidados"
}'Example response (200)
{
"data": {
"id": "3226ecbe-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"name": "Consentimiento de ortodoncia",
"description": "Ortodoncia fija: riesgos, alternativas y cuidados",
"specialty_id": null,
"status": "draft",
"signature_methods": [
"tablet",
"public_link"
],
"current_version": null,
"published_at": null,
"retired_at": null,
"created_by_user_id": null,
"created_at": "2026-09-22T22:21:17.641Z",
"updated_at": "2026-09-22T22:21:17.660Z"
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/consent-templates/{id}/draft
Save the draft text and its clauses
A PUBLISHED consent text can never be edited — it is the evidence of what a patient agreed to, and editing it retroactively changes what they agreed to. allows_marketing_use cannot be set without allows_photos. Requires clinic_admin:write.
Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
id | path | uuid | yes |
Body
| Field | Type | Required | Constraints |
|---|---|---|---|
body_html | string | — | máx. 200000 |
allows_photos | boolean | — | |
allows_marketing_use | boolean | — | |
change_note | string | null | — | máx. 400 |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/consent-templates/<id>/draft \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"body_html": "<p>Declaro haber sido informada del tratamiento, sus riesgos y sus alternativas.</p>",
"allows_photos": true,
"change_note": "Se agregan las alternativas"
}'Example response (200)
{
"data": {
"id": "c692273d-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"template_id": "3226ecbe-0000-4000-8000-000000000001",
"version": 1,
"body_html": "<p>Declaro haber sido informada del tratamiento, sus riesgos y sus alternativas.</p>",
"allows_photos": true,
"allows_marketing_use": false,
"status": "draft",
"change_note": "Se agregan las alternativas",
"published_at": null,
"published_by_user_id": null,
"created_by_user_id": null,
"created_at": "2026-09-22T22:21:17.641Z",
"updated_at": "2026-09-22T22:21:17.668Z"
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/consent-templates/{id}/publish
Publish the draft version
Refused when the text is empty: the text is what the patient agrees to. Requires clinic_admin:write.
Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
id | path | uuid | yes |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/consent-templates/<id>/publish \
-H "Authorization: Bearer $VITRINA_KEY"Example response (200)
{
"data": {
"template": {
"id": "3226ecbe-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"name": "Consentimiento de ortodoncia",
"description": "Ortodoncia fija: riesgos, alternativas y cuidados",
"specialty_id": null,
"status": "published",
"signature_methods": [
"tablet",
"public_link"
],
"current_version": 1,
"published_at": "2026-09-22T22:21:17.677Z",
"retired_at": null,
"created_by_user_id": null,
"created_at": "2026-09-22T22:21:17.641Z",
"updated_at": "2026-09-22T22:21:17.675Z"
},
"version": {
"id": "c692273d-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"template_id": "3226ecbe-0000-4000-8000-000000000001",
"version": 1,
"body_html": "<p>Declaro haber sido informada del tratamiento, sus riesgos y sus alternativas.</p>",
"allows_photos": true,
"allows_marketing_use": false,
"status": "published",
"change_note": "Se agregan las alternativas",
"published_at": "2026-09-22T22:21:17.677Z",
"published_by_user_id": null,
"created_by_user_id": null,
"created_at": "2026-09-22T22:21:17.641Z",
"updated_at": "2026-09-22T22:21:17.675Z"
}
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/consent-templates/{id}/retire
Retire a consent template
No new consents; every signed one keeps rendering its pinned text. Requires clinic_admin:write.
Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
id | path | uuid | yes |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/consent-templates/<id>/retire \
-H "Authorization: Bearer $VITRINA_KEY"Example response (200)
{
"data": {
"id": "f5a82842-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"name": "Consentimiento en desuso",
"description": null,
"specialty_id": null,
"status": "retired",
"signature_methods": [
"tablet",
"public_link"
],
"current_version": null,
"published_at": null,
"retired_at": "2026-09-22T22:21:17.772Z",
"created_by_user_id": null,
"created_at": "2026-09-22T22:21:17.764Z",
"updated_at": "2026-09-22T22:21:17.771Z"
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/consent-templates/{id}/versions
Start version n+1 from the live one
Requires clinic_admin:write.
Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
id | path | uuid | yes |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/consent-templates/<id>/versions \
-H "Authorization: Bearer $VITRINA_KEY"Example response (201)
{
"data": {
"id": "c692273d-0000-4000-8000-000000000002",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"template_id": "3226ecbe-0000-4000-8000-000000000001",
"version": 2,
"body_html": "<p>Declaro haber sido informada del tratamiento, sus riesgos y sus alternativas.</p>",
"allows_photos": true,
"allows_marketing_use": false,
"status": "draft",
"change_note": null,
"published_at": null,
"published_by_user_id": null,
"created_by_user_id": null,
"created_at": "2026-09-22T22:21:17.687Z",
"updated_at": "2026-09-22T22:21:17.687Z"
}
}Answers: 201 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/form-templates
Form templates (the configurable ficha)
The «tipo de ficha» catalogue. Requires clinic:read — a receptionist picking a ficha must be able to list them. Templates name no patient, so they write no PHI access event.
Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
status | query | draft \ | published \ | retired |
kind | query | intake \ | encounter \ | followup |
specialty_id | query | uuid | no | |
patient_fillable | query | true \ | false | no |
curl https://api.vitrinadev.com/api/v1/clinic/form-templates \
-H "Authorization: Bearer $VITRINA_KEY"Example response (200)
{
"data": {
"data": [
{
"id": "3226ecbe-0000-4000-8000-000000000002",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"name": "Ficha de ingreso",
"description": null,
"specialty_id": null,
"kind": "intake",
"status": "draft",
"patient_fillable": true,
"current_version": null,
"published_at": null,
"retired_at": null,
"created_by_user_id": null,
"created_at": "2026-09-22T22:21:17.699Z",
"updated_at": "2026-09-22T22:21:17.699Z"
}
],
"total": 1
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/form-templates
Create a form template (with its first draft version)
A template is never born without a version — the row and its v1 draft are written in one transaction. Requires clinic_admin:write.
Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.
Body
| Field | Type | Required | Constraints |
|---|---|---|---|
name | string | yes | mín. 1, máx. 160 |
description | string | null | — | máx. 600 |
specialty_id | string | null | — | |
kind | intake \ | encounter \ | followup |
patient_fillable | boolean | — | |
schema | object | — |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/form-templates \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Ficha de ingreso",
"kind": "intake",
"patient_fillable": true,
"schema": {
"sections": [
{
"key": "salud",
"title": "Antecedentes de salud",
"fields": [
{
"key": "alergias",
"type": "text",
"label": "¿Tiene alergias?",
"required": true
},
{
"key": "embarazo",
"type": "select",
"label": "¿Está embarazada?",
"options": [
{
"value": "si",
"label": "Sí"
},
{
"value": "no",
"label": "No"
}
]
}
]
}
]
}
}'Example response (201)
{
"data": {
"template": {
"id": "3226ecbe-0000-4000-8000-000000000002",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"name": "Ficha de ingreso",
"description": null,
"specialty_id": null,
"kind": "intake",
"status": "draft",
"patient_fillable": true,
"current_version": null,
"published_at": null,
"retired_at": null,
"created_by_user_id": null,
"created_at": "2026-09-22T22:21:17.699Z",
"updated_at": "2026-09-22T22:21:17.699Z"
},
"version": {
"id": "c692273d-0000-4000-8000-000000000003",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"template_id": "3226ecbe-0000-4000-8000-000000000002",
"version": 1,
"schema": {
"sections": [
{
"key": "salud",
"title": "Antecedentes de salud",
"fields": [
{
"key": "alergias",
"type": "text",
"label": "¿Tiene alergias?",
"required": true
},
{
"key": "embarazo",
"type": "select",
"label": "¿Está embarazada?",
"options": [
{
"label": "Sí",
"value": "si"
},
{
"label": "No",
"value": "no"
}
]
}
]
}
]
},
"status": "draft",
"change_note": null,
"published_at": null,
"published_by_user_id": null,
"created_by_user_id": null,
"created_at": "2026-09-22T22:21:17.699Z",
"updated_at": "2026-09-22T22:21:17.699Z"
}
}
}Answers: 201 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/form-templates/{id}
One template with every version, the live one and the draft
The editor reads this: live is what a new response would pin, draft is what «publicar cambios» would promote. Requires clinic:read.
Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
id | path | uuid | yes |
curl https://api.vitrinadev.com/api/v1/clinic/form-templates/<id> \
-H "Authorization: Bearer $VITRINA_KEY"Example response (200)
{
"data": {
"template": {
"id": "3226ecbe-0000-4000-8000-000000000002",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"name": "Ficha de ingreso",
"description": null,
"specialty_id": null,
"kind": "intake",
"status": "draft",
"patient_fillable": true,
"current_version": null,
"published_at": null,
"retired_at": null,
"created_by_user_id": null,
"created_at": "2026-09-22T22:21:17.699Z",
"updated_at": "2026-09-22T22:21:17.699Z"
},
"versions": [
{
"id": "c692273d-0000-4000-8000-000000000003",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"template_id": "3226ecbe-0000-4000-8000-000000000002",
"version": 1,
"schema": {
"sections": [
{
"key": "salud",
"title": "Antecedentes de salud",
"fields": [
{
"key": "alergias",
"type": "text",
"label": "¿Tiene alergias?",
"required": true
},
{
"key": "embarazo",
"type": "select",
"label": "¿Está embarazada?",
"options": [
{
"label": "Sí",
"value": "si"
},
{
"label": "No",
"value": "no"
}
]
}
]
}
]
},
"status": "draft",
"change_note": null,
"published_at": null,
"published_by_user_id": null,
"created_by_user_id": null,
"created_at": "2026-09-22T22:21:17.699Z",
"updated_at": "2026-09-22T22:21:17.699Z"
}
],
"live": null,
"draft": {
"id": "c692273d-0000-4000-8000-000000000003",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"template_id": "3226ecbe-0000-4000-8000-000000000002",
"version": 1,
"schema": {
"sections": [
{
"key": "salud",
"title": "Antecedentes de salud",
"fields": [
{
"key": "alergias",
"type": "text",
"label": "¿Tiene alergias?",
"required": true
},
{
"key": "embarazo",
"type": "select",
"label": "¿Está embarazada?",
"options": [
{
"label": "Sí",
"value": "si"
},
{
"label": "No",
"value": "no"
}
]
}
]
}
]
},
"status": "draft",
"change_note": null,
"published_at": null,
"published_by_user_id": null,
"created_by_user_id": null,
"created_at": "2026-09-22T22:21:17.699Z",
"updated_at": "2026-09-22T22:21:17.699Z"
}
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
PATCH /clinic/form-templates/{id}
Rename or re-scope a template
IDENTITY ONLY — never the questions. Renaming a ficha must not fork it, which is exactly why the questions live on a version and not here. Requires clinic_admin:write.
Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
id | path | uuid | yes |
Body
| Field | Type | Required | Constraints |
|---|---|---|---|
name | string | — | mín. 1, máx. 160 |
description | string | null | — | máx. 600 |
specialty_id | string | null | — | |
kind | intake \ | encounter \ | followup |
patient_fillable | boolean | — |
curl -X PATCH https://api.vitrinadev.com/api/v1/clinic/form-templates/<id> \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"description": "La completa el paciente antes de la primera atención"
}'Example response (200)
{
"data": {
"id": "3226ecbe-0000-4000-8000-000000000002",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"name": "Ficha de ingreso",
"description": "La completa el paciente antes de la primera atención",
"specialty_id": null,
"kind": "intake",
"status": "draft",
"patient_fillable": true,
"current_version": null,
"published_at": null,
"retired_at": null,
"created_by_user_id": null,
"created_at": "2026-09-22T22:21:17.699Z",
"updated_at": "2026-09-22T22:21:17.730Z"
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/form-templates/{id}/draft
Save the questions of the draft version
Creates the draft if there is none. A PUBLISHED version can never be edited — the database refuses it — because it is what existing responses render against. Requires clinic_admin:write.
Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
id | path | uuid | yes |
Body
| Field | Type | Required | Constraints |
|---|---|---|---|
schema | object | yes | |
change_note | string | null | — | máx. 400 |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/form-templates/<id>/draft \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"change_note": "Se agrega la pregunta por medicamentos",
"schema": {
"sections": [
{
"key": "salud",
"title": "Antecedentes de salud",
"fields": [
{
"key": "alergias",
"type": "text",
"label": "¿Tiene alergias?",
"required": true
},
{
"key": "medicamentos",
"type": "text",
"label": "¿Toma algún medicamento?"
}
]
}
]
}
}'Example response (200)
{
"data": {
"id": "c692273d-0000-4000-8000-000000000003",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"template_id": "3226ecbe-0000-4000-8000-000000000002",
"version": 1,
"schema": {
"sections": [
{
"key": "salud",
"title": "Antecedentes de salud",
"fields": [
{
"key": "alergias",
"type": "text",
"label": "¿Tiene alergias?",
"required": true
},
{
"key": "medicamentos",
"type": "text",
"label": "¿Toma algún medicamento?"
}
]
}
]
},
"status": "draft",
"change_note": "Se agrega la pregunta por medicamentos",
"published_at": null,
"published_by_user_id": null,
"created_by_user_id": null,
"created_at": "2026-09-22T22:21:17.699Z",
"updated_at": "2026-09-22T22:21:17.739Z"
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/form-templates/{id}/publish
Publish the draft version
The draft becomes live, the previous live version is retired, and the template points at the new number — one transaction. Existing responses keep rendering the version they pinned. Requires clinic_admin:write.
Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
id | path | uuid | yes |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/form-templates/<id>/publish \
-H "Authorization: Bearer $VITRINA_KEY"Example response (200)
{
"data": {
"template": {
"id": "3226ecbe-0000-4000-8000-000000000002",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"name": "Ficha de ingreso",
"description": "La completa el paciente antes de la primera atención",
"specialty_id": null,
"kind": "intake",
"status": "published",
"patient_fillable": true,
"current_version": 1,
"published_at": "2026-09-22T22:21:17.748Z",
"retired_at": null,
"created_by_user_id": null,
"created_at": "2026-09-22T22:21:17.699Z",
"updated_at": "2026-09-22T22:21:17.746Z"
},
"version": {
"id": "c692273d-0000-4000-8000-000000000003",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"template_id": "3226ecbe-0000-4000-8000-000000000002",
"version": 1,
"schema": {
"sections": [
{
"key": "salud",
"title": "Antecedentes de salud",
"fields": [
{
"key": "alergias",
"type": "text",
"label": "¿Tiene alergias?",
"required": true
},
{
"key": "medicamentos",
"type": "text",
"label": "¿Toma algún medicamento?"
}
]
}
]
},
"status": "published",
"change_note": "Se agrega la pregunta por medicamentos",
"published_at": "2026-09-22T22:21:17.748Z",
"published_by_user_id": null,
"created_by_user_id": null,
"created_at": "2026-09-22T22:21:17.699Z",
"updated_at": "2026-09-22T22:21:17.746Z"
}
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/form-templates/{id}/retire
Retire a template
No new responses; every existing one still renders forever. Requires clinic_admin:write.
Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
id | path | uuid | yes |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/form-templates/<id>/retire \
-H "Authorization: Bearer $VITRINA_KEY"Example response (200)
{
"data": {
"id": "f5a82842-0000-4000-8000-000000000002",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"name": "Ficha en desuso",
"description": null,
"specialty_id": null,
"kind": "intake",
"status": "retired",
"patient_fillable": false,
"current_version": null,
"published_at": null,
"retired_at": "2026-09-22T22:21:17.785Z",
"created_by_user_id": null,
"created_at": "2026-09-22T22:21:17.776Z",
"updated_at": "2026-09-22T22:21:17.784Z"
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/form-templates/{id}/versions
Start version n+1 from the live one
Seeded from the published version, because a new version of a ficha is almost always the old one plus a question. Idempotent: a template that already has a draft returns it. Requires clinic_admin:write.
Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
id | path | uuid | yes |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/form-templates/<id>/versions \
-H "Authorization: Bearer $VITRINA_KEY"Example response (201)
{
"data": {
"id": "c692273d-0000-4000-8000-000000000004",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"template_id": "3226ecbe-0000-4000-8000-000000000002",
"version": 2,
"schema": {
"sections": [
{
"key": "salud",
"title": "Antecedentes de salud",
"fields": [
{
"key": "alergias",
"type": "text",
"label": "¿Tiene alergias?",
"required": true
},
{
"key": "medicamentos",
"type": "text",
"label": "¿Toma algún medicamento?"
}
]
}
]
},
"status": "draft",
"change_note": null,
"published_at": null,
"published_by_user_id": null,
"created_by_user_id": null,
"created_at": "2026-09-22T22:21:17.756Z",
"updated_at": "2026-09-22T22:21:17.756Z"
}
}Answers: 201 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/retention
Retention policy per document kind
EVERY kind is returned, always — a settings screen with holes in it is a screen a clinic cannot reason about. Kinds the tenant never configured render their legal floor and say so (is_default). Requires clinic:read.
Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.
curl https://api.vitrinadev.com/api/v1/clinic/retention \
-H "Authorization: Bearer $VITRINA_KEY"Example response (200)
{
"data": {
"data": [
{
"kind": "examen",
"retention_years": 15,
"legal_minimum_years": 15,
"is_default": true
},
{
"kind": "imagen",
"retention_years": 15,
"legal_minimum_years": 15,
"is_default": true
},
{
"kind": "informe",
"retention_years": 15,
"legal_minimum_years": 15,
"is_default": true
}
],
"total": 10
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
PUT /clinic/retention
Set how many years one kind of document is kept
REFUSED below the legal minimum, with the legal reason — 15 years for clinical documents (Ley 20.584 / Decreto 41), 6 for tax documents (SII). The floor is also a CHECK constraint, so it is a property of the database rather than of a form validator. Requires clinic_admin:write.
Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.
Body
| Field | Type | Required | Constraints |
|---|---|---|---|
kind | examen \ | imagen \ | informe \ |
retention_years | integer | yes | ≥ 1, ≤ 100 |
curl -X PUT https://api.vitrinadev.com/api/v1/clinic/retention \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"kind": "consentimiento",
"retention_years": 15
}'Example response (200)
{
"data": {
"kind": "consentimiento",
"retention_years": 15,
"legal_minimum_years": 15,
"is_default": false
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429