API access
If your clinic already runs another system — practice management, billing, or a spreadsheet — you can connect it to Pingclínica instead of copying data by hand.
With an API key your system can:
- read appointments, to show them in your own calendar or a report;
- import and keep up to date your patient and doctor lists.
You set this up in Configuración → Acceso API.
The health questionnaire is never served over this API. It is the only clinical data Pingclínica stores, and no API key can reach it regardless of the permissions you grant.
Creating a key
- Go to Configuración → Acceso API and click Nueva clave.
- Give it a name you'll recognise later (e.g. "Practice management").
- Tick only the permissions that system actually needs.
- Optionally set an expiry date.
You'll see the key exactly once. Save it right then — we never show it again. If you lose it, create a new one and delete the old.
A key looks like this:
pc_x7Kd9mQr2vBnL4pTz8wYcH3jF6sA1eR5uN0iO
Treat it like a password. It belongs on your server, never in a browser or a mobile app: anyone who sees it can read your clinic's data.
Permissions
Each key carries only the permissions you tick.
| Permission | What it allows |
|---|---|
| Leer citas | Read the schedule |
| Leer pacientes | Read the patient roster |
| Crear y editar pacientes | Import or sync patients |
| Eliminar pacientes | Delete from the roster |
| Leer doctores | Read doctors and schedules |
| Crear y editar doctores | Import doctors and their hours |
| Desactivar doctores | Retire a doctor from the schedule |
Deleting is a separate permission from creating and editing, on purpose: a key that only imports data can't wipe your roster if the program holding it has a bug.
A key that attempts something it lacks permission for gets a 403, and nothing happens.
Using a key
The key goes in the Authorization header of every call:
curl https://api.pingclinica.com/api/public/v1/whoami \
-H "Authorization: Bearer pc_your_key_here"
Never put it in the URL (?api_key=…) — it would be written into the logs of every server it passes through. We reject that form outright.
To check that a key works, call /whoami. It returns your clinic, its timezone, and that key's permissions:
{
"clinic": { "id": "…", "name": "Clínica Vista", "timezone": "America/Guayaquil" },
"scopes": ["appointments:read", "patients:write"],
"expires_at": null
}
Reading appointments
curl "https://api.pingclinica.com/api/public/v1/appointments?from=2026-09-01&to=2026-09-30" \
-H "Authorization: Bearer pc_your_key_here"
Response:
{
"data": [
{
"id": "8f14e45f-…",
"scheduled_at": "2026-09-15T14:00:00+00:00",
"scheduled_at_local": "2026-09-15T09:00:00-05:00",
"duration_minutes": 30,
"procedure_type": "consultation",
"status": "confirmed",
"patient_name": "Ana Torres",
"patient_phone": "593991112233",
"patient_id": "3c6e0b8a-…",
"provider_name": "Dra. Vega",
"provider_id": "1b6453892-…",
"created_at": "2026-09-01T18:22:10+00:00",
"updated_at": "2026-09-02T11:05:44+00:00"
}
],
"has_more": false,
"next_offset": null
}
Every appointment carries its time twice: scheduled_at in UTC, so your system can do arithmetic without ambiguity, and scheduled_at_local in your clinic's timezone, so it reads the way your team experiences it.
This is the same shape the HTTPS notifications send, so if you already handle those, you don't need a second parser.
Filters
| Parameter | Example |
|---|---|
from, to | ?from=2026-09-01&to=2026-09-30 (dates in your clinic's timezone) |
status | ?status=cancelled |
provider_id | ?provider_id=1b645389-… |
updated_since | ?updated_since=2026-09-08T12:00:00Z |
limit, offset | ?limit=100&offset=200 (200 per page maximum) |
Syncing without repeating work
To fetch only what changed since last time, use updated_since:
- Request
?updated_since=<the last timestamp you stored>. - Page through with
next_offsetuntilhas_moreisfalse. - Store the highest
updated_atyou saw, and use it next run.
With updated_since we order by modification time, not appointment time. That matters: if someone edits an appointment while you're paging, it moves to the end rather than shifting the pages you already read — so nothing slips past you unread.
Importing patients and doctors
To load an existing list, send it in one call:
curl -X POST https://api.pingclinica.com/api/public/v1/patients/bulk \
-H "Authorization: Bearer pc_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"items": [
{ "name": "Ana Torres", "phone": "0991112233", "external_id": "PAC-001" },
{ "name": "Beto Paz", "phone": "0994445566", "external_id": "PAC-002" }
]
}'
The response tells you what happened row by row:
{
"summary": { "created": 1, "updated": 0, "failed": 1 },
"results": [
{ "index": 0, "status": "created", "id": "3c6e0b8a-…" },
{ "index": 1, "status": "error", "error": "invalid_phone" }
]
}
One failing row does not cancel the others. Fix that row and send it again.
Re-running an import is safe
Use external_id — the identifier that patient or doctor already has in your system. When you send it, we recognise the record and update it instead of creating a duplicate, so you can run the import nightly without piling up copies.
- Patients: without an
external_idwe fall back to the phone number.0991112233and593991112233are the same patient to us. - Doctors: there is no way to recognise a doctor without
external_id— two doctors can share a name — so without one, a new record is always created.
Doctors' schedules
You can import the weekly schedule:
{
"name": "Dra. Vega",
"external_id": "EMP-7",
"schedule": {
"mon": [{ "start": "09:00", "end": "13:00" }, { "start": "14:00", "end": "18:00" }],
"fri": [{ "start": "09:00", "end": "12:00" }]
}
}
Days are mon, tue, wed, thu, fri, sat, sun. Times are 24-hour with two digits (09:00, not 9:00). A day that isn't listed is a day the doctor doesn't work.
We validate the schedule on the way in and tell you what's wrong, rather than storing it and letting your calendar quietly stop offering times.
Deleting
- Patients are removed from the roster. Their past appointments remain, keeping the name and phone they were booked with.
- Doctors are deactivated, not deleted. A doctor with appointments can't be removed without destroying that history, so we take them off the schedule and leave the record intact. This is what the dashboard does too.
Rate limit
Each key allows 60 calls per minute. Past that you get a 429 with a Retry-After header; wait and retry.
A bulk call counts as one call no matter how many rows it carries (up to 500). Importing a thousand patients is two calls, not a thousand.
Rotating a key
There is no "rotate": to change a key, create the new one, update your system, and only then delete the old one. Both work during that window, so nothing goes down.
Deleting a key takes effect immediately — the next call using it gets a 401. If you suspect a key has leaked, delete it without hesitation; you can create another in seconds.
Errors
| Code | Meaning |
|---|---|
401 | The key doesn't exist, was deleted, or expired |
403 | The key is valid but lacks that permission |
404 | No such record in your clinic |
409 | A record with that phone or external_id already exists |
422 | Something in the request is malformed (check the message) |
429 | Over the per-minute rate limit |
402 | Subscription suspended: you can read, but not write |