Como este contrato é mantido
Autentique o parceiro #
https://devapiprescriptions.amplimed.cloud
/partners/authenticate
Troque clientId e clientSecret por um token de acesso.
A integração começa trocando as credenciais do parceiro por um token de acesso, usado como
Bearer token na criação do atendimento. O token tem validade curta, informada em
expires_at.
Esta chamada é server-to-server
clientSecret nunca pode sair do
backend do parceiro. Não o coloque em JavaScript, em aplicativo mobile nem no repositório — quem tem o
segredo consegue abrir atendimentos em nome do seu serviço. Guarde-o em variável de ambiente ou em um
gerenciador de segredos.Os nomes dos campos mudaram na V2
clientSecret (e não
secretKey), e a resposta traz
access_token,
token_type e
expires_at em
snake_case — não token /
tokenType. As duas versões não são
intercambiáveis.Cache do token
O expires_at vem no formato
Y-m-d H:i:s. Guarde o token em
cache até esse momento em vez de autenticar a cada atendimento — é o que a integração de referência deste
projeto faz. Sempre respeite o expires_at devolvido em vez de
assumir um prazo fixo.
Quando a autenticação falha
Credencial recusada não se resolve repetindo a chamada — não coloque retry em loop aqui. Confira o ambiente para o qual você está apontando e se as credenciais são daquele ambiente. Retry com backoff faz sentido para falha de rede e erro 5xx, não para credencial inválida.
Corpo da requisição
| Campo | Tipo | Detalhes |
|---|---|---|
clientId
|
string |
obrigatório
Identificador do parceiro, fornecido pela Amplimed. |
clientSecret
|
string |
obrigatório
Segredo do parceiro. Na V1 o campo chama-se clientSecret; na V2 passou a ser secretKey. |
Resposta 200
Token de acesso do parceiro.
| Campo | Tipo | Detalhes |
|---|---|---|
access_token
|
string |
obrigatório
Token de acesso do parceiro. |
token_type
|
string |
obrigatório
Tipo do token, usado para montar o header Authorization. |
expires_at
|
string |
obrigatório
Momento de expiração do token, no formato Y-m-d H:i:s. |
curl -X POST 'https://devapiprescriptions.amplimed.cloud/partners/authenticate' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"clientId":"SEU_CLIENT_ID","clientSecret":"SEU_CLIENT_SECRET"}'
use Illuminate\Support\Facades\Http;
$response = Http::acceptJson()
->post('https://devapiprescriptions.amplimed.cloud/partners/authenticate', [
'clientId' => 'SEU_CLIENT_ID',
'clientSecret' => 'SEU_CLIENT_SECRET',
]);
if ($response->failed()) {
// Trate o erro com $response->status() e $response->json('error_code').
}
$data = $response->json();
$curl = curl_init('https://devapiprescriptions.amplimed.cloud/partners/authenticate');
$payload = json_encode([
'clientId' => 'SEU_CLIENT_ID',
'clientSecret' => 'SEU_CLIENT_SECRET',
]);
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => ['Accept: application/json', 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => $payload,
]);
$body = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
$data = json_decode($body, true);
const response = await fetch('https://devapiprescriptions.amplimed.cloud/partners/authenticate', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"clientId": "SEU_CLIENT_ID",
"clientSecret": "SEU_CLIENT_SECRET"
}),
});
if (!response.ok) {
// Trate o erro com response.status e o error_code do corpo.
}
const data = await response.json();
import requests
response = requests.post(
'https://devapiprescriptions.amplimed.cloud/partners/authenticate',
headers={'Accept': 'application/json'},
json={
'clientId': 'SEU_CLIENT_ID',
'clientSecret': 'SEU_CLIENT_SECRET',
},
)
response.raise_for_status()
data = response.json()
{
"access_token": "eyJhbGciOiJIUzI1NiJ9.TOKEN_DE_ACESSO.ASSINATURA",
"token_type": "Bearer",
"expires_at": "2026-01-01 00:15:00"
}
Crie o atendimento #
https://devapiprescriptions.amplimed.cloud
/partners/attendance
Envie profissional e paciente e receba a URL da prescrição.
Com o token de acesso em mãos, você cria o atendimento. A resposta traz a
url da prescrição — é ela que o
parceiro carrega no src do iframe.
Um atendimento por consulta: não reaproveite a mesma URL para outro paciente.
Header de autorização
Monte o header com o token_type e o
access_token da etapa anterior,
resultando em Authorization: Bearer
<access_token>. Envie o tipo uma única vez — repetir o prefixo (Bearer Bearer ...) é um erro comum
quando se concatena token_type com um helper que já
adiciona o prefixo sozinho.
embedded_type define como a prescrição aparece
coupled renderiza a prescrição
dentro do fluxo da sua página, num iframe fixo.
suspended abre em uma camada
flutuante sobre a página, ocupando a tela até ser fechada. A escolha é sua e pode variar por tela — as duas
estão demonstradas em Demonstrações.
Campos do paciente
Na V1 os campos do paciente são em português e planos (nome,
documento,
celular…). O
celular vai somente com dígitos,
incluindo código do país e DDD, e a
data_nascimento no formato
Y-m-d. Na V2 esses dados foram
reorganizados em objetos aninhados e em inglês.
LGPD: dado de paciente trafega aqui
url devolvida dá
acesso ao atendimento: trate-a como credencial e não a exponha fora da sessão do profissional.Requer o token de parceiro
Authorization: Bearer
<token>.Corpo da requisição
| Campo | Tipo | Detalhes |
|---|---|---|
id_profissional
|
string |
obrigatório
Identificador do profissional na Amplimed. |
embedded_type
|
string |
obrigatório
coupled
suspended
Modo de incorporação: coupled renderiza o iframe no fluxo da página; suspended abre em camada flutuante. |
paciente
|
object |
obrigatório
Dados do paciente do atendimento. A integração de referência envia todos os campos abaixo; a obrigatoriedade individual de cada um não está formalizada no contrato da V1. |
paciente.nome
|
string |
opcional
|
paciente.documento
|
string |
opcional
|
paciente.tipo_documento
|
string |
opcional
Tipo do documento enviado, por exemplo CPF. |
paciente.data_nascimento
|
string |
opcional
Data de nascimento no formato Y-m-d. |
paciente.sexo_biologico
|
string |
opcional
|
paciente.celular
|
string |
opcional
Telefone celular com código do país e DDD, somente dígitos. |
paciente.cidade
|
string |
opcional
|
paciente.estado
|
string |
opcional
Sigla da unidade federativa. |
paciente.email
|
string email |
opcional
|
Resposta 200
Atendimento criado.
| Campo | Tipo | Detalhes |
|---|---|---|
url
|
string |
obrigatório
URL da prescrição embarcada, carregada pelo parceiro no src do iframe. |
curl -X POST 'https://devapiprescriptions.amplimed.cloud/partners/attendance' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer SEU_TOKEN_DE_PARCEIRO' \
-H 'Content-Type: application/json' \
-d '{"id_profissional":"20332bce8072e0fa50e094939b337012","embedded_type":"suspended","paciente":{"nome":"Maria Oliveira","documento":"98765432100","tipo_documento":"CPF","data_nascimento":"1990-05-15","sexo_biologico":"Feminino","celular":"5549999999999","cidade":"Chapecó","estado":"SC","email":"maria@exemplo.com"}}'
use Illuminate\Support\Facades\Http;
$response = Http::acceptJson()
->withToken($partnerToken)
->post('https://devapiprescriptions.amplimed.cloud/partners/attendance', [
'id_profissional' => '20332bce8072e0fa50e094939b337012',
'embedded_type' => 'suspended',
'paciente' => [
'nome' => 'Maria Oliveira',
'documento' => '98765432100',
'tipo_documento' => 'CPF',
'data_nascimento' => '1990-05-15',
'sexo_biologico' => 'Feminino',
'celular' => '5549999999999',
'cidade' => 'Chapecó',
'estado' => 'SC',
'email' => 'maria@exemplo.com',
],
]);
if ($response->failed()) {
// Trate o erro com $response->status() e $response->json('error_code').
}
$data = $response->json();
$curl = curl_init('https://devapiprescriptions.amplimed.cloud/partners/attendance');
$payload = json_encode([
'id_profissional' => '20332bce8072e0fa50e094939b337012',
'embedded_type' => 'suspended',
'paciente' => [
'nome' => 'Maria Oliveira',
'documento' => '98765432100',
'tipo_documento' => 'CPF',
'data_nascimento' => '1990-05-15',
'sexo_biologico' => 'Feminino',
'celular' => '5549999999999',
'cidade' => 'Chapecó',
'estado' => 'SC',
'email' => 'maria@exemplo.com',
],
]);
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => ['Accept: application/json', 'Content-Type: application/json', 'Authorization: Bearer '.$partnerToken],
CURLOPT_POSTFIELDS => $payload,
]);
$body = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
$data = json_decode($body, true);
const response = await fetch('https://devapiprescriptions.amplimed.cloud/partners/attendance', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: `Bearer ${partnerToken}`,
},
body: JSON.stringify({
"id_profissional": "20332bce8072e0fa50e094939b337012",
"embedded_type": "suspended",
"paciente": {
"nome": "Maria Oliveira",
"documento": "98765432100",
"tipo_documento": "CPF",
"data_nascimento": "1990-05-15",
"sexo_biologico": "Feminino",
"celular": "5549999999999",
"cidade": "Chapecó",
"estado": "SC",
"email": "maria@exemplo.com"
}
}),
});
if (!response.ok) {
// Trate o erro com response.status e o error_code do corpo.
}
const data = await response.json();
import requests
response = requests.post(
'https://devapiprescriptions.amplimed.cloud/partners/attendance',
headers={'Accept': 'application/json', 'Authorization': f'Bearer {partner_token}'},
json={
'id_profissional': '20332bce8072e0fa50e094939b337012',
'embedded_type': 'suspended',
'paciente': {
'nome': 'Maria Oliveira',
'documento': '98765432100',
'tipo_documento': 'CPF',
'data_nascimento': '1990-05-15',
'sexo_biologico': 'Feminino',
'celular': '5549999999999',
'cidade': 'Chapecó',
'estado': 'SC',
'email': 'maria@exemplo.com',
},
},
)
response.raise_for_status()
data = response.json()
{
"url": "https://prescricao.amplimed.com.br/atendimento/ATENDIMENTO_ID?token=..."
}
Carregue a URL no iframe e escute os eventos da prescrição.
A prescrição V1 roda dentro de um iframe: você carrega a
url devolvida pela etapa anterior
e escuta os eventos que a prescrição emite por
window.postMessage.
O iframe precisa de acesso a storage
allow="storage-access" no
iframe. Sem isso, navegadores que bloqueiam cookies de terceiros impedem a prescrição de manter a sessão
do profissional.Eventos emitidos
Cada mensagem chega como um objeto com
type e
data. Ignore mensagens cujo
type você não conhece: a
janela recebe mensagens de outras origens também.
| Evento | Quando acontece |
|---|---|
attendance.close
|
O profissional fechou o atendimento. No modo suspenso, é o gancho para fechar a camada flutuante. |
prescription.generate
|
Uma prescrição foi gerada. |
attest.generate
|
Um atestado foi gerado. |
examRequest.list
|
Uma solicitação de exames foi emitida. |
Valide a origem das mensagens
event.origin
antes de agir sobre a mensagem, aceitando apenas a origem da prescrição Amplimed. Sem essa checagem,
qualquer página aberta em outra aba poderia disparar o seu tratamento de eventos.<!-- A url vem do seu backend, de POST /partners/attendance. -->
<iframe
id="iframe-prescription"
src=""
allow="storage-access"
style="width: 100%; min-height: 65vh; border: 0"
></iframe>
// A url vem do seu backend, de POST /partners/attendance.
const { url } = await criarAtendimentoNoSeuBackend();
const iframe = document.getElementById('iframe-prescription');
iframe.src = url;
window.addEventListener('message', (event) => {
if (event.origin !== PRESCRIPTION_ORIGIN) return;
switch (event.data.type) {
case 'attendance.close':
// Fecha a camada flutuante, se estiver no modo suspenso.
break;
case 'prescription.generate':
case 'attest.generate':
case 'examRequest.list':
// Registre o documento gerado no seu sistema.
break;
}
});
import { useEffect } from 'react';
export function Prescricao({ url, onFechar, onDocumento }) {
useEffect(() => {
function aoReceberMensagem(event) {
if (event.origin !== PRESCRIPTION_ORIGIN) return;
if (event.data.type === 'attendance.close') {
onFechar();
return;
}
if (['prescription.generate', 'attest.generate', 'examRequest.list'].includes(event.data.type)) {
onDocumento(event.data);
}
}
window.addEventListener('message', aoReceberMensagem);
// Sem o cleanup, cada remontagem acumula um listener a mais.
return () => window.removeEventListener('message', aoReceberMensagem);
}, [onFechar, onDocumento]);
return (
<iframe
src={url}
allow="storage-access"
style={{ width: '100%', minHeight: '65vh', border: 0 }}
/>
);
}
<script setup>
import { onBeforeUnmount, onMounted } from 'vue';
const props = defineProps({ url: String });
const emit = defineEmits(['fechar', 'documento']);
function aoReceberMensagem(event) {
if (event.origin !== PRESCRIPTION_ORIGIN) return;
if (event.data.type === 'attendance.close') {
emit('fechar');
return;
}
if (['prescription.generate', 'attest.generate', 'examRequest.list'].includes(event.data.type)) {
emit('documento', event.data);
}
}
onMounted(() => window.addEventListener('message', aoReceberMensagem));
// Sem o cleanup, cada remontagem acumula um listener a mais.
onBeforeUnmount(() => window.removeEventListener('message', aoReceberMensagem));
</script>
<template>
<iframe
:src="url"
allow="storage-access"
style="width: 100%; min-height: 65vh; border: 0"
></iframe>
</template>