Skip to content
linqofor developers
On this page

API reference

Connect your system to the linqo calendar

Read the catalog of services and professionals, check open slots and book appointments from your own software — with a key the account owner issues and revokes whenever they want.

OpenAPI 3.1.0version 1.0.015 routes

Overview

The API is REST over JSON and answers at https://v1.uselinqo.com. Every key belongs to one company: everything it reads and writes stays inside that account, no exceptions.

This entire page — routes, fields, enums and error codes — is generated from GET /v1/openapi, the contract the server itself publishes, derived from the code that answers the calls. A new route or a new field shows up here without anyone rewriting anything, and it is the same document a client generator consumes.

JavaScript
const res = await fetch('https://v1.uselinqo.com/v1/openapi');
const spec = await res.json();

That endpoint is public: no credential, no quota, served with Cache-Control: public, max-age=300. A description that required the very key it explains would be useless to anyone still deciding whether to integrate.

Do not send Authorization to this route. A GET with no non-standard header is a simple request, which the browser fires directly. Adding the header turns it into a preflight — which this route does not answer — and sends a credential to an endpoint that has no use for it.

API keys

The account administrator issues keys from the dashboard. At creation they pick a name, the scopes and, optionally, an expiry date.

  • The token is shown exactly once. It only exists in the creation response: what we store is a hash, and there is no way to recover the original. Copy it right away and keep it in a secrets manager — if you lose it, issue another.
  • At least one scope is required. A key only reaches what its scopes allow; everything else answers SCOPE_MISSING.
  • Notifications start switched off. This is deliberate: a key created without much thought cannot send email to the company’s clients. Turning it on is an explicit decision by whoever administers the account.
  • Rate limit and quota are not configurable. The per-minute and per-month ceilings belong to the platform, not to the account. The dashboard shows the numbers; nobody edits them.
  • Revoking takes up to 60 seconds. The authorization decision is cached for a minute, so a revoked key may still answer within that window. Plan credential rotation around it.

The key management screen is still being built in the dashboard. Until it ships, request yours at support@uselinqo.com.

Authentication

Every route other than /v1/openapi expects the key in the Authorization header, as a Bearer token.

HTTP
Authorization: Bearer bk_prod_<keyId>.<secret>

The environment travels inside the token itself. A development key pasted into production fails while the token is being read, with a message that says so — not as an unexplained 401.

401 is the only response that does not use the error envelope. It comes from API Gateway, before the API code runs, and an HTTP API authorizer cannot reshape that body. Handle 401 by status, not by code. The exception is /v1/mcp, which authenticates on its own — see AI agents.

Scopes

Each route requires at most one scope, and a key carries whichever ones the administrator picked. Grant the minimum the integration needs: a read-only key that leaks cannot book anything.

3 routes require no scope:

Rate limits and quotas

There are two per-minute buckets — one for reads, one for writes — and a monthly quota per key. Each route declares which bucket it falls into: that is the cost shown on its card below.

Agents do not have a bucket of their own. Through /v1/mcp, only tools/call spends, and it spends what the route behind the tool would — a read or a write; tools/list and server/discover are free. See AI agents.

Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset: read those headers instead of counting calls on your side. Blowing the minute returns RATE_LIMITED and blowing the month returns QUOTA_EXCEEDED, both with status 429 — the first clears on its own, the second only at the turn of the month.

Every response also carries X-Request-Id. Keep the value in your logs: it is how support finds the call.

Straight from the API — the numbers in force right now

The surface a third party reaches with a key the company issued. Budgets, per key, on the Free plan: 120 reads/min, 20 writes/min, 1000 requests/month. They are the platform’s to set: the panel shows them and writes neither. The 401 is the one response that does not use the error envelope: it comes from API Gateway, which an HTTP API authorizer cannot reshape. A revoked key keeps working for up to 60 seconds: the authorizer caches its decision for that long. One route takes no credential at all: `GET /v1/openapi`, this document. A description that requires the key it explains is one nobody can read before deciding to integrate. The same surface is reachable by an agent at `POST /v1/mcp`, which speaks JSON-RPC rather than REST. It is one path here because every frame arrives as one request; the tools it offers are listed on that operation under `x-mcp-tools`. A key created while the panel is in the sandbox is a test key: `bk_prod_test_<keyId>.<secret>`. It reaches the company’s sandbox — a copy of its services, professionals and hours, with clients and appointments of its own — never notifies anyone, and spends no monthly quota. The per-minute limits are the same as a live key’s.

Pagination

Listings accept limit and cursor and return data along with next_cursor. The cursor is opaque: pass the value back exactly as it came, without interpreting it.

JavaScript
let cursor = null;
do {
  const url = new URL('https://v1.uselinqo.com/v1/services');
  if (cursor) url.searchParams.set('cursor', cursor);

  const res = await fetch(url, {
    headers: { Authorization: `Bearer ${process.env.LINQO_API_KEY}` },
  });
  const page = await res.json();

  handle(page.data);
  cursor = page.next_cursor;
} while (cursor);

