Ir para o conteúdo
TripMap / Documentação

API TripMap para agências.

Conecte seu sistema ao TripMap para cadastrar viajantes, organizar viagens e enviar documentos.

API v1REST · JSON

Uma conexão com
toda a sua operação.

Chave da agência Respostas em JSON
URL base de produção
https://api.tripmap.com.br

Integração em produção. As operações usam os dados da sua agência.

Versão do contrato: 1.0.0. Atualizado em 10/10/2026.

Integre o sistema da agência para cadastrar viajantes, criar viagens com roteiro, associar pessoas e enviar documentos. Nesta versão, a integração está disponível somente em produção; não há ambiente público de testes. As operações de escrita criam ou alteram registros na conta da agência e podem enviar convites reais.

AmbienteURL base, sem /api/v1Uso
Produçãohttps://api.tripmap.com.brOperação da agência quando a integração estiver disponível no painel

As rotas abaixo já incluem /api/v1. Exemplo: URL base + /api/v1/viajantes = https://api.tripmap.com.br/api/v1/viajantes. Não acrescente /api/v1 à variável da base. Se a aba API não estiver disponível no painel de produção, acione o suporte antes de iniciar.

A chave de integração fica no servidor da agência. O viajante entra no app por e-mail e código, usando a autenticação própria do app.

Acesso e chaves

Todos os planos com assinatura ativa e trials dentro da validade permitem integração. Assinatura ausente, trial vencido, inadimplência, cancelamento e agência suspensa impedem o acesso.

  • Entre no painel de produção como proprietário ativo da agência.
  • Abra Configurações → API, na seção Chaves da API.
  • Informe um nome para identificar o sistema, por exemplo Meu CRM, e clique em Criar chave. O nome aceita de 1 a 40 caracteres.
  • Em Guarde esta chave, clique em Copiar chave e salve o segredo em uma variável de ambiente no servidor integrador. A chave completa aparece somente nessa criação; se perder a cópia, crie outra chave.
  • Valide a autenticação com uma consulta de leitura usando a URL base de produção e Authorization: Bearer <SUA_CHAVE>.

O integrador usa somente a chave tm_live_...; não precisa receber a senha nem a sessão do painel do proprietário. Somente o proprietário ativo cria, lista e revoga chaves. Há no máximo cinco chaves ativas por agência. O segredo nunca aparece na listagem. Desativar o criador ou movê-lo para outra agência revoga suas chaves permanentemente; reativar o membro não reativa chaves. Excluir o criador invalida o acesso.

Nesta versão, a chave permite todas as rotas deste contrato na própria agência e não expira automaticamente. Para rotacionar, crie uma chave nova, troque-a no servidor integrador, confirme a consulta de teste e clique em Revogar na chave anterior. A revogação impede novas chamadas imediatamente. Não coloque o segredo na URL, no app, no código executado no navegador, nos logs nem no Git.

O guia da API fica em https://api.tripmap.com.br/api/docs/agency, com download em https://api.tripmap.com.br/api/docs/agency.md; ambos são públicos. Eles apresentam o mesmo contrato da documentação da landing. Leia Limites e erros se a primeira consulta retornar 401, 403 ou 429.

Rotas

GET individual e recibos de POST/PATCH de viajante incluem também phone_alt, nationality, cpf, passport_number, passport_expiry, birth_date, seat_preference, dietary_restrictions e notes. Viagem inclui description, notes, pax_count, cover_image_url, tags e status_manual. Listas permanecem resumidas. O status retornado é efetivo na data de Brasília, preservando estados manuais/cancelados.

PATCH de viagem conserva a escolha de status manual feita no dashboard. Alterar datas valida dias e intervalos dos itens e rejeita atomicamente um roteiro incompatível; não encurta hospedagens ou transportes automaticamente.

DELETE de viajante associado por grupo retorna 409 group_membership_conflict, sem alterar os vínculos. Ajuste o grupo no dashboard antes de repetir a desvinculação. Convites reconhecem passageiros vinculados por grupo; convite explícito para cliente que não pertence à viagem retorna 404.

:id e :travelerId aceitam o UUID retornado pelo TripMap ou o external_id. Um external ID com aparência de UUID também funciona: a busca usa o UUID primeiro e, se não existir na agência, busca o external ID. :documentoId é sempre o UUID do documento. Ao montar URLs, use encodeURIComponent(id) para cada ID e URLSearchParams para filtros; caracteres como /, espaço, ? e # não devem ser concatenados diretamente.

