API guide

Base URL: https://www.open-sensations.com/v1. JSON over HTTPS. The API version is in the path (/v1); changes within it are additions only.

Authentication

Send Authorization: Bearer osn_live_… on POST /v1/classify. GET /v1/meta and GET /v1/health are public.

Keys are issued by the project, one for each app that connects. A key has its own rate limit and can be revoked. To run without a key, download the weights: see Models.

Quick start

curl https://www.open-sensations.com/v1/meta

curl https://www.open-sensations.com/v1/classify \
  -H "Authorization: Bearer osn_live_…" \
  -H "Content-Type: application/json" \
  -d '{"blocks":[{"id":"a","text":"My hands are freezing."}]}'
const response = await fetch("https://www.open-sensations.com/v1/classify", {
  method: "POST",
  headers: { authorization: "Bearer osn_live_…", "content-type": "application/json" },
  body: JSON.stringify({ blocks: [{ id: "a", text: "My hands are freezing." }] }),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const { results, labels, provenance } = await response.json();
console.log(results[0].scores, provenance.model);

Routes

Classify

Request

{
  "blocks": [
    { "id": "a", "text": "My hands are freezing.", "before": "…", "after": "…" }
  ],
  "client": { "name": "my-app" }
}

Response

{
  "results": [
    { "id": "a", "scores": [0.02, 0.01, …] }
  ],
  "labels": "senses@0",
  "provenance": {
    "observer": { "id": "open-sensations", "version": "0.2.0" },
    "implementation": "…",
    "model": { "id": "…", "revision": "…" },
    "requestId": "req_…",
    "at": "2026-01-01T00:00:00.000Z",
    "durationMs": 120
  },
  "usage": { "blocks": 1, "scored": 1, "chars": 22 }
}

Reading the scores

Each score says how strongly the sentence's words are tied to that sense. The senses are scored independently, so several can be high at once. Vision runs high on most concrete words, so read it against the other senses. A score is not an intensity, and not evidence that anyone (or any model) feels anything. See limitations.

Errors

Every error has the same shape. retryable says whether the same request can succeed later.

{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests for this key. Try again in 12 seconds.",
    "retryable": true,
    "retryAfterSeconds": 12,
    "requestId": "req_…"
  }
}
StatusCodeMeaning
400invalid_requestThe body isn't JSON, or a field is missing, the wrong type or too long.
400unknown_implementationThe request named an implementation this observer doesn't offer.
401unauthorizedThe key is missing, malformed, unknown or revoked.
413payload_too_largeThe body is over 256 KB.
429rate_limitedToo many requests for this key. Wait for the Retry-After header.
502upstream_errorThe model couldn't score any block. Check retryable before trying again.
503model_loadingThe model is starting. Wait for the Retry-After header.
503unavailableThe observer isn't configured to classify yet.
500internalSomething went wrong on our side.

Limits

Browsers

The API answers cross-origin requests from any origin and never uses cookies. A key in browser code is visible to anyone, so call the API from your server.