Paginate until next_cursor comes back null, never until a page comes back short. The database cuts the read at 1 MB, so a page with few items can still carry a cursor — stopping there would silently leave records behind.

Idempotency

Booking an appointment requires the Idempotency-Key header. Without it the call is refused with IDEMPOTENCY_KEY_REQUIRED.

The reason is concrete: whoever hit a timeout cannot tell “my attempt went through” from “someone else took the slot”. With the key, repeating the request returns the earlier response instead of booking twice. The response is kept for 24 hours; the same key with a different body is a 400, IDEMPOTENCY_KEY_REUSED.

Generate a fresh value per booking attempt — a UUID does the job — and reuse it across every retry of that same attempt.

Field selection

Listings accept fields with comma-separated names, to return only what your screen uses. A name the resource does not publish returns INVALID_FIELDS — the error is explicit on purpose, so that a mistyped field does not become a silently empty column.

curl
curl 'https://v1.uselinqo.com/v1/services?fields=id,name,price' \
  -H 'Authorization: Bearer $LINQO_API_KEY'

Errors

Other than 401, every error arrives in the same envelope: { code, message }. Program against code, which is stable; message is for a human reading a log.

CodeHTTPMeaning
IDEMPOTENCY_KEY_REQUIRED400A booking must carry `Idempotency-Key`
IDEMPOTENCY_KEY_REUSED400That `Idempotency-Key` was used with a different body
INVALID_CURSOR400`cursor` is not one we issued
INVALID_FIELDS400`fields` named something this resource does not publish
RANGE_TOO_WIDE400The date range exceeds what a single request may read
VALIDATION_ERROR400The body or query does not match the schema
SCOPE_MISSING403The key does not hold the scope this route requires
NOT_FOUND404No such row in this company
CLIENT_BUSY422The client is already booked at that time
MODALITY_NOT_OFFERED422The professional offers no service in that modality
SERVICE_MODALITY_MISMATCH422The professional does not offer one of the services in that modality
SLOT_TAKEN422The professional is already booked at that time
QUOTA_EXCEEDED429The monthly quota for this key is spent
RATE_LIMITED429The per-minute budget for this key is spent

AI agents

An AI agent reaches the same /v1 surface through POST /v1/mcp, which speaks the Model Context Protocol — JSON-RPC over HTTP — instead of REST. A tool is only listed, and only callable, when the connection holds the scope its route requires.

Quick start

Three steps in Claude Code. The account administrator needs to be the one approving in step 2.

  1. Add the server. In a terminal:
    Terminal
    claude mcp add --transport http linqo https://v1.uselinqo.com/v1/mcp
  2. Authenticate. Inside Claude Code, run /mcp, pick linqo and choose Authenticate. The browser opens on the linqo dashboard: sign in, review the scopes and approve.
  3. Check it worked. /mcp now lists linqo as connected, with its tools. Ask the agent what it can do with linqo — it discovers the tools on its own; there is nothing to configure.

Things to ask once it is connected

  • “Which slots does Ana have open on Thursday?”
  • “How many appointments do we have tomorrow?”
  • “Book physiotherapy with Ana on Thursday at 2 pm for Bruno.” — needs appointments:write.

Connect with OAuth

This is what the quick start uses. An MCP client with OAuth support needs only the URL — no key is copied anywhere. In clients configured by file, such as Cursor, declare just the URL; the client opens the browser the first time it connects:

mcp.json
{
  "mcpServers": {
    "linqo": {
      "url": "https://v1.uselinqo.com/v1/mcp"
    }
  }
}

What happens behind the scenes:

  1. The first call, without a credential, answers 401 with WWW-Authenticate pointing to the resource metadata (RFC 9728). From there the client discovers the authorization server (RFC 8414).
  2. The client opens the browser on the linqo dashboard. The account administrator signs in, sees which app is asking and where the answer goes, and approves — any requested scope can be unchecked first.
  3. The client exchanges the code for an access token and renews it by itself. Access tokens last one hour; refresh tokens, 30 days, rotated on every use.
  • Only an administrator can approve. A professional who signs in is told to ask the account administrator.
  • Each connection is a key. It shows up in the dashboard as a connected app, takes the same rate limits and quota, and counts toward the plan’s limit of active keys. Revoking it there cuts the agent off immediately.
  • OAuth tokens only work here. A lat_… access token is accepted by /v1/mcp alone; the REST routes answer 401 to it.
  • Public clients, PKCE required. Clients identify themselves by a metadata document URL or register dynamically (RFC 7591); no client secret is issued, and only S256 is accepted. Discovery lives at /.well-known/oauth-authorization-server.

Connect with an API key

A client without OAuth — or a server-side integration — sends a regular key as a Bearer token, exactly as on the REST routes. Nothing changed for integrations that already use one. Keep the token in an environment variable and pass it as a header:

