Developers
The Meevia API
Read the schedule, book appointments and look up the catalog and the patient record from your own system. This page takes you from your first key to your first successful response.
On this page
What it is and who it's for
The API opens up, to your system, the parts of the clinic that make sense to automate: the schedule, the catalog of professionals and procedures, and the patient's basic record. Whatever you do through it follows the same rules that apply to the people running the clinic inside the platform.
It's HTTP with JSON, with no required library and no SDK to install. If your environment knows how to make a request and read a response, it already knows how to talk to Meevia.
Common cases
- A website or app of your own where the patient picks a time and books without help.
- A system the clinic already uses that needs to see the day's schedule without anyone typing it twice.
- Internal automation: a dashboard of your own, a report, a routine that confirms attendance.
What the clinic needs to have
API access is part of the clinic's plan. If the current plan doesn't include it, the very first call comes back refused, and no change to your code will fix that: it's a conversation with the sales team.
What isn't here yet
This version reads and writes the schedule, and reads the catalog and the patient. Meevia telling your system when something happens at the clinic, along with billing and payments, is not part of it.
How to get a key
A key is born in the developer panel, which lives at developers.meevia.app and has a shell of its own, outside the clinic's system. It works that way because the person building the integration often doesn't belong to the clinic. There is no path through the API for creating a key. The panel's interface is in Brazilian Portuguese, so the tab and role names quoted below are exactly the ones you will read on screen.
- 1Open the developer panelThe address is developers.meevia.app. Anyone already working inside the clinic's system reaches the same place through Integrações (Integrations), in the API e Webhooks card, which is a door to the panel.
- 2Sign in with your account, or ask for an invitationWhoever administers the clinic signs in straight away. Anyone from outside has to be invited: in the Acessos (Access) tab, whoever administers the panel invites by e-mail and chooses between Somente ver (view only), which can look but not act, and Pode administrar (can administer), which also issues keys. Asking for the invitation beats asking for a ready-made key, because a key that travels by message has already leaked.
- 3Pick the clinicThe panel serves every clinic you have access to, and the one picked at the top owns everything the tabs show. If you work for several, this is where you switch.
- 4In the Chaves (Keys) tab, create a new keyGive it a name that says where it will be used. Six months from now, that name is the only way to revoke the right key without taking down the wrong integration.
- 5Pick the mode and the scopesThe mode decides whether the key touches the real clinic or the test environment. The scopes decide what it can do. Tick only what the integration needs.
- 6Copy the key before closing the windowThe full key appears exactly once, at the moment it is issued. Meevia keeps the piece that identifies it and never the secret part, so closing without copying has no recovery: revoke the key and issue another.
One key, one clinic
A key is good for the clinic that issued it, and for that one only. If you serve several clinics, that means several keys, each stored separately and replaceable without touching the others. No key can see more than one clinic.
Where to keep it
Keep the key on the server, in your environment's variable vault. A key sitting inside the code of an app or a web page is a published key, because anybody can read it.
When to revoke
Revoking takes effect at once and cannot be undone: the key stops working and its calls start coming back refused. Do it when you change suppliers, when you shut an integration down, and at the faintest suspicion of a leak.
Who can do what
The Pode administrar role in the panel opens both of the things that change something: issuing and revoking keys, and resetting the test environment. It comes by invitation and applies per clinic, so someone from outside does both without depending on anybody. Resetting has a second path: whoever administers the clinic can also reset. Someone holding only Somente ver sees the keys and issues none; the panel says so on screen instead of hiding the button.
Authentication
Every call goes out authenticated. The key travels in the authorization header, in the scheme the example shows, and that's the whole of it: no login, no session, no token that expires halfway through.
curl -X GET \
-H "Authorization: Bearer mv_live_a1b2c3d49f8e7d6c5b4a39281706f5e4d3c2b1a0" \
"https://wnricobmssysbwfhbmsm.supabase.co/functions/v1/api-v1/v1/practitioners"The key is a single value, and it is that whole value that travels in the header. It has three pieces glued together: the prefix says which mode the key is in, the middle piece identifies it and is what shows up in the panel's Chaves tab, and the last one is the secret part. Meevia never stores that last piece in the clear, so not even support can read yours. Always send the complete key: sending only the secret part is the slip that produces the most authorization refusals.
A key that is missing, mistyped, revoked or expired all get the same refusal, without saying which of the four it was. That is deliberate. Saying which one would hand a clue to whoever is guessing.
Two modes
Production
mv_live_
Touches the real clinic. An appointment created from here shows up on the front desk's schedule and sets off whatever the clinic has set up to go off.
Test
mv_test_
Touches a separate environment, with made-up data, that exists only for you to experiment in. Nothing that happens there comes anywhere near the real clinic.
The mode is decided when the key is issued and does not change afterwards. In practice you keep both keys and swap the environment variable to flip from one side to the other.
The key in the examples is made up and works nowhere: it is there only so you can recognize the shape of yours. Notice that its prefix is a production one. If you follow the walkthrough in test mode, as this page recommends, yours will start differently.
Scopes
Each scope opens one part of the API, and a key only does what its scopes allow. A missing scope shows up on the first call that needs it, not when the key is issued.
appointments:read
Read the schedule
List the appointments in a date range and open one of them.
appointments:write
Change the schedule
Create an appointment, change the status of one, and cancel.
catalog:read
Read the catalog
Professionals, procedures and a given day's free time slots.
patients:read
Read a patient
Open the basic record of a patient you already know by identifier. There is no open search by name in this version.
Grant the minimum. An integration that only shows the schedule on a wall display has no business booking appointments. And a lean key is a small amount of damage on the day it leaks.
Scopes cannot be edited once the key is issued. Widening them means issuing a new key with what was missing, swapping it in your environment and revoking the old one.
Test environment
The clinic gets a test environment of its very own, isolated from the real clinic. Issuing the first test key already creates it, so there is nothing to provision and nothing to request.
What's already inside
There's one sample patient, one sample procedure and one sample professional. None of them exists in the real clinic. The professional and procedure lists return the identifiers of those two, so looking up the catalog and free time slots works in your very first sitting. Registering people and services belongs to the clinic: through the API, what you create is an appointment.
What you can't test here
The API writes in three places: creating an appointment, changing its status and cancelling it. None of the three can be exercised here today, and the reason comes in a chain. Creating needs the patient's identifier, which appears neither on screen nor in the result of any query, because there is no patient listing. Changing the status and cancelling need an appointment's identifier, and it would only exist if creating had worked: the appointment list starts out empty here. Anyone who needs to test a write does it in the real clinic, with the care that deserves: book into an empty slot and cancel as soon as you have confirmed it worked.
How to reset it
The panel's Ambiente de teste (Test environment) tab has a button that throws away the current environment and hands you another, clean one, with the same starting data. Test keys already issued keep working: they are tied to the clinic, not to the environment that was thrown away.
Resetting erases everything you created there, and there is no undo. It is what you want when a test has dirtied the environment, and what you very much don't want in the middle of a test run.
Resetting belongs to whoever holds Pode administrar in the panel, or administers the clinic. Someone from outside with that role resets it alone, with nobody to ask.
The API address
Every call goes to the same address, and it is one and the same for everybody. There is no address per clinic: what tells one clinic from another is the key, never the URL.
https://wnricobmssysbwfhbmsm.supabase.co/functions/v1/api-v1You'll notice a piece of the path that looks repeated. That is correct. One of them belongs to the infrastructure that hosts the API and the other to the version of ours. Copy the address exactly as it appears here; trimming the part that looks left over gets you a response saying the address doesn't exist.
First call, from zero to your first response
It takes five minutes, counting from the sign-in. Do it in test mode: if something turns out other than expected, the one who pays for it is a make-believe patient.
- 1Issue a test keyFollow the path described in How to get a key, ticking test mode and the catalog read scope. Copy the key.
- 2Store the key in an environment variableThat way the key stays out of your terminal history and out of the middle of the command you're about to send a colleague.
- 3Ask for the list of professionalsIt's the simplest call here: it takes no parameters at all, and the test environment already comes with a professional inside. If it answers, your key, your scope and your address are all correct at once.
- 4Check what came backA successful response carries the test professional. From there on the recipe is the same for everything else: the address changes, the header stays as it is.
Swap the example key for the variable you stored yours in at the previous step, or for the whole key. The one shown here is made up, and comes back refused.
curl -X GET \
-H "Authorization: Bearer mv_live_a1b2c3d49f8e7d6c5b4a39281706f5e4d3c2b1a0" \
"https://wnricobmssysbwfhbmsm.supabase.co/functions/v1/api-v1/v1/practitioners"{
"data": [
{
"id": "1f6d5c84-3b92-4a07-8e15-d6c3f9b204ae",
"name": "Dra. Helena Costa",
"specialty": "Dermatologia"
}
]
}If what came back isn't what you expected
Compare it against this page's catalog of refusals, which says what happened and what to do in each case. It's worth looking at the panel's Requisições (Requests) tab too, which shows the address called, the response and the time of each call that reached us. It keeps only that summary, never the content you sent.
The shape of the responses
Responses come wrapped the same way every time, and each call shows the shape of what it gives back. Program against the wrapper and ignore what you don't recognize: new information turns up over time, and an integration that ignores what it doesn't expect survives that without needing you.
The wrappers
- A single record, inside the standard wrapper.
data - A list, inside that same wrapper.
data - A list in pages, together with the marker that asks for the next one.
datanext_cursorMay come with no value - This response breaks the pattern of the others: it carries the day you asked about alongside the time slots.
dateslots - The shape of every refusal, on any call.
error
Long lists
Lists come in pages. The response carries a continuation marker: while it comes back filled in, send that same marker on the next call to fetch the next page. When it comes back with no value, you're done. Don't try to guess the total or build the paging yourself.
What each response carries
Appointment
| Field | Type |
|---|---|
id | uuid |
status | string |
starts_at | datetime |
ends_atMay come with no value | datetime |
patient_idMay come with no value | uuid |
professional_idMay come with no value | uuid |
procedure_ids | array<uuid> |
created_at | datetime |
Patient
| Field | Type |
|---|---|
id | uuid |
name | string |
phoneMay come with no value | string |
emailMay come with no value | string |
Professional
| Field | Type |
|---|---|
id | uuid |
nameMay come with no value | string |
specialtyMay come with no value | string |
Procedure
| Field | Type |
|---|---|
id | uuid |
name | string |
price | number |
duration_minutes | integer |
Free time slot
| Field | Type |
|---|---|
time | string |
professional_id | uuid |
Refusal
| Field | Type |
|---|---|
code | string |
message | string |
detailsDoesn't always come | object |
The calls
Each one brings what it takes in, what it gives back and an example ready to paste into your terminal. Swap the example's identifiers for yours.
MethodGETAddress/v1/appointments
Lists the appointments in a date range, in pages.
- Scope required
appointments:readRead the schedule- Repeating
- Doesn't apply
- Response
- Appointment
datanext_cursorMay come with no value
| Parameter | Type | Where it goes |
|---|---|---|
fromOptional
| date | query |
toOptional
| date | query |
statusOptional
| string | query |
professional_idOptional | uuid | query |
limitOptional
| integer | query |
cursorOptional
| string | query |
curl -X GET \
-H "Authorization: Bearer mv_live_a1b2c3d49f8e7d6c5b4a39281706f5e4d3c2b1a0" \
"https://wnricobmssysbwfhbmsm.supabase.co/functions/v1/api-v1/v1/appointments?from=2026-10-01&to=2026-10-31&limit=2"{
"data": [
{
"id": "9c1f4b2a-7e83-4d61-b0a5-2f8c6d31e4a7",
"status": "agendado",
"starts_at": "2026-10-03T14:30:00",
"ends_at": "2026-10-03T15:30:00",
"patient_id": "4d7e2c19-5a6b-4f30-9e81-c2a5b7d04f63",
"professional_id": "1f6d5c84-3b92-4a07-8e15-d6c3f9b204ae",
"procedure_ids": [
"2a9f8b31-6c74-4e05-9d28-a3f1e7b5c06d"
],
"created_at": "2026-09-28T11:04:22.517Z"
},
{
"id": "5e0b7a94-1d62-4c38-97af-8b41d2e6c03f",
"status": "confirmado",
"starts_at": "2026-10-03T16:00:00",
"ends_at": "2026-10-03T16:45:00",
"patient_id": "4d7e2c19-5a6b-4f30-9e81-c2a5b7d04f63",
"professional_id": "1f6d5c84-3b92-4a07-8e15-d6c3f9b204ae",
"procedure_ids": [
"2a9f8b31-6c74-4e05-9d28-a3f1e7b5c06d"
],
"created_at": "2026-09-28T11:04:22.517Z"
}
],
"next_cursor": "MjAyNi0xMC0wM3wxNDozMDowMHw5YzFmNGIyYS03ZTgzLTRkNjEtYjBhNS0yZjhjNmQzMWU0YTc="
}curl -X GET \
-H "Authorization: Bearer mv_live_a1b2c3d49f8e7d6c5b4a39281706f5e4d3c2b1a0" \
"https://wnricobmssysbwfhbmsm.supabase.co/functions/v1/api-v1/v1/appointments?from=2026-10-01&to=2026-10-31&limit=2&cursor=MjAyNi0xMC0wM3wxNDozMDowMHw5YzFmNGIyYS03ZTgzLTRkNjEtYjBhNS0yZjhjNmQzMWU0YTc%3D"{
"data": [],
"next_cursor": null
}MethodPOSTAddress/v1/appointments
Books an appointment for a patient, with a professional, at a given time.
- Scope required
appointments:writeChange the schedule- Repeating
- Requires a repeat value
- Response
- Appointment
data
| Parameter | Type | Where it goes |
|---|---|---|
Idempotency-KeyRequired | string | header |
patient_idRequired | uuid | body |
professional_idRequired | uuid | body |
procedure_idsRequired
| array<uuid> | body |
starts_atRequired
| datetime | body |
statusOptional
| string | body |
curl -X POST \
-H "Authorization: Bearer mv_live_a1b2c3d49f8e7d6c5b4a39281706f5e4d3c2b1a0" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7f3c05a1-9b24-4e6d-8c17-0a5f3d92b4e8" \
-d '{
"patient_id": "4d7e2c19-5a6b-4f30-9e81-c2a5b7d04f63",
"professional_id": "1f6d5c84-3b92-4a07-8e15-d6c3f9b204ae",
"procedure_ids": [
"2a9f8b31-6c74-4e05-9d28-a3f1e7b5c06d"
],
"starts_at": "2026-10-03T14:30:00",
"status": "agendado"
}' \
"https://wnricobmssysbwfhbmsm.supabase.co/functions/v1/api-v1/v1/appointments"{
"data": {
"id": "9c1f4b2a-7e83-4d61-b0a5-2f8c6d31e4a7",
"status": "agendado",
"starts_at": "2026-10-03T14:30:00",
"ends_at": "2026-10-03T15:30:00",
"patient_id": "4d7e2c19-5a6b-4f30-9e81-c2a5b7d04f63",
"professional_id": "1f6d5c84-3b92-4a07-8e15-d6c3f9b204ae",
"procedure_ids": [
"2a9f8b31-6c74-4e05-9d28-a3f1e7b5c06d"
],
"created_at": "2026-09-28T11:04:22.517Z"
}
}curl -X POST \
-H "Authorization: Bearer mv_live_a1b2c3d49f8e7d6c5b4a39281706f5e4d3c2b1a0" \
-H "Content-Type: application/json" \
-d '{
"patient_id": "4d7e2c19-5a6b-4f30-9e81-c2a5b7d04f63",
"professional_id": "1f6d5c84-3b92-4a07-8e15-d6c3f9b204ae",
"procedure_ids": [
"2a9f8b31-6c74-4e05-9d28-a3f1e7b5c06d"
],
"starts_at": "2026-10-03T14:30:00"
}' \
"https://wnricobmssysbwfhbmsm.supabase.co/functions/v1/api-v1/v1/appointments"{
"error": {
"code": "validation_failed",
"message": "Cabeçalho Idempotency-Key é obrigatório para criar agendamento."
}
}MethodGETAddress/v1/appointments/{id}
Opens one specific appointment.
- Scope required
appointments:readRead the schedule- Repeating
- Doesn't apply
- Response
- Appointment
data
| Parameter | Type | Where it goes |
|---|---|---|
idRequired | uuid | path |
curl -X GET \
-H "Authorization: Bearer mv_live_a1b2c3d49f8e7d6c5b4a39281706f5e4d3c2b1a0" \
"https://wnricobmssysbwfhbmsm.supabase.co/functions/v1/api-v1/v1/appointments/9c1f4b2a-7e83-4d61-b0a5-2f8c6d31e4a7"{
"data": {
"id": "9c1f4b2a-7e83-4d61-b0a5-2f8c6d31e4a7",
"status": "agendado",
"starts_at": "2026-10-03T14:30:00",
"ends_at": "2026-10-03T15:30:00",
"patient_id": "4d7e2c19-5a6b-4f30-9e81-c2a5b7d04f63",
"professional_id": "1f6d5c84-3b92-4a07-8e15-d6c3f9b204ae",
"procedure_ids": [
"2a9f8b31-6c74-4e05-9d28-a3f1e7b5c06d"
],
"created_at": "2026-09-28T11:04:22.517Z"
}
}MethodPATCHAddress/v1/appointments/{id}/status
Changes an appointment's status, within what is allowed.
- Scope required
appointments:writeChange the schedule- Repeating
- Safe to repeat
- Response
- Appointment
data
| Parameter | Type | Where it goes |
|---|---|---|
idRequired | uuid | path |
statusRequired
| string | body |
curl -X PATCH \
-H "Authorization: Bearer mv_live_a1b2c3d49f8e7d6c5b4a39281706f5e4d3c2b1a0" \
-H "Content-Type: application/json" \
-d '{
"status": "confirmado"
}' \
"https://wnricobmssysbwfhbmsm.supabase.co/functions/v1/api-v1/v1/appointments/9c1f4b2a-7e83-4d61-b0a5-2f8c6d31e4a7/status"{
"data": {
"id": "9c1f4b2a-7e83-4d61-b0a5-2f8c6d31e4a7",
"status": "confirmado",
"starts_at": "2026-10-03T14:30:00",
"ends_at": "2026-10-03T15:30:00",
"patient_id": "4d7e2c19-5a6b-4f30-9e81-c2a5b7d04f63",
"professional_id": "1f6d5c84-3b92-4a07-8e15-d6c3f9b204ae",
"procedure_ids": [
"2a9f8b31-6c74-4e05-9d28-a3f1e7b5c06d"
],
"created_at": "2026-09-28T11:04:22.517Z"
}
}curl -X PATCH \
-H "Authorization: Bearer mv_live_a1b2c3d49f8e7d6c5b4a39281706f5e4d3c2b1a0" \
-H "Content-Type: application/json" \
-d '{
"status": "realizado"
}' \
"https://wnricobmssysbwfhbmsm.supabase.co/functions/v1/api-v1/v1/appointments/9c1f4b2a-7e83-4d61-b0a5-2f8c6d31e4a7/status"{
"error": {
"code": "status_transition_not_allowed",
"message": "Transição de status não permitida por esta API.",
"details": {
"from": "agendado",
"to": "realizado"
}
}
}MethodPOSTAddress/v1/appointments/{id}/cancel
Cancels an appointment.
- Scope required
appointments:writeChange the schedule- Repeating
- Safe to repeat
- Response
- Appointment
data
| Parameter | Type | Where it goes |
|---|---|---|
idRequired | uuid | path |
curl -X POST \
-H "Authorization: Bearer mv_live_a1b2c3d49f8e7d6c5b4a39281706f5e4d3c2b1a0" \
"https://wnricobmssysbwfhbmsm.supabase.co/functions/v1/api-v1/v1/appointments/9c1f4b2a-7e83-4d61-b0a5-2f8c6d31e4a7/cancel"{
"data": {
"id": "9c1f4b2a-7e83-4d61-b0a5-2f8c6d31e4a7",
"status": "cancelado",
"starts_at": "2026-10-03T14:30:00",
"ends_at": "2026-10-03T15:30:00",
"patient_id": "4d7e2c19-5a6b-4f30-9e81-c2a5b7d04f63",
"professional_id": "1f6d5c84-3b92-4a07-8e15-d6c3f9b204ae",
"procedure_ids": [
"2a9f8b31-6c74-4e05-9d28-a3f1e7b5c06d"
],
"created_at": "2026-09-28T11:04:22.517Z"
}
}MethodGETAddress/v1/patients/{id}
Opens a patient's basic record.
- Scope required
patients:readRead a patient- Repeating
- Doesn't apply
- Response
- Patient
data
| Parameter | Type | Where it goes |
|---|---|---|
idRequired | uuid | path |
curl -X GET \
-H "Authorization: Bearer mv_live_a1b2c3d49f8e7d6c5b4a39281706f5e4d3c2b1a0" \
"https://wnricobmssysbwfhbmsm.supabase.co/functions/v1/api-v1/v1/patients/4d7e2c19-5a6b-4f30-9e81-c2a5b7d04f63"{
"data": {
"id": "4d7e2c19-5a6b-4f30-9e81-c2a5b7d04f63",
"name": "Ana Ribeiro",
"phone": "+5511987654321",
"email": "ana.ribeiro@example.com"
}
}MethodGETAddress/v1/practitioners
Lists the clinic's professionals.
- Scope required
catalog:readRead the catalog- Repeating
- Doesn't apply
- Response
- Professional
data
This call takes no parameters.
curl -X GET \
-H "Authorization: Bearer mv_live_a1b2c3d49f8e7d6c5b4a39281706f5e4d3c2b1a0" \
"https://wnricobmssysbwfhbmsm.supabase.co/functions/v1/api-v1/v1/practitioners"{
"data": [
{
"id": "1f6d5c84-3b92-4a07-8e15-d6c3f9b204ae",
"name": "Dra. Helena Costa",
"specialty": "Dermatologia"
}
]
}MethodGETAddress/v1/procedures
Lists the procedures in the catalog.
- Scope required
catalog:readRead the catalog- Repeating
- Doesn't apply
- Response
- Procedure
data
This call takes no parameters.
curl -X GET \
-H "Authorization: Bearer mv_live_a1b2c3d49f8e7d6c5b4a39281706f5e4d3c2b1a0" \
"https://wnricobmssysbwfhbmsm.supabase.co/functions/v1/api-v1/v1/procedures"{
"data": [
{
"id": "2a9f8b31-6c74-4e05-9d28-a3f1e7b5c06d",
"name": "Limpeza de pele profunda",
"price": 280,
"duration_minutes": 60
}
]
}MethodGETAddress/v1/availability
Shows a day's free time slots, and you can narrow it down to one professional or one procedure.
- Scope required
catalog:readRead the catalog- Repeating
- Doesn't apply
- Response
- Free time slot
dateslots
| Parameter | Type | Where it goes |
|---|---|---|
dateRequired
| date | query |
professional_idOptional | uuid | query |
procedure_idOptional | uuid | query |
curl -X GET \
-H "Authorization: Bearer mv_live_a1b2c3d49f8e7d6c5b4a39281706f5e4d3c2b1a0" \
"https://wnricobmssysbwfhbmsm.supabase.co/functions/v1/api-v1/v1/availability?date=2026-10-03&procedure_id=2a9f8b31-6c74-4e05-9d28-a3f1e7b5c06d"{
"date": "2026-10-03",
"slots": [
{
"time": "09:00",
"professional_id": "1f6d5c84-3b92-4a07-8e15-d6c3f9b204ae"
},
{
"time": "10:00",
"professional_id": "1f6d5c84-3b92-4a07-8e15-d6c3f9b204ae"
}
]
}Repeating without duplicating
The network drops in the middle of the call, the timeout runs out, and you're left not knowing whether the appointment was created. Repeating on your own can book the same patient twice.
That's why every creation travels with a repeat value you make up yourself, one per operation. If it turns up again with the same content, Meevia hands back the answer it already gave, without processing anything a second time.
Idempotency-Key
How to choose that value
Generate a unique value per operation (a random identifier does the job) and store it alongside your attempt, so you can reuse exactly the same one when the time comes to repeat. Generating a new one on every attempt is the same as having no protection at all.
The stored answer comes back as it was, including when it was a refusal. Repeating with the value of an attempt that went wrong gives you the same error, not a fresh attempt.
Reusing the same value with different content is refused, and that refusal usually gives away two distinct operations sharing one value. The same response turns up when a twin call is still in flight, and there waiting a few moments and repeating sorts it out.
The value is good for one day. After that it is forgotten, and a repeat goes back to being processed for real.
An attempt left without a response is given up for lost after a few minutes, and the next call carrying that value takes its place. That way a request that died halfway doesn't block you until the following day.
- Minutes to wait before repeating an attempt that got no response
5
What happens when you repeat each call
Doesn't apply
It's a read, and a read changes nothing by being repeated.
Requires a repeat value
Without it the call is refused. It's the protection against booking the same patient twice when the network fails halfway.
Safe to repeat
Repeating lands in the same place: asking for the status the appointment is already in gives you the appointment as it stands. There's no need to send a repeat value here.
Usage limits
Each key has a ceiling of calls per minute of its own. It's per key, not per clinic. Two integrations in the same clinic don't compete for one limit, and one that fired off too many calls doesn't take the other down.
- Calls per minute, on each key
120
The count resets as each clock minute turns over, not on a window that slides. In practice, two bursts fired around the turn land in different minutes, so a short spike can get through even when it adds up to more than the ceiling. This isn't slack you can count on: what keeps an integration standing is its average pace.
When you go over, the response tells you how many seconds to wait in a header of its own, not in the body. Respect that number instead of trying again straight away. If this becomes routine, the ceiling isn't the problem: space the calls out, keep a copy of what changes little (the catalog changes little) and stop asking in a loop.
Retry-After
Status changes that are allowed
The API accepts a closed set of changes, and the table shows which: each starting status has the destinations it allows, and anything absent from the table is refused. Marking an appointment as done and recording a no-show are left out by product decision. Both generate a sale, a charge and the use of a session from a package. They stay in the hands of the people running the clinic.
| Current status | Can change to |
|---|---|
pre_agendado | agendadoconfirmadocancelado |
agendado | confirmadocancelado |
confirmado | cancelado |
em_espera | cancelado |
The statuses
pre_agendado- Booked, but not yet confirmed by anybody. It's the status a new appointment is born in when you don't ask for another.
agendado- It's on the schedule and the clinic is counting on it.
confirmado- The patient has confirmed they're coming. It's what the front desk looks at to know what to expect on the day.
em_espera- It's in the queue, waiting for a slot to open. The API puts nobody here, but it does read who is, and from here the only possible change is cancelling.
cancelado- Cancelled, and that's final: from here the API changes nothing more.
realizado- The appointment happened. The clinic is who records that, because the change generates the sale. The API reads it and doesn't write it.
faltou- The patient didn't turn up. This one belongs to the clinic too: it feeds the no-show charge and the use of a session from a package. The API reads it and doesn't write it.
When a call doesn't work out
Every refusal comes back in the same shape, with a short code that doesn't change and a message in Portuguese. In your program, always decide by the code, never by the message: the message is there for your log and can be rewritten at any moment.
Some refusals also carry a breakdown of what didn't pass. Not every refusal carries that breakdown, so your code can't depend on it.
plan_requiredStatus code 403
The key is valid, but the clinic's plan doesn't include access to the API.
No change to your code will fix this. Let the clinic know, and they take it to Meevia support: it's a matter of the plan.
scope_requiredStatus code 403
The key is valid, but it doesn't carry the scope this call requires.
Look at the call's entry to see which scope it asks for, and compare that with the key's scopes in the panel's Chaves tab. Scopes can't be edited: issue another key with what was missing, swap it in your environment and revoke the old one.
not_foundStatus code 404
Two different situations land here: either the address matches nothing, or what you asked for doesn't exist in this clinic.
Check the address first, letter by letter, against the call's example; one plural too many lands you here. If the address is right, then it's the record that's missing: it may have been deleted, or it may belong to another clinic. Remember too that the test environment has data of its own. An identifier copied from the real clinic doesn't exist inside it.
method_not_allowedStatus code 405
The address exists, but it doesn't answer that kind of request.
Check the call's entry for the method it expects. Nearly always this is a read attempted as a write, or the other way round.
validation_failedStatus code 422
Something in what you sent didn't pass the check: a required value is missing, or one of them arrived in a shape that doesn't work.
The refusal's message says what didn't pass. Compare it with the call's parameter table, paying particular attention to the date and time format, which is where most mistakes happen.
rate_limitedStatus code 429
You went past that key's ceiling of calls per minute.
Wait the number of seconds that comes in the response's wait header. It isn't in the body. That's why so many people never find the number and end up trying again straight away, which only digs the hole deeper. If it happens often, adjust your pace: space the calls out, keep a copy of what changes little and stop asking in a loop.
idempotency_conflictStatus code 409
The repeat value you sent has already been used for something else, or a twin call is still in flight.
If it was something else, start generating a new value for every new operation; reusing one across different operations is what causes this. If it's the twin, wait a few moments and repeat with the same value: as soon as the first one finishes, the stored answer is handed back.
status_transition_not_allowedStatus code 422
The change you asked for isn't allowed from the status the appointment is in.
Read the table of allowed changes, which shows where you can go from each status. Marking as done and recording a no-show aren't in it, and remain the clinic's own operation. The refusal comes back with the current status and the one you asked for, which usually reveals that the appointment had already moved somewhere else.
sandbox_not_readyStatus code 409
The key is a test key and the clinic's test environment isn't up yet.
Try again in a few moments. If it persists, the environment needs resetting, which rebuilds it from scratch, and you do that yourself if you hold Pode administrar in the panel. A production key never lands here.
internal_errorStatus code 500
Something broke on our side.
It isn't your code. Try again after a pause. If it persists, talk to Meevia support and give them the time and what you called; the panel's Requisições tab helps to pin it down.
Need a hand
Tell us what you called, what you expected and what came back. The panel's Requisições tab shows what reached us, and that's where we start looking.
This page and the API
If you find a difference between what's written here and what the API answered, the API is right, and we want to hear about it. The date below says when this page was last checked against it.