MétodoRotaResultado
POST/api/v1/viajantesCria ou atualiza o viajante pelo e-mail/external ID
GET/api/v1/viajantesLista; filtros email, external_id
GET / PATCH/api/v1/viajantes/:idConsulta / atualização parcial
POST/api/v1/viagensCria viagem, viajante opcional e roteiro em uma transação
GET/api/v1/viagensLista; filtro external_id
GET / PATCH/api/v1/viagens/:idConsulta com roteiro / atualização de dados da viagem
POST/api/v1/viagens/:id/viajantes/:travelerIdVincula um viajante existente
DELETE/api/v1/viagens/:id/viajantes/:travelerIdDesvincula; 204
POST/api/v1/viagens/:id/viajantes/:travelerId/conviteTenta enviar um convite pendente
POST / GET/api/v1/viagens/:id/documentosUpload / lista
POST / GET/api/v1/viajantes/:id/documentosUpload / lista
GET/api/v1/viagens/:id/documentos/:documentoId/downloadURL assinada, válida por 300 segundos
GET/api/v1/viajantes/:id/documentos/:documentoId/downloadURL assinada, válida por 300 segundos
DELETE/api/v1/viagens/:id/documentos/:documentoIdExclusão lógica; 204
DELETE/api/v1/viajantes/:id/documentos/:documentoIdExclusão lógica; 204

Todas as listas aceitam limit entre 1 e 100 (padrão 100) e offset inteiro entre 0 e 1.000.000 (padrão 0). A resposta inclui pagination: {limit, offset, next_offset}; next_offset: null indica o fim. Ordenação por criação e UUID, ambos descendentes. Alterações durante a paginação podem deslocar resultados; deduplique pelo UUID retornado. Não há filtro incremental por data nem webhook de alterações nesta versão.

Escritas, identidade e datas

JSON deve ser um objeto, nunca null ou um array. Datas precisam existir no calendário e usar YYYY-MM-DD. Horários precisam de fuso explícito, por exemplo 2099-11-02T10:00:00-03:00. Strings acima do limite são rejeitadas, nunca truncadas.

external_id tem até 120 caracteres e é único por agência em cada tipo de recurso. Repetir POST da viagem existente retorna 200 com a viagem e o viajante persistidos, sem substituir o roteiro. Criação retorna 201. Chamadas simultâneas com o mesmo external ID criam somente uma viagem.

Viajante: name (até 200) e email (até 320) são obrigatórios no POST. O e-mail é normalizado para minúsculas e os textos têm espaços externos removidos. Repetir o e-mail na mesma agência atualiza apenas campos enviados. Omissão preserva dados; null limpa os campos pessoais opcionais listados abaixo, com exceção de external_id. Nome/e-mail não podem ser apagados. Identificadores que apontem para pessoas diferentes retornam 409 identity_conflict. Use PATCH para trocar o e-mail do mesmo viajante. Um external ID atribuído não pode ser trocado nem apagado com null; guarde a relação com o ID do seu sistema.

POST/PATCH de viajante aceitam name, email, phone, phone_alt, birth_date, nationality, cpf, passport_number, passport_expiry, seat_preference, dietary_restrictions, notes e external_id. Telefones até 40 caracteres; CPF até 20; nacionalidade até 80; passaporte/preferência até 40; restrições até 200; notas até 2000.

Trocar o e-mail remove o vínculo da conta anterior e reavalia o acesso pela conta do novo e-mail. Os convites ficam pendentes para o novo endereço. Uma troca durante envio ainda sem confirmação retorna 409 invite_in_progress; consulte a seção Convite e acesso ao app.

Viagem: external_id, title (até 200), start_date e end_date são obrigatórios no POST. O fim não pode anteceder o início. Campos opcionais: description e notes (até 4000), pax_count (inteiro de 1 a 10000, padrão 1), cover_image_url (HTTPS, até 500), tags (até 20 strings de 80 caracteres), traveler e days.