Claude Code
claude mcp add --transport http linqo \
  https://v1.uselinqo.com/v1/mcp \
  --header "Authorization: Bearer $LINQO_API_KEY"

The same thing as configuration, for Claude Code and Cursor (Cursor ignores type):

mcp.json
{
  "mcpServers": {
    "linqo": {
      "type": "http",
      "url": "https://v1.uselinqo.com/v1/mcp",
      "headers": { "Authorization": "Bearer bk_prod_<keyId>.<secret>" }
    }
  }
}

Do not commit the token. Avoid --scope project in Claude Code: it writes to .mcp.json inside your repository, header and all. The default scope and --scope user write to ~/.claude.json, outside it. To keep the secret out of every file, reference an environment variable in the header value — ${LINQO_API_KEY} in Claude Code, ${env:LINQO_API_KEY} in Cursor.

The agent only sees the tools the key’s scopes reach — a tool outside them is not listed at all, rather than listed and refused. A read-only key makes an agent that answers questions about the calendar without changing it: a sensible start.

Unlike the rest of the API, a 401 here comes from the endpoint itself: it uses the error envelope (UNAUTHORIZED) and carries WWW-Authenticate. An invalid, expired or revoked token adds error="invalid_token", without saying which.

Tools

Quota is spent by tools/call only, at the cost of the route behind the tool. Listing tools (tools/list) and discovering the server (server/discover) are free.

12 tools, each driving one route. The route is the contract: follow the link to see exactly what a tool reads and writes.

ToolRouteScopeCost
list_servicesGET /v1/servicesservices:readread
get_serviceGET /v1/services/{serviceId}services:readread
list_service_professionalsGET /v1/services/{serviceId}/professionalsprofessionals:readread
list_professionalsGET /v1/professionalsprofessionals:readread
get_professionalGET /v1/professionals/{professionalId}professionals:readread
check_availabilityGET /v1/professionals/{professionalId}/availabilityappointments:readread
list_clientsGET /v1/clientsclients:readread
get_clientGET /v1/clients/{clientId}clients:readread
list_appointmentsGET /v1/appointmentsappointments:readread
get_appointmentGET /v1/appointments/{appointmentId}appointments:readread
book_appointmentPOST /v1/appointmentsappointments:writewrite
update_appointment_statusPATCH /v1/appointments/{appointmentId}appointments:writewrite

Routes

15 routes, grouped by resource. Each one states the scope it requires and which rate-limit bucket it falls into.

Metadata

get/v1/openapi

The OpenAPI 3.1 description of this API, generated from the code that serves it

public · no credentialcost read
curl
curl 'https://v1.uselinqo.com/v1/openapi'
get/v1/meta

Describe this key: the routes it may call, its scopes and its limits

any valid keycost read
curl
curl 'https://v1.uselinqo.com/v1/meta' \
  -H 'Authorization: Bearer $LINQO_API_KEY'

Response 200

Devolve Meta.

What this key reaches. No scope, and off the monthly quota.

FieldTypeDescription
stagestringnullable
key_idstring
modestring`test` for a sandbox key: its own data, no notifications, no monthly quota.
livetest
scopesarray<string>
notificationsstring
offon
limitsobject
routesarray<object>

Cabeçalhos: X-Request-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset

Services

get/v1/services

List the services the company offers

scopeservices:readcost read
curl
curl 'https://v1.uselinqo.com/v1/services' \
  -H 'Authorization: Bearer $LINQO_API_KEY'

Query parameters

ParameterTypeDescription
limitintegerDefault: 50.
cursorstring
fieldsstring
modalitystring
in_personvirtualat_home

Response 200

Devolve ServicePage.
FieldTypeDescription
data*array<Service>
next_cursor*stringnullableOpaque. Page until it is null, never until a page comes back short: DynamoDB cuts a page at 1 MB read, so a short page can still carry a cursor.

Cabeçalhos: X-Request-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset

get/v1/services/{serviceId}

Read one service

scopeservices:readcost read
curl
curl 'https://v1.uselinqo.com/v1/services/<serviceId>' \
  -H 'Authorization: Bearer $LINQO_API_KEY'

Path parameters

ParameterTypeDescription
serviceId*string

Query parameters

ParameterTypeDescription
fieldsstring

Response 200

Devolve Service.

A service the company offers.

FieldTypeDescription
id*stringService id.
name*stringService name, as the clinic set it.
duration*integerHow long a visit of this service takes, in minutes. The end time of a booking is derived from it, which is why a request cannot send one.
price*numberPrice in the company currency. The price of a booking is the sum of its services, never a value the caller sends.
description*stringnullableFree text, or null.
active*booleanAn inactive service stays readable and is not offered for new bookings.
created_at*string (date-time)When the row was created, UTC.
updated_at*string (date-time)When it last changed, UTC.

