DevelopersAPI TEMA v2
Rapidoc · TEMA v2 · Boas práticas

Boas práticas de consumo

Como integrar com menos chamadas e sem esbarrar no limite da API: o que salvar na sua base, quando ir à API e como montar cada jornada — cadastro, encaminhamentos e agendamento.

Limite 30 req/min por IPMínimo salvo uuid do beneficiário

Limite de uso da API

30 requisições por minuto, por IP

É o padrão do ambiente. Acima desse teto as chamadas são recusadas com 403 até a janela de um minuto virar — na média, sobra uma chamada a cada 2 segundos para toda a sua integração.

O limite vale por IP, não por usuário. Por isso ele é resolvido na arquitetura, não em cada tela:

  • Uma saída só. Todas as chamadas à API Rapidoc passam por um ponto do seu backend, com uma fila que respeita o teto de 30 por minuto. O navegador ou o app nunca chamam a API direto.
  • Leia da sua base primeiro. Planos, especialidades, beneficiários e encaminhamentos PENDING ficam salvos do seu lado; a API só é chamada quando o dado não existe ou precisa mudar.
  • Agrupe. Cadastre até 10 vidas por POST /beneficiaries e busque a disponibilidade do mês inteiro numa chamada, em vez de uma por dia.
  • Espere antes de repetir. Chamada recusada com 403 por limite volta para a fila com espera crescente (backoff) — reenviar na hora só consome o limite de novo.

O que salvar na sua base

Dados de referência mudam pouco: salve uma vez e leia localmente. O mínimo é o uuid do beneficiário.

Sua base · o que salvar e de onde vem
Beneficiário
  • uuid — obrigatório, chave de todas as chamadas da vida
  • cpf e nome, para localizar a vida sem consultar a API
  • documentType e documentNumber, quando estrangeiro
  • situação (ativa ou inativa)
vem de
POST/beneficiaries

Salve o uuid devolvido no cadastro. Ele não muda, mesmo se a vida for inativada e reativada.

Planos
  • uuid do plano
  • paymentType e serviceType
  • nome e descrição
vem de
GET/plans

Atualize quando o contrato mudar (ou uma vez ao dia). O array plans do beneficiário sai daqui.

Especialidades dos planos
  • uuid da especialidade
  • nome
  • plano ao qual pertence
vem de
GET/plans

Já vêm em specialties[] de cada plano — sem chamada extra a /specialties. Mudam junto com o plano.

Encaminhamentos PENDING
  • uuid do encaminhamento
  • beneficiary.uuid, status, createdAt
  • urlPath (PDF)
vem de
GET/beneficiary-medical-referrals?status=PENDING

Não salve: disponibilidade de agenda e status de agendamentos. Esses dados mudam a todo momento e sempre vêm da API.

Sincronizar cadastro

Planos e beneficiários entram na sua base uma vez; o dia a dia lê dali.

Jornada · sincronizar planos e beneficiários
  1. API Rapidoc
    GET/tema/api/plans

    Quando o contrato mudar, ou uma vez ao dia. Uma chamada traz planos e especialidades.

  2. Sua baseSalva planos e especialidades

    uuid, paymentType e serviceType de cada plano; uuid e nome de cada especialidade.

  3. Sua baseMonta o payload das vidas com os planos salvos

    O array plans completo dos titulares sai da sua base, sem consultar a API a cada cadastro. Dependentes vão só com o holder.

  4. API Rapidoc
    POST/tema/api/beneficiaries

    Até 10 vidas por requisição.

  5. Sua baseSalva uuid, cpf, documentType e documentNumber de cada beneficiário

    Toda chamada da vida usa o uuid salvo. cpf e documento servem para reencontrar a vida depois — GET /beneficiaries/{cpf} ou GET /beneficiaries/document/{documentNumber} para estrangeiros.

Encaminhamentos PENDING: sua base primeiro

Uma sincronização periódica mantém os pendentes na sua base; a busca da vida na API é só o fallback.

Sincronização periódica

Jornada · sincronizar encaminhamentos pendentes do cliente
  1. API Rapidoc
    GET/tema/api/beneficiary-medical-referrals?status=PENDING

    Rotina periódica do seu backend. Uma chamada traz os pendentes de todas as vidas do cliente.

  2. Sua baseGrava os PENDING e atualiza os que saíram da lista

    Encaminhamento salvo que não voltou mais como PENDING mudou de situação — não use para agendar.