PATCH da viagem aceita somente title, start_date, end_date, description, notes, pax_count, cover_image_url e tags. O corpo deve conter ao menos um campo permitido; campo desconhecido é rejeitado. Omissão preserva o campo. description, notes e cover_image_url aceitam null para limpar. Para limpar tags, use tags: []; tags: null, título/datas nulos e pax_count: null são rejeitados. Não substitui roteiro; não aceita days. Datas novas devem continuar abrangendo todos os dias e intervalos dos itens já gravados. No POST, traveler, days, tags e items devem ser omitidos quando ausentes, e não enviados como null. Campos desconhecidos no POST e no POST/PATCH de viajante não são gravados; use apenas os campos deste contrato.

O status é calculado pelas datas em Brasília: futura confirmed, nas datas da viagem ongoing, depois completed. A integração não congela o status manualmente. Uma operação que coloque o viajante em duas viagens em andamento, diretamente ou por grupo, causa 409 traveler_already_on_ongoing_trip, inclusive sob concorrência com o dashboard.

Também são rejeitados períodos automáticos sobrepostos do mesmo viajante ativo, de hoje em diante: 409 traveler_trip_period_conflict. Viagens de viajantes distintos podem ter as mesmas datas. Início e fim são inclusivos; a próxima viagem do mesmo viajante deve começar depois do último dia da anterior. A regra considera o principal e os vínculos individuais ou por grupo, inclusive em alterações feitas pelo dashboard. Viagens futuras com períodos distintos continuam permitidas enquanto o viajante está em uma viagem atual. Viagens canceladas ou com status manual confirmed, completed ou draft não reservam período. Status manual ongoing reserva de hoje em diante sem fim automático; encerre esse status no dashboard antes de associar outra viagem futura. Sobreposições históricas não são apagadas nem canceladas automaticamente; alterações descritivas continuam possíveis, mas novos vínculos ou mudanças de período precisam respeitar a regra.

GET da viagem inclui o viajante principal em travelers, mesmo sem vínculo individual separado, e evita duplicá-lo quando esse vínculo também existe. Referências a grupos permanecem no formato group_id. Os dias vêm por day_number e UUID crescentes. Dentro de cada dia, os itens vêm por sort_order e UUID crescentes, refletindo a ordem editada no dashboard.

Roteiro e voos

days aceita até 366 dias em ordem crescente, sem datas repetidas e dentro do intervalo da viagem. Cada dia aceita date, title (200), summary (1000) e items. Máximo de 80 itens por viagem.

Tipos: flight, accommodation, activity, car_rental, transfer, land_transport, sea_transport, insurance, restaurant e note. Todo item exige title (200), exceto voo. Ordem é a ordem do array; os itens são gravados confirmados e em BRL.

Campos de itens comuns: start_at, end_at, address (300), origin (300), destination (300), notes (2000), confirmation_number (80). Horários devem estar dentro do intervalo da viagem. Na hospedagem são check-in/check-out; no transporte, partida/chegada; no aluguel, retirada/devolução. O texto local do ISO é usado nos cartões; os instantes persistidos preservam UTC.

Atividades com start_at devem começar na data local de days[].date. O fim pode atravessar a meia-noite, desde que continue dentro do período da viagem. Hospedagens podem atravessar dias e não exigem check-in no dia do bloco. Para voos, a partida e a chegada locais retornadas pelo provedor também precisam estar dentro do período; uma chegada fora dele retorna 400 antes de gravar a viagem.

Voo exige flight_number (2 a 10 letras/números, exemplo G31442), date (padrão: dia do roteiro) e origin opcional (IATA, três letras). A consulta ao provedor preenche aeroportos, companhia, horários, terminal e portão. 404 flight_not_found indica consulta sem resultados ou 404 do provedor; 409 flight_ambiguous: reenvie com o aeroporto de origem correto. Falhas de rede, timeout de 12 segundos, resposta inválida, quota ou erro do provedor retornam 503 flight_lookup_unavailable, com Retry-After: 30 e retry_after: 30. Espere e repita com o mesmo external ID; não troque o número do voo só por esse erro. Falha de busca não deixa viagem, dia ou viajante parcialmente gravados.