Cabeçalhos: X-Request-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset

get/v1/services/{serviceId}/professionals

List the professionals who attend a service

scopeprofessionals:readcost read
curl
curl 'https://v1.uselinqo.com/v1/services/<serviceId>/professionals' \
  -H 'Authorization: Bearer $LINQO_API_KEY'

Path parameters

ParameterTypeDescription
serviceId*string

Query parameters

ParameterTypeDescription
fieldsstring

Response 200

Devolve ProfessionalPage.
FieldTypeDescription
data*array<Professional>
next_cursor*stringnullableOpaque. Page until it is null, never until a page comes back short: DynamoDB cuts a page at 1 MB read, so a short page can still carry a cursor.

Cabeçalhos: X-Request-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset

Professionals

get/v1/professionals

List the professionals on the roster

scopeprofessionals:readcost read
curl
curl 'https://v1.uselinqo.com/v1/professionals' \
  -H 'Authorization: Bearer $LINQO_API_KEY'

Query parameters

ParameterTypeDescription
limitintegerDefault: 50.
cursorstring
fieldsstring
sortstringDefault: "created_at".
created_atnext_available
service_idstring
modalitystring
in_personvirtualat_home

Response 200

Devolve ProfessionalPage.
FieldTypeDescription
data*array<Professional>
next_cursor*stringnullableOpaque. Page until it is null, never until a page comes back short: DynamoDB cuts a page at 1 MB read, so a short page can still carry a cursor.

Cabeçalhos: X-Request-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset

get/v1/professionals/{professionalId}

Read one professional

scopeprofessionals:readcost read
curl
curl 'https://v1.uselinqo.com/v1/professionals/<professionalId>' \
  -H 'Authorization: Bearer $LINQO_API_KEY'

Path parameters

ParameterTypeDescription
professionalId*string

Query parameters

ParameterTypeDescription
fieldsstring

Response 200

Devolve Professional.

A professional. The CPF and the company id are not published.

FieldTypeDescription
id*stringProfessional id.
name*stringProfessional name.
email*string (email)nullableContact e-mail, or null.
phone*stringnullableContact phone in E.164 (e.g. +5511987654321), or null.
specialty*stringnullableClinical specialty, or null.
council_registration*stringnullableProfessional council registration, or null.
avatar_url*stringnullablePublic picture URL, or null.
bio*stringnullableFree text, or null.
active*booleanAn inactive professional stays readable and takes no new bookings.
timezone*stringnullableIANA timezone of this professional's calendar, or null when it follows the company's.
created_at*string (date-time)When the row was created, UTC.
updated_at*string (date-time)When it last changed, UTC.
next_availableobjectnullableOnly with `sort=next_available`: the earliest free slot (minimum notice and booking window applied, 60 days at most), or null when there is none.

Cabeçalhos: X-Request-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset

get/v1/professionals/{professionalId}/availability

List the start times a visit of a given length could take on a date or in a range of at most 92 days

scopeappointments:readcost read
curl
curl 'https://v1.uselinqo.com/v1/professionals/<professionalId>/availability?date=2026-03-14&service_id=<service_id>' \
  -H 'Authorization: Bearer $LINQO_API_KEY'

Path parameters

ParameterTypeDescription
professionalId*string

Query parameters

ParameterTypeDescription
datestring
fromstring
tostring
service_id*string
tzstring
modalitystring
in_personvirtualat_home

Response 200

Devolve Availability.

With `date`: one day, flat. With `from` and `to`: the same per day, under `days`.

Either AvailabilityDay

FieldTypeDescription
date*string (date)
timezone*stringIANA timezone `slots` are read in: the professional’s, or the company’s.
duration_minutes*integer
slots*array<string>Free starts, `HH:mm`, wall clock in `timezone`.
starts_at*array<string>The same starts as ISO 8601 instants, in the offset of `tz` when sent, else of `timezone`.

Or AvailabilityRange

FieldTypeDescription
from*string (date)
to*string (date)
timezone*string
duration_minutes*integer
days*array<object>One per date from `from` to `to`, in order; a full day has empty `slots`.

Cabeçalhos: X-Request-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset

Clients

get/v1/clients

List clients, redacted to identity and contact

scopeclients:readcost read
curl
curl 'https://v1.uselinqo.com/v1/clients' \
  -H 'Authorization: Bearer $LINQO_API_KEY'

Query parameters

ParameterTypeDescription
limitintegerDefault: 50.
cursorstring
fieldsstring

Response 200

Devolve ClientPage.
FieldTypeDescription
data*array<Client>
next_cursor*stringnullableOpaque. Page until it is null, never until a page comes back short: DynamoDB cuts a page at 1 MB read, so a short page can still carry a cursor.

Cabeçalhos: X-Request-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset

get/v1/clients/{clientId}

Read one client, redacted to identity and contact

