Try it
Paste an API key, edit the request and send it to the live API. No key yet? Get a free beta key.
Uses your key against the live API. Counts toward your usage.
Quick start
- Create a free account.
- Open Dashboard → API keys and create a key. You can reveal and copy it there at any time.
- Send your first request and wait up to 10 seconds for the answer:
curl -X POST "https://www.laya-api.de/api/v1/systemone?wait=10" \
-H "Authorization: Bearer $LAYA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"state": "Help! My payouts have been failing for 3 days.",
"model": "laya-latest",
"questions": {
"is_urgent": {
"type": "noul",
"instructions": "Does this convey urgency?",
"criteria": {
"true": "Explicitly time-sensitive",
"false": "No urgency expressed"
}
}
}
}'
If the answer is ready within 10 seconds you get 200 and the finished job:
{
"id": "01J9Z3K6V8X2N4QH7R5T0WBC1D",
"object": "job",
"status": "completed",
"model": "laya-latest",
"metadata": null,
"created_at": "2026-09-28T09:14:02Z",
"started_at": "2026-09-28T09:14:02Z",
"completed_at": "2026-09-28T09:14:03Z",
"result": {
"model": "laya-1.0.0",
"answers": {
"is_urgent": { "type": "noul", "noul": 0.95 }
},
"usage": { "input_tokens": 296, "output_tokens": 20 }
},
"error": null
}
Authentication
Send your key as a bearer token on every request. Keys start with
laya_sk_. You can create several keys and revoke them one by one in the dashboard.
Keep keys on your server; never ship them in browser or mobile code.
Authorization: Bearer laya_sk_…
Create a request
POST /api/v1/systemone
The body uses the same fields as Jev.
| Field | Type | Description |
|---|---|---|
| state required | string | object | array | The content to evaluate: plain text, or structured data such as a support ticket or a list of search results. |
| model required | string | laya-latest or laya-1. |
| questions required | map<id, Question> | 1 to 32 named questions. Each id matches ^[A-Za-z0-9_-]{1,64}$ and becomes the key of its answer. |
| metadata | object | Anything you want to attach, such as your own ticket id. It is echoed back on the job. |
Optional query parameter: wait, 0 to 30 seconds. See
Waiting and polling.
Optional header: Idempotency-Key. See
Idempotency.
Questions
Every question has a type, instructions
(string, object or array) and criteria. The criteria define every
answer the model is allowed to give.
noul: yes or no
Criteria must have exactly the keys true and false.
"is_urgent": {
"type": "noul",
"instructions": "Does this convey urgency?",
"criteria": {
"true": "Explicitly time-sensitive",
"false": "No urgency expressed"
}
}
choice: one option out of many
Criteria map each option key to a description. 2 to 50 options.
"team": {
"type": "choice",
"instructions": "Which team should handle this ticket?",
"criteria": {
"billing": "Payments, invoices, refunds",
"bug": "Something in the product is broken",
"feature_request": "The customer wants something new"
}
}
score: a rating on ordered levels
Criteria are a list of level descriptions, lowest first. 2 to 11 levels.
"politeness": {
"type": "score",
"instructions": "How polite is the agent's reply?",
"criteria": ["Rude", "Neutral", "Friendly"]
}
Waiting and polling
Every request becomes a job. Without wait, or when the job isn't
finished in time, you get 202 Accepted with the job in status
queued or processing. When the job is
completed or failed within the wait, you get 200 OK.
To check on a job later, fetch it by id:
GET /api/v1/jobs/{id}
curl "https://www.laya-api.de/api/v1/jobs/01J9Z3K6V8X2N4QH7R5T0WBC1D" \
-H "Authorization: Bearer $LAYA_API_KEY"
Jobs are kept, but their content is not: 7 days
after a job completes or fails, its metadata,
result and error come back as
null. Store the answers you need on your side.
A small polling loop in JavaScript:
const headers = {
Authorization: `Bearer ${process.env.LAYA_API_KEY}`,
"Content-Type": "application/json",
};
let job = await fetch("https://www.laya-api.de/api/v1/systemone?wait=10", {
method: "POST",
headers,
body: JSON.stringify({ state, model: "laya-latest", questions }),
}).then((r) => r.json());
while (job.status === "queued" || job.status === "processing") {
await new Promise((resolve) => setTimeout(resolve, 1000));
job = await fetch(`https://www.laya-api.de/api/v1/jobs/${job.id}`, { headers }).then((r) => r.json());
}
if (job.status === "completed") {
console.log(job.result.answers);
}
The same in Python:
import os, time, requests
BASE = "https://www.laya-api.de/api/v1"
headers = {"Authorization": f"Bearer {os.environ['LAYA_API_KEY']}"}
job = requests.post(
f"{BASE}/systemone",
params={"wait": 10},
headers=headers,
json={"state": state, "model": "laya-latest", "questions": questions},
).json()
while job["status"] in ("queued", "processing"):
time.sleep(1)
job = requests.get(f"{BASE}/jobs/{job['id']}", headers=headers).json()
print(job["result"]["answers"] if job["status"] == "completed" else job["error"])
The job object
{
"id": "01J9Z3K6V8X2N4QH7R5T0WBC1D", // ULID
"object": "job",
"status": "queued | processing | completed | failed",
"model": "laya-latest", // the model you asked for
"metadata": { … } | null, // echoed from your request
"created_at": "2026-09-28T09:14:02Z",
"started_at": "…" | null,
"completed_at": "…" | null,
"result": { // set when completed
"model": "laya-1.0.0", // the exact model version that ran
"answers": { … },
"usage": { "input_tokens": 296, "output_tokens": 20 }
} | null,
"error": { "type": "…", "message": "…" } | null // set when failed
}
Answers
result.answers has one entry per question id, in the same format as Jev.
"answers": {
"is_urgent": { "type": "noul", "noul": 0.95 },
"team": {
"type": "choice",
"choice": "billing",
"probabilities": { "billing": 0.81, "bug": 0.12, "feature_request": 0.07 },
"confidence": 0.92
},
"politeness": {
"type": "score",
"score": 1.5,
"probabilities": { "0": 0.1, "1": 0.3, "2": 0.6 },
"legend": { "0": "Rude", "1": "Neutral", "2": "Friendly" },
"confidence": 0.9
}
}
- noul: the probability that the answer is yes, from 0 to 1.
- choice: the most likely option, the probability of every option, and how confident the model is overall.
- score: the probability-weighted level (so 1.5 sits between level 1 and level 2), the probability of each level, and a legend mapping level numbers back to your descriptions.
Confidence is not the same as probability. A choice can have a clear winner and still low confidence when the state is ambiguous. A common pattern: act automatically above a confidence threshold, and send everything else to a human.
List jobs
GET /api/v1/jobs
Newest first. Optional query parameters: status
(queued, processing,
completed or failed),
limit (1 to 100) and cursor.
Pass next_cursor from one page as cursor
to get the next one. It is null on the last page.
{
"data": [ { "id": "01J9Z3K6V8X2N4QH7R5T0WBC1D", "object": "job", … } ],
"next_cursor": "eyJpZCI6IjAxSjla…" | null
}
Idempotency
Networks fail. To retry a POST safely, send an Idempotency-Key
header (any string up to 255 characters, such as a UUID). Sending the same key again returns
the original job instead of creating a second one.
Idempotency-Key: 9f1c2b8e-3a4d-4c1e-9b7a-2f6d8e0c5a11
Usage and tokens
Each completed job reports usage.input_tokens and
usage.output_tokens. Only input tokens count toward your usage.
Output tokens are free. Your dashboard shows input tokens per day and per month. During the
open beta, all usage is free.
Errors and limits
| Status | Meaning |
|---|---|
| 401 | The key is missing, wrong or revoked. |
| 404 | No job with that id exists on your account. |
| 422 | The request body didn't validate. The response names each field that failed. |
| 429 | Rate limit reached: 60 requests per minute per key during the beta. Wait for the number of seconds in the Retry-After header, then retry with exponential backoff. |
{
"message": "The questions.team.criteria field must have at least 2 items.",
"errors": {
"questions.team.criteria": ["The questions.team.criteria field must have at least 2 items."]
}
}
A job with status failed is not an HTTP error: the request was fine,
but the job couldn't be processed. Read error.type and
error.message for the reason, and send a new request if you want to try again.