Na hora de agendar

Jornada · encaminhamento pendente da vida
  1. Sua baseBusca encaminhamento PENDING da vida na sua base

    Pelo uuid do beneficiário salvo.

  2. DecisãoEncontrou?
    SimUsa o uuid salvo — nenhuma chamada à API.
    NãoFallback: busca os encaminhamentos daquela vida na API (próximo passo).
  3. API Rapidoc
    GET/tema/api/beneficiaries/{uuid}/medical-referrals

    Só no fallback. Traz todos os encaminhamentos da vida — filtre os PENDING do seu lado.

  4. Sua baseSalva os encaminhamentos PENDING devolvidos

    Sem pendente: o agendamento segue sem encaminhamento.

Agendar consulta

Especialidade e encaminhamento saem da sua base; a API só é chamada para horários e para criar a consulta.

Especialista

Jornada · agendamento com especialista
  1. Sua baseLista as especialidades dos planos da vida

    Da tabela de especialidades salva. Garante que a especialidade está nos planos — fora disso a API recusa com 400.

  2. Sua baseResolve o encaminhamento PENDING

    Pela jornada anterior. Psicologia e Nutrição não usam encaminhamento.

  3. API Rapidoc
    GET/tema/api/specialty-availability?specialtyUuid={uuid}&beneficiaryUuid={uuid}&dateInitial=14/05/2026&dateFinal=14/06/2026

    Uma chamada traz os horários de até 1 mês a partir do dia da consulta: dateInitial é o dia da chamada (não pode ser data passada) e dateFinal, até 1 mês depois.

  4. Front-endExibe os horários do mês e o beneficiário escolhe

    Síncrono com a API: os horários vêm sempre da chamada acima, com o mês inteiro, nunca da sua base. Troca de dia na tela filtra essa lista.

  5. API Rapidoc
    POST/tema/api/appointments

    Com availabilityUuid, specialtyUuid e beneficiaryMedicalReferralUuid (ou approveAdditionalPayment: true quando não houver encaminhamento). A partir daqui o fluxo é síncrono com a API Rapidoc.

  6. DecisãoResposta 201?
    SimConsulta criada. Status, link e cancelamento são lidos direto da API — nada a salvar; a sincronização periódica tira o encaminhamento dos pendentes.
    NãoHorário já usado por outro agendamento: reconsulte a disponibilidade e ofereça outro horário.

Generalista

Jornada · pronto atendimento
  1. Sua basePega o uuid do beneficiário na sua base
  2. API Rapidoc
    GET/tema/api/beneficiaries/{uuid}/request-appointment

    Direto: é fila de pronto atendimento, sem disponibilidade nem encaminhamento.

Câmera e microfone

A consulta em vídeo acontece na página aberta pela URL de atendimento. Se o seu app ou site não liberar câmera e microfone para essa página, o beneficiário entra na sala, mas o profissional não o vê nem o ouve.

São sempre duas etapas, seja qual for a plataforma:

  1. Pedir a permissão no seu lado — ao navegador ou ao sistema do celular — antes de abrir a URL.
  2. Repassar a permissão para a página da consulta, que roda dentro de um iframe (web) ou de um WebView (app).
Só funciona em HTTPS

Navegadores e WebViews bloqueiam câmera e microfone em páginas servidas por HTTP. A página que abre o iframe precisa estar em HTTPS, inclusive em homologação.

Web (iframe)

Peça a permissão com getUserMedia assim que tiver a URL de atendimento e só então mostre o iframe. Com o acesso já concedido à sua página, o navegador repassa câmera e microfone para a consulta pelo atributo allow. Feche o stream logo depois: ele só serve para disparar o pedido de permissão.

Na Web, a sala de consulta também oferece compartilhamento de tela, usado pelo beneficiário para mostrar um exame, um documento ou qualquer outra imagem ao profissional durante o atendimento. Esse recurso é exclusivo do iframe (não existe no WebView do app) e depende da permissão display-capture: sem ela no allow do iframe (e sem restrição no Permissions-Policy da sua página), o navegador bloqueia o compartilhamento de tela dentro da consulta.

