Marcações
A API de Marcações permite-lhe marcar reuniões para os seus contactos nos seus tipos de evento, e depois obtê-las, listá-las, atualizá-las, cancelá-las ou eliminá-las. Também responde à pergunta que surge primeiro na maioria dos fluxos de marcação — que horários estão realmente livres — e cobre a parte do calendário: listar os Google Calendars que tem ligados e importar eventos que já existem neles. Quando uma ligação ao Google Calendar está ativa, o evento de calendário correspondente é criado e mantido sincronizado automaticamente em segundo plano. Os restaurantes que utilizam Zenchef, Formitable, OpenTable ou TheFork para o seu próprio sistema de reservas também podem ser verificados e ligados aqui, para que o Agente de IA reserve mesas reais em vez de marcações internas — e as empresas de marcações que gerem a sua agenda no Trafft podem ligar-se da mesma forma.
Todos os caminhos nesta página são relativos ao URL base https://api.dmchamp.com/v1. Cada pedido necessita da sua chave de API — consulte Autenticação para obter a lista completa de formas de a enviar. Os exemplos abaixo utilizam o cabeçalho X-API-Key, com um exemplo cURL que mostra também o formulário de consulta ?apiKey=.
Eventos vs. marcações: Um tipo de evento é uma definição de espaço reservável (o tipo de reunião, a sua duração, as suas salas). Uma marcação é uma instância reservada de um tipo de evento para um contacto específico. Reserva uma marcação referenciando o contacto e o tipo de evento.
O objeto de marcação
Cada endpoint que devolve uma marcação utiliza a mesma estrutura:
| Campo | Descrição |
|---|---|
id |
ID único da marcação. |
contact_id |
ID do contacto com quem a marcação foi feita. |
event_id |
ID do tipo de evento em que a marcação foi feita. |
status |
Confirmed ou Canceled. |
start_time |
Início da marcação, ISO 8601 em UTC. |
end_time |
Fim da marcação, ISO 8601 em UTC. |
created_at |
Quando a marcação foi criada. |
last_modified_at |
Quando a marcação foi alterada pela última vez. |
room_name |
Sala ou recurso onde a marcação foi feita, quando o tipo de evento utiliza salas. |
description |
Descrição de formato livre da marcação. |
summary |
Resumo ou título curto. |
cancelation_reason |
Motivo fornecido quando a marcação foi cancelada, se aplicável. |
google_calendar_event_id |
ID do evento do Google Calendar associado. Definido assim que a sincronização do calendário termina; null quando nenhum calendário está ligado ou enquanto a sincronização ainda está em curso. |
calendar_synced |
true assim que a marcação estiver ligada a um evento de calendário. |
imported |
true quando a marcação foi importada de um calendário externo em vez de reservada diretamente. |
is_recurring |
true quando a marcação faz parte de uma série recorrente. |
recurrence_frequency |
Frequência de repetição da marcação, quando recorrente. |
recurring_event_id |
ID da série recorrente a que esta marcação pertence. |
recurring_interval |
Intervalo entre repetições, quando recorrente. |
recurring_sequence |
Posição desta marcação dentro da sua série recorrente. |
end_after_x_occurrences |
Número de ocorrências após as quais a série recorrente termina. |
booking_provider |
Sistema de origem de onde veio a reserva, quando feita através de um fornecedor de reservas ligado. |
Sobre a sincronização de calendário: Logo após reservar ou alterar uma marcação, o
google_calendar_event_idpode ainda sernulle ocalendar_syncedpode serfalseporque a sincronização é executada em segundo plano um momento depois. Obtenha a marcação novamente pouco depois para ver os campos de calendário preenchidos.
Encontrar horários disponíveis
GET /appointments/available-slots
Devolve os horários que estão genuinamente livres num tipo de evento entre dois momentos. Esta é normalmente a primeira chamada num fluxo de marcação: mostre estes horários, deixe a pessoa escolher um e, em seguida, envie a hora escolhida para Marcar um compromisso.
A resposta já tem em conta o horário de funcionamento e a duração dos intervalos do próprio tipo de evento, as suas salas, as marcações que já efetuou nele e tudo o que está bloqueado nos Google Calendars ligados — por isso, um horário que aparece aqui é um horário que pode marcar.
| Parâmetro de consulta | Obrigatório | Descrição |
|---|---|---|
event_id |
Sim | O tipo de evento a verificar. Deve pertencer à sua conta. |
start_time |
Sim | Início da janela para a qual pretende horários, data-hora ISO 8601. |
end_time |
Sim | Fim da janela, data-hora ISO 8601. O dia final completo está incluído. |
Os resultados são devolvidos agrupados por dia — e, quando o tipo de evento utiliza salas, um grupo por sala por dia:
| Campo | Descrição |
|---|---|
date |
O dia que o grupo cobre, escrito DD/MM/YYYY. |
day |
Nome do dia da semana em minúsculas, por exemplo monday. |
room_name |
A sala ou recurso a que este grupo pertence, quando o tipo de evento utiliza salas. |
available_slots |
Os blocos marcáveis nesse dia, do mais cedo para o mais tarde. |
Cada entrada em available_slots tem:
| Campo | Descrição |
|---|---|
start_time |
Início do bloco como HH:mm. |
end_time |
Fim do bloco como HH:mm. |
available |
true — apenas é devolvido tempo livre. |
spots_left |
Quantas marcações ainda cabem neste bloco. Apenas presente em tipos de evento que aceitam mais do que uma marcação por intervalo. |
Os horários são locais ao tipo de evento, não UTC.
date,start_timeeend_timesão valores de relógio de parede no fuso horário do próprio tipo de evento (a sua substituição, ou o fuso horário da sua conta quando não tem nenhum). Marcar um compromisso espera um instante UTC ISO 8601, por isso converta o horário que escolheu antes de o enviar.
cURL
curl "https://api.dmchamp.com/v1/appointments/available-slots?event_id=event_xyz789&start_time=2026-06-15T00:00:00.000Z&end_time=2026-06-19T00:00:00.000Z" \
-H "X-API-Key: YOUR_API_KEY"
JavaScript
const params = new URLSearchParams({
event_id: "event_xyz789",
start_time: "2026-06-15T00:00:00.000Z",
end_time: "2026-06-19T00:00:00.000Z",
});
const res = await fetch(
`https://api.dmchamp.com/v1/appointments/available-slots?${params}`,
{ headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
console.log(data.data);
Python
import requests
res = requests.get(
"https://api.dmchamp.com/v1/appointments/available-slots",
headers={"X-API-Key": "YOUR_API_KEY"},
params={
"event_id": "event_xyz789",
"start_time": "2026-06-15T00:00:00.000Z",
"end_time": "2026-06-19T00:00:00.000Z",
},
)
print(res.json()["data"])
Resposta (200 OK):
{
"success": true,
"data": [
{
"date": "15/06/2026",
"day": "monday",
"room_name": "Room A",
"available_slots": [
{ "start_time": "10:00", "end_time": "10:30", "available": true },
{ "start_time": "10:30", "end_time": "11:00", "available": true }
]
},
{
"date": "16/06/2026",
"day": "tuesday",
"room_name": "Room A",
"available_slots": [
{ "start_time": "09:00", "end_time": "09:30", "available": true, "spots_left": 2 }
]
}
]
}
Um dia sem nada livre simplesmente não aparece. A falta de event_id, start_time ou end_time devolve 400; um tipo de evento que não está na sua conta devolve 404.
Reservar uma marcação
POST /appointments
Reserva uma nova marcação para um contacto num dos seus tipos de evento. A hora de fim é calculada automaticamente a partir da duração do espaço do tipo de evento.
A reserva é verificada quanto a conflitos: se o espaço solicitado se sobrepuser a uma marcação confirmada existente no mesmo tipo de evento, o pedido falha com um 409 e nada é criado.
| Campo | Obrigatório | Descrição |
|---|---|---|
contact_id |
Sim | ID do contacto para o qual reservar. Deve pertencer à sua conta. |
event_id |
Sim | ID do tipo de evento em que reservar. Deve pertencer à sua conta. |
start_time |
Sim | Início pretendido como uma data-hora ISO 8601. |
room_name |
Não | Nome da sala ou recurso, quando o tipo de evento utiliza salas. |
cURL (utilizando o formulário de consulta ?apiKey=)
curl -X POST "https://api.dmchamp.com/v1/appointments?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"contact_id": "contact_abc123",
"event_id": "event_xyz789",
"start_time": "2026-06-15T10:00:00.000Z",
"room_name": "Room A"
}'
JavaScript
const res = await fetch("https://api.dmchamp.com/v1/appointments", {
method: "POST",
headers: {
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
contact_id: "contact_abc123",
event_id: "event_xyz789",
start_time: "2026-06-15T10:00:00.000Z",
room_name: "Room A",
}),
});
const data = await res.json();
console.log(data.appointment_id);
Python
import requests
res = requests.post(
"https://api.dmchamp.com/v1/appointments",
headers={
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"contact_id": "contact_abc123",
"event_id": "event_xyz789",
"start_time": "2026-06-15T10:00:00.000Z",
"room_name": "Room A",
},
)
print(res.json()["appointment_id"])
Resposta (201 Created):
{
"success": true,
"appointment_id": "aBcD1234eFgH5678",
"appointment": {
"id": "aBcD1234eFgH5678",
"contact_id": "contact_abc123",
"event_id": "event_xyz789",
"status": "Confirmed",
"start_time": "2026-06-15T10:00:00.000Z",
"end_time": "2026-06-15T10:30:00.000Z",
"created_at": "2026-06-10T09:00:00.000Z",
"last_modified_at": "2026-06-10T09:00:00.000Z",
"room_name": "Room A",
"google_calendar_event_id": null,
"calendar_synced": false
}
}
Obter um agendamento
GET /appointments/{appointmentId}
Devolve um único agendamento pelo seu ID, incluindo o seu estado de sincronização de calendário.
cURL
curl "https://api.dmchamp.com/v1/appointments/aBcD1234eFgH5678" \
-H "X-API-Key: YOUR_API_KEY"
JavaScript
const res = await fetch(
"https://api.dmchamp.com/v1/appointments/aBcD1234eFgH5678",
{ headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
console.log(data.appointment);
Python
import requests
res = requests.get(
"https://api.dmchamp.com/v1/appointments/aBcD1234eFgH5678",
headers={"X-API-Key": "YOUR_API_KEY"},
)
print(res.json()["appointment"])
Resposta (200 OK):
{
"success": true,
"appointment": {
"id": "aBcD1234eFgH5678",
"contact_id": "contact_abc123",
"event_id": "event_xyz789",
"status": "Confirmed",
"start_time": "2026-06-15T10:00:00.000Z",
"end_time": "2026-06-15T10:30:00.000Z",
"room_name": "Room A",
"google_calendar_event_id": "abc123googleevent",
"calendar_synced": true
}
}
Listar agendamentos
GET /appointments
Lista os agendamentos da sua conta, do mais recente para o mais antigo, com paginação baseada em cursor.
| Parâmetro de consulta | Obrigatório | Descrição |
|---|---|---|
contact_id |
Não | Devolve apenas agendamentos para este contacto. As listagens filtradas por contacto incluem apenas agendamentos confirmados |
date |
Não | Devolve apenas agendamentos neste dia de calendário (YYYY-MM-DD). Requer contact_id. |
status |
Não | Filtrar por Confirmed ou Canceled. Disponível apenas sem contact_id. |
limit |
Não | Tamanho da página, um número inteiro entre 1 e 100. Predefinição 50. |
cursor |
Não | O valor next_cursor de uma resposta anterior. |
Algumas regras a ter em conta:
- Sem filtros, obtém todos os agendamentos da conta, página a página.
- Por contacto — defina
contact_idpara ver os agendamentos confirmados de um contacto. Pode restringir a um único dia passando tambémdate. - Por estado — defina
status(semcontact_id) para listar apenas agendamentosConfirmedou apenasCanceledem toda a conta. - O filtro
datesemcontact_id, oustatus=Canceledjuntamente comcontact_id, devolve um400.
cURL
curl "https://api.dmchamp.com/v1/appointments?contact_id=contact_abc123&date=2026-06-15" \
-H "X-API-Key: YOUR_API_KEY"
JavaScript
const params = new URLSearchParams({
contact_id: "contact_abc123",
date: "2026-06-15",
});
const res = await fetch(
`https://api.dmchamp.com/v1/appointments?${params}`,
{ headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
console.log(data.appointments, data.next_cursor);
Python
import requests
res = requests.get(
"https://api.dmchamp.com/v1/appointments",
headers={"X-API-Key": "YOUR_API_KEY"},
params={"contact_id": "contact_abc123", "date": "2026-06-15"},
)
data = res.json()
print(data["appointments"], data["next_cursor"])
Resposta (200 OK):
{
"success": true,
"appointments": [
{
"id": "aBcD1234eFgH5678",
"contact_id": "contact_abc123",
"event_id": "event_xyz789",
"status": "Confirmed",
"start_time": "2026-06-15T10:00:00.000Z",
"end_time": "2026-06-15T10:30:00.000Z",
"calendar_synced": true
}
],
"next_cursor": null
}
Para navegar entre os resultados, passe o next_cursor de uma resposta como o cursor do pedido seguinte. Continue até que next_cursor seja null. Consulte Erros e Paginação para o padrão de paginação partilhado.
Atualizar um agendamento
PUT /appointments/{appointmentId}
Reagende um compromisso ou altere os seus detalhes. Envie apenas os campos que pretende alterar — é necessário pelo menos um. O início e o fim combinados devem manter-se por ordem cronológica (end_time deve ser posterior a start_time). As alterações são sincronizadas automaticamente com o evento do calendário associado.
| Campo | Descrição |
|---|---|
start_time |
Novo início, data-hora ISO 8601. |
end_time |
Novo fim, data-hora ISO 8601. Deve ser posterior à hora de início. |
room_name |
Novo nome da sala ou recurso. |
description |
Nova descrição, ou null para a limpar. |
summary |
Novo resumo, ou null para o limpar. |
cURL
curl -X PUT "https://api.dmchamp.com/v1/appointments/aBcD1234eFgH5678" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"start_time": "2026-06-16T10:00:00.000Z",
"end_time": "2026-06-16T10:30:00.000Z"
}'
JavaScript
const res = await fetch(
"https://api.dmchamp.com/v1/appointments/aBcD1234eFgH5678",
{
method: "PUT",
headers: {
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
start_time: "2026-06-16T10:00:00.000Z",
end_time: "2026-06-16T10:30:00.000Z",
}),
}
);
const data = await res.json();
console.log(data.appointment);
Python
import requests
res = requests.put(
"https://api.dmchamp.com/v1/appointments/aBcD1234eFgH5678",
headers={
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"start_time": "2026-06-16T10:00:00.000Z",
"end_time": "2026-06-16T10:30:00.000Z",
},
)
print(res.json()["appointment"])
Resposta (200 OK):
{
"success": true,
"appointment_id": "aBcD1234eFgH5678",
"appointment": {
"id": "aBcD1234eFgH5678",
"contact_id": "contact_abc123",
"event_id": "event_xyz789",
"status": "Confirmed",
"start_time": "2026-06-16T10:00:00.000Z",
"end_time": "2026-06-16T10:30:00.000Z",
"calendar_synced": true
}
}
Cancelar um agendamento
POST /appointments/{appointmentId}/cancel
Cancela um agendamento confirmado, registando opcionalmente um motivo. O agendamento permanece na sua conta com o estado Canceled e o evento de calendário associado é removido automaticamente em segundo plano. O cancelamento de um agendamento já cancelado devolve um 400.
| Campo | Obrigatório | Descrição |
|---|---|---|
cancellation_reason |
Não | Motivo do cancelamento, guardado no agendamento. |
cURL
curl -X POST "https://api.dmchamp.com/v1/appointments/aBcD1234eFgH5678/cancel" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"cancellation_reason": "Client asked to reschedule next month"
}'
JavaScript
const res = await fetch(
"https://api.dmchamp.com/v1/appointments/aBcD1234eFgH5678/cancel",
{
method: "POST",
headers: {
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
cancellation_reason: "Client asked to reschedule next month",
}),
}
);
const data = await res.json();
console.log(data.success);
Python
import requests
res = requests.post(
"https://api.dmchamp.com/v1/appointments/aBcD1234eFgH5678/cancel",
headers={
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
json={"cancellation_reason": "Client asked to reschedule next month"},
)
print(res.json()["success"])
Resposta (200 OK):
{
"success": true,
"appointment_id": "aBcD1234eFgH5678"
}
Eliminar um agendamento
DELETE /appointments/{appointmentId}
Elimina permanentemente um agendamento e as suas referências. Se apenas pretende cancelar a marcação mantendo o registo, utilize cancelar em vez disso.
cURL
curl -X DELETE "https://api.dmchamp.com/v1/appointments/aBcD1234eFgH5678" \
-H "X-API-Key: YOUR_API_KEY"
JavaScript
const res = await fetch(
"https://api.dmchamp.com/v1/appointments/aBcD1234eFgH5678",
{ method: "DELETE", headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
console.log(data.success);
Python
import requests
res = requests.delete(
"https://api.dmchamp.com/v1/appointments/aBcD1234eFgH5678",
headers={"X-API-Key": "YOUR_API_KEY"},
)
print(res.json()["success"])
Resposta (200 OK):
{
"success": true
}
Listar os seus Google Calendars ligados
GET /appointments/google-calendars
Devolve os Google Calendars disponíveis nesta conta, diretamente do Google — útil para mostrar ao titular da conta um seletor de qual calendário importar abaixo, ou apenas para confirmar que a ligação está ativa.
Isto só funciona depois de a conta ter ligado o Google Calendar (Definições → Integrações) com, pelo menos, acesso de leitura. Se não o tiver feito, ou se o acesso concedido já não incluir o âmbito de leitura do calendário, receberá um 400 a indicar-lhe que deve ligá-lo (ou ligá-lo novamente).
cURL
curl "https://api.dmchamp.com/v1/appointments/google-calendars" \
-H "X-API-Key: YOUR_API_KEY"
JavaScript
const res = await fetch("https://api.dmchamp.com/v1/appointments/google-calendars", {
headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
console.log(data.data);
Python
import requests
res = requests.get(
"https://api.dmchamp.com/v1/appointments/google-calendars",
headers={"X-API-Key": "YOUR_API_KEY"},
)
print(res.json()["data"])
Resposta (200 OK):
{
"success": true,
"data": [
{
"id": "primary",
"summary": "jane@example.com",
"timeZone": "America/New_York",
"accessRole": "owner",
"primary": true
},
{
"id": "abcdefg1234567890@group.calendar.google.com",
"summary": "Bookings",
"timeZone": "America/New_York",
"accessRole": "writer"
}
]
}
Cada entrada tem o formato CalendarListEntry da própria Google, pelo que os nomes dos campos seguem a camelCase da Google e não a snake_case habitual desta API — são dados da Google transmitidos tal como estão, não os nossos. Uma ligação em falta ou revogada devolve um 400 com um erro a explicar que o Google Calendar precisa de ser ligado (ou ligado novamente).
Importar eventos de um Google Calendar
POST /appointments/import-calendar-events
Extrai os eventos que já se encontram nos Google Calendar(s) ligados a uma campanha ou a um Agente de IA e transforma-os em marcações — útil na primeira vez que liga um calendário que já tem reservas. Isto pode demorar algum tempo (cada evento passa por um processo de extração para determinar a quem se destina), pelo que nunca é executado em linha: o pedido coloca em fila de espera um trabalho de fundo e devolve-lhe um job_id para consulta.
| Campo | Obrigatório | Descrição |
|---|---|---|
campaign_id |
Um destes dois | A campanha cujos calendários ligados devem ser importados. |
agent_id |
Um destes dois | O Agente de IA cujos calendários ligados devem ser importados. |
identifier |
Sim | "EMAIL" ou "PHONE_NUMBER" — que informação de contacto extrair de cada evento do calendário para corresponder ou criar o contacto a que pertence. |
Envie exatamente um de campaign_id / agent_id, nunca ambos e nunca nenhum — qualquer uma destas combinações devolve um 400. O que enviar tem de pertencer à sua conta, caso contrário receberá um 404.
cURL
curl -X POST "https://api.dmchamp.com/v1/appointments/import-calendar-events?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "agent_abc123",
"identifier": "EMAIL"
}'
JavaScript
const res = await fetch("https://api.dmchamp.com/v1/appointments/import-calendar-events", {
method: "POST",
headers: {
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
agent_id: "agent_abc123",
identifier: "EMAIL",
}),
});
const data = await res.json();
console.log(data.job_id);
Python
import requests
res = requests.post(
"https://api.dmchamp.com/v1/appointments/import-calendar-events",
headers={
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
json={"agent_id": "agent_abc123", "identifier": "EMAIL"},
)
print(res.json()["job_id"])
Resposta (202 Accepted):
{
"success": true,
"job_id": "jK9mQ2xR7pL4wN1t",
"status": "queued",
"campaign_id": null,
"agent_id": "agent_abc123"
}
campaign_id e agent_id devolvem o que enviou; o outro é sempre null.
Consultar o trabalho de importação
GET /appointments/import-calendar-events/{jobId}
curl "https://api.dmchamp.com/v1/appointments/import-calendar-events/jK9mQ2xR7pL4wN1t" \
-H "X-API-Key: YOUR_API_KEY"
Resposta (200 OK):
{
"success": true,
"job_id": "jK9mQ2xR7pL4wN1t",
"status": "completed",
"message": "Imported 12 events as appointments.",
"error": null
}
status |
Significado |
|---|---|
queued |
Ainda não processado. Continue a consultar. |
processing |
A importação está em curso. Continue a consultar. |
completed |
Concluído — message contém um breve resumo legível. |
failed |
Algo correu mal — error contém o motivo. |
GET num jobId que não existe (ou que pertence a uma conta diferente) devolve 404.
Integrações de reservas externas (Zenchef / Formitable / OpenTable / TheFork / Trafft)
O Zenchef e o Formitable são sistemas de reservas de restaurantes através dos quais o seu Agente de IA pode reservar mesas reais; o Trafft é uma plataforma de agendamento para empresas de marcações, ligada uma vez por conta em vez de por restaurante. As duas plataformas de restauração possuem cada uma um widget de reserva público e não autenticado (https://api.dmchamp.com/v1/zenchef-widget/... e https://api.dmchamp.com/v1/formitable-widget/...) que é apresentado dentro do chat para o cliente — esses caminhos de widget são páginas HTML simples destinadas a serem abertas num navegador, não endpoints de API JSON, pelo que não estão documentados aqui. O que se segue são os endpoints de gestão de conta: verificar se um ID de restaurante pertence ao titular da conta e, em seguida, adicioná-lo, atualizá-lo ou removê-lo.
Zenchef
A ligação de um restaurante Zenchef é um processo de verificação de dois passos, para que o titular da conta prove que realmente gere o restaurante antes de este ser ligado ao bot: primeiro, verifique se o ID existe (sem revelar o nome), depois peça-lhe para escrever o nome do restaurante e verifique se corresponde.
Passo 1 — Verificar se um ID de restaurante existe
POST /appointments/zenchef-restaurants/check
| Campo | Obrigatório | Descrição |
|---|---|---|
restaurant_id |
Sim | O ID do restaurante Zenchef a verificar. |
curl -X POST "https://api.dmchamp.com/v1/appointments/zenchef-restaurants/check?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "restaurant_id": "12345" }'
Resposta (200 OK):
{
"success": true,
"data": { "exists": true, "requiresNameVerification": true }
}
exists: false significa que nenhum restaurante Zenchef tem esse ID — não há mais nada a fazer. Limitado a 10 verificações por cada 5 minutos por conta; exceder este limite devolve 429.
Passo 2 — Verificar o nome do restaurante
POST /appointments/zenchef-restaurants/verify-name
| Campo | Obrigatório | Descrição |
|---|---|---|
restaurant_id |
Sim | O ID do restaurante Zenchef do passo 1. |
user_input_name |
Sim | O nome que o titular da conta escreveu — comparado com o nome real do restaurante no Zenchef (insensível a maiúsculas/minúsculas e espaços). |
curl -X POST "https://api.dmchamp.com/v1/appointments/zenchef-restaurants/verify-name?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "restaurant_id": "12345", "user_input_name": "The Blue Door Bistro" }'
Resposta (200 OK):
{
"success": true,
"data": {
"verified": true,
"restaurantDetails": {
"id": "12345",
"name": "The Blue Door Bistro",
"address": "1 Rue de Rivoli, Paris",
"status": "active"
}
}
}
verified: false significa que o nome não correspondeu — restaurantDetails é omitido, peça ao titular da conta para tentar novamente. Limitado a 3 tentativas por cada 5 minutos (mais restrito do que a verificação de existência, uma vez que este é o passo de prova real). Um restaurant_id que já não é resolvido no Zenchef devolve 404.
Passo 3 — Guardar o restaurante
POST /appointments/zenchef-restaurants
| Campo | Obrigatório | Descrição |
|---|---|---|
restaurant_id |
Sim | 1–64 caracteres, letras/números/underscore/hífen. |
restaurant_name |
Sim | O nome do restaurante verificado do passo 2. |
curl -X POST "https://api.dmchamp.com/v1/appointments/zenchef-restaurants?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "restaurant_id": "12345", "restaurant_name": "The Blue Door Bistro" }'
Resposta (201 Created):
{ "success": true, "data": { "restaurantId": "12345" } }
Atualizar um restaurante Zenchef guardado
PUT /appointments/zenchef-restaurants/{restaurantId}
| Campo | Obrigatório | Descrição |
|---|---|---|
restaurant_name |
Não | Novo nome de apresentação. |
is_active |
Não | Defina false para impedir que o bot efetue reservas neste restaurante sem o remover. |
curl -X PUT "https://api.dmchamp.com/v1/appointments/zenchef-restaurants/12345" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "is_active": false }'
Resposta (200 OK): mesma estrutura que a resposta de guardar acima.
Remover um restaurante Zenchef
DELETE /appointments/zenchef-restaurants/{restaurantId}
curl -X DELETE "https://api.dmchamp.com/v1/appointments/zenchef-restaurants/12345" \
-H "X-API-Key: YOUR_API_KEY"
Resposta (200 OK): { "success": true, "data": { "restaurantId": "12345" } }
Um restaurantId que não esteja atualmente na conta devolve 404 ao atualizar ou eliminar.
Formitable
O Formitable não necessita da prova de nome em dois passos como o Zenchef — os seus IDs de restaurante já estão delimitados por empresa, pelo que uma chamada de verificação é suficiente. Também possui uma consulta de detalhes utilizada para colocar em cache o URL do website do restaurante durante a configuração.
Verificar um ID de restaurante
POST /appointments/formitable-restaurants/verify
| Campo | Obrigatório | Descrição |
|---|---|---|
restaurant_id |
Sim | O ID de restaurante Formitable. |
language |
Não | Etiqueta de idioma para o pedido de sondagem. O valor predefinido é "nl". |
curl -X POST "https://api.dmchamp.com/v1/appointments/formitable-restaurants/verify?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "restaurant_id": "the-blue-door", "language": "en" }'
Resposta (200 OK):
{
"success": true,
"data": {
"verified": true,
"restaurantDetails": {
"restaurantId": "the-blue-door",
"productCount": 4,
"sampleProductTitle": "Dinner for two",
"language": "en"
}
}
}
Um restaurant_id que o Formitable não reconheça devolve 404. Limitado a 10 tentativas por cada 5 minutos por conta.
Obter detalhes do restaurante
GET /appointments/formitable-restaurants/{restaurantId}/details?language=en
Obtém o perfil público do restaurante a partir do Formitable, incluindo o seu website — utilizado para colocar em cache o URL do website durante a configuração do restaurante. language é um parâmetro de consulta opcional, com o valor predefinido "en".
curl "https://api.dmchamp.com/v1/appointments/formitable-restaurants/the-blue-door/details?language=en" \
-H "X-API-Key: YOUR_API_KEY"
Resposta (200 OK):
{
"success": true,
"data": {
"uid": "the-blue-door",
"name": "The Blue Door Bistro",
"website": "https://thebluedoorbistro.com",
"email": "info@thebluedoorbistro.com",
"telephone": "+31201234567",
"streetAddress": "Prinsengracht 1",
"zipcode": "1015 AB",
"city": "Amsterdam",
"country": "Netherlands",
"countryCode": "NL",
"currency": "EUR"
}
}
Guardar o restaurante
POST /appointments/formitable-restaurants
| Campo | Obrigatório | Descrição |
|---|---|---|
restaurant_id |
Sim | 1–64 caracteres, letras/números/underscore/hífen. |
restaurant_name |
Sim | Nome a apresentar. |
language |
Sim | Etiqueta de idioma ISO, p. ex. "en" ou "en-GB". |
website_url |
Não | O website do restaurante, a partir da consulta de detalhes acima. Deve ser http(s)://. |
curl -X POST "https://api.dmchamp.com/v1/appointments/formitable-restaurants?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"restaurant_id": "the-blue-door",
"restaurant_name": "The Blue Door Bistro",
"language": "en",
"website_url": "https://thebluedoorbistro.com"
}'
Resposta (201 Created): { "success": true, "data": { "restaurantId": "the-blue-door" } }
Atualizar um restaurante Formitable guardado
PUT /appointments/formitable-restaurants/{restaurantId}
| Campo | Obrigatório | Descrição |
|---|---|---|
restaurant_name |
Não | Novo nome a apresentar. |
language |
Não | Nova etiqueta de idioma ISO. |
is_active |
Não | Defina false para impedir que o bot efetue reservas neste restaurante sem o remover. |
website_url |
Não | Novo URL do website. |
curl -X PUT "https://api.dmchamp.com/v1/appointments/formitable-restaurants/the-blue-door" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "is_active": false }'
Resposta (200 OK): mesma estrutura que a resposta de guardar acima.
Remover um restaurante Formitable
DELETE /appointments/formitable-restaurants/{restaurantId}
curl -X DELETE "https://api.dmchamp.com/v1/appointments/formitable-restaurants/the-blue-door" \
-H "X-API-Key: YOUR_API_KEY"
Resposta (200 OK): { "success": true, "data": { "restaurantId": "the-blue-door" } }
Um restaurantId que não esteja atualmente na conta devolve 404 ao atualizar ou eliminar.
OpenTable
Os restaurantes OpenTable são acedidos através das credenciais de parceiro OpenTable da plataforma, e o OpenTable apenas permite que essas credenciais vejam os restaurantes que ligaram a listagem da plataforma dentro do Marketplace de Integrações do OpenTable. Portanto, tal como no Formitable, uma chamada de verificação é suficiente: um ID de Restaurante alcançável (o “RID” numérico) prova tanto que o restaurante existe como que ligou a integração. Até que a listagem de parceiro OpenTable seja ativada na plataforma, a chamada de verificação responde 503.
Verificar um restaurante OpenTable
POST /appointments/opentable-restaurants/verify
| Campo | Obrigatório | Descrição |
|---|---|---|
restaurant_id |
Sim | O ID de Restaurante (RID) do OpenTable, um número como 1038007. |
curl -X POST "https://api.dmchamp.com/v1/appointments/opentable-restaurants/verify?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "restaurant_id": "1038007" }'
Resposta (200 OK):
{
"success": true,
"data": {
"verified": true,
"restaurantDetails": {
"restaurantId": "1038007",
"diningAreaCount": 2,
"diningAreaNames": ["Main Room", "Garden"],
"tableTypes": ["default", "outdoor", "bar"]
}
}
}
verified: false significa que nenhum restaurante OpenTable tem esse ID. Um 403 significa que o restaurante existe, mas ainda não ligou a integração da plataforma dentro do OpenTable. Limitado a 10 tentativas por cada 5 minutos por conta.
Adicionar um restaurante OpenTable
POST /appointments/opentable-restaurants
| Campo | Obrigatório | Descrição |
|---|---|---|
restaurant_id |
Sim | O ID de Restaurante verificado. |
restaurant_name |
Sim | Nome de exibição (uma etiqueta; também o que a IA chama ao restaurante). |
website_url |
Não | Um URL http(s) mostrado aos convidados quando a IA os reencaminha para o restaurante. |
curl -X POST "https://api.dmchamp.com/v1/appointments/opentable-restaurants?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "restaurant_id": "1038007", "restaurant_name": "The Blue Door", "website_url": "https://thebluedoor.example" }'
Resposta (201 Created): { "success": true, "data": { "restaurantId": "1038007" } }
Atualizar um restaurante OpenTable guardado
PUT /appointments/opentable-restaurants/{restaurantId}
Envie qualquer um de restaurant_name, is_active (pausa com false) ou website_url (uma string vazia limpa o campo); os campos omitidos permanecem inalterados.
curl -X PUT "https://api.dmchamp.com/v1/appointments/opentable-restaurants/1038007" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "is_active": false }'
Resposta (200 OK): { "success": true, "data": { "restaurantId": "1038007" } }
Remover um restaurante OpenTable
DELETE /appointments/opentable-restaurants/{restaurantId}
curl -X DELETE "https://api.dmchamp.com/v1/appointments/opentable-restaurants/1038007" \
-H "X-API-Key: YOUR_API_KEY"
Resposta (200 OK): { "success": true, "data": { "restaurantId": "1038007" } }
Um restaurantId que não esteja atualmente na conta devolve 404 na atualização.
TheFork
Os restaurantes do TheFork são acedidos através das credenciais de parceiro TheFork da plataforma, e o TheFork apenas permite que essas credenciais vejam restaurantes que tenham o parceiro da plataforma ativado na sua conta TheFork. Portanto, tal como no Formitable e no OpenTable, uma chamada de verificação é suficiente: um ID de Restaurante alcançável prova tanto que o restaurante existe como que o parceiro está ativado nele. O ID é o UUID que o TheFork atribui ao restaurante no TheFork Manager, enviado como uma string. Até que o TheFork tenha aprovado a plataforma como parceiro e emitido as credenciais, a chamada de verificação responde 503 — consulte TheFork para saber o que isso significa atualmente.
Verificar um restaurante TheFork
POST /appointments/thefork-restaurants/verify
| Campo | Obrigatório | Descrição |
|---|---|---|
restaurant_id |
Sim | O ID de Restaurante do TheFork, um UUID como 9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60. |
curl -X POST "https://api.dmchamp.com/v1/appointments/thefork-restaurants/verify?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "restaurant_id": "9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60" }'
Resposta (200 OK):
{
"success": true,
"data": {
"verified": true,
"restaurantDetails": {
"restaurantId": "9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60",
"partySizes": [1, 2, 3, 4, 5, 6, 7, 8]
}
}
}
partySizes são os tamanhos de grupo que o restaurante aceita online nos próximos 30 dias. Um 404 ou 403 significa que o TheFork não nos forneceu um restaurante com esse ID — ou o ID está incorreto, ou o parceiro da plataforma ainda não está ativado nesse restaurante; um 400 significa que o ID não é um UUID. Limitado a 10 tentativas por cada 5 minutos por conta.
Adicionar um restaurante TheFork
POST /appointments/thefork-restaurants
| Campo | Obrigatório | Descrição |
|---|---|---|
restaurant_id |
Sim | O ID de Restaurante verificado (UUID). |
restaurant_name |
Sim | Nome de exibição (uma etiqueta; também o nome pelo qual a IA chama o restaurante). |
website_url |
Não | Um URL http(s) mostrado aos convidados quando a IA os encaminha para o restaurante. |
curl -X POST "https://api.dmchamp.com/v1/appointments/thefork-restaurants?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "restaurant_id": "9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60", "restaurant_name": "The Blue Door", "website_url": "https://thebluedoor.example" }'
Resposta (201 Created): { "success": true, "data": { "restaurantId": "9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60" } }
Atualizar um restaurante TheFork guardado
PUT /appointments/thefork-restaurants/{restaurantId}
Envie qualquer um de restaurant_name, is_active (pausa com false) ou website_url (uma string vazia limpa o campo); os campos omitidos permanecem inalterados.
curl -X PUT "https://api.dmchamp.com/v1/appointments/thefork-restaurants/9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "is_active": false }'
Resposta (200 OK): { "success": true, "data": { "restaurantId": "9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60" } }
Remover um restaurante TheFork
DELETE /appointments/thefork-restaurants/{restaurantId}
curl -X DELETE "https://api.dmchamp.com/v1/appointments/thefork-restaurants/9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60" \
-H "X-API-Key: YOUR_API_KEY"
Resposta (200 OK): { "success": true, "data": { "restaurantId": "9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60" } }
Um restaurantId que não esteja atualmente na conta devolve 404 ao atualizar ou eliminar.
Trafft
O Trafft é ligado uma vez para toda a conta, não por localização: um endereço de empresa mais as credenciais da API do painel de administração do Trafft (Features & Integrations → API & Connectors, parte do plano Business do Trafft). A chamada de ligação verifica essas credenciais junto do Trafft antes de guardar qualquer coisa, pelo que um endereço errado, credenciais erradas ou um plano sem acesso à API falharão aqui em vez de durante uma conversa com o cliente. O segredo do cliente é guardado encriptado e nunca é devolvido por nenhum endpoint.
Todas as três chamadas de escrita (POST, PUT, DELETE) requerem a permissão de edição de Integrações; o estado GET necessita de visualização de Integrações.
Ligar Trafft
POST /appointments/trafft/connect
| Campo | Obrigatório | Descrição |
|---|---|---|
subdomain |
Sim | O endereço da empresa — a parte antes de .admin.trafft.com no URL em que inicia sessão, por exemplo acme. Um endereço completo é aceite e reduzido ao mesmo valor. |
client_id |
Sim | ID de cliente da página API & Connectors do Trafft. |
client_secret |
Sim | Segredo do cliente da mesma página. Guardado encriptado, nunca devolvido. |
company_name |
Não | Uma etiqueta para a sua própria lista. |
curl -X POST "https://api.dmchamp.com/v1/appointments/trafft/connect?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"subdomain": "acme",
"client_id": "YOUR_TRAFFT_CLIENT_ID",
"client_secret": "YOUR_TRAFFT_CLIENT_SECRET",
"company_name": "Acme Salon"
}'
Resposta (200 OK):
{
"success": true,
"data": {
"subdomain": "acme",
"service_count": 12,
"employee_count": 4,
"location_count": 2
}
}
As contagens são devolvidas pelo Trafft durante a verificação — são a forma mais rápida de confirmar que as credenciais apontam para a conta que pretendia.
Obter o estado da ligação
GET /appointments/trafft
curl "https://api.dmchamp.com/v1/appointments/trafft" \
-H "X-API-Key: YOUR_API_KEY"
Resposta (200 OK):
{
"success": true,
"data": {
"connected": true,
"subdomain": "acme",
"hostname": "acme.admin.trafft.com",
"client_id": "YOUR_TRAFFT_CLIENT_ID",
"company_name": "Acme Salon",
"is_active": true,
"service_count": 12,
"employee_count": 4,
"location_count": 2
}
}
connected: false significa que ainda não foi configurado nada. O segredo do cliente nunca é incluído nesta resposta.
Atualizar a ligação
PUT /appointments/trafft
| Campo | Obrigatório | Descrição |
|---|---|---|
is_active |
Não | Defina false para pausar — a IA deixa de efetuar marcações no Trafft, mas a ligação permanece. true retoma-a. |
company_name |
Não | Nova etiqueta. |
curl -X PUT "https://api.dmchamp.com/v1/appointments/trafft" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "is_active": false }'
Resposta (200 OK): o mesmo objeto de ligação que o GET acima.
Desligar Trafft
DELETE /appointments/trafft
curl -X DELETE "https://api.dmchamp.com/v1/appointments/trafft" \
-H "X-API-Key: YOUR_API_KEY"
Resposta (200 OK): { "success": true }
Desligar remove apenas as credenciais armazenadas. As marcações já existentes no Trafft permanecem inalteradas.
Formato de erro em todos os endpoints Zenchef/Formitable/OpenTable/TheFork: ao contrário do resto desta página, os erros aqui apresentam o estado duas vezes — uma como estado HTTP e outra como
error_codeno corpo — por exemplo{ "success": false, "error": "Restaurant not found", "error_code": 404 }. Trate-o da mesma forma que qualquer outro erro: verifiquesuccess, leiaerrorpara obter a mensagem.
Erros da API de marcações
Os endpoints de marcações devolvem o envelope de erro padrão:
{
"success": false,
"error": "Appointment not found"
}
| Estado | Quando ocorre num endpoint de marcação |
|---|---|
400 |
Falta um campo obrigatório ou este é inválido — por exemplo, um start_time incorreto, uma end_time que não é posterior a start_time, uma combinação de filtros inválida, ausência de campos para atualizar ou uma marcação já cancelada. |
404 |
A marcação, o contacto ou o tipo de evento não foi encontrado. |
409 |
O intervalo de tempo solicitado já está ocupado (conflito de agendamento). |
Os códigos partilhados que qualquer endpoint pode devolver — 401, 403 (o seu plano não inclui acesso à API), 429 (limite de taxa) e 500 — estão listados com orientações de repetição em Erros e Paginação.
Utilize o seu próprio cliente Google OAuth (ecrã de consentimento do Calendário)
Quando uma conta liga o Google Calendar, a janela de início de sessão da Google indica o projeto do cliente OAuth — por predefinição, o da plataforma. Uma agência pode registar o seu próprio cliente Google OAuth 2.0 na conta da agência; a partir desse momento, a ligação ao calendário para essa conta e para todas as subcontas associadas é efetuada através desse cliente, pelo que o ecrã de consentimento apresenta o nome e o logótipo da agência. Nada mais muda: o fluxo de ligação, a sincronização bidirecional e os endpoints de marcação acima referidos funcionam exatamente como antes.
Apenas Google Calendar. O OAuth da caixa de correio Gmail para o canal de E-mail não é afetado.
O que o seu cliente precisa primeiro
- Um cliente OAuth 2.0 do tipo Aplicação Web no seu projeto Google Cloud, com a API Google Calendar ativada nesse projeto.
- Cada entrada
redirect_uris(devolvida pelos endpoints abaixo) adicionada em URIs de redirecionamento autorizados do cliente. A primeira entrada é o seu domínioapi.verificado quando o tiver — a Google apenas verifica uma marca cujo redirecionamento reside num domínio que lhe pertence — seguido pelo host neutro da plataforma como alternativa utilizada até lá. - O ecrã de consentimento com a sua marca, o seu domínio em Domínios autorizados e os dois âmbitos (scopes) do Calendário declarados (
scopesna resposta). Até a aplicação ser publicada e verificada pela Google, os utilizadores veem um aviso de aplicação não verificada e o cliente está limitado a 100 utilizadores.
Guardar o seu cliente
PUT /account-config/google-oauth-client
| Campo | Obrigatório | Descrição |
|---|---|---|
client_id |
Sim | O ID de cliente OAuth 2.0, terminado em .apps.googleusercontent.com. |
client_secret |
Sim | O segredo do cliente. Verificado junto da Google antes de ser armazenado, e depois encriptado. Nunca devolvido por qualquer endpoint. |
curl -X PUT "https://api.dmchamp.com/v1/account-config/google-oauth-client?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"client_id": "123456789012-abcdefghijklmnop.apps.googleusercontent.com",
"client_secret": "GOCSPX-your-client-secret"
}'
Resposta
{
"success": true,
"configured": true,
"client_id": "123456789012-abcdefghijklmnop.apps.googleusercontent.com",
"redirect_uris": [
"https://api.youragency.com/v1/auth-google-callback",
"https://api.dmchamp.com/v1/auth-google-callback"
],
"scopes": [
"https://www.googleapis.com/auth/calendar.events",
"https://www.googleapis.com/auth/calendar.readonly"
],
"setup": ["…"]
}
Um segredo incorreto ou um ID de cliente desconhecido é recusado com 400 e o motivo da própria Google em error, e nada é armazenado.
Ler ou remover
GET /account-config/google-oauth-client devolve o mesmo resumo a qualquer momento — configured: false mais o redirect_uris e o scopes antes de qualquer coisa ser guardada, para que possa configurar primeiro o lado do Google. DELETE /account-config/google-oauth-client remove o cliente: as novas ligações revertem para o cliente da plataforma e os calendários que foram ligados através do cliente removido têm de ser ligados novamente, porque apenas o cliente que emitiu uma ligação a pode atualizar.
Os membros da equipa precisam de Integrações: ver para GET e Integrações: editar para PUT / DELETE.
Próximos passos
- Contactos — crie e procure os contactos para os quais efetua reservas.
- Mensagens e Conversas — envie uma confirmação ou um lembrete a um contacto.
- Webhooks — receba notificações quando os agendamentos forem alterados.