Router — Démarrage rapide
Router est une passerelle IA distincte — remplace https://api.openai.com par une URL unique qui route vers 40+ modèles (Anthropic, OpenAI, Google, Cohere, Mistral, open-source). Guard anti-exfiltration actif par défaut, facturation CAD, fallback automatique, streaming SSE, tool calling, vision — tout OpenAI-compatible.
1. Obtenir une clé API
- Connectez-vous à meridian.signalorange.ca/login
- Paramètres → Clés API → Nouvelle clé
- Donnez-lui un nom (
production,staging,local-dev) - Copiez la clé immédiatement — elle ne sera plus affichée
export MERIDIAN_API_KEY="sk-mer-..."
Besoin de gérer les clés programmatiquement (CI, rotation) ? Voir Router — API keys self-service.
2. Premier appel — curl
curl https://router.signalorange.ca/api/v1/chat/completions \
-H "Authorization: Bearer $ROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "auto",
"messages": [
{"role": "user", "content": "Bonjour, présente-toi en une phrase."}
]
}'
Réponse :
{
"id": "chatcmpl-abc",
"object": "chat.completion",
"choices": [
{"index": 0, "message": {"role": "assistant", "content": "Bonjour ! ..."}, "finish_reason": "stop"}
],
"model": "anthropic/claude-sonnet-4",
"usage": {"total_tokens": 142},
"billing": {"cost_cad_cents": 1},
"guard": {"detections_count": 0, "request_id": "chatcmpl-abc"}
}
Le champ guard.request_id peut être utilisé pour inspecter ce que Guard a filtré via l'API d'audit.
3. Python (SDK OpenAI drop-in)
from openai import OpenAI
client = OpenAI(
base_url="https://router.signalorange.ca/api/v1",
api_key=os.environ["MERIDIAN_API_KEY"],
)
response = client.chat.completions.create(
model="auto",
messages=[{"role": "user", "content": "Bonjour"}],
)
print(response.choices[0].message.content)
client.models.list() fonctionne aussi — Router expose /api/v1/models au format OpenAI.
4. Node.js / TypeScript
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://router.signalorange.ca/api/v1",
apiKey: process.env.MERIDIAN_API_KEY,
});
const response = await client.chat.completions.create({
model: "auto",
messages: [{ role: "user", content: "Bonjour" }],
});
5. litellm
import litellm
response = litellm.completion(
model="openai/auto",
messages=[{"role": "user", "content": "Bonjour"}],
api_base="https://router.signalorange.ca/api/v1",
api_key=os.environ["MERIDIAN_API_KEY"],
)
Streaming (SSE)
Ajoutez stream: true pour recevoir la réponse en chunks OpenAI-compatibles :
stream = client.chat.completions.create(
model="auto",
messages=[{"role": "user", "content": "Raconte-moi une histoire."}],
stream=True,
)
for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
print(delta, end="", flush=True)
Raw SSE (pour debug) :
curl https://router.signalorange.ca/api/v1/chat/completions \
-H "Authorization: Bearer $ROUTER_API_KEY" \
-H "Content-Type: application/json" \
-N \
-d '{"model":"auto","messages":[{"role":"user","content":"Hi"}],"stream":true}'
Format renvoyé :
data: {"id":"chatcmpl-...","choices":[{"delta":{"content":"Hello"},"finish_reason":null}]}
data: {"id":"chatcmpl-...","choices":[{"delta":{"content":" there"},"finish_reason":null}]}
data: {"id":"chatcmpl-...","choices":[{"delta":{},"finish_reason":"stop"}]}
data: [DONE]
Tool calling / function calling
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Returns current weather for a city",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}
}]
response = client.chat.completions.create(
model="claude-sonnet-4",
messages=[{"role": "user", "content": "Weather in Montréal?"}],
tools=tools,
tool_choice="auto",
)
# If the model decides to call the tool:
if response.choices[0].finish_reason == "tool_calls":
for call in response.choices[0].message.tool_calls:
print(call.function.name, call.function.arguments)
Supporte tool_choice: "none" | "auto" | "required" | {"type": "function", "function": {"name": "..."}}.
JSON mode
Force le modèle à retourner du JSON valide :
{
"model": "auto",
"messages": [
{"role": "system", "content": "Tu renvoies UNIQUEMENT du JSON valide."},
{"role": "user", "content": "Extrais le nom et la ville: Alice vit à Québec."}
],
"response_format": {"type": "json_object"}
}
La réponse content sera un document JSON parsable.
Structured outputs (json_schema)
Pour exiger une forme précise, pas seulement du JSON valide :
{
"model": "auto",
"messages": [
{"role": "user", "content": "Extrais le nom et la ville: Alice vit à Québec."}
],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "personne",
"strict": true,
"schema": {
"type": "object",
"required": ["nom", "ville"],
"properties": {
"nom": {"type": "string"},
"ville": {"type": "string"}
},
"additionalProperties": false
}
}
}
}
Comportement :
- Modèles OpenAI / Anthropic : schéma imposé par le fournisseur (natif).
- Autres modèles : schéma transmis tel quel + sortie validée par Router. Si la sortie n'est pas du JSON valide ou ne respecte pas le schéma → HTTP 422
schema_validation_failed(extrait brut enerror.raw_preview, aucune facturation — la réservation est libérée). La sortie n'est jamais corrigée silencieusement. GET /api/v1/modelsexposesupported_parameterspar modèle (support natif annoncé par le catalogue amont).
Outils serveur (web_search / fetch)
{"type": "web_search"} et {"type": "fetch"} dans tools sont exécutés par Router pendant la conversation (pas de clé cliente requise) :
{
"model": "auto",
"messages": [{"role": "user", "content": "Quel est le taux directeur actuel ? Vérifie en ligne."}],
"tools": [{"type": "web_search"}, {"type": "fetch"}]
}
Router injecte les définitions appelables, exécute les tool_calls du modèle (recherche puis lecture de pages, jusqu'à 2 tours), puis répond avec tool_executions: [{name, arguments, result}] — result reprend le format exact des endpoints POST /api/v1/tools/web_search et POST /api/v1/tools/fetch, texte intégral inclus. Chaque tour de modèle est facturé normalement en plus du coût fixe de l'outil.
Vision multimodal
Passez un tableau de parts comme content :
response = client.chat.completions.create(
model="gpt-5",
messages=[{
"role": "user",
"content": [
{"type": "text", "text": "Décris cette image en 2 phrases."},
{"type": "image_url", "image_url": {"url": "https://example.com/photo.jpg"}}
]
}],
)
Guard redacte les parts text (par exemple si le texte contient un NAS) mais laisse passer les parts image_url telles quelles. Les modèles vision (GPT-5, Claude Opus, Gemini 2.5 Pro) voient l'image directement.
Idempotency-Key
Retry-safe : si vous renvoyez la même requête avec le même header Idempotency-Key, Router retourne la réponse en cache sans ré-appeler le modèle :
curl https://router.signalorange.ca/api/v1/chat/completions \
-H "Authorization: Bearer $ROUTER_API_KEY" \
-H "Idempotency-Key: req_2026-04-21-doc-42" \
-H "Content-Type: application/json" \
-d '{"model":"auto","messages":[...]}'
- Clé unique par requête (UUID recommandé)
- Fenêtre de 24h
- Si vous réutilisez la clé avec un body différent → HTTP 422
idempotency_conflict - Retournée avec header
Idempotent-Replayed: true - Pas supporté avec
stream: true(les streams ne se rejouent pas)
Strict mode (désactiver fallback)
Par défaut, si le modèle demandé tombe, Router bascule automatiquement vers un modèle compatible. Avec strict: true, Router n'essaie que le modèle demandé :
{
"model": "anthropic/claude-opus-4-7",
"strict": true,
"messages": [...]
}
Si Claude Opus n'est pas disponible → HTTP 502 provider_error, pas de fallback silencieux. Utile pour :
- A/B tests où la métrique exige le modèle exact
- Compliance qui impose un modèle contractuel
- Debug quand vous voulez un échec explicite
Preflight — estimer le coût avant l'appel
curl https://router.signalorange.ca/api/v1/chat/completions/estimate \
-H "Authorization: Bearer $ROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"auto","messages":[...],"max_tokens":500}'
Réponse :
{
"model": "anthropic/claude-sonnet-4",
"estimated_prompt_tokens": 142,
"max_completion_tokens": 500,
"cost_usd_cents": 62,
"cost_cad_cents": 96,
"exchange_rate": 1.35,
"disclaimer": "Estimation based on character-count/4 heuristic..."
}
Aucun appel upstream, aucun token consommé. Voir Router — Facturation pour les détails.
Et après ?
- Router — Catalogue de modèles — quel modèle pour quel usage
- Router — Guard anti-exfiltration — ce qui est filtré + configuration par organisation
- Router — API d'audit — inspecter ce que Guard a redacté
- Router — Webhooks — réagir aux événements en temps réel
- Router — API keys — rotation programmatique
- Router — Facturation CAD — crédits, usage, estimation
- Router — Codes d'erreur — gestion 400/402/403/412/429/502
- Référence API complète — tous les endpoints avec schémas