scopeclients:readcost read
curl
curl 'https://v1.uselinqo.com/v1/clients/<clientId>' \
  -H 'Authorization: Bearer $LINQO_API_KEY'

Path parameters

ParameterTypeDescription
clientId*string

Query parameters

ParameterTypeDescription
fieldsstring

Response 200

Devolve Client.

Identity and contact only. The CPF, birth date, address and the clinic’s notes stay in the panel.

FieldTypeDescription
id*stringClient id.
name*stringClient name. A client the API created from an inline booking is named "Reserva via API" until the clinic confirms it.
email*string (email)nullableClient e-mail, or null.
phone*stringnullableClient phone in E.164 (e.g. +5511987654321), or null. It is what an inline booking matches an existing client on.

Cabeçalhos: X-Request-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset

Appointments

get/v1/appointments

List appointments in a date range of at most 92 days

scopeappointments:readcost read
curl
curl 'https://v1.uselinqo.com/v1/appointments?from=2026-03-14&to=2026-03-21' \
  -H 'Authorization: Bearer $LINQO_API_KEY'

Query parameters

ParameterTypeDescription
limitintegerDefault: 50.
cursorstring
fieldsstring
from*string
to*string
professional_idstring
service_idstring
statusstring
scheduledconfirmedcompletedcancelledno_show

Response 200

Devolve AppointmentPage.
FieldTypeDescription
data*array<Appointment>
next_cursor*stringnullableOpaque. Page until it is null, never until a page comes back short: DynamoDB cuts a page at 1 MB read, so a short page can still carry a cursor.

Cabeçalhos: X-Request-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset

post/v1/appointments

Book an appointment; requires an Idempotency-Key header

scopeappointments:writecost write
curl
curl -X POST 'https://v1.uselinqo.com/v1/appointments' \
  -H 'Authorization: Bearer $LINQO_API_KEY' \
  -H 'Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000' \
  -H 'Content-Type: application/json' \
  -d '{
  "professional_id": "<professional_id>",
  "service_ids": [
    "<service_ids>"
  ]
}'

Headers

HeaderTypeDescription
Idempotency-Key*stringA caller whose request timed out cannot tell "my retry worked" from "someone else took the slot". The response is kept for 24 h; the same key with a different body is 400.

Body (required)

FieldTypeDescription
professional_id*string
service_ids*array<string>
datestring
start_timestring
start_atstring (date-time)
client_idstring
clientobject
modalitystring
in_personvirtualat_home
virtual_roomstring
linqoexternal
notesstringnullable
address_zipcodestringnullable
address_streetstringnullable
address_numberstringnullable
address_complementstringnullable
address_neighborhoodstringnullable
address_citystringnullable
address_statestringnullable
notifyboolean

Response 201

Devolve Appointment.

A booked visit.

FieldTypeDescription
id*stringAppointment id.
professional_id*stringWho attends the visit.
client_id*stringWho the visit is for.
client_name*stringThe client name, denormalised so a listing needs no second read.
date*string (date)Day of the visit, `YYYY-MM-DD`.
start_time*stringStart, `HH:mm`.
end_time*stringEnd, `HH:mm`. Derived from the duration of the services, which is why a request cannot send it.
timezone*stringIANA timezone `date`, `start_time` and `end_time` are read in: the professional’s, or the company’s.
start_at*string (date-time)nullableThe start as an ISO 8601 instant with the timezone offset, e.g. `2026-09-16T10:00:00-03:00`.
end_at*string (date-time)nullableThe end as an ISO 8601 instant with the timezone offset.
service_id*stringnullableThe first service booked, or null. The full list has a route of its own.
service*stringThe services booked, as text, for display.
price*numbernullableThe sum of the services booked. Never a value the caller sends.
payment_method*stringnullableHow the visit was paid, or null.
cashpixcreditdebittransferboleto
status*stringWhere the visit stands.
scheduledconfirmedcompletedcancelledno_show
modality*stringWhere the visit happens.
in_personvirtualat_home
virtual_room*stringnullableFor a virtual visit: `linqo` when it happens in the linqo video room, whose link the backend writes into `virtual_link`; `external` for a meeting URL set in the panel. Null on any other modality.
linqoexternal
virtual_link*stringnullableMeeting URL for a virtual visit, or null. With `virtual_room: linqo` it is the client’s link to the video room. Read-only here: it is rendered into the mail sent from the company identity, so a third party cannot set it.
address_zipcode*stringnullableVisit address, or null.
address_street*stringnullableVisit address, or null.
address_number*stringnullableVisit address, or null.
address_complement*stringnullableVisit address, or null.
address_neighborhood*stringnullableVisit address, or null.
address_city*stringnullableVisit address, or null.
address_state*stringnullableVisit address, or null.
notes*stringnullableFree text, or null.
confirmed_at*string (date-time)nullableWhen it was confirmed, UTC, or null.
cancelled_at*string (date-time)nullableWhen it was cancelled, UTC, or null.
cancellation_reason*stringnullableWhy it was cancelled, or null.
created_at*string (date-time)When the row was created, UTC.
updated_at*string (date-time)When it last changed, UTC.

