Descrição
Retorna os detalhes completos de uma nota específica, incluindo:
- Transcrição completa (quando disponível)
- Seções estruturadas (anamnese, exame físico, diagnóstico, etc.)
- Metadados e informações do áudio
- URLs de download do áudio e transcrição
As seções estruturadas (note_sections) estão disponíveis apenas neste endpoint. Elas não são retornadas na listagem GET /notes
Parâmetros da Requisição
Path Parameters
| Parâmetro | Tipo | Obrigatório | Descrição |
id | UUID | ✅ Sim | ID único da nota |
| Header | Obrigatório | Valor |
X-API-KEY | ✅ Sim | Sua chave de API |
Exemplos de Requisição
cURL
curl -H "X-API-KEY: cvn_live_abc123def456..." \
https://api.connectvets.com/notes/6a4fe1de-52c4-4b2b-a30f-4b3fa9d7d8b3
JavaScript/TypeScript
async function getNoteById(noteId) {
const response = await fetch(`https://api.connectvets.com/notes/${noteId}`, {
headers: {
'X-API-KEY': 'cvn_live_abc123def456...'
}
});
if (!response.ok) {
throw new Error(`Nota não encontrada: ${response.status}`);
}
return response.json();
}
// Uso
const note = await getNoteById('6a4fe1de-52c4-4b2b-a30f-4b3fa9d7d8b3');
console.log(`Nota: ${note.name}`);
console.log(`Status: ${note.transcription_status}`);
console.log(`Seções: ${note.note_sections.length}`);
Python
import requests
def get_note_by_id(api_key, note_id):
url = f"https://api.connectvets.com/notes/{note_id}"
headers = {"X-API-KEY": api_key}
response = requests.get(url, headers=headers)
if response.status_code == 404:
raise ValueError(f"Nota {note_id} não encontrada")
response.raise_for_status()
return response.json()
# Uso
api_key = "cvn_live_abc123def456..."
note_id = "6a4fe1de-52c4-4b2b-a30f-4b3fa9d7d8b3"
note = get_note_by_id(api_key, note_id)
print(f"Paciente: {note['name']}")
print(f"Status: {note['transcription_status']}")
if note['note_sections']:
print("Seções encontradas:")
for section in note['note_sections']:
print(f"- {section['title']}: {len(section['content'])} caracteres")
package main
import (
"encoding/json"
"fmt"
"net/http"
)
func getNoteByID(apiKey, noteID string) (*Note, error) {
url := fmt.Sprintf("https://api.connectvets.com/notes/%s", noteID)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
req.Header.Set("X-API-KEY", apiKey)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode == 404 {
return nil, fmt.Errorf("nota %s não encontrada", noteID)
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("erro %d: %s", resp.StatusCode, resp.Status)
}
var note Note
err = json.NewDecoder(resp.Body).Decode(¬e)
return ¬e, err
}
// Uso
note, err := getNoteByID("cvn_live_abc123def456...", "6a4fe1de-52c4-4b2b-a30f-4b3fa9d7d8b3")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Paciente: %s\n", note.Name)
fmt.Printf("Seções: %d\n", len(note.NoteSections))
Resposta de Sucesso (200 OK)
{
"id": "6a4fe1de-52c4-4b2b-a30f-4b3fa9d7d8b3",
"name": "Rex",
"gender": "male",
"audio_name": "rex_visit_20240214.wav",
"audio_url": "https://cdn.connectvets.com/audio/6a4fe1de.wav",
"transcription_status": "completed",
"transcription_url": "https://cdn.connectvets.com/transcripts/6a4fe1de.txt",
"external_id": "CLIENTE_123",
"metadata": [
{"key": "procedimento", "value": "vacina"},
{"key": "veterinario", "value": "Dr. João Silva"},
{"key": "peso", "value": "12.5kg"}
],
"note_sections": [
{
"id": "9b7d8b6a-12e3-45fa-9c1c-7e12f5c4a1b2",
"title": "Anamnese",
"label": "anamnesis",
"content": "Paciente Rex, cão macho de 3 anos, da raça Golden Retriever. Proprietário relata que o animal tem apresentado diminuição do apetite nos últimos 3 dias. Não houve vômitos ou diarréia. Animal ativo, mas menos que o habitual.",
"order": "1"
},
{
"id": "0c0d1e2f-3a4b-5c6d-7e8f-9a0b1c2d3e4f",
"title": "Exame Físico",
"label": "physical_exam",
"content": "Peso: 28kg. Temperatura: 38.8°C. Frequência cardíaca: 90 bpm. Frequência respiratória: 24 mpm. Mucosas rosadas e úmidas. Linfonodos normais. Abdômen sem alterações à palpação.",
"order": "2"
},
{
"id": "1d1e2f3g-4b5c-6d7e-8f9g-0a1b2c3d4e5f",
"title": "Diagnóstico",
"label": "diagnosis",
"content": "Suspeita de gastrite leve. Possivelmente relacionada à mudança de alimentação recente. Recomendado dieta leve e observação.",
"order": "3"
},
{
"id": "2e2f3g4h-5c6d-7e8f-9g0h-1b2c3d4e5f6g",
"title": "Tratamento",
"label": "treatment",
"content": "Prescrito omeprazol 20mg, 1 comprimido pela manhã por 7 dias. Dieta com ração gastro intestinal. Retorno em 1 semana ou se piorar dos sintomas.",
"order": "4"
}
],
"created_at": "2024-02-14T18:25:43Z",
"updated_at": "2024-02-14T18:30:23Z"
}
Seções Estruturadas
Tipos de Seções Comuns
| Label | Título | Descrição |
anamnesis | Anamnese | Histórico e relato do proprietário |
physical_exam | Exame Físico | Avaliação física e sinais vitais |
diagnosis | Diagnóstico | Conclusões e diagnósticos |
treatment | Tratamento | Prescrições e recomendações |
observations | Observações | Notas adicionais |
follow_up | Seguimento | Instruções de retorno |
Estrutura das Seções
| Campo | Tipo | Descrição |
id | UUID | Identificador único da seção |
title | String | Nome exibido da seção |
label | String | Identificador interno (para programação) |
content | String | Conteúdo transcrito da seção |
order | String | Ordem de exibição (1, 2, 3…) |
Estados da Nota
Nota Pendente/Processando
{
"id": "7b5fe2ef-63d5-5c3c-b41f-5c4fa8e8d9c4",
"name": "Luna",
"transcription_status": "processing",
"transcription_url": null,
"note_sections": [],
"created_at": "2024-02-15T09:15:22Z",
"updated_at": "2024-02-15T09:16:10Z"
}
Quando o status é pending ou processing, os campos transcription_url e note_sections estarão vazios ou nulos
{
"id": "8c6fe3f0-74e6-6d4d-c52f-6d5fa9f9e0d5",
"name": "Milo",
"transcription_status": "failed",
"transcription_url": null,
"note_sections": [],
"created_at": "2024-02-16T14:20:15Z",
"updated_at": "2024-02-16T14:22:30Z"
}
Casos de Uso Práticos
Exibir Detalhes no Frontend
// Componente React para exibir nota
function NoteDetails({ noteId }) {
const [note, setNote] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function loadNote() {
try {
const noteData = await getNoteById(noteId);
setNote(noteData);
} catch (error) {
console.error('Erro ao carregar nota:', error);
} finally {
setLoading(false);
}
}
loadNote();
}, [noteId]);
if (loading) return <div>Carregando...</div>;
if (!note) return <div>Nota não encontrada</div>;
return (
<div>
<h1>{note.name}</h1>
<p>Status: {note.transcription_status}</p>
{note.note_sections.map(section => (
<div key={section.id}>
<h3>{section.title}</h3>
<p>{section.content}</p>
</div>
))}
</div>
);
}
Baixar Transcrição
// Baixar arquivo de transcrição
async function downloadTranscription(note) {
if (note.transcription_status !== 'completed' || !note.transcription_url) {
throw new Error('Transcrição não disponível');
}
const response = await fetch(note.transcription_url);
const text = await response.text();
// Criar download
const blob = new Blob([text], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${note.name}_transcricao.txt`;
a.click();
URL.revokeObjectURL(url);
}
Buscar Seção Específica
// Encontrar seção por label
function findSection(note, label) {
return note.note_sections.find(section => section.label === label);
}
// Extrair informações específicas
function extractKeyInfo(note) {
const anamnesis = findSection(note, 'anamnesis');
const diagnosis = findSection(note, 'diagnosis');
const treatment = findSection(note, 'treatment');
return {
paciente: note.name,
problema: anamnesis?.content.substring(0, 100) + '...',
diagnostico: diagnosis?.content || 'Não informado',
tratamento: treatment?.content || 'Não informado'
};
}
Gerar Relatório
// Gerar relatório estruturado
function generateReport(note) {
if (note.transcription_status !== 'completed') {
return 'Nota ainda não processada';
}
let report = `RELATÓRIO VETERINÁRIO\n\n`;
report += `Paciente: ${note.name}\n`;
report += `Data: ${new Date(note.created_at).toLocaleDateString('pt-BR')}\n`;
// Metadados
if (note.metadata.length > 0) {
report += `\nInformações Adicionais:\n`;
note.metadata.forEach(meta => {
report += `- ${meta.key}: ${meta.value}\n`;
});
}
report += `\n${'='.repeat(50)}\n\n`;
// Seções ordenadas
const sections = note.note_sections.sort((a, b) =>
parseInt(a.order) - parseInt(b.order)
);
sections.forEach(section => {
report += `${section.title.toUpperCase()}\n`;
report += `${'-'.repeat(section.title.length)}\n`;
report += `${section.content}\n\n`;
});
return report;
}
// Sincronizar nota com banco local
async function syncNoteToLocal(noteId) {
try {
const remoteNote = await getNoteById(noteId);
// Verificar se há atualizações
const localNote = await getLocalNote(noteId);
if (localNote && localNote.updated_at >= remoteNote.updated_at) {
return { updated: false, note: localNote };
}
// Salvar/atualizar no banco local
await saveLocalNote(remoteNote);
return { updated: true, note: remoteNote };
} catch (error) {
console.error(`Erro ao sincronizar nota ${noteId}:`, error);
throw error;
}
}
Tratamento de Erros
404 Not Found
{
"error": "not_found",
"message": "Note not found",
"code": "NOTE_NOT_FOUND"
}
Causas:
- ID da nota inexistente
- Nota pertence a outro tenant/clínica
- Nota foi deletada
401 Unauthorized
{
"error": "unauthorized",
"message": "API key is required",
"code": "MISSING_API_KEY"
}
Exemplo de Tratamento
async function safeGetNote(noteId) {
try {
const response = await fetch(`/notes/${noteId}`, {
headers: { 'X-API-KEY': apiKey }
});
if (response.status === 404) {
return { error: 'Nota não encontrada', note: null };
}
if (response.status === 401) {
return { error: 'Acesso negado', note: null };
}
if (!response.ok) {
return { error: `Erro ${response.status}`, note: null };
}
const note = await response.json();
return { error: null, note };
} catch (error) {
return { error: error.message, note: null };
}
}
// Uso com tratamento
const { error, note } = await safeGetNote(noteId);
if (error) {
console.error(error);
} else {
console.log('Nota carregada:', note.name);
}
Downloads e Arquivos
Baixar Áudio Original
async function downloadAudio(note) {
const response = await fetch(note.audio_url);
const blob = await response.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = note.audio_name;
a.click();
URL.revokeObjectURL(url);
}
Verificar Disponibilidade
function checkAvailability(note) {
return {
audio: !!note.audio_url,
transcription: note.transcription_status === 'completed' && !!note.transcription_url,
sections: note.note_sections.length > 0,
completed: note.transcription_status === 'completed'
};
}
const availability = checkAvailability(note);
if (availability.transcription) {
console.log('Transcrição disponível para download');
}
Cache Inteligente
// Cache com TTL para notas completadas
const noteCache = new Map();
async function getCachedNote(noteId) {
const cached = noteCache.get(noteId);
// Cache hit para notas completadas (não mudam)
if (cached && cached.transcription_status === 'completed') {
return cached;
}
// Cache hit com TTL para notas em processamento
if (cached && Date.now() - cached._cached_at < 30000) { // 30s
return cached;
}
// Cache miss - buscar nova versão
const note = await getNoteById(noteId);
note._cached_at = Date.now();
noteCache.set(noteId, note);
return note;
}
Próximos Passos
API Key para autenticação