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
GET /v1/metareturns everything a client needs to attach the observer: its identity, the 6 labels in order with their groups and meanings, how scores are to be read, the model and revision, and the limits.POST /v1/classifyscores blocks of text. One result per block, in request order.GET /v1/healthreports whether the observer is up and whether the model is warm.
Classify
Request
{
"blocks": [
{ "id": "a", "text": "My hands are freezing.", "before": "…", "after": "…" }
],
"client": { "name": "my-app" }
}blocks: 1 to 32 blocks. Each hastext(up to 4,000 characters) and an optionalidthat is echoed back.beforeandafter: optional context, up to 600 characters each. It is accepted but not scored.client.name: optional, to identify your app in our logs.
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 }
}scoresholds 6 numbers from 0 to 1, in the label order that/v1/metareturns.- A block that couldn't be scored has
"scores": nullwith areasonandretryable. A blank block has the reasonempty. provenancenames the model and revision that produced the scores. Store it with them.
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_…"
}
}| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | The body isn't JSON, or a field is missing, the wrong type or too long. |
| 400 | unknown_implementation | The request named an implementation this observer doesn't offer. |
| 401 | unauthorized | The key is missing, malformed, unknown or revoked. |
| 413 | payload_too_large | The body is over 256 KB. |
| 429 | rate_limited | Too many requests for this key. Wait for the Retry-After header. |
| 502 | upstream_error | The model couldn't score any block. Check retryable before trying again. |
| 503 | model_loading | The model is starting. Wait for the Retry-After header. |
| 503 | unavailable | The observer isn't configured to classify yet. |
| 500 | internal | Something went wrong on our side. |
Limits
- 60 requests per minute for each key.
- 32 blocks per request, 4,000 characters per block, 256 KB per request body.
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.