passengers aceita até 200 objetos: name (obrigatório, 200), client_id (UUID do TripMap opcional), email (320), seat (10), cabin (40), baggage (80), pnr (20), ticket_number (40). Um client_id deve pertencer a um viajante ativo da agência; se também enviar e-mail, ambos devem apontar para a mesma pessoa. Sem ID, o e-mail resolve qualquer viajante ativo da própria agência, mesmo antes de vinculá-lo à viagem. Se ainda não estiver cadastrado, os dados do passageiro serão associados quando a pessoa for cadastrada e vinculada, individualmente ou por grupo. Atribuição ao voo não cria vínculo nem convite automaticamente. Sem e-mail/ID, só o nome do principal explicitamente informado na criação pode ser associado; outros homônimos não são inferidos. O app usa os dados para assento, cabine, bagagem e bilhete. Se um passageiro antigo estiver associado incorretamente, ajuste-o explicitamente no dashboard.

Exemplo:

json
{
  "external_id": "crm-8841",
  "title": "Lisboa com a Ana",
  "start_date": "2099-11-02",
  "end_date": "2099-11-08",
  "traveler": {"external_id": "cli-19", "name": "Ana Souza", "email": "ana@example.com"},
  "days": [{
    "date": "2099-11-02",
    "title": "Chegada",
    "items": [{"type": "transfer", "title": "Aeroporto ao hotel", "origin": "Aeroporto", "destination": "Hotel", "start_at": "2099-11-02T15:00:00+00:00"}]
  }]
}

Exemplo completo de integração

Use Node.js 20 ou superior no servidor da agência. Configure TRIPMAP_API_BASE=https://api.tripmap.com.br, sem /api/v1, e TRIPMAP_API_KEY com uma chave válida de produção. O exemplo é um módulo ES (.mjs). Configure TRIPMAP_TIMEOUT_MS se precisar de mais tempo para roteiros com várias consultas de voo. Os dados abaixo são fictícios; use um PDF válido e substitua o e-mail por um endereço controlado. Executar o exemplo cria registros reais na agência; a criação com viajante pode tentar enviar convite.

javascript
import { readFile } from "node:fs/promises";

const base = process.env.TRIPMAP_API_BASE?.replace(/\/$/, "");
const key = process.env.TRIPMAP_API_KEY;
if (!base || !key) throw new Error("Configure a base e a chave no servidor.");
if (base !== "https://api.tripmap.com.br") {
  throw new Error("A base deve ser a origem HTTPS de produção, sem /api/v1.");
}
const timeout = Number(process.env.TRIPMAP_TIMEOUT_MS ?? 120000);
if (!Number.isSafeInteger(timeout) || timeout <= 0) throw new Error("Timeout inválido.");

async function call(path, { method = "GET", json, form } = {}) {
  const response = await fetch(base + path, {
    method,
    signal: AbortSignal.timeout(timeout),
    headers: {
      Authorization: "Bearer " + key,
      ...(json === undefined ? {} : { "Content-Type": "application/json" })
    },
    body: form ?? (json === undefined ? undefined : JSON.stringify(json))
  });
  const raw = await response.text();
  let data = null;
  if (raw) {
    try { data = JSON.parse(raw); }
    catch { throw new Error("Resposta inesperada; confirme o ambiente e acione o suporte."); }
  }
  if (!response.ok) {
    const error = new Error(data?.error ?? "request_failed");
    error.status = response.status;
    error.field = data?.field;
    error.retryAfter = response.headers.get("Retry-After") ?? data?.retry_after;
    throw error; // Trate os erros; não registre chave nem dados pessoais.
  }
  return { status: response.status, data };
}

// Primeira consulta: confirma a chave e o ambiente antes de qualquer escrita.
await call("/api/v1/viajantes?limit=1");

// 201 na primeira criação; 200 quando o e-mail/external ID já existe.
const saved = await call("/api/v1/viajantes", {
  method: "POST",
  json: { external_id: "cli-19", name: "Ana Souza", email: "ana@example.com" }
});
const traveler = saved.data.traveler;
await call("/api/v1/viajantes/" + encodeURIComponent(traveler.id), {
  method: "PATCH", json: { phone: "+55 11 90000-0000" }
});

// 201 na primeira criação; replay retorna 200 sem substituir o roteiro.
const created = await call("/api/v1/viagens", {
  method: "POST",
  json: {
    external_id: "crm-8841",
    title: "Lisboa com a Ana",
    start_date: "2099-11-02",
    end_date: "2099-11-08",
    traveler: { external_id: "cli-19", name: "Ana Souza", email: "ana@example.com" },
    days: [{
      date: "2099-11-02",
      title: "Chegada",
      items: [{
        type: "activity", title: "Passeio",
        start_at: "2099-11-02T15:00:00+00:00"
      }]
    }]
  }
});
const trip = created.data.trip;
// Confira created.data.traveler.invite: a gravação não garante entrega do e-mail.
const detail = await call("/api/v1/viagens/" + encodeURIComponent(trip.id));
// detail.data.trip.days[].items[] usa start_datetime/end_datetime no retorno,
// enquanto a escrita usa start_at/end_at. Metadata preserva a data/hora local.