tsx · pedir permissão e abrir a consulta
async function abrirConsulta(urlAtendimento: string) {
  try {
    const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
    stream.getTracks().forEach((track) => track.stop());
    setUrlConsulta(urlAtendimento);
  } catch (erro) {
    if (erro instanceof DOMException && erro.name === 'NotAllowedError') {
      avisar('Libere a câmera e o microfone nas configurações do navegador para iniciar a consulta.');
    } else if (erro instanceof DOMException && erro.name === 'NotFoundError') {
      avisar('Nenhuma câmera ou microfone foi encontrado neste dispositivo.');
    } else {
      avisar('Não foi possível acessar a câmera e o microfone.');
    }
  }
}
tsx · iframe da consulta
<iframe
  src={urlConsulta}
  allow="camera; microphone; display-capture; fullscreen"
  style={{ width: '100%', height: '80vh', border: 0 }}
/>

Headers da sua página

A URL de atendimento não tem domínio fixo: ele pode variar de um atendimento para outro. Use sempre a URL recebida da API, sem montar nem validar o domínio no seu código.

  • Permissions-Policy — se o seu site não declara camera, microphone e display-capture nesse header, não precisa fazer nada: o allow do iframe já delega o acesso para a origem da URL carregada. Se declara, restringir a (self) bloqueia a consulta.
  • Content-Security-Policy — se o seu site restringe frame-src, a URL de atendimento precisa ser permitida. Como o domínio pode mudar, alinhe com a Rapidoc antes de fechar uma lista.
headers · o que bloqueia a consulta
Permissions-Policy: camera=(self), microphone=(self), display-capture=(self)
Content-Security-Policy: frame-src 'self'

React Native (WebView)

O exemplo usa react-native-webview para abrir a consulta e react-native-permissions para pedir o acesso ao usuário. Declare as permissões no Android e no iOS, peça ao usuário e só então abra o WebView.

Android — AndroidManifest.xml

xml · android/app/src/main/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.CAMERA" />
  <uses-permission android:name="android.permission.RECORD_AUDIO" />
  <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

  <uses-feature android:name="android.hardware.camera" android:required="false" />
  <uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
  <uses-feature android:name="android.hardware.microphone" android:required="false" />

  <application>
    <!-- ... -->
  </application>
</manifest>

android:required="false" mantém o app disponível na loja para aparelhos sem câmera ou autofoco.

iOS — Info.plist

xml · ios/SeuApp/Info.plist
<key>NSCameraUsageDescription</key>
<string>Usamos a câmera para que o profissional possa ver você durante a consulta.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Usamos o microfone para que o profissional possa ouvir você durante a consulta.</string>

Sem essas chaves, o iOS encerra o app no momento em que a câmera é pedida. O texto aparece para o usuário na janela de permissão — escreva o motivo com clareza.

No iOS, o react-native-permissions também precisa das permissões declaradas no Podfile:

ruby · ios/Podfile
setup_permissions([
  'Camera',
  'Microphone',
])

Pedir a permissão

tsx · permissões antes de abrir a consulta
import { Platform } from 'react-native';
import { PERMISSIONS, RESULTS, openSettings, requestMultiple } from 'react-native-permissions';

const PERMISSOES =
  Platform.OS === 'ios'
    ? [PERMISSIONS.IOS.CAMERA, PERMISSIONS.IOS.MICROPHONE]
    : [PERMISSIONS.ANDROID.CAMERA, PERMISSIONS.ANDROID.RECORD_AUDIO];

export async function liberarCameraMicrofone(): Promise<boolean> {
  const resultado = Object.values(await requestMultiple(PERMISSOES));

  if (resultado.every((status) => status === RESULTS.GRANTED)) {
    return true;
  }
  if (resultado.some((status) => status === RESULTS.BLOCKED)) {
    await openSettings();
  }
  return false;
}

BLOCKED significa que o usuário negou antes e o sistema não mostra mais a janela: a única saída é levá-lo às configurações do app.

WebView

tsx · tela da consulta
import { WebView } from 'react-native-webview';

export function Consulta({ urlAtendimento }: { urlAtendimento: string }) {
  return (
    <WebView
      source={{ uri: urlAtendimento }}
      javaScriptEnabled
      allowsInlineMediaPlayback
      mediaPlaybackRequiresUserAction={false}
      mediaCapturePermissionGrantType="grantIfSameHostElsePrompt"
    />
  );
}
PropPor quê
javaScriptEnabledA sala de consulta é uma aplicação web e depende de JavaScript.
allowsInlineMediaPlaybackNo iOS, sem ele o vídeo abre em tela cheia do player nativo em vez de ficar dentro da sala.
mediaPlaybackRequiresUserActionfalse deixa o áudio e o vídeo do profissional começarem sem um toque extra do usuário.
mediaCapturePermissionGrantTypeNo iOS, concede câmera e microfone à página da consulta sem repetir o pedido a cada acesso, desde que o app já tenha a permissão.

