developers · v0.1

Send a frame.
Get back a face.

REST over HTTPS. JSON in, JSON out. The same schema whether you POST a JPEG, a base64 blob, or stream a WebSocket. Sub-20-millisecond on CPU, scales linearly per core, zero per-request setup.

01 / auth

A token. A header.

During the private beta, requests carry an Authorization: Bearer … header. The token is scoped to your project and rate-limited per-second, not per-month — Eyeconic is built for live avatars, not batch jobs.

⏵ beta · request access from the manifesto page

http · headers
POST /v1/analyze HTTP/1.1
Host: api.eyeconic.tech
Authorization: Bearer eyc_live_••••••••••••••
Content-Type: multipart/form-data; boundary=…
Accept: application/json
02 / endpoints

Three routes. One schema.

POST /v1/analyze Multipart upload. One image, one face, full analysis JSON. The path of least resistance.
POST /v1/analyze/base64 Same response, but the image is sent as a base64 string in JSON. Better for browser → API loops.
GET /health Returns {"ok": true} plus model version. Useful for load balancers and uptime monitors.
GET /docs Interactive Swagger UI. Try a request from the browser. Open ↗
03 / code

Wire it up in four lines.

shell · bash
# single-shot analysis from a JPEG on disk
curl -X POST https://api.eyeconic.tech/v1/analyze \
  -H "Authorization: Bearer $EYC_TOKEN" \
  -F "image=@face.jpg"
python · requests
import requests

resp = requests.post(
    "https://api.eyeconic.tech/v1/analyze",
    headers={"Authorization": f"Bearer {TOKEN}"},
    files={"image": open("face.jpg", "rb")},
    timeout=2,
)
data = resp.json()
print(data["eyes"]["left"]["gaze"])
# -> [-0.18, 0.04]
javascript · browser
const grab = async () => {
  const v = document.querySelector('video');
  const c = document.createElement('canvas');
  c.width = v.videoWidth; c.height = v.videoHeight;
  c.getContext('2d').drawImage(v, 0, 0);

  const r = await fetch('/v1/analyze/base64', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      image_base64: c.toDataURL('image/jpeg', 0.8),
    }),
  });
  return r.json();
};
unity · C# coroutine
IEnumerator StreamFrame(byte[] jpeg) {
    var form = new WWWForm();
    form.AddBinaryData("image", jpeg, "f.jpg", "image/jpeg");

    using var req = UnityWebRequest.Post(EYC_URL, form);
    req.SetRequestHeader("Authorization", $"Bearer {token}");
    yield return req.SendWebRequest();

    var face = JsonUtility.FromJson<EyeFrame>(req.downloadHandler.text);
    ApplyToMetaHuman(face.blendshapes);
}
websocket · streaming
const ws = new WebSocket('wss://api.eyeconic.tech/v1/stream');
ws.addEventListener('open', () => {
  ws.send(JSON.stringify({ token: EYC_TOKEN, fps: 30 }));
  setInterval(() => ws.send(grabFrame()), 33);
});
ws.addEventListener('message', (e) => {
  const face = JSON.parse(e.data);
  rig.apply(face.blendshapes);
});
// rolling p50: 14.8 ms · client-perceived
04 / blendshape catalog

Fifty-two shapes.

ARKit-compatible names. If you already rig MetaHuman, VRoid, VRM, or Apple's reference faces, the keys map 1:1. No renaming.

eyeBlinkLeft
eyeBlinkRight
eyeLookDownLeft
eyeLookDownRight
eyeLookInLeft
eyeLookInRight
eyeLookOutLeft
eyeLookOutRight
eyeLookUpLeft
eyeLookUpRight
eyeSquintLeft
eyeSquintRight
eyeWideLeft
eyeWideRight
browDownLeft
browDownRight
browInnerUp
browOuterUpLeft
browOuterUpRight
cheekPuff
cheekSquintLeft
cheekSquintRight
noseSneerLeft
noseSneerRight
jawForward
jawLeft
jawRight
jawOpen
mouthClose
mouthFunnel
mouthPucker
mouthLeft
mouthRight
mouthSmileLeft
mouthSmileRight
mouthFrownLeft
mouthFrownRight
mouthDimpleLeft
mouthDimpleRight
mouthStretchLeft
mouthStretchRight
mouthRollLower
mouthRollUpper
mouthShrugLower
mouthShrugUpper
mouthPressLeft
mouthPressRight
mouthLowerDownLeft
mouthLowerDownRight
mouthUpperUpLeft
mouthUpperUpRight
tongueOut

Got a token?

Press a button, you'll be looking at JSON in under two minutes.