const file = new FormData();
file.set("external_id", "voucher-19");
file.set("type", "tour_voucher");
file.set("title", "Voucher do passeio");
file.set("file", new Blob([await readFile("./voucher.pdf")], {
  type: "application/pdf"
}), "voucher.pdf");
// Não defina Content-Type manualmente: fetch inclui o boundary multipart.
const uploaded = await call("/api/v1/viagens/" + encodeURIComponent(trip.id) + "/documentos", {
  method: "POST", form: file
});
const document = uploaded.data.document;
const downloaded = await call(
  "/api/v1/viagens/" + encodeURIComponent(trip.id) + "/documentos/" + encodeURIComponent(document.id) + "/download"
);
// downloaded.data = { url, expires_in }; use a URL imediatamente e em privado.

// Paginação: travelers/trips/documents são as chaves de coleção de cada rota.
let offset = 0;
do {
  const query = new URLSearchParams({ limit: "100", offset: String(offset) });
  const result = await call("/api/v1/viajantes?" + query);
  for (const person of result.data.travelers) {
    // Integre pelo person.id sem registrar os dados pessoais em logs.
  }
  offset = result.data.pagination.next_offset;
} while (offset !== null);

Exemplo de resposta de POST de viajante, HTTP 201 (ou 200 no replay):

json
{
  "traveler": {
    "id": "11111111-1111-4111-8111-111111111111",
    "external_id": "cli-19",
    "name": "Ana Souza",
    "email": "ana@example.com",
    "phone": null,
    "phone_alt": null,
    "nationality": null,
    "cpf": null,
    "passport_number": null,
    "seat_preference": null,
    "dietary_restrictions": null,
    "notes": null,
    "birth_date": null,
    "passport_expiry": null
  }
}

Respostas e campos retornados

OperaçãoEnvelopeHTTP de sucesso
POST de viajante{traveler}201 novo; 200 existente
GET/PATCH de viajante{traveler}200
POST de viagem{trip, traveler}201 nova; 200 replay
GET/PATCH de viagem{trip}200
POST de vínculo{linked: true}200
POST de convite{invite, error?, retry_after?}200 concluído; 202 pendente
POST de documento{document}201 novo; 200 replay
Listas{travelers, pagination}, {trips, pagination} ou {documents, pagination}200
Download{url, expires_in}200
DELETE de vínculo/documentoSem corpo; não tente ler JSON204

Viajante e viagem

O objeto traveler do exemplo anterior é o formato de detalhe e dos recibos POST/PATCH. A lista traz apenas id, external_id, name, email e phone; consulte GET individual para obter os demais campos. Os campos pessoais opcionais retornam string ou null; birth_date e passport_expiry, quando preenchidos, usam YYYY-MM-DD. Registros legados podem ter nome/e-mail nulos. id é UUID; external_id é string ou null para registros criados sem esse identificador.

Campo da viagemTipo/observação
id, external_idUUID; identificador externo string ou null
titleString; registros legados podem retornar null
start_date, end_dateYYYY-MM-DD; registros legados podem retornar null
statusdraft, confirmed, ongoing, completed ou cancelled; efetivo em Brasília
status_manualBooleano; presente nos detalhes/recibos, indica escolha pelo dashboard
description, notes, cover_image_urlString ou null, presentes nos detalhes/recibos
pax_countInteiro, ou null em registro legado; não substitui vínculos de viajantes
tagsArray de strings, ou null em registro legado
days, travelersArrays presentes somente no GET individual, não no POST/PATCH/lista

A lista de viagens traz somente id, external_id, title, start_date, end_date e status. No POST, traveler fora do objeto trip é o principal informado/persistido, com o estado de convite. Se não houver principal, é null; não representa o roster completo. No GET, trip.travelers contém referências {client_id, group_id}, com UUID ou null em cada campo. A referência individual/principal usa client_id; a referência de grupo usa group_id. Membros do grupo não são expandidos nem gerenciados por esta API; consulte-os no dashboard. Um mesmo vínculo individual/principal aparece uma única vez.