Cabeçalhos: X-Request-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset

get/v1/appointments/{appointmentId}

Read one appointment

scopeappointments:readcost read
curl
curl 'https://v1.uselinqo.com/v1/appointments/<appointmentId>' \
  -H 'Authorization: Bearer $LINQO_API_KEY'

Path parameters

ParameterTypeDescription
appointmentId*string

Query parameters

ParameterTypeDescription
fieldsstring

Response 200

Devolve Appointment.

A booked visit.

FieldTypeDescription
id*stringAppointment id.
professional_id*stringWho attends the visit.
client_id*stringWho the visit is for.
client_name*stringThe client name, denormalised so a listing needs no second read.
date*string (date)Day of the visit, `YYYY-MM-DD`.
start_time*stringStart, `HH:mm`.
end_time*stringEnd, `HH:mm`. Derived from the duration of the services, which is why a request cannot send it.
timezone*stringIANA timezone `date`, `start_time` and `end_time` are read in: the professional’s, or the company’s.
start_at*string (date-time)nullableThe start as an ISO 8601 instant with the timezone offset, e.g. `2026-09-16T10:00:00-03:00`.
end_at*string (date-time)nullableThe end as an ISO 8601 instant with the timezone offset.
service_id*stringnullableThe first service booked, or null. The full list has a route of its own.
service*stringThe services booked, as text, for display.
price*numbernullableThe sum of the services booked. Never a value the caller sends.
payment_method*stringnullableHow the visit was paid, or null.
cashpixcreditdebittransferboleto
status*stringWhere the visit stands.
scheduledconfirmedcompletedcancelledno_show
modality*stringWhere the visit happens.
in_personvirtualat_home
virtual_room*stringnullableFor a virtual visit: `linqo` when it happens in the linqo video room, whose link the backend writes into `virtual_link`; `external` for a meeting URL set in the panel. Null on any other modality.
linqoexternal
virtual_link*stringnullableMeeting URL for a virtual visit, or null. With `virtual_room: linqo` it is the client’s link to the video room. Read-only here: it is rendered into the mail sent from the company identity, so a third party cannot set it.
address_zipcode*stringnullableVisit address, or null.
address_street*stringnullableVisit address, or null.
address_number*stringnullableVisit address, or null.
address_complement*stringnullableVisit address, or null.
address_neighborhood*stringnullableVisit address, or null.
address_city*stringnullableVisit address, or null.
address_state*stringnullableVisit address, or null.
notes*stringnullableFree text, or null.
confirmed_at*string (date-time)nullableWhen it was confirmed, UTC, or null.
cancelled_at*string (date-time)nullableWhen it was cancelled, UTC, or null.
cancellation_reason*stringnullableWhy it was cancelled, or null.
created_at*string (date-time)When the row was created, UTC.
updated_at*string (date-time)When it last changed, UTC.

Cabeçalhos: X-Request-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset

patch/v1/appointments/{appointmentId}

Change an appointment status, including cancelling it

scopeappointments:writecost write
curl
curl -X PATCH 'https://v1.uselinqo.com/v1/appointments/<appointmentId>' \
  -H 'Authorization: Bearer $LINQO_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
  "status": "scheduled"
}'

Path parameters

ParameterTypeDescription
appointmentId*string

Body (required)

FieldTypeDescription
status*string
scheduledconfirmedcompletedcancelledno_show
cancellation_reasonstringnullable
notifyboolean

Response 200

Devolve Appointment.

A booked visit.

FieldTypeDescription
id*stringAppointment id.
professional_id*stringWho attends the visit.
client_id*stringWho the visit is for.
client_name*stringThe client name, denormalised so a listing needs no second read.
date*string (date)Day of the visit, `YYYY-MM-DD`.
start_time*stringStart, `HH:mm`.
end_time*stringEnd, `HH:mm`. Derived from the duration of the services, which is why a request cannot send it.
timezone*stringIANA timezone `date`, `start_time` and `end_time` are read in: the professional’s, or the company’s.
start_at*string (date-time)nullableThe start as an ISO 8601 instant with the timezone offset, e.g. `2026-09-16T10:00:00-03:00`.
end_at*string (date-time)nullableThe end as an ISO 8601 instant with the timezone offset.
service_id*stringnullableThe first service booked, or null. The full list has a route of its own.
service*stringThe services booked, as text, for display.
price*numbernullableThe sum of the services booked. Never a value the caller sends.
payment_method*stringnullableHow the visit was paid, or null.
cashpixcreditdebittransferboleto
status*stringWhere the visit stands.
scheduledconfirmedcompletedcancelledno_show
modality*stringWhere the visit happens.
in_personvirtualat_home
virtual_room*stringnullableFor a virtual visit: `linqo` when it happens in the linqo video room, whose link the backend writes into `virtual_link`; `external` for a meeting URL set in the panel. Null on any other modality.
linqoexternal
virtual_link*stringnullableMeeting URL for a virtual visit, or null. With `virtual_room: linqo` it is the client’s link to the video room. Read-only here: it is rendered into the mail sent from the company identity, so a third party cannot set it.
address_zipcode*stringnullableVisit address, or null.
address_street*stringnullableVisit address, or null.
address_number*stringnullableVisit address, or null.
address_complement*stringnullableVisit address, or null.
address_neighborhood*stringnullableVisit address, or null.
address_city*stringnullableVisit address, or null.
address_state*stringnullableVisit address, or null.
notes*stringnullableFree text, or null.
confirmed_at*string (date-time)nullableWhen it was confirmed, UTC, or null.
cancelled_at*string (date-time)nullableWhen it was cancelled, UTC, or null.
cancellation_reason*stringnullableWhy it was cancelled, or null.
created_at*string (date-time)When the row was created, UTC.
updated_at*string (date-time)When it last changed, UTC.