No Android, o react-native-webview repassa o pedido da página para as permissões do app — por isso CAMERA e RECORD_AUDIO precisam estar concedidas antes de abrir o WebView.

Revisão dos endpoints

Quando cada chamada vale a pena, e o que dela fica salvo do seu lado.

EndpointSalvar na sua baseQuando chamar
GET /plansSim — planos e especialidadesQuando o contrato mudar, ou uma vez ao dia
GET /specialtiesNão é necessárioEvite: as especialidades já vêm em specialties[] do GET /plans
POST /beneficiariesSim — uuid de cada vidaNo cadastro, em lotes de até 10
PUT /beneficiaries/{uuid}Atualize o registro localSó quando algo mudou, com o array plans completo (titular) ou o holder (dependente)
GET /beneficiaries/{cpf}Sim — recupera o uuidSó quando a vida não está na sua base
GET /beneficiaries/document/{documentNumber}Sim — recupera o uuidMesmo caso, para estrangeiros identificados por documento
GET /beneficiariesSimCarga inicial ou conciliação eventual — nunca a cada tela
GET /beneficiary-medical-referralsSim — os PENDINGSincronização periódica, com status=PENDING
GET /beneficiaries/{uuid}/medical-referralsSim — os PENDING da vidaFallback, só quando não há pendente salvo para a vida
GET /specialty-availabilityNãoUma vez por especialidade, com dateInitial/dateFinal cobrindo até 1 mês a partir do dia da consulta; reconsulte se o horário foi tomado
POST /appointmentsNão — daqui em diante, síncrono com a APIUma vez por agendamento
GET /beneficiaries/{uuid}/request-appointmentNãoUma vez, quando o beneficiário pede o pronto atendimento
GET /appointmentsNãoCom filtros (beneficiaryUuid, status, período), nunca a lista inteira em loop
DELETE /appointments/{uuid}NãoUma vez, com 48 horas de antecedência

Custo em chamadas

O mesmo agendamento com especialista, com e sem os dados de referência salvos.

EtapaSem base localCom base local
Planos e especialidades da vidaGET /plansSua base
Encaminhamento pendenteGET /beneficiary-medical-referralsSua base (sincronizada; API da vida só no fallback)
HoráriosGET /specialty-availability a cada dia navegadoGET /specialty-availability do mês, uma vez
AgendarPOST /appointmentsPOST /appointments
Total3 + uma por dia navegado2 chamadas

Faça e não faça

Faça
  • Salvar uuid, cpf e documento do beneficiário assim que o cadastro responder
  • Guardar planos e o uuid das especialidades de cada plano
  • Consultar encaminhamentos PENDING na sua base antes da API
  • Buscar a disponibilidade do mês numa chamada só
  • Sincronizar periodicamente os encaminhamentos PENDING do cliente
  • Centralizar as chamadas numa fila no backend, abaixo de 30 por minuto
  • Pedir câmera e microfone antes de abrir a URL de atendimento
  • Servir em HTTPS a página que abre o iframe da consulta
  • Abrir exatamente a URL de atendimento recebida da API, sem fixar o domínio no código
  • Explicar no Info.plist, em linguagem simples, por que a câmera e o microfone são usados
  • Quando a permissão estiver bloqueada, orientar o usuário e levá-lo às configurações
Não faça
  • Chamar GET /plans ou GET /specialties a cada tela
  • Buscar o beneficiário na API para descobrir um uuid que já estava salvo
  • Consultar a disponibilidade dia a dia enquanto o usuário navega no calendário
  • Guardar horários livres para agendar depois — eles podem ser tomados
  • Reenviar na hora uma chamada recusada, sem espera
  • Ignorar erro de certificado SSL no WebView (ex.: onReceivedSslError seguido de proceed())
  • Liberar tráfego HTTP sem criptografia (usesCleartextTraffic no Android, NSAllowsArbitraryLoads no iOS)
  • Restringir camera/microphone a (self) na página que abre o iframe
  • Deixar aberto o stream do getUserMedia usado só para pedir a permissão
  • Pedir permissões que a consulta não usa, como localização ou armazenamento