API développeur
Lecture publique des sessions : index, blocs originaux, NDJSON, WebSocket et export.
Endpoints publics, sans clé, en lecture seule : suivis XR, audio, passthrough vidéo et capteurs Android. L’écriture et les actions d’administration (enregistrer, renommer, supprimer) restent authentifiées.
https://rag.ingevision.cloud/xr-agent/public/apiFlux live
wss://rag.ingevision.cloud/xr-agent/public/api/live/{session}JSON continu — NDJSON
Script Python prêt à utiliser (bibliothèque standard, sans dépendance). python xr_agent_collect.py SESSION_ID session.ndjson ajoute les mesures au fichier, reprend après coupure et déduplique les identifiants. Ctrl+C pour arrêter.
Un objet JSON par ligne. Par défaut, renvoie l’historique puis reste ouvert pour les nouveaux blocs. Sans clé. Les nouvelles données n’arrivent que pendant un enregistrement lancé depuis l’observatoire.
GET /sessions/{session}/stream.ndjson
# Historique uniquement (réponse terminée)
GET /sessions/{session}/stream.ndjson?follow=0
# Nouvelles données uniquement
GET /sessions/{session}/stream.ndjson?replay=0Chaque mesure contient type: sample, id, session, stream, seq, sampleIndex, les timestamps du bloc et data (mesure originale, avec ses propres timestamps et flags). Audio et vidéo : type: media, lien url relatif au domaine, SHA-256 et taille ; pas de binaire encodé en JSON. heartbeat toutes les 15 secondes sans données en cours, à ignorer.
curl -N "https://rag.ingevision.cloud/xr-agent/public/api/sessions/SESSION_ID/stream.ndjson" >> session.ndjson
Les identifiants se récupèrent via /sessions, y compris pendant la pause. À la reconnexion, l’historique est rejoué : dédupliquer par id, ne pas filtrer seulement par la plus grande séquence (les uploads arrivent parfois dans le désordre). L’ordre inter-flux et l’ordre du replay ne sont pas chronologiques ; utiliser les timestamps. La livraison live suit l’enregistrement des blocs, pas chaque instant physique de mesure. Un client trop lent peut être déconnecté et reprendre avec replay.
01 / Endpoints
| GET | Réponse |
|---|---|
| /sessions | Liste des identifiants de sessions. |
| /monitor/sessions | Sessions triées par dernière réception, volumes et compteurs par flux, nom, date de création, taille sur disque (diskBytes) et session en cours d’enregistrement. |
| /control | État de l’enregistrement piloté depuis l’observatoire, dernier statut du casque (connecté, porté, batterie, partage vidéo, suivis) et espace utilisé sur le quota de 20 Go. |
| /sessions/{session}/latest | Derniers échantillons, derniers événements par type, état réseau, audio RMS et index du dernier bloc de chaque flux. |
| /sessions/{session} | Index complet : séquence, SHA-256, octets, temps d’acquisition et heure de réception. |
| /sessions/{session}/{stream}/{seq} | Bloc original, identique à celui acquis avant compression de transport. |
| /sessions/{session}/preview.jpg | Première image JPEG du dernier bloc H.264 reçu. 404 avant la première vidéo. |
| /sessions/{session}/export.tar | Archive tar non compressée d’une session, diffusée en continu : README.txt, manifest.json, <session>/session.json (index) et les blocs originaux <session>/<flux>/<seq>.ndjson|pcm|h264. Content-Length exact. |
| /export.tar | Même archive pour toutes les sessions, un dossier par session. Longueur inconnue à l’avance. Deux exports simultanés au maximum (429 sinon). |
Le WebSocket /live/{session} diffuse chaque nouveau bloc après stockage. Aucun rattrapage automatique : connecter le WebSocket, lire l’index, puis dédupliquer les blocs par (session, stream, seq). CORS autorise les lectures depuis un autre site.
02 / Récupérer une session en Python
import json, urllib.request, hashlib
BASE = "https://rag.ingevision.cloud/xr-agent/public/api"
def read(path):
with urllib.request.urlopen(BASE + path, timeout=30) as r:
return r.read()
def get(path):
return json.loads(read(path))
sessions = get("/monitor/sessions")["sessions"]
if not sessions:
raise SystemExit("Aucune session")
session = sessions[0]["session"]
latest = get(f"/sessions/{session}/latest")
print(latest["data"].get("telemetry"))
index = get(f"/sessions/{session}")
for chunk in index["chunks"]:
stream, seq = chunk["stream"], chunk["seq"]
payload = read(f"/sessions/{session}/{stream}/{seq}")
assert hashlib.sha256(payload).hexdigest() == chunk["sha256"]
# Enregistrer ou transmettre payload à votre service.
if stream == "telemetry":
for line in payload.splitlines():
sample = json.loads(line)
# Vérifier queryOk et les flags de validité avant interprétation.
03 / Consommer les blocs live en JavaScript
const base = "https://rag.ingevision.cloud/xr-agent/public/api";
const { sessions } = await fetch(base + "/monitor/sessions").then(r => r.json());
if (!sessions.length) throw new Error("Aucune session");
const session = sessions[0].session;
const socket = new WebSocket(base.replace("https:", "wss:") + "/live/" + session);
socket.binaryType = "arraybuffer";
socket.onmessage = ({ data }) => {
const bytes = new Uint8Array(data);
const headerLength = new DataView(data).getUint32(0, false);
const header = JSON.parse(new TextDecoder().decode(bytes.slice(4, 4 + headerLength)));
const payload = bytes.slice(4 + headerLength);
console.log(header.stream, header.seq, payload.byteLength);
// payload est le bloc original : NDJSON, PCM ou H.264.
};
// Ajouter une reconnexion avec délai progressif et un rattrapage via l’index.
04 / Formats et interprétation
| telemetry | NDJSON, une ligne par frame Unity : tête, manettes, entrées, yeux, visage, corps et mains. Valeurs natives OVRPlugin. |
| meta | Modèle, versions, identifiant de session, noms des expressions et des os, conventions. |
| events | États du casque, transport, focus, erreurs, inventaire Android et video_frame_index. |
| android | NDJSON avec sensorId, type, accuracy, timeUs et valeurs. Les types privés Meta nécessitent leur propre interprétation. |
| audio | PCM signé 16 bits little-endian, mono, 48 000 Hz. Les interruptions de focus créent des trous temporels. |
| video | H.264 Annex B, 1280 × 720, cible 30 images/s. Chaque bloc commence sur une image clé avec paramètres codec. L’événement video_frame_index fournit les offsets et timestamps des images. |
| scene | Ancres et géométrie d’une scène sauvegardée lorsqu’elle est accessible. Son absence ne prouve pas un problème réseau. |
Horloges. timeUs/endUs : microsecondes monotones depuis le démarrage Android. receivedUtc : réception serveur, distincte de l’acquisition. Les statuts fournissent des paires timeUs/utcMs, et les frames timeUs/ovrTimeSeconds. Préserver ces horloges pour synchroniser les modalités.
Coordonnées. OVRPlugin : mètres, main droite, quaternion x/y/z/w. Repère demandé EyeLevel / Device ; Y ne mesure pas la hauteur au sol. Scène : repère Unity main gauche. Consulter les champs de convention avant fusion.
Validité. queryOk=false implique une mesure indisponible. Un appel réussi peut encore contenir IsValid=false. Les articulations corporelles sont estimées ; les poids de confiance faciaux peuvent ne pas être renseignés. Ne pas inventer une mesure à partir de ces champs. Le diamètre pupillaire et les images des caméras internes ne sont pas disponibles.
Continuité. Le casque suspend les mesures hors focus. Les limites réseau peuvent causer du retard et des pertes, exposées dans capture_health.transport. Comparer les séquences et timestamps ; ne pas traiter un snapshot ancien comme une acquisition actuelle. Le dashboard affiche un aperçu JPEG à 1 image/s, pas la totalité des images vidéo.