Cabeçalhos: X-Request-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset

Agents (MCP)

post/v1/mcp

The MCP endpoint: JSON-RPC over HTTP, one frame per request

curl
curl -X POST 'https://v1.uselinqo.com/v1/mcp' \
  -H 'Authorization: Bearer $LINQO_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
  "jsonrpc": "2.0",
  "method": "<method>"
}'

Headers

HeaderTypeDescription
MCP-Protocol-VersionstringRequired of a `2026-07-28` client, absent on a legacy one. When present it must match the version in `params._meta`.
2026-07-282025-11-252025-06-182025-03-26
Mcp-MethodstringThe method named in the body. A disagreement is refused, in either era.

Body (required)

FieldTypeDescription
jsonrpc*string
2.0
idstringnullable
method*string`server/discover`, `tools/list` or `tools/call`; `initialize` and `ping` on a legacy client.
paramsobject

Response 200

Devolve McpResponse.

A JSON-RPC 2.0 response: `result` on success, `error` otherwise, never both.

FieldTypeDescription
jsonrpc*string
2.0
idstringnullable
resultobject
errorobject

Models

The resources the routes return and accept. The descriptions come straight from the contract — the same text any integrator reads in /v1/openapi.

Service

A service the company offers.

FieldTypeDescription
id*stringService id.
name*stringService name, as the clinic set it.
duration*integerHow long a visit of this service takes, in minutes. The end time of a booking is derived from it, which is why a request cannot send one.
price*numberPrice in the company currency. The price of a booking is the sum of its services, never a value the caller sends.
description*stringnullableFree text, or null.
active*booleanAn inactive service stays readable and is not offered for new bookings.
created_at*string (date-time)When the row was created, UTC.
updated_at*string (date-time)When it last changed, UTC.

Professional

A professional. The CPF and the company id are not published.

FieldTypeDescription
id*stringProfessional id.
name*stringProfessional name.
email*string (email)nullableContact e-mail, or null.
phone*stringnullableContact phone in E.164 (e.g. +5511987654321), or null.
specialty*stringnullableClinical specialty, or null.
council_registration*stringnullableProfessional council registration, or null.
avatar_url*stringnullablePublic picture URL, or null.
bio*stringnullableFree text, or null.
active*booleanAn inactive professional stays readable and takes no new bookings.
timezone*stringnullableIANA timezone of this professional's calendar, or null when it follows the company's.
created_at*string (date-time)When the row was created, UTC.
updated_at*string (date-time)When it last changed, UTC.
next_availableobjectnullableOnly with `sort=next_available`: the earliest free slot (minimum notice and booking window applied, 60 days at most), or null when there is none.

Client

Identity and contact only. The CPF, birth date, address and the clinic’s notes stay in the panel.

FieldTypeDescription
id*stringClient id.
name*stringClient name. A client the API created from an inline booking is named "Reserva via API" until the clinic confirms it.
email*string (email)nullableClient e-mail, or null.
phone*stringnullableClient phone in E.164 (e.g. +5511987654321), or null. It is what an inline booking matches an existing client on.

Appointment

A booked visit.

