DM Champ Docs

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_id pode ainda ser null e o calendar_synced pode ser false porque 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_time e end_time sã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_id para ver os agendamentos confirmados de um contacto. Pode restringir a um único dia passando também date.
  • Por estado — defina status (sem contact_id) para listar apenas agendamentos Confirmed ou apenas Canceled em toda a conta.
  • O filtro date sem contact_id, ou status=Canceled juntamente com contact_id, devolve um 400.

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_code no corpo — por exemplo { "success": false, "error": "Restaurant not found", "error_code": 404 }. Trate-o da mesma forma que qualquer outro erro: verifique success, leia error para 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

  1. Um cliente OAuth 2.0 do tipo Aplicação Web no seu projeto Google Cloud, com a API Google Calendar ativada nesse projeto.
  2. Cada entrada redirect_uris (devolvida pelos endpoints abaixo) adicionada em URIs de redirecionamento autorizados do cliente. A primeira entrada é o seu domínio api. 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á.
  3. 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 (scopes na 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.