GET
/
notes
/
{noteId}
Obter nota específica
curl --request GET \
  --url https://api-sandbox.connectvets.com.br/notes/v1/notes/{noteId} \
  --header 'X-API-KEY: <api-key>'
{
  "data": {
    "id": "6a4fe1de-52c4-4b2b-a30f-4b3fa9d7d8b3",
    "name": "Rex",
    "gender": "male",
    "audio_name": "consulta_rex_20240214.wav",
    "audio_url": "https://cdn.connectvets.com/audio/6a4fe1de.wav",
    "transcription_status": "completed",
    "transcription_url": "https://cdn.connectvets.com/transcriptions/6a4fe1de.txt",
    "external_id": "CLIENTE_123",
    "metadata": [
      {
        "key": "procedimento",
        "value": "vacina"
      }
    ],
    "created_at": "2024-02-14T18:25:43Z",
    "updated_at": "2024-02-14T18:30:23Z",
    "note_sections": [
      {
        "id": "abc123-def456-ghi789",
        "title": "Anamnese",
        "label": "anamnese",
        "content": "Paciente apresenta sintomas de...",
        "order": "1"
      }
    ]
  }
}

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âmetroTipoObrigatórioDescrição
idUUID✅ SimID único da nota

Headers

HeaderObrigatórioValor
X-API-KEY✅ SimSua 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")

Go

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(&note)
    return &note, 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

LabelTítuloDescrição
anamnesisAnamneseHistórico e relato do proprietário
physical_examExame FísicoAvaliação física e sinais vitais
diagnosisDiagnósticoConclusões e diagnósticos
treatmentTratamentoPrescrições e recomendações
observationsObservaçõesNotas adicionais
follow_upSeguimentoInstruções de retorno

Estrutura das Seções

CampoTipoDescrição
idUUIDIdentificador único da seção
titleStringNome exibido da seção
labelStringIdentificador interno (para programação)
contentStringConteúdo transcrito da seção
orderStringOrdem 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

Nota com Erro

{
  "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;
}

Sincronização com Sistema Local

// 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');
}

Performance e Cache

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

Authorizations

X-API-KEY
string
header
required

API Key para autenticação

Path Parameters

noteId
string<uuid>
required

ID da nota

Response

200
application/json

Nota encontrada

The response is of type object.