Clinic lines of care
The clinic's short list of what patients come for (Implants, Aesthetics, Endodontics…): every roster specialty and every ad belongs to one line, and every lead carries one.
Download the full API pública projection: openapi.json.
List services and their prices explains this resource in prose, with runnable examples.
| Method | Path | What it does |
|---|---|---|
GET | /clinic/lineas | List the clinic's Líneas, with the mapping and the unmapped |
POST | /clinic/lineas | Add a Línea |
PATCH | /clinic/lineas/{id} | Rename or archive a Línea |
POST | /clinic/lineas/{id}/keywords | Add a keyword for leads without an ad |
POST | /clinic/lineas/{id}/merge | Merge a Línea into another |
POST | /clinic/lineas/{id}/rules | Map ads to a Línea by a name fragment |
DELETE | /clinic/lineas/keywords/{id} | Remove a keyword |
DELETE | /clinic/lineas/rules/{id} | Remove a name fragment rule |
PUT | /clinic/lineas/specialties/{id} | Place a roster specialty in a Línea |
GET /clinic/lineas
List the clinic's Líneas, with the mapping and the unmapped
A Línea is one entry in the clinic's own short list of what patients come for (Implantes, Estética, Endodoncia, TTM…), usually fewer than its specialties. The list is seeded from the roster's specialties on first read and is editable. Every roster specialty and every ad maps into exactly one Línea: specialties and rules (name fragments an ad's campaign, ad set or ad name must contain) say how; keywords are the words a lead without an ad says when it wants the Línea (seeded for dental clinics, editable); unmapped_specialties and the ads with linea_id: null are the ones the owner still has to place. Each lead carries exactly one Línea (linea on the lead). 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/lineas \
-H "Authorization: Bearer $VITRINA_KEY"Example response (200)
{
"data": {
"lineas": [
{
"id": "5b1f0c1e-0000-4000-8000-000000000001",
"nombre": "Implantes",
"is_default": false,
"archived": false,
"leads": 41,
"open_leads": 33,
"rules": [
{
"id": "5b1f0c1e-0000-4000-8000-000000000003",
"match": "implant",
"source": "seed"
}
],
"keywords": [
{
"id": "5b1f0c1e-0000-4000-8000-000000000005",
"keyword": "implant",
"source": "seed"
}
],
"specialties": [
{
"id": "5b1f0c1e-0000-4000-8000-000000000002",
"nombre": "Implantología"
}
]
}
],
"unmapped_specialties": [
{
"id": "5b1f0c1e-0000-4000-8000-000000000004",
"nombre": "Prostodoncia"
}
],
"ads": [
{
"external_id": "120250735360390233",
"campaign_name": "Campaña de mensajes IMPLANTES",
"ad_set_name": "INTERESES",
"ad_name": "Implantes 2",
"linea_id": "5b1f0c1e-0000-4000-8000-000000000001",
"via": "rule",
"matched": "implant",
"conversations": 119,
"leads": 38
}
]
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/lineas
Add a Línea
Find-or-create by name (accent- and case-insensitive). 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 |
|---|---|---|---|
nombre | string | yes | mín. 1, máx. 80 |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/lineas \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"nombre": "Implantes"
}'Example response (201)
{
"data": {
"id": "5b1f0c1e-0000-4000-8000-000000000001",
"tenant_id": "5b1f0c1e-0000-4000-8000-0000000000aa",
"nombre": "Implantes",
"is_default": false,
"archived_at": null,
"created_at": "2026-10-09T12:00:00.000Z",
"updated_at": "2026-10-09T12:00:00.000Z"
}
}Answers: 201 · 400 · 401 · 403 · 404 · 409 · 429
PATCH /clinic/lineas/{id}
Rename or archive a Línea
An archived Línea leaves the pickers but keeps the leads that carry it. The default Línea («General») cannot be archived. 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 |
|---|---|---|---|
nombre | string | — | mín. 1, máx. 80 |
archived | boolean | — |
curl -X PATCH https://api.vitrinadev.com/api/v1/clinic/lineas/<id> \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"nombre": "Implantología"
}'Example response (200)
{
"data": {
"id": "5b1f0c1e-0000-4000-8000-000000000001",
"tenant_id": "5b1f0c1e-0000-4000-8000-0000000000aa",
"nombre": "Implantes",
"is_default": false,
"archived_at": null,
"created_at": "2026-10-09T12:00:00.000Z",
"updated_at": "2026-10-09T12:00:00.000Z"
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/lineas/{id}/keywords
Add a keyword for leads without an ad
A lead that did not come from an ad gets its Línea from its own words before it falls back to General: the lead's title, the booked cita's tratamiento, the score reason, the conversation summary and the notes are matched (accent- and case-insensitive, from the start of a word) against each Línea's keywords. The first of those fields that names a Línea decides; within it the Línea with the most keywords wins. An ad's Línea and a person's choice are never replaced. No model call. A keyword already pointing at another Línea answers 409. 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 |
|---|---|---|---|
keyword | string | yes | mín. 2, máx. 80 |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/lineas/<id>/keywords \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"keyword": "botox"
}'Example response (201)
{
"data": {
"id": "5b1f0c1e-0000-4000-8000-000000000005",
"tenant_id": "5b1f0c1e-0000-4000-8000-0000000000aa",
"linea_id": "5b1f0c1e-0000-4000-8000-000000000001",
"keyword": "botox",
"source": "owner",
"created_at": "2026-10-10T12:00:00.000Z"
}
}Answers: 201 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/lineas/{id}/merge
Merge a Línea into another
Moves every lead, specialty and rule of this Línea to into_id, then removes it. Irreversible. 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 |
|---|---|---|---|
into_id | uuid | yes |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/lineas/<id>/merge \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"into_id": "5b1f0c1e-0000-4000-8000-000000000001"
}'Example response (200)
{
"data": {
"into": {
"id": "5b1f0c1e-0000-4000-8000-000000000001",
"tenant_id": "5b1f0c1e-0000-4000-8000-0000000000aa",
"nombre": "Implantes",
"is_default": false,
"archived_at": null,
"created_at": "2026-10-09T12:00:00.000Z",
"updated_at": "2026-10-09T12:00:00.000Z"
},
"moved": {
"leads": 4,
"specialties": 1,
"rules": 2
}
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/lineas/{id}/rules
Map ads to a Línea by a name fragment
An ad whose campaign, ad set or ad name contains the fragment belongs to this Línea; the longest fragment wins. A fragment already pointing at another Línea answers 409. 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 |
|---|---|---|---|
match | string | yes | mín. 2, máx. 80 |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/lineas/<id>/rules \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"match": "GABRIELA RIQUELME"
}'Example response (201)
{
"data": {
"id": "5b1f0c1e-0000-4000-8000-000000000003",
"tenant_id": "5b1f0c1e-0000-4000-8000-0000000000aa",
"linea_id": "5b1f0c1e-0000-4000-8000-000000000001",
"match": "GABRIELA RIQUELME",
"source": "owner",
"created_at": "2026-10-09T12:00:00.000Z"
}
}Answers: 201 · 400 · 401 · 403 · 404 · 409 · 429
DELETE /clinic/lineas/keywords/{id}
Remove a keyword
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 DELETE https://api.vitrinadev.com/api/v1/clinic/lineas/keywords/<id> \
-H "Authorization: Bearer $VITRINA_KEY"Answers: 204 · 400 · 401 · 403 · 404 · 409 · 429
DELETE /clinic/lineas/rules/{id}
Remove a name fragment rule
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 DELETE https://api.vitrinadev.com/api/v1/clinic/lineas/rules/<id> \
-H "Authorization: Bearer $VITRINA_KEY"Answers: 204 · 400 · 401 · 403 · 404 · 409 · 429
PUT /clinic/lineas/specialties/{id}
Place a roster specialty in a Línea
id is a clinic_specialty id. linea_id: null takes it out of its Línea. 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 |
|---|---|---|---|
linea_id | string | null | yes |
curl -X PUT https://api.vitrinadev.com/api/v1/clinic/lineas/specialties/<id> \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"linea_id": "5b1f0c1e-0000-4000-8000-000000000001"
}'Example response (200)
{
"data": {
"id": "5b1f0c1e-0000-4000-8000-000000000002",
"linea_id": "5b1f0c1e-0000-4000-8000-000000000001"
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429