Exemplo fictício de GET individual de viagem:

json
{
  "trip": {
    "id": "22222222-2222-4222-8222-222222222222",
    "external_id": "crm-8841",
    "title": "Lisboa com a Ana",
    "start_date": "2099-11-02",
    "end_date": "2099-11-08",
    "status": "confirmed",
    "status_manual": false,
    "description": null,
    "notes": null,
    "pax_count": 1,
    "cover_image_url": null,
    "tags": [],
    "days": [{
      "id": "33333333-3333-4333-8333-333333333333",
      "day_number": 1,
      "date": "2099-11-02",
      "title": "Chegada",
      "summary": null,
      "items": [{
        "id": "44444444-4444-4444-8444-444444444444",
        "type": "activity",
        "title": "Passeio",
        "start_datetime": "2099-11-02T15:00:00+00:00",
        "end_datetime": null,
        "metadata": {"api_start_local_date": "2099-11-02"},
        "sort_order": 0
      }]
    }],
    "travelers": [{
      "client_id": "11111111-1111-4111-8111-111111111111",
      "group_id": null
    }]
  }
}

Dias, itens e normalização

O dia contém id UUID, day_number inteiro, date data, title/summary string ou null e items array. items contém id, type, title, start_datetime, end_datetime, metadata e sort_order. title e horários podem ser nulos; horários preenchidos são ISO com fuso e representam o instante, sem garantir o mesmo offset textual enviado. sort_order define a ordem. Notas e confirmação não vêm em campos separados no GET: ficam em metadata.notas e metadata.conf, quando preenchidas.

Na escrita, use start_at/end_at. Na leitura, use start_datetime/end_datetime. metadata é um objeto de dados auxiliares ou null; só use as propriedades descritas neste contrato. Outras propriedades podem ser acrescentadas. O objeto não é um campo gravável da integração.

Tipo enviadoTipo retornadoMetadados relevantes
transfertransfersubtype: transfer, origem, destino, partida
land_transporttransfersubtype: coletivo, origem, destino, partida
sea_transporttransfersubtype: aquatico, porto_embarque, porto_desembarque, partida
car_rentaltransfersubtype: rental, retirada_local, devolucao_local, retirada_datetime, devolucao_datetime
accommodationaccommodationnome, checkin, checkout, endereco, address
flightflightnumero, cia, origem, destino, partida_local, chegada_local, terminal, gate, passengers_json
Demais tipos aceitosMesmo tipo enviadoaddress, quando informado; horários nos campos do item

Valores opcionais podem estar ausentes ou vazios. api_start_local_date/api_end_local_date conservam as datas locais de itens escritos com horários. Em voos, passengers_json é uma string JSON; faça parse para obter um array, nunca execute o conteúdo. Os campos de leitura de passageiro são clientId, name, classe, seat, bagagens, pnr e numero_bilhete; clientId pode estar vazio enquanto não houver associação. O GET não devolve o e-mail de resolução do passageiro. Não confunda atribuição ao voo com vínculo à viagem.

Documentos

Recibo e listagem usam o mesmo objeto, sem o conteúdo binário nem URL permanente do arquivo:

json
{
  "document": {
    "id": "55555555-5555-4555-8555-555555555555",
    "external_id": "voucher-19",
    "type": "tour_voucher",
    "title": "Voucher do passeio",
    "file_name": "voucher.pdf",
    "byte_size": 12000
  }
}

id é UUID; external_id é string ou null. type segue a enumeração de Documentos. title/file_name são strings; registros legados podem retornar null. byte_size é inteiro de bytes do arquivo gravado, ou null em legado; imagens podem ficar menores após processamento. Documentos pessoais acrescentam document_number string ou null e expiry_date data ou null. Para abrir o arquivo, solicite a rota /download.

Exemplo de listagem paginada vazia: {"documents":[],"pagination":{"limit":100,"offset":0,"next_offset":null}}. POST de vínculo retorna exatamente {"linked":true}; DELETE bem-sucedido retorna HTTP 204 sem corpo.

Convite e acesso ao app

A criação com traveler vincula a pessoa e tenta convidá-la. A resposta traz traveler.invite: sent, already_active ou pending. Sem viajante, traveler é null.