FieldTypeDescription
id*stringAppointment id.
professional_id*stringWho attends the visit.
client_id*stringWho the visit is for.
client_name*stringThe client name, denormalised so a listing needs no second read.
date*string (date)Day of the visit, `YYYY-MM-DD`.
start_time*stringStart, `HH:mm`.
end_time*stringEnd, `HH:mm`. Derived from the duration of the services, which is why a request cannot send it.
timezone*stringIANA timezone `date`, `start_time` and `end_time` are read in: the professional’s, or the company’s.
start_at*string (date-time)nullableThe start as an ISO 8601 instant with the timezone offset, e.g. `2026-09-16T10:00:00-03:00`.
end_at*string (date-time)nullableThe end as an ISO 8601 instant with the timezone offset.
service_id*stringnullableThe first service booked, or null. The full list has a route of its own.
service*stringThe services booked, as text, for display.
price*numbernullableThe sum of the services booked. Never a value the caller sends.
payment_method*stringnullableHow the visit was paid, or null.
cashpixcreditdebittransferboleto
status*stringWhere the visit stands.
scheduledconfirmedcompletedcancelledno_show
modality*stringWhere the visit happens.
in_personvirtualat_home
virtual_room*stringnullableFor a virtual visit: `linqo` when it happens in the linqo video room, whose link the backend writes into `virtual_link`; `external` for a meeting URL set in the panel. Null on any other modality.
linqoexternal
virtual_link*stringnullableMeeting URL for a virtual visit, or null. With `virtual_room: linqo` it is the client’s link to the video room. Read-only here: it is rendered into the mail sent from the company identity, so a third party cannot set it.
address_zipcode*stringnullableVisit address, or null.
address_street*stringnullableVisit address, or null.
address_number*stringnullableVisit address, or null.
address_complement*stringnullableVisit address, or null.
address_neighborhood*stringnullableVisit address, or null.
address_city*stringnullableVisit address, or null.
address_state*stringnullableVisit address, or null.
notes*stringnullableFree text, or null.
confirmed_at*string (date-time)nullableWhen it was confirmed, UTC, or null.
cancelled_at*string (date-time)nullableWhen it was cancelled, UTC, or null.
cancellation_reason*stringnullableWhy it was cancelled, or null.
created_at*string (date-time)When the row was created, UTC.
updated_at*string (date-time)When it last changed, UTC.

ServicePage

FieldTypeDescription
data*array<Service>
next_cursor*stringnullableOpaque. Page until it is null, never until a page comes back short: DynamoDB cuts a page at 1 MB read, so a short page can still carry a cursor.

ProfessionalPage

FieldTypeDescription
data*array<Professional>
next_cursor*stringnullableOpaque. Page until it is null, never until a page comes back short: DynamoDB cuts a page at 1 MB read, so a short page can still carry a cursor.

ClientPage

FieldTypeDescription
data*array<Client>
next_cursor*stringnullableOpaque. Page until it is null, never until a page comes back short: DynamoDB cuts a page at 1 MB read, so a short page can still carry a cursor.

AppointmentPage

FieldTypeDescription
data*array<Appointment>
next_cursor*stringnullableOpaque. Page until it is null, never until a page comes back short: DynamoDB cuts a page at 1 MB read, so a short page can still carry a cursor.

Availability

With `date`: one day, flat. With `from` and `to`: the same per day, under `days`.

One of AvailabilityDay or AvailabilityRange.

AvailabilityDay

FieldTypeDescription
date*string (date)
timezone*stringIANA timezone `slots` are read in: the professional’s, or the company’s.
duration_minutes*integer
slots*array<string>Free starts, `HH:mm`, wall clock in `timezone`.
starts_at*array<string>The same starts as ISO 8601 instants, in the offset of `tz` when sent, else of `timezone`.

AvailabilityRange

FieldTypeDescription
from*string (date)
to*string (date)
timezone*string
duration_minutes*integer
days*array<object>One per date from `from` to `to`, in order; a full day has empty `slots`.

Meta

What this key reaches. No scope, and off the monthly quota.

FieldTypeDescription
stagestringnullable
key_idstring
modestring`test` for a sandbox key: its own data, no notifications, no monthly quota.
livetest
scopesarray<string>
notificationsstring
offon
limitsobject
routesarray<object>

McpRequest

A JSON-RPC 2.0 request. A frame with no `id` is a notification.

FieldTypeDescription
jsonrpc*string
2.0
idstringnullable
method*string`server/discover`, `tools/list` or `tools/call`; `initialize` and `ping` on a legacy client.
paramsobject

McpResponse

A JSON-RPC 2.0 response: `result` on success, `error` otherwise, never both.

FieldTypeDescription
jsonrpc*string
2.0
idstringnullable
resultobject
errorobject

CreateAppointment

FieldTypeDescription
professional_id*string
service_ids*array<string>
datestring
start_timestring
start_atstring (date-time)
client_idstring
clientobject
modalitystring
in_personvirtualat_home
virtual_roomstring
linqoexternal
notesstringnullable
address_zipcodestringnullable
address_streetstringnullable
address_numberstringnullable
address_complementstringnullable
address_neighborhoodstringnullable
address_citystringnullable
address_statestringnullable
notifyboolean

PatchAppointment

FieldTypeDescription
status*string
scheduledconfirmedcompletedcancelledno_show
cancellation_reasonstringnullable
notifyboolean

Something missing?

Tell us what you are building at support@uselinqo.com. The API grows from real usage, and knowing what is missing is what decides what ships next.