pending significa que a viagem foi gravada e o convite precisa de atenção. invite_error explica a falha e retry_after, quando presente, informa a espera em segundos. Repita o POST da mesma viagem ou use a rota de convite após corrigir uma falha conhecida. A rota de convite responde 202 enquanto pendente e 200 quando concluído.

Um convite confirmado não é reenviado por repetir a viagem. Não presuma que 201 significa e-mail entregue. sent significa aceito para envio, não confirmação de entrega ou leitura; already_active indica uma conta já associada para esse viajante.

Estado/código do convite pendentePróxima ação
rate_limitedAguarde retry_after segundos e repita a rota de convite; não crie outra viagem
invite_rejectedConfira e-mail, vínculo e situação do viajante no painel; se persistir, acione suporte
invite_unavailableO convite não foi concluído. Acione suporte se persistir; após resolução, repita a mesma rota
invite_in_progressOutro envio ainda não terminou. Consulte novamente o recurso; não force reenvio ou troca de e-mail. Se persistir, suporte
invite_confirmation_requiredResultado do envio sem confirmação. Acione suporte antes de repetir; não faça reenvio automático

Exemplo de resposta HTTP 202 da rota de convite: {"invite":"pending","error":"rate_limited","retry_after":90}. Quando concluída, responde HTTP 200 com {"invite":"sent"} ou {"invite":"already_active"}. Na criação da viagem, as mesmas informações ficam em traveler.invite, traveler.invite_error e traveler.retry_after; a viagem continua gravada mesmo com convite pendente. O campo error de uma resposta 202 é um estado de convite, não falha na gravação da viagem.

O vínculo de uma pessoa existente é uma operação separada do convite. Se o viajante não vê a viagem no app:

  • Confira se está vinculado à viagem, como principal, individualmente ou por grupo.
  • Confira se usa a conta do mesmo e-mail cadastrado e se o acesso está autorizado pela agência.
  • Confira o status no painel. Rascunho manual e rascunho futuro automático ficam ocultos. Rascunho automático que já passou para em andamento/concluído pelas datas pode aparecer. Outros status não são ocultados apenas por essa regra de rascunho.
  • Se a viagem tiver uma proposta comercial no dashboard, ela só aparece no app após o aceite; sem proposta, essa condição não se aplica. Propostas não são gerenciadas por esta API.

A chave da agência não autentica o viajante, e sent não garante acesso ao app.

Suporte da integração

Escreva para plutotecnologia@gmail.com, canal de contato do TripMap, com assunto Integração API — agência — ambiente. Informe ambiente, horário com fuso, método/rota, HTTP, código público do erro e IDs da viagem/viajante/documento envolvidos. Não envie chave, senha, sessão do painel, documentos pessoais ou payload completo. Para envio sem confirmação, informe também o estado invite/invite_error; aguarde a orientação antes de reenviar.

Documentos

O download de arquivo hospedado no TripMap retorna URL temporária com expires_in: 300, em segundos. Documentos existentes no painel que usem links externos HTTP/HTTPS retornam o link cadastrado com expires_in: null; isso significa que o TripMap não define a validade, não que o link seja garantidamente permanente. Somente documentos ativos acessíveis à própria agência são retornados.

POST usa multipart/form-data: type, title (200) e um único file obrigatórios; external_id (120) opcional. O nome do arquivo aceita até 180 caracteres. Arquivos: PDF válido (.pdf), JPEG (.jpg/.jpeg), PNG (.png) ou WebP (.webp), até 25 MiB; extensão deve corresponder ao conteúdo. O conteúdo é validado, não apenas extensão/MIME. Imagens acima de 40 milhões de pixels são rejeitadas mesmo se tiverem menos de 25 MiB. Imagens são reduzidas a até 1920 × 1920 e gravadas em JPEG; a compressão pode alterar a qualidade e o nome passa a terminar em .jpg. PDFs seguem íntegros. PDFs ilegíveis ou protegidos que não possam ser validados são rejeitados.

Tipos da viagem: travel_insurance, passport, visa, hotel_voucher, flight_ticket, tour_voucher, transfer, contract, other. Tipos do viajante: rg, cpf, cnh, passport, visa, travel_insurance, other. Documentos pessoais aceitam também document_number (80) e expiry_date.

A primeira gravação responde 201. O mesmo external ID no mesmo pai responde 200, mantendo o documento existente, sem substituir o arquivo ou seus metadados; em outra viagem/pessoa responde 409 document_parent_conflict. Chamadas simultâneas não duplicam o documento. Use external_id também nos uploads para repetir uma tentativa sem criar cópias. Sem esse identificador, uma repetição pode criar outro documento.

GET lista metadados. A rota /download retorna {"url":"...","expires_in":300}; trate a URL assinada como temporária e privada. DELETE retira o documento da visualização por exclusão lógica.

Limites e erros

Autenticação e quotas são verificadas antes de ler o corpo. O limite de dez tentativas de upload/minuto por chave inclui requisições inválidas e repetições com external ID; cada POST conta uma vez, mesmo quando não cria outro arquivo. Após 429, respeite Retry-After e repita com o mesmo external ID para manter a idempotência.

LimiteValor
Requisições60/minuto/chave
Tentativas de upload10/minuto/chave
Tentativas de convite20/hora/agência, somando todas as chaves
JSON1.000.000 bytes reais, incluindo requisição sem Content-Length
Multipart completo26 MiB
Arquivo25 MiB

Quota direta retorna 429 com Retry-After. Quota de convite na criação deixa invite: pending, preservando a resposta da viagem já gravada. Repetições continuam contando como requisições; convites já concluídos não consomem nova tentativa.

HTTPerror
400invalid_json, validation_error (pode incluir field), invalid_file, file_too_large, too_many_items, invalid_document_url
401unauthorized
403agency_suspended, subscription_required
404not_found, flight_not_found
409identity_conflict, document_parent_conflict, traveler_already_on_ongoing_trip, traveler_trip_period_conflict, flight_ambiguous, group_membership_conflict, invite_in_progress
413payload_too_large
429rate_limited
500internal_error
503flight_lookup_unavailable, storage_unavailable

Como recuperar uma chamada

SituaçãoAção
400/413Corrija JSON/campo/formato/tamanho antes de repetir; use field quando houver
401Confira chave, cabeçalho e ambiente; se revogada, crie outra no painel
403Confira assinatura, validade do trial e suspensão com o proprietário; repetir não resolve
404Confira ambiente, ID e recurso ativo; recurso de outra agência também responde 404
409Corrija identidade, vínculo, grupo ou datas conforme o código; não crie outra identidade para contornar o conflito
429Espere Retry-After segundos, com poucas tentativas e limite; preserve o external ID
503Aguarde a orientação de espera quando presente; mantenha o external ID e acione suporte se persistir
500 ou falha de rede/timeoutA escrita pode ter sido concluída. Consulte o recurso pelo external ID ou repita o POST com o mesmo ID; nunca suponha que houve rollback
202 de conviteA viagem já está gravada. Siga a tabela de convites; não trate como falha de criação

Formato de erro HTTP: {"error":"validation_error","field":"start_date"}; field é opcional. Falha temporária de consulta de voo usa {"error":"flight_lookup_unavailable","retry_after":30} com HTTP 503 e Retry-After: 30. Não existe um campo message obrigatório: tome decisões pelo HTTP e pelo código error. Códigos de convite pendente são descritos na seção própria e podem aparecer em HTTP 202 ou dentro do recibo de criação.

Repetir POST de viajante pode atualizar campos enviados; não é uma consulta sem efeitos. Prefira GET individual ou lista com filtro para conferir o que foi gravado. GET não expõe o histórico de entrega de convites; depois de um envio sem confirmação, a consulta da viagem confirma a gravação, mas a orientação sobre o convite continua sendo do suporte.

As quotas valem para tentativas, inclusive erros e replays. Uma carga de mil viajantes exige ao menos cerca de 17 minutos com uma única chave a 60 requisições/minuto, antes de viagens, documentos, convites e tempo de processamento. Use fila com controle de ritmo; não crie chaves adicionais para contornar a quota de convites por agência. Não há escrita em lote neste contrato.

Dados de outra agência retornam 404. Guarde chaves e URLs temporárias em privado e evite logs com dados pessoais. O guia contém exemplos fictícios; antes de executar operações de escrita, confirme os dados que serão cadastrados na conta da agência.

Fora deste contrato: exclusão de viagens/viajantes, lixeira, substituição de roteiro via PATCH, grupos, cobrança, equipe, mensagens e proposta comercial.

Agendar uma demonstração