# Secnd documentation > Secnd serves Ren 1.1, a decision model, through an HTTP API. Send a situation and a typed question (choice, score or true/false) to POST /v1/decide; the answer is a probability for every option and an action, act or ask_human, from a threshold you set. Private beta: API access is by request until launch. Numerical examples are illustrative, not measured model results. Source: https://docs.secndai.com/ # Introduction A situation and a typed question in. A calibrated answer out. Ren 1.1 is Secnd's decision model. Give it the facts, ask a narrow question, and receive a structured answer with probabilities. Your application controls the available answers and what happens next. The model is served only through the Secnd API. It does not write a conversation, generate a tool implementation, or run your business logic. ## Three question types | Question | Use it for | What to inspect | | --- | --- | --- | | Choice | Select a queue, document class, or tool | Selected option and probability distribution | | Score | Judge against an ordered rubric | Level probabilities and expected score | | True/false | Check one explicit proposition | Probability assigned to true | ## Keep decisions reviewable A calibrated probability is a frequency statement across comparable decisions, not proof that this decision is correct. Confidence and the server-side gate help route uncertainty to a human. Test your own data and define the cost of a wrong action before enabling automation. > Private beta. API access is by request until launch: [request access](https://secndai.com/contact). This documentation describes an integration under development. The API reference is the local gateway contract snapshot; a documented proposal is not a deployed capability. Numerical examples are illustrative, not measured model results. ## Start building - [Make your first request](/quickstart) with curl, Python, or TypeScript. - [Choose a question type](/primitives/index) and define the answer space. - [Understand the gate](/confidence) before connecting an action. - [Read the failure modes](/honest-limits) and plan a human fallback. --- Source: https://docs.secndai.com/quickstart # Quickstart Make one request with curl, Python, or TypeScript. Until launch, API access is by request: [request access](https://secndai.com/contact). The console at console.secndai.com is not open yet. Use an invited account and create a server-side API key. Set `SECND_API_KEY` through your secret manager. Check `GET /v1/models` and substitute the full model ID your account returns; these examples use `ren-1.1.0` (Ren 1.1). ## curl ```bash curl --fail-with-body https://api.secndai.com/v1/decide \ -H "Authorization: Bearer $SECND_API_KEY" \ -H 'Content-Type: application/json' \ --data '{ "model": "ren-1.1.0", "type": "choice", "situation": "A customer says the same order was charged twice.", "question": "Which queue should handle this ticket?", "options": [ {"id": "billing", "description": "Payment and invoice problems"}, {"id": "delivery", "description": "Shipping and parcel problems"} ], "threshold": 0.9 }' ``` ## Python Install the local SDK from the repository root with `uv pip install ./packages/sdk-python` in your environment. The import is `secnd`. ```python import os from secnd import Secnd client = Secnd(api_key=os.environ["SECND_API_KEY"]) result = client.decide({ "model": "ren-1.1.0", "type": "choice", "situation": "A customer says the same order was charged twice.", "question": "Which queue should handle this ticket?", "options": [ {"id": "billing", "description": "Payment and invoice problems"}, {"id": "delivery", "description": "Shipping and parcel problems"}, ], "threshold": 0.9, }) print(result["choice"], result["probability"], result["action"]) ``` ## TypeScript Build and install the local `@secnd/sdk` package as described in the [SDK overview](/sdk/index). ```typescript import { Secnd } from "@secnd/sdk"; const apiKey = process.env.SECND_API_KEY; if (!apiKey) throw new Error("Set SECND_API_KEY"); const client = new Secnd({ apiKey }); const result = await client.decide({ model: "ren-1.1.0", type: "choice", situation: "A customer says the same order was charged twice.", question: "Which queue should handle this ticket?", options: [ { id: "billing", description: "Payment and invoice problems" }, { id: "delivery", description: "Shipping and parcel problems" }, ], threshold: 0.9, }); console.log(result.choice, result.probability, result.action); ``` ## Handle the answer Check `action` before running any downstream operation. On `ask_human`, enqueue the situation and candidate answer for review. On `act`, your code still enforces permissions and business rules. The API itself does not move a ticket or issue a refund. The response also includes the full probability map, resolved model ID, effective threshold, and usage. See [probability and the gate](/confidence), [errors](/errors), and [request limits](/limits) before adding production traffic. --- Source: https://docs.secndai.com/coding-agents # Use Secnd with coding agents Point Claude Code, Codex or Cursor at the Secnd docs and skill. Until launch, API access is by request: [request access](https://secndai.com/contact). The console at console.secndai.com is not open yet. A coding agent writes a correct Secnd integration when it reads the contract before it writes code. These docs publish plain-text files for agents: - [llms.txt](https://docs.secndai.com/llms.txt): an index of every page, each linked as Markdown. - [llms-full.txt](https://docs.secndai.com/llms-full.txt): every page in one Markdown file. - [skill.md](https://docs.secndai.com/skill.md): the Secnd skill, a short guide to calling `POST /v1/decide`. Any page is also available as Markdown: add `.md` to its URL, for example `https://docs.secndai.com/quickstart.md`. ## Copy the agent prompt Paste this at the start of a task that touches the Secnd API: ```prompt I am integrating the Secnd decision API (Ren 1.1) into this project. 1. Read https://docs.secndai.com/llms.txt first, then the pages for the question type we need. 2. Call POST https://api.secndai.com/v1/decide from server code only. Read the key from the SECND_API_KEY environment variable. 3. Pin the model to ren-1.1.0 and send an explicit threshold. 4. Before any side effect, branch on "action": on "ask_human", send the case to human review; on "act", still enforce our permissions and business rules. 5. Handle errors by HTTP status and "code". If the call fails, never invent an answer. Check every field name against https://docs.secndai.com/openapi.json instead of guessing. ``` ## Install the Secnd skill The skill is one `SKILL.md` file. It tells the agent when to use Secnd, how to ask a choice, score or true/false question, how to read `probability` and `action`, and where the docs are. ### Claude Code Save it as a project skill: ```bash mkdir -p .claude/skills/secnd curl -fsSL https://docs.secndai.com/skill.md -o .claude/skills/secnd/SKILL.md ``` Use `~/.claude/skills/secnd` instead to make it available in every project. Claude Code loads the skill when a task matches its description, and `/secnd` runs it directly. ### Codex, Cursor and other agents Add this line to the instructions file your agent reads, such as `AGENTS.md`: ```text Before writing code that uses the Secnd API, read https://docs.secndai.com/llms.txt and https://docs.secndai.com/skill.md. ``` ## Review what the agent writes Check the generated code for the same things you would check by hand: - The API key stays on the server and out of logs. - True/false questions use `"type": "noul"`, without `options` or `levels`. - Every side effect waits for `action`, and `ask_human` reaches a person. See [escalate to a human](/patterns/escalate-to-human). - Failed calls are retried within a bounded budget and never turned into an answer. See [errors](/errors). --- Source: https://docs.secndai.com/authentication # Authentication Keep bearer keys on your server. Until launch, API access is by request: [request access](https://secndai.com/contact). The console at console.secndai.com is not open yet. Create a key in the Secnd console at [console.secndai.com/keys](https://console.secndai.com/keys). Copy the secret when it is first shown; later views show only its prefix. Keep it in your server's secret store as `SECND_API_KEY`. Send this header to both `POST /v1/decide` and `GET /v1/models`: ```http Authorization: Bearer ``` Never embed a key in browser JavaScript, mobile binaries, a public repository, or a docs playground shared with another person. Your browser should call your own backend, which supplies the key. ## Rotate or revoke Create a replacement, update your server's secret, verify one request, then revoke the old key in the console. If a key is exposed, revoke it immediately. A revoked key receives `401`; retries cannot fix it. API keys authorize inference. Console sign-in is separate and does not replace a bearer key in API requests. Dashboard administration is account-scoped. --- Source: https://docs.secndai.com/models # Models Pin a model before tuning a workflow. Ren 1.1 is available through the API only. Model weights are not distributed with the SDKs. | Identifier | Meaning | | --- | --- | | `ren-1.1.0` | Ren 1.1; full release ID, released 2026-11-13 | | `ren-latest` | Stable alias; currently resolves to `ren-1.1.0` | | `ren-preview` | Preview alias; currently resolves to `ren-1.1.0` | A semantic version is an identifier, not a benchmark claim or a guarantee that your account can access it. In the current gateway, both aliases resolve to the configured pinned model. ```bash curl --fail-with-body https://api.secndai.com/v1/models \ -H "Authorization: Bearer $SECND_API_KEY" ``` The response has a `data` array listing the full release ID and both aliases. Each entry includes `id`, `aliases`, `release_date` (date only, `2026-11-13`), `max_options`, `max_input_tokens`, and `price_per_million_input_tokens_usd`. Decision responses return the resolved ID in both `model` and `usage.model`. Unknown IDs, shortened IDs such as `ren-1.1`, and case variants such as `REN-LATEST` return `400` with `code: "unknown_model"` and a message naming `ren-1.1.0`, `ren-latest`, and `ren-preview`. IDs are case-sensitive. ## Version changes Pin the full ID when comparing distributions or choosing thresholds. When changing it, reevaluate the full workflow on permitted development data, including human escalation and downstream actions. An alias can change without your application code changing. ## Usage and price Every successful decision includes `usage.input_tokens`, `usage.output_tokens` (always `0`), and `usage.model`. Input usage is measured on the complete formatted input. The price per million input tokens is operator configuration exposed by model discovery; there is no public fixed price or billing-provider integration promised here. Local development's configured price is not a commercial offer. Agree production access and pricing with Secnd before relying on them. --- Source: https://docs.secndai.com/changelog # Changelog Track the API contract and model identifiers. ## 2026-11-13 — Ren 1.1 (ren-1.1.0) - Public model Ren uses semantic versions. Pin `ren-1.1.0`; `ren-latest` and `ren-preview` both resolve to it. Responses echo `ren-1.1.0` in `model` and `usage.model`. - `GET /v1/models` lists the full ID and both aliases with `release_date: "2026-11-13"`. - Unknown, shortened, and case-variant IDs return `400 unknown_model`, naming all valid IDs. ## 2026-11-12 — API rules before go-live - The gated number is `probability`, the chosen answer's probability. `p` and `confidence` are removed. For true/false, the probability of true is `probabilities.true`. - Status codes: JSON nesting deeper than 64 is `400`; option and level counts are `422`; bodies over 4 MB and inputs over 2048 tokens stay `413`. A request that can never succeed never receives `429`. - Unknown fields are rejected at every level. Every response carries `X-Request-Id`; errors are `{code, message}` and never repeat request content. - Default rate limits rise to 300 requests per minute per key and 600 per account. `429` responses add `retry-after-ms`; decisions carry `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`. ## 2026-11-12 — private-beta integration draft The documented gateway surface includes typed choice, score, and true/false decisions; full semantic model IDs and aliases; bearer authentication; input-token usage; and server-side routing thresholds. `ren-1.1.0` is the full release identifier used in examples. Deployment and account availability must be checked through `GET /v1/models`. This entry records a contract milestone, not a measured model-quality improvement or a deployment announcement. Future model changes should record the new pinned ID and any changes to input handling, primitives, calibration, or gate behavior. Recheck your thresholds before adopting a new version. --- Source: https://docs.secndai.com/primitives/index # Question types Choose the smallest question that expresses your decision. All question types use `POST /v1/decide`. Supply a `model`, a nonblank `situation`, and a clear `question`. Set `type` explicitly in new integrations. | Type | Request-specific field | Result-specific field | | --- | --- | --- | | [`choice`](/primitives/choice) | `options`: 2–255 named alternatives | `choice`, `probabilities` | | [`score`](/primitives/score) | `levels`: 2–10 ordered levels, numeric string IDs from 1 through 10 | `expected_score`, level probabilities | | [`noul`](/primitives/true-false) | No options or levels; ask one true/false proposition | `probabilities.true`: probability of true | Every type returns `choice`, `probability` (of the chosen answer), `probabilities`, `action`, `threshold`, `gate`, and `usage`. `choice` is always a string ID, including numeric levels and `true` or `false`. The model chooses among supplied answers. Include a real fallback option when your categories are not exhaustive; there is no hidden abstention class. A routing action of `ask_human` is separate from an answer option. Unknown fields are rejected. Few-shot `examples` are not supported: omit that field. Text-only decisions are documented here; do not send image assets or multipart bodies. --- Source: https://docs.secndai.com/primitives/choice # Choice Select one of the alternatives your application can handle. Use short, distinct IDs for routing and descriptions to explain their meaning. IDs must be unique and nonblank. Supply 2–255 options. ```json request { "model": "ren-1.1.0", "type": "choice", "situation": "A customer reports being charged twice for one order.", "question": "Which support queue should handle this ticket?", "options": [ {"id": "billing", "description": "Charges, invoices, and payment disputes"}, {"id": "delivery", "description": "Shipping and missing parcels"}, {"id": "other", "description": "The ticket does not clearly fit the named queues"} ], "threshold": 0.9 } ``` `choice` is the selected option ID; `probabilities` contains one entry for each offered ID and sums to one within numeric tolerance. `probability` is the selected option's probability, the number the gate compares with `threshold`. High probability only compares the offered options. It cannot prove that an omitted category would fit worse. Review ambiguous boundaries and option-order sensitivity on your own development examples. --- Source: https://docs.secndai.com/primitives/score # Score Express an ordered rubric rather than asking for free-form numbers. Provide at least two and at most ten levels, ordered strictly increasingly by numeric ID. IDs are strings from `"1"` through `"10"`; they need not be consecutive. Define each level in terms a reviewer could apply. ```json request { "model": "ren-1.1.0", "type": "score", "situation": "The draft reply acknowledges the issue and gives a specific next step, but omits the timeline.", "question": "How complete is the support reply?", "levels": [ {"id": "1", "description": "Does not address the issue"}, {"id": "2", "description": "Addresses the issue but misses an important detail"}, {"id": "3", "description": "Addresses the issue with all required details"} ], "threshold": 0.9 } ``` `choice` is the most probable level. `expected_score` is the sum of each numeric level multiplied by its probability, so it may be fractional. The `probability` used by the gate is the probability of the most probable level, not the expected score divided by the maximum level. For illustration, probabilities `{"1": 0.1, "2": 0.6, "3": 0.3}` give an expected score of `2.2`, a chosen level of `"2"`, and `probability` of `0.6`. This arithmetic example is not a model result. Keep arithmetic, totals, and fixed business rules in code. The model judges the stated rubric; your application decides how to use it. --- Source: https://docs.secndai.com/primitives/true-false # True/false Ask whether one stated proposition holds. The API type is `noul`. Write one proposition with a clear meaning for both true and false. Do not include `options` or `levels`. ```json request { "model": "ren-1.1.0", "type": "noul", "situation": "The customer wrote: Please cancel my subscription before it renews.", "question": "Does the customer explicitly request cancellation?", "threshold": 0.9 } ``` `probabilities` has `"true"` and `"false"` keys; the probability of true is `probabilities.true`. `choice` is the more probable answer, and `probability` is the probability of that chosen answer. A low `probabilities.true` can therefore accompany a high `probability` for false. Always read `choice` and `action` before acting; `action: "act"` does not mean the proposition is true. For missing evidence, ask a separate question or escalate. False should not silently mean both “contradicted” and “unknown” when those require different actions. --- Source: https://docs.secndai.com/confidence # Probability and the gate Use probabilities to route review, with an explicit risk policy. `probabilities` describes the answer distribution. `probability` is the calibrated probability of the selected answer, `probabilities[choice]`. It is not an independent verifier or a separately learned certainty score, and it is not a normalized "confidence" that rescales by the number of options: `probability: 0.9` means 0.9. ## Server-side routing Send `threshold` between `0` and `1`. The default is `0.9`, unless the service operator configures a different default. The effective value is returned in both `threshold` and `gate.threshold`. - `probability >= threshold` produces `action: "act"`. - `probability < threshold` produces `action: "ask_human"`. Equality passes. The gate is a routing policy; it does not execute a refund, invoke a tool, or contact a reviewer for you. Your backend must honor `action` and enforce authorization and business constraints. A threshold is not a guaranteed error bound. Calibrated probabilities describe frequencies over comparable decisions and can drift when data, prompts, rubrics, or model versions change. Validate the threshold against the cost of errors in your workflow. ## An illustrative response ```json response { "id": "dec_illustrative", "model": "ren-1.1.0", "type": "choice", "choice": "billing", "probability": 0.82, "probabilities": {"billing": 0.82, "delivery": 0.18}, "action": "ask_human", "threshold": 0.9, "gate": {"threshold": 0.9}, "usage": {"input_tokens": 96, "output_tokens": 0, "model": "ren-1.1.0"} } ``` These values are invented to show the wire format. They are not measurements. A higher threshold generally sends more work to review; it cannot repair missing categories or misleading evidence. For true/false decisions, `probability` describes the winning answer, including false; the probability of true is `probabilities.true`. For scores, it describes the winning level, not the expected score. --- Source: https://docs.secndai.com/patterns/index # Patterns Keep code in control while the model judges narrow questions. Start with a small decision and an explicit fallback. The model judges meaning; deterministic code checks eligibility, authorization, budgets, and side effects. Useful patterns include confidence routing, a human-review queue, a companion true/false fit check, and bounded concurrent requests. None requires a second inference endpoint or hidden batch operation. Evaluate the entire workflow: the chosen answer, the fraction sent to review, whether the review queue is staffed, and the effect of errors on users. Record the pinned model, rubric, threshold, and final human decision when your data policy permits it. - [Confidence routing](/patterns/confidence-routing): use the effective server policy. - [Human escalation](/patterns/escalate-to-human): persist and resolve uncertain cases. - [Companion check](/patterns/companion-check): test whether an offered category fits. - [Batching](/patterns/batching): process independent cases with bounded concurrency. --- Source: https://docs.secndai.com/patterns/confidence-routing # Confidence routing Use the returned action to choose automation or review. Define the wrong-action cost first. Select a threshold on representative development data; keep the final evaluation set separate. A threshold copied from an example is not a validated policy. ```python # result is the response from client.decide(...). if result["action"] == "ask_human": next_step = {"kind": "review", "candidate": result["choice"]} else: next_step = {"kind": "route", "queue": result["choice"]} ``` Your code consumes `next_step` after checking allowed queues and user permissions. Keep that code separate from probability interpretation so a model response cannot invent a queue or bypass policy. Log the resolved model and effective threshold alongside the final outcome if permitted. Monitor reviewed mistakes and distribution shifts. Retune on development data when a model, option set, or business rubric changes. Do not overwrite `ask_human` with a lower local threshold. Read [probability and the gate](/confidence) for the exact comparison and the meaning of `probability`. --- Source: https://docs.secndai.com/patterns/escalate-to-human # Escalate to a human Make uncertainty an explicit operational path. When the API returns `ask_human`, create a review item containing only the evidence the reviewer needs: the original situation, question, option descriptions, suggested answer, probability distribution, pinned model ID, and threshold. Use your own request or case ID for deduplication. Do not use a new decision response ID as the sole deduplication key: a retry can produce another ID for the same business case. ## Review flow 1. Persist the pending case before acknowledging it to the caller. 2. Present the evidence and all options; label the model's answer as a suggestion. 3. Require a human decision before any irreversible action. 4. Record who approved the action and apply your normal authorization checks. 5. Expire stale reviews and provide a visible status to the user. A queue with no reviewer is a blocked workflow. Set an ownership and response-time policy before enabling it. If inference fails entirely, use the same review path or return a temporary failure; never fabricate a confident answer. Use human outcomes for workflow evaluation only under your data policy. Do not assume customer data is automatically training data. --- Source: https://docs.secndai.com/patterns/companion-check # Companion true/false check Check category fit before trusting a forced choice. A choice is relative to the offered options. If none fits, it can still assign one option a large probability. Ask a separate true/false question about whether the available categories cover the case. ```json request { "model": "ren-1.1.0", "type": "noul", "situation": "Ticket: Please change my postal address. Available queues: billing handles charges; delivery handles shipments.", "question": "Does at least one of the described queues clearly cover this request?", "threshold": 0.9 } ``` Only proceed to the choice request when the check returns both `choice: "true"` and `action: "act"`. A confident false also returns `act`; it should route to review or a fallback, not to the choice action. Use the same facts and category descriptions in both requests. Record both resolved model IDs. If either request fails or asks for a human, keep the whole case in review. These judgments can share the same blind spots. Do not multiply their probabilities or treat the pair as an independent proof. Compare the complete two-step policy against a single choice with an explicit fallback option. --- Source: https://docs.secndai.com/patterns/batching # Batching and concurrency Fan out independent decisions without inventing a batch endpoint. The API accepts one typed decision per request. To process a collection, use bounded client-side concurrency. Each request has its own usage and routing action; there is no atomic batch response. ```python import asyncio import os from secnd import AsyncSecnd async def classify_many(situations): client = AsyncSecnd(api_key=os.environ["SECND_API_KEY"]) semaphore = asyncio.Semaphore(4) async def classify(situation): async with semaphore: return await client.decide({ "model": "ren-1.1.0", "type": "noul", "situation": situation, "question": "Is a cancellation explicitly requested?", "threshold": 0.9, }) return await asyncio.gather( *(classify(situation) for situation in situations), return_exceptions=True, ) ``` The concurrency value is illustrative, not a throughput recommendation. Start small and stay within both key and account limits. `return_exceptions=True` preserves one output position per input; explicitly handle exceptions as failed items, not false answers. For large collections, use a bounded producer queue rather than creating one coroutine per row. Retry failed items within a fixed budget, preserve your own case IDs, and deduplicate downstream side effects. --- Source: https://docs.secndai.com/cookbooks/index # Cookbooks Concrete decision boundaries for common workflows. Each recipe supplies one complete request, a handling policy, and a failure case to evaluate. The requests are valid examples, not measured model outputs or universal business policies. Start with a pinned model, your own labeled development cases, and a staffed fallback. Keep money movement, account permissions, policy eligibility, and irreversible actions in deterministic application code. The recipes cover support triage, refund decisions, content moderation, lead routing, tool selection, and document classification. Thresholds illustrate the request format; validate yours against the risks of each workflow. - [Support triage](/cookbooks/support-triage): choose the owning queue. - [Refund decision](/cookbooks/refund-decision): interpret intent after eligibility checks. - [Content moderation](/cookbooks/content-moderation): judge against a stated policy. - [Lead routing](/cookbooks/lead-routing): route expressed requirements. - [Tool selection](/cookbooks/tool-selection): choose from an authorized allowlist. - [Document classification](/cookbooks/document-classification): label extracted text. --- Source: https://docs.secndai.com/cookbooks/support-triage # Support triage Suggest a support queue while preserving a fallback. Route the customer's intent rather than relying on one keyword. Remove secrets and unrelated ticket history before sending the situation. ```json request { "model": "ren-1.1.0", "type": "choice", "situation": "I can sign in, but yesterday's payment appears twice on my statement.", "question": "Which queue owns the primary issue?", "options": [ {"id": "billing", "description": "Charges, invoices, and payment disputes"}, {"id": "access", "description": "Sign-in and account-access problems"}, {"id": "review", "description": "Mixed intent or no clear owner"} ], "threshold": 0.9 } ``` On `act`, route only if `choice` names an allowed queue. The `review` option and every `ask_human` result enter the human queue. Return a temporary failure or review item on API errors. Test mixed billing/access tickets and requests that fit none of the queues. Review incorrect routing and cases where a new category is needed. The model must not close a ticket or modify payment details. --- Source: https://docs.secndai.com/cookbooks/refund-decision # Refund decision Separate a refund recommendation from payment execution. First compute deterministic eligibility: purchase ownership, refund window, amount, and previous refunds. The model can interpret the customer's intent; it cannot override those checks. ```json request { "model": "ren-1.1.0", "type": "choice", "situation": "Eligibility checks passed. Customer: The item arrived cracked and I want my money back. A replacement is also available.", "question": "Which resolution best matches the customer's explicit request?", "options": [ {"id": "refund", "description": "Customer clearly requests a refund"}, {"id": "replace", "description": "Customer clearly prefers a replacement"}, {"id": "review", "description": "Intent is ambiguous, contradictory, or unsupported"} ], "threshold": 0.95 } ``` Treat `refund` as a recommendation. Your backend checks eligibility again, obtains any required approval, and uses the payment provider's idempotency mechanism. Never put the payment operation inside an inference retry loop. Send `review` or `ask_human` to a reviewer. Test negation, multiple orders, already-refunded cases, and requests that quote someone else's words. The illustrative threshold is not evidence of financial safety. --- Source: https://docs.secndai.com/cookbooks/content-moderation # Content moderation Classify against a stated policy and retain human appeal. Give the model the actual policy categories. Treat quoted user content as evidence to classify, never as instructions that can rewrite the policy. ```json request { "model": "ren-1.1.0", "type": "choice", "situation": "Policy: direct personal threats are prohibited; ordinary criticism is allowed. Comment under review: Your service is slow and I want an explanation.", "question": "How should this comment be handled under the stated policy?", "options": [ {"id": "allow", "description": "Clearly permitted criticism without a direct threat"}, {"id": "review", "description": "Meaning, context, or policy applicability is uncertain"}, {"id": "restrict", "description": "Clearly contains a prohibited direct personal threat"} ], "threshold": 0.95 } ``` Route every `ask_human` and `review` decision to a reviewer. For restrictive actions, apply your approval, notice, and appeal policy even if the gate returns `act`. Evaluate quotations, counterspeech, sarcasm, dialect differences, and attempts to inject instructions. A confident label does not replace a complete moderation policy or establish that a user violated the law. --- Source: https://docs.secndai.com/cookbooks/lead-routing # Lead routing Route expressed product needs to the right sales workflow. Use stated requirements rather than inferring sensitive attributes or personal worth. Keep region, account ownership, and eligibility rules in code. ```json request { "model": "ren-1.1.0", "type": "choice", "situation": "The prospect asks about a shared account, higher request limits, and a data processing agreement for their engineering team.", "question": "Which team should follow up on the stated requirements?", "options": [ {"id": "enterprise", "description": "Team access, contractual terms, or larger workloads"}, {"id": "developer", "description": "Individual API integration and getting started"}, {"id": "review", "description": "Needs are unclear or span unrelated workflows"} ], "threshold": 0.9 } ``` Create a routing suggestion on `act`; let your CRM enforce ownership and communication permissions. A model decision does not authorize sending an email. Review ambiguous requests and all `ask_human` results. Evaluate short messages, existing customers asking for support, and research inquiries that mention enterprise features without purchase intent. --- Source: https://docs.secndai.com/cookbooks/tool-selection # Tool selection Choose from a fixed allowlist, then validate arguments in code. Offer only tools the current user may access. Describe what each tool does; never let the model create a tool name or executable command. ```json request { "model": "ren-1.1.0", "type": "choice", "situation": "The authenticated user asks: Where is order 123? The application has confirmed they own this order.", "question": "Which allowed next step matches this request?", "options": [ {"id": "lookup_shipment", "description": "Read shipment status for an authorized order"}, {"id": "lookup_invoice", "description": "Read an authorized order's invoice"}, {"id": "review", "description": "No offered tool fits or the request is ambiguous"} ], "threshold": 0.95 } ``` Dispatch through a fixed map only after `action: "act"`. Validate the order ID, ownership, and argument schema separately. The response chooses a tool; it does not generate tool arguments. Send `review` and `ask_human` to clarification. Test requests requiring unavailable tools, malicious instructions in retrieved text, and attempts to access another person's order. --- Source: https://docs.secndai.com/cookbooks/document-classification # Document classification Classify the document you can actually fit in the request. Extract text with your own parser and retain a stable document ID. Check whether the relevant content fits the complete input-token limit; never silently classify a truncated document as if it were complete. ```json request { "model": "ren-1.1.0", "type": "choice", "situation": "Document text: Invoice 0041. Amount due: 120 USD. Payment due within 30 days. This document requests payment; it does not confirm payment.", "question": "Which document class best fits the supplied text?", "options": [ {"id": "invoice", "description": "Requests payment for goods or services"}, {"id": "receipt", "description": "Confirms a completed payment"}, {"id": "other", "description": "Neither class clearly fits"} ], "threshold": 0.9 } ``` On `act`, attach the class to the document record after checking it is allowed. Route `other` and `ask_human` to review. Treat API failures as incomplete processing. Evaluate mixed documents, cover pages, quoted examples, poor extraction, and documents outside your taxonomy. Classification is not a reliable way to compute totals or extract legally binding payment terms. --- Source: https://docs.secndai.com/sdk/index # SDKs Use a thin typed client or call the HTTP API directly. The Python SDK imports as `secnd`; the TypeScript package is `@secnd/sdk`. Both send the same request object to `/v1/decide`, expose typed errors, and retry `429` and server errors with bounded backoff. These are local workspace packages. Registry publication is not assumed. The Python distribution is currently named `secnd-sdk`; its import name remains `secnd`. Build the TypeScript workspace from the repository root before installing its local package: ```bash npm run build --workspace @secnd/sdk npm install ./packages/sdk-ts ``` Install Python from the repository root into your project's environment: ```bash uv pip install ./packages/sdk-python ``` Use the language-specific pages for constructor options, asynchronous calls, and error handling. Model weights are not part of either package. See [Python](/sdk/python) for sync and async clients, or [TypeScript](/sdk/typescript) for cancellation and typed requests. --- Source: https://docs.secndai.com/sdk/python # Python Synchronous and asynchronous clients with typed errors. Requires Python 3.12 or newer. Install the local `packages/sdk-python` project as described in the SDK overview. Store the key on your server. ```python import os from secnd import Secnd client = Secnd(api_key=os.environ["SECND_API_KEY"]) result = client.decide({ "model": "ren-1.1.0", "type": "noul", "situation": "The customer asks to cancel before renewal.", "question": "Is cancellation explicitly requested?", "threshold": 0.9, }) print(result["choice"], result["probability"], result["action"]) ``` `Secnd(api_key, base_url="https://api.secndai.com", max_retries=2, timeout=30.0)` accepts a request dictionary. Timeout is in seconds per attempt. Set `max_retries` from 0 through 5. HTTPS is required except on localhost. ## Async ```python import asyncio import os from secnd import AsyncSecnd async def main(): client = AsyncSecnd(api_key=os.environ["SECND_API_KEY"]) result = await client.decide({ "model": "ren-1.1.0", "type": "noul", "situation": "Please cancel my plan.", "question": "Is cancellation explicitly requested?", }) print(result["action"]) asyncio.run(main()) ``` The async client uses a thread-backed transport. It keeps the event loop free while a request waits; cancelling the coroutine does not necessarily stop the underlying HTTP request. Bound concurrency and rely on request timeouts. ## Errors and retries Catch `AuthenticationError`, `ValidationError`, `RateLimitError`, or `ServerError`; each extends `SecndError`. Error instances expose `status`, `code`, `retry_after`, and `request_id` (the `X-Request-Id` header) plus the message through `str(error)`. Network or invalid-response failures can raise the base `SecndError`. Retries cover `429` and server errors only, with exponential backoff (up to 30 seconds) or the full server delay up to 60 seconds, preferring `retry-after-ms` over `Retry-After`; a longer `Retry-After` raises `RateLimitError` with `retry_after` instead of retrying. Transport failures are surfaced without automatic retry. A 30-second per-attempt timeout is not a 30-second total deadline. --- Source: https://docs.secndai.com/sdk/typescript # TypeScript Use typed requests, bounded retries, and cancellation. Use Node.js 22 or newer with native `fetch` and `AbortSignal`. Install the local `@secnd/sdk` workspace package after building it; registry publication is not assumed. ```typescript import { Secnd, type DecideRequest } from "@secnd/sdk"; const apiKey = process.env.SECND_API_KEY; if (!apiKey) throw new Error("Set SECND_API_KEY"); const client = new Secnd({ apiKey }); const request: DecideRequest = { model: "ren-1.1.0", type: "noul", situation: "The customer asks to cancel before renewal.", question: "Is cancellation explicitly requested?", threshold: 0.9, }; const result = await client.decide(request); console.log(result.choice, result.probability, result.action); ``` Constructor options: `apiKey` (required), `baseURL` (default `https://api.secndai.com`), `maxRetries` (default 2; 0–5), `timeoutMs` (default 30000 per attempt), and an optional `fetch` implementation. HTTPS is required except on localhost. ## Bound the whole call ```typescript const result = await client.decide(request, { signal: AbortSignal.timeout(10_000), }); ``` Pass an `AbortSignal` to enforce an application deadline across attempts and retry waits. Catch errors rather than treating cancellation as a decision. ## Typed errors `AuthenticationError`, `ValidationError`, `RateLimitError`, and `ServerError` extend `SecndError`. Instances expose `status`, `code`, `message`, optional `retryAfter`, and optional `requestId` (the `X-Request-Id` header). Network, cancellation, and invalid-response failures can use the base class. The client retries `429` and server errors with exponential backoff, honoring the full server delay up to 60 seconds (`retry-after-ms` when present, else `Retry-After`); a longer delay throws a typed error carrying `retryAfter` instead of retrying. It surfaces transport failures directly. Do not put this client, or its bearer key, in browser code. --- Source: https://docs.secndai.com/limits # Request limits Reject oversized input rather than silently shortening it. | Limit | Current contract | | --- | --- | | Encoded request body | At most 4 MB (4,000,000 bytes) | | Decompressed request body | At most 4 MB (4,000,000 bytes) | | Complete model input | At most 2048 tokenizer tokens | | Choice options | 2–255 | | Score levels | 2–10, strictly increasing numeric string IDs from 1 through 10 | | Option ID | 1–128 characters, unique and nonblank | | JSON nesting | At most 64 levels | | Compression | One layer of `gzip`, `x-gzip`, or `zstd`; zstd window at most 8 MiB | The 4 MB transport limit applies independently to the encoded and decompressed bodies. A body above either cap receives `413`. This is separate from the 2048-token model limit. The token limit includes the situation, question, option descriptions, and model formatting. It is checked with the actual inference tokenizer. Character count and an unrelated tokenizer are not reliable substitutes. Over-limit input is rejected with `413`, never truncated. Send UTF-8 JSON using `Content-Type: application/json`. Use finite numbers only; do not include a byte order mark. Nesting beyond 64 levels receives `400`. Unsupported media types, encodings, and stacked encodings receive `415`; malformed JSON or compression receives `400`. A structurally invalid decision, including an option or level count outside the ranges above or an unknown field at any level, receives `422`. ## Compression Compression saves transport bytes, not model tokens. Both compressed and decompressed sizes must fit. Set `Content-Encoding` to the format you actually send: ```bash printf '%s' '{"model":"ren-latest","type":"noul","situation":"Please cancel my plan.","question":"Is cancellation explicitly requested?"}' \ | gzip \ | curl --fail-with-body https://api.secndai.com/v1/decide \ -H "Authorization: Bearer $SECND_API_KEY" \ -H 'Content-Type: application/json' \ -H 'Content-Encoding: gzip' \ --data-binary @- ``` Use the body for the question and evidence. Do not add images, multipart data, arbitrary metadata, or few-shot examples: those are not supported request fields. --- Source: https://docs.secndai.com/errors # Errors Use the HTTP status and the shared JSON error envelope. API errors use one JSON shape: ```json {"code": "invalid_key", "message": "A valid API key is required."} ``` Branch on the HTTP status and stable `code`, not the prose in `message`. Messages never repeat your request content. Intermediaries can still return non-JSON failures; treat those as transport failures rather than successful decisions. Every response, success or error, carries an `X-Request-Id` header (`req_` and 32 hex characters). Log it and quote it when contacting Secnd. The SDK errors expose it as `requestId` (TypeScript) and `request_id` (Python). | Status | Meaning | Your next step | | --- | --- | --- | | `400` | Malformed JSON or compression, or JSON nested deeper than 64 levels | Fix the body or headers | | `401` | Missing, invalid, or revoked bearer key | Fix credentials; do not blindly retry | | `413` | Body over 4 MB, or input over 2048 tokens | Reduce the request; no automatic truncation | | `415` | Unsupported content type or content encoding | Use JSON and a supported single encoding | | `400 unknown_model` | Unknown, shortened, or case-variant model ID | Use `ren-1.1.0`, `ren-latest`, or `ren-preview` | | `422` | Unknown or missing fields at any level, option or level count, duplicate IDs, invalid levels | Fix the schema | | `429` | Key or account rate limit reached | Honor `retry-after-ms` or `Retry-After` and back off | | `502` | Invalid or unavailable inference response | Retry within a bounded budget | | `503` | Gateway temporarily unavailable | Retry within a bounded budget | | `504` | Inference timed out | Retry within a bounded budget | | `529` | Inference overloaded | Back off and reduce concurrency | If inference fails, do not manufacture an answer or proceed as if the gate passed. Queue review or return a temporary failure from your own application. A `429` always means the same request can succeed later. Requests the gateway can reject outright (`400`, `413`, `415`, `422`) are rejected for what they are, before rate limiting, and do not count against your limit. The 2048-token limit is checked by inference after rate limiting, so an over-limit request counts as one request and returns `413`. The SDKs retry `429` and server errors with bounded backoff. Apply a separate application timeout and avoid wrapping SDK retries in an unbounded retry loop. Authentication and validation errors need corrected input. --- Source: https://docs.secndai.com/rate-limits # Rate limits and usage Budget requests across keys and the whole account. Limits apply per key and per account. Creating more keys does not bypass the account limit. The console's usage view shows effective request limits and usage by key and model. The gateway defaults to 300 requests per minute per key (5 per second on average) and 600 per minute per account, counted in one-minute windows. These are operator-configurable defaults, not a production entitlement. Check your console for the values assigned to your account. ## Headers Every `/v1/decide` response that reached the limiter carries: | Header | Meaning | | --- | --- | | `X-RateLimit-Limit` | Requests allowed in the current window by the tightest limit (key or account) | | `X-RateLimit-Remaining` | Requests left in that window | | `X-RateLimit-Reset` | Seconds until that window resets | ## When limited A `429` response adds `Retry-After` (whole seconds) and `retry-after-ms` (milliseconds). Wait at least that delay before retrying. The SDKs prefer `retry-after-ms`, otherwise accept seconds or an HTTP date, and wait the full delay up to 60 seconds. For a longer delay they do not retry; they raise a rate-limit error that carries the requested delay. A request that can never succeed (malformed, invalid or oversized) gets its real error, never `429`, and does not count against the limit. Keep concurrency bounded and share a queue across workers using the same key. Backoff alone cannot make an oversized workload fit a fixed limit. Ask Secnd for a limit review before a planned traffic increase. ## Measure usage Successful decisions return input-token usage and `output_tokens: 0`. Dashboard usage groups requests and input tokens by key and resolved model. The configured per-million input-token price supports an estimated usage cost; there is no billing-provider integration in this beta contract. Request retries can add requests and token usage. Keep billing, request logs, and the downstream business action separate: a retried decision must not issue the same refund twice. --- Source: https://docs.secndai.com/honest-limits # Where the model fails Design around uncertainty, incomplete categories, and changing data. Ren 1.1 returns structured judgments. It does not establish truth, prove a policy was followed, or authorize an action. We make no benchmark or accuracy claim on this page. ## Incomplete or overlapping options Probability is relative to the offered answers. Missing categories and overlapping descriptions can produce a confident but unhelpful selection. Add a meaningful fallback, use a companion fit check, and test category boundaries. ## Arithmetic, dates, and exact rules Calculate totals, compare timestamps, and apply eligibility rules in deterministic code. A model judgment should not override a database fact or a policy formula. ## Missing evidence and ambiguous intent A yes/no question can conflate false with unknown. An ordered score can conceal disagreement about the rubric. Ask for the missing facts or route the case to review instead of increasing confidence by changing wording repeatedly. ## Long, noisy, or adversarial text Relevant evidence can be buried in unrelated text. User-controlled text can contain instructions that conflict with your question. Include only necessary evidence, keep policy separate, validate outputs, and test adversarial examples. The server rejects input beyond 2048 formatted tokens; it does not silently shorten it. ## Option order and wording Equivalent descriptions or reordered options can change a judgment. Keep IDs stable and test plausible variants on development examples before fixing your workflow policy. ## Calibration and distribution shift Calibration on one collection does not guarantee calibration on your users, language, or future traffic. The `probability` field is the chosen-answer probability, not an independent correctness check. Reevaluate on representative cases when the model or workflow changes, and monitor human overrides. ## Consequential actions Use explicit authorization, deterministic eligibility checks, and appropriate human review for financial, medical, legal, or similarly consequential decisions. The server's `act` field is a routing signal, not professional advice or proof that an action is safe. Use [human escalation](/patterns/escalate-to-human) when a model cannot resolve a case responsibly. Keep a path for review, correction, and appeal. --- Source: https://docs.secndai.com/api-reference/index # API reference Every gateway endpoint, parameter, schema, and response from the OpenAPI contract. This reference is generated from the local OpenAPI contract. Expand fields and response codes to inspect their schemas. Generated schema illustrations contain placeholder values; replace them with your own inputs. Download the [OpenAPI document](/openapi.json) to use the same contract in your tools. ## POST /v1/decide Choose one offered option Complete formatted model input is limited to 2048 tokens; inference checks its exact tokenizer before encoding and never truncates (413). Body limit is 4 MB compressed and decompressed (413). JSON nesting is limited to 64 (400). Supports gzip, x-gzip and zstd, one layer only. Choice takes 2 to 255 options and score 2 to 10 levels (422 outside). Probabilities cover offered options only. probability is the chosen option's probability. The routing gate acts when probability >= threshold (default 0.9), otherwise asks a human. This configurable routing rule is not a certified precision guarantee. One question per request: send situation (text) or state (JSON), a question, a type and its options or levels. Unknown fields are rejected at every level. Requests that can never succeed are rejected before rate limiting and never get 429. Every response carries X-Request-Id; error bodies are {code, message} and never repeat request content. ```json { "operationId": "decide", "summary": "Choose one offered option", "description": "Complete formatted model input is limited to 2048 tokens; inference checks its exact tokenizer before encoding and never truncates (413). Body limit is 4 MB compressed and decompressed (413). JSON nesting is limited to 64 (400). Supports gzip, x-gzip and zstd, one layer only. Choice takes 2 to 255 options and score 2 to 10 levels (422 outside). Probabilities cover offered options only. probability is the chosen option's probability. The routing gate acts when probability >= threshold (default 0.9), otherwise asks a human. This configurable routing rule is not a certified precision guarantee. One question per request: send situation (text) or state (JSON), a question, a type and its options or levels. Unknown fields are rejected at every level. Requests that can never succeed are rejected before rate limiting and never get 429. Every response carries X-Request-Id; error bodies are {code, message} and never repeat request content.", "security": [ { "ApiKey": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DecideRequest" } } } }, "responses": { "200": { "description": "Decision with typed fields, routing gate and exact input-token usage", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DecideResponse" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" }, "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" }, "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" }, "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" } } }, "400": { "description": "Malformed JSON or compression, or JSON nested deeper than 64 levels; unknown model IDs return unknown_model and name ren-1.1.0, ren-latest, ren-preview", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "401": { "description": "Invalid authentication", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "402": { "description": "Insufficient organization credits when BILLING_ENFORCED is enabled. Charging is off by default.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "413": { "description": "Body over 4 MB or input over 2048 tokens; never truncated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "415": { "description": "Unsupported content type or content encoding", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "422": { "description": "Request does not match the schema: unknown field, option or level count, or blank text", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "429": { "description": "Rate limited; wait Retry-After seconds (retry-after-ms milliseconds). Only for requests that can succeed later", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" }, "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" }, "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" }, "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }, "Retry-After": { "$ref": "#/components/headers/RetryAfter" }, "retry-after-ms": { "$ref": "#/components/headers/RetryAfterMs" } } }, "502": { "description": "Invalid inference response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "503": { "description": "Service unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "504": { "description": "Inference timeout", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "529": { "description": "Inference is overloaded; retry with backoff", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } } } } ``` ## GET /v1/models ```json { "operationId": "models", "responses": { "200": { "description": "Pinned model, aliases and configured limits", "content": { "application/json": { "schema": { "type": "object", "required": [ "data" ], "properties": { "data": { "type": "array", "items": { "type": "object", "required": [ "id", "aliases", "max_options", "max_input_tokens", "price_per_million_input_tokens_usd", "release_date" ], "properties": { "id": { "type": "string", "pattern": "^ren-(?:\\d+\\.\\d+\\.\\d+|latest|preview)$" }, "aliases": { "type": "array", "items": { "type": "string" } }, "max_options": { "type": "integer", "const": 255 }, "max_input_tokens": { "type": "integer", "const": 2048 }, "price_per_million_input_tokens_usd": { "type": "number", "minimum": 0 }, "release_date": { "type": "string", "format": "date", "pattern": "^\\d{4}-\\d{2}-\\d{2}$", "description": "Release date of the resolved model (YYYY-MM-DD).", "example": "2026-11-13" } } } } } } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "401": { "description": "Invalid authentication", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } } }, "security": [ { "ApiKey": [] } ] } ``` ## GET /admin/keys ```json { "operationId": "listKeys", "security": [ { "Session": [] } ], "responses": { "200": { "description": "Keys without plaintext", "content": { "application/json": { "schema": { "type": "object", "required": [ "keys" ], "properties": { "keys": { "type": "array", "items": { "$ref": "#/components/schemas/ApiKey" } } } } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "400": { "description": "Malformed JSON or compression, or JSON nested deeper than 64 levels", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "401": { "description": "Invalid authentication", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "403": { "description": "Signed in without private beta access", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "413": { "description": "Body over 4 MB or input over 2048 tokens; never truncated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "422": { "description": "Request does not match the schema: unknown field, option or level count, or blank text", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "429": { "description": "Rate limited; wait Retry-After seconds (retry-after-ms milliseconds). Only for requests that can succeed later", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" }, "Retry-After": { "$ref": "#/components/headers/RetryAfter" }, "retry-after-ms": { "$ref": "#/components/headers/RetryAfterMs" } } }, "502": { "description": "Invalid inference response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "503": { "description": "Service unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "504": { "description": "Inference timeout", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } } } } ``` ## POST /admin/keys ```json { "operationId": "createKey", "security": [ { "Session": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateKeyRequest" } } } }, "responses": { "201": { "description": "Plaintext key returned once", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreatedKey" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "400": { "description": "Malformed JSON or compression, or JSON nested deeper than 64 levels", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "401": { "description": "Invalid authentication", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "403": { "description": "Signed in without private beta access", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "413": { "description": "Body over 4 MB or input over 2048 tokens; never truncated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "422": { "description": "Request does not match the schema: unknown field, option or level count, or blank text", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "429": { "description": "Rate limited; wait Retry-After seconds (retry-after-ms milliseconds). Only for requests that can succeed later", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" }, "Retry-After": { "$ref": "#/components/headers/RetryAfter" }, "retry-after-ms": { "$ref": "#/components/headers/RetryAfterMs" } } }, "502": { "description": "Invalid inference response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "503": { "description": "Service unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "504": { "description": "Inference timeout", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } } } } ``` ## DELETE /admin/keys/{id} ```json { "operationId": "revokeKey", "security": [ { "Session": [] } ], "parameters": [ { "in": "path", "name": "id", "required": true, "schema": { "type": "string" } } ], "responses": { "204": { "description": "Revoked", "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "400": { "description": "Malformed JSON or compression, or JSON nested deeper than 64 levels", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "401": { "description": "Invalid authentication", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "403": { "description": "Signed in without private beta access", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "404": { "description": "Unknown key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "413": { "description": "Body over 4 MB or input over 2048 tokens; never truncated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "422": { "description": "Request does not match the schema: unknown field, option or level count, or blank text", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "429": { "description": "Rate limited; wait Retry-After seconds (retry-after-ms milliseconds). Only for requests that can succeed later", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" }, "Retry-After": { "$ref": "#/components/headers/RetryAfter" }, "retry-after-ms": { "$ref": "#/components/headers/RetryAfterMs" } } }, "502": { "description": "Invalid inference response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "503": { "description": "Service unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "504": { "description": "Inference timeout", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } } } } ``` ## GET /admin/usage ```json { "operationId": "usage", "security": [ { "Session": [] } ], "parameters": [ { "in": "query", "name": "limit", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 } } ], "responses": { "200": { "description": "Recent account-scoped request metadata", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountUsage" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "400": { "description": "Malformed JSON or compression, or JSON nested deeper than 64 levels", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "401": { "description": "Invalid authentication", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "403": { "description": "Signed in without private beta access", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "413": { "description": "Body over 4 MB or input over 2048 tokens; never truncated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "422": { "description": "Request does not match the schema: unknown field, option or level count, or blank text", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "429": { "description": "Rate limited; wait Retry-After seconds (retry-after-ms milliseconds). Only for requests that can succeed later", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" }, "Retry-After": { "$ref": "#/components/headers/RetryAfter" }, "retry-after-ms": { "$ref": "#/components/headers/RetryAfterMs" } } }, "502": { "description": "Invalid inference response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "503": { "description": "Service unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "504": { "description": "Inference timeout", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } } } } ``` ## GET /openapi.json ```json { "operationId": "openapi", "responses": { "200": { "description": "OpenAPI 3.1 contract", "content": { "application/json": { "schema": { "type": "object" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } } } } ``` --- Source: https://docs.secndai.com/api-reference/decide # Make a decision Send one typed question and receive a structured answer. The fields, schemas, and errors below are generated from the gateway OpenAPI file. The API accepts one decision per request. Its response contains the resolved model, probabilities, routing action, and usage. Numerical examples elsewhere in these docs are illustrative. A successful response is not evidence that your workflow is ready for automation. The gateway also exposes the public [OpenAPI document](https://api.secndai.com/openapi.json). This site includes a local snapshot so reference generation does not require an API connection. ## POST /v1/decide Choose one offered option Complete formatted model input is limited to 2048 tokens; inference checks its exact tokenizer before encoding and never truncates (413). Body limit is 4 MB compressed and decompressed (413). JSON nesting is limited to 64 (400). Supports gzip, x-gzip and zstd, one layer only. Choice takes 2 to 255 options and score 2 to 10 levels (422 outside). Probabilities cover offered options only. probability is the chosen option's probability. The routing gate acts when probability >= threshold (default 0.9), otherwise asks a human. This configurable routing rule is not a certified precision guarantee. One question per request: send situation (text) or state (JSON), a question, a type and its options or levels. Unknown fields are rejected at every level. Requests that can never succeed are rejected before rate limiting and never get 429. Every response carries X-Request-Id; error bodies are {code, message} and never repeat request content. ```json { "operationId": "decide", "summary": "Choose one offered option", "description": "Complete formatted model input is limited to 2048 tokens; inference checks its exact tokenizer before encoding and never truncates (413). Body limit is 4 MB compressed and decompressed (413). JSON nesting is limited to 64 (400). Supports gzip, x-gzip and zstd, one layer only. Choice takes 2 to 255 options and score 2 to 10 levels (422 outside). Probabilities cover offered options only. probability is the chosen option's probability. The routing gate acts when probability >= threshold (default 0.9), otherwise asks a human. This configurable routing rule is not a certified precision guarantee. One question per request: send situation (text) or state (JSON), a question, a type and its options or levels. Unknown fields are rejected at every level. Requests that can never succeed are rejected before rate limiting and never get 429. Every response carries X-Request-Id; error bodies are {code, message} and never repeat request content.", "security": [ { "ApiKey": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DecideRequest" } } } }, "responses": { "200": { "description": "Decision with typed fields, routing gate and exact input-token usage", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DecideResponse" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" }, "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" }, "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" }, "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" } } }, "400": { "description": "Malformed JSON or compression, or JSON nested deeper than 64 levels; unknown model IDs return unknown_model and name ren-1.1.0, ren-latest, ren-preview", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "401": { "description": "Invalid authentication", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "402": { "description": "Insufficient organization credits when BILLING_ENFORCED is enabled. Charging is off by default.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "413": { "description": "Body over 4 MB or input over 2048 tokens; never truncated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "415": { "description": "Unsupported content type or content encoding", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "422": { "description": "Request does not match the schema: unknown field, option or level count, or blank text", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "429": { "description": "Rate limited; wait Retry-After seconds (retry-after-ms milliseconds). Only for requests that can succeed later", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" }, "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" }, "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" }, "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }, "Retry-After": { "$ref": "#/components/headers/RetryAfter" }, "retry-after-ms": { "$ref": "#/components/headers/RetryAfterMs" } } }, "502": { "description": "Invalid inference response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "503": { "description": "Service unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "504": { "description": "Inference timeout", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "529": { "description": "Inference is overloaded; retry with backoff", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } } } } ``` --- Source: https://docs.secndai.com/api-reference/list-models # List models Discover the pinned model, aliases, configured price, and limits. Use the pinned ID in the returned `data` array when validating a workflow. Aliases can move to another full release; the decision response always echoes the resolved pinned ID. ## GET /v1/models ```json { "operationId": "models", "responses": { "200": { "description": "Pinned model, aliases and configured limits", "content": { "application/json": { "schema": { "type": "object", "required": [ "data" ], "properties": { "data": { "type": "array", "items": { "type": "object", "required": [ "id", "aliases", "max_options", "max_input_tokens", "price_per_million_input_tokens_usd", "release_date" ], "properties": { "id": { "type": "string", "pattern": "^ren-(?:\\d+\\.\\d+\\.\\d+|latest|preview)$" }, "aliases": { "type": "array", "items": { "type": "string" } }, "max_options": { "type": "integer", "const": 255 }, "max_input_tokens": { "type": "integer", "const": 2048 }, "price_per_million_input_tokens_usd": { "type": "number", "minimum": 0 }, "release_date": { "type": "string", "format": "date", "pattern": "^\\d{4}-\\d{2}-\\d{2}$", "description": "Release date of the resolved model (YYYY-MM-DD).", "example": "2026-11-13" } } } } } } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } }, "401": { "description": "Invalid authentication", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }, "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } } } }, "security": [ { "ApiKey": [] } ] } ``` --- # OpenAPI components ## Referenced components ```json { "securitySchemes": { "ApiKey": { "type": "http", "scheme": "bearer" }, "Session": { "type": "http", "scheme": "bearer", "description": "Console session token (Better Auth) for an invited account with beta access." } }, "schemas": { "Option": { "type": "object", "additionalProperties": false, "required": [ "id" ], "properties": { "id": { "type": "string", "minLength": 1, "maxLength": 128 }, "description": { "type": "string" } } }, "ChoiceRequest": { "type": "object", "additionalProperties": false, "required": [ "model", "question", "type", "options" ], "properties": { "model": { "type": "string", "pattern": "^ren-(?:\\d+\\.\\d+\\.\\d+|latest|preview)$" }, "situation": { "type": "string", "minLength": 1, "description": "Plain-text situation. Send exactly one of situation or state." }, "state": { "description": "Structured situation as any non-null JSON value; a string must be nonblank. Send exactly one of situation or state.", "not": { "type": "null" } }, "question": { "type": "string", "minLength": 1, "description": "The single question the model answers about the situation." }, "type": { "const": "choice", "type": "string" }, "options": { "type": "array", "minItems": 2, "maxItems": 255, "items": { "$ref": "#/components/schemas/Option" } }, "threshold": { "type": "number", "minimum": 0, "maximum": 1, "description": "Routing threshold; defaults to 0.9 unless configured by the operator." } }, "oneOf": [ { "required": [ "situation" ] }, { "required": [ "state" ] } ] }, "ScoreRequest": { "type": "object", "additionalProperties": false, "required": [ "model", "question", "type", "levels" ], "properties": { "model": { "type": "string", "pattern": "^ren-(?:\\d+\\.\\d+\\.\\d+|latest|preview)$" }, "situation": { "type": "string", "minLength": 1, "description": "Plain-text situation. Send exactly one of situation or state." }, "state": { "description": "Structured situation as any non-null JSON value; a string must be nonblank. Send exactly one of situation or state.", "not": { "type": "null" } }, "question": { "type": "string", "minLength": 1, "description": "The single question the model answers about the situation." }, "type": { "const": "score", "type": "string" }, "levels": { "type": "array", "minItems": 2, "maxItems": 10, "items": { "$ref": "#/components/schemas/Option" } }, "threshold": { "type": "number", "minimum": 0, "maximum": 1, "description": "Routing threshold; defaults to 0.9 unless configured by the operator." } }, "description": "Levels have unique numeric string IDs from 1 through 10, ordered strictly increasingly. At least two levels are required by the engine.", "oneOf": [ { "required": [ "situation" ] }, { "required": [ "state" ] } ] }, "DecideRequest": { "oneOf": [ { "$ref": "#/components/schemas/ChoiceRequest" }, { "$ref": "#/components/schemas/ScoreRequest" }, { "$ref": "#/components/schemas/NoulRequest" } ] }, "DecideResponse": { "type": "object", "additionalProperties": false, "required": [ "id", "model", "choice", "probability", "probabilities", "action", "gate", "type", "threshold", "usage" ], "properties": { "id": { "type": "string" }, "model": { "type": "string", "pattern": "^ren-\\d+\\.\\d+\\.\\d+$" }, "choice": { "type": "string" }, "probabilities": { "type": "object", "minProperties": 2, "maxProperties": 255, "additionalProperties": { "type": "number", "minimum": 0, "maximum": 1 } }, "action": { "type": "string", "enum": [ "act", "ask_human" ] }, "gate": { "type": "object", "additionalProperties": false, "required": [ "threshold" ], "properties": { "threshold": { "type": "number", "minimum": 0, "maximum": 1 } } }, "type": { "type": "string", "enum": [ "choice", "score", "noul" ] }, "threshold": { "type": "number", "minimum": 0, "maximum": 1 }, "expected_score": { "type": "number", "minimum": 1, "maximum": 10 }, "probability": { "type": "number", "minimum": 0, "maximum": 1, "description": "Calibrated probability of the chosen option. The gate acts when probability >= threshold. For noul, the probability of true is probabilities.true." }, "usage": { "$ref": "#/components/schemas/Usage" } } }, "Error": { "type": "object", "additionalProperties": false, "required": [ "code", "message" ], "properties": { "code": { "type": "string" }, "message": { "type": "string" } } }, "ApiKey": { "type": "object", "additionalProperties": false, "required": [ "id", "name", "prefix", "created_at", "revoked_at" ], "properties": { "id": { "type": "string" }, "name": { "type": "string", "minLength": 1, "maxLength": 80 }, "prefix": { "type": "string" }, "created_at": { "type": "string" }, "revoked_at": { "type": [ "string", "null" ] }, "creator_id": { "type": [ "string", "null" ], "description": "Original key creator. Organization access survives removal of this member." }, "creator_name": { "type": [ "string", "null" ], "description": "Creator display name when available." } } }, "CreatedKey": { "type": "object", "additionalProperties": false, "required": [ "id", "name", "prefix", "created_at", "revoked_at", "key" ], "properties": { "id": { "type": "string" }, "name": { "type": "string", "minLength": 1, "maxLength": 80 }, "prefix": { "type": "string" }, "created_at": { "type": "string" }, "revoked_at": { "type": [ "string", "null" ] }, "key": { "type": "string" }, "creator_id": { "type": [ "string", "null" ], "description": "Original key creator. Organization access survives removal of this member." }, "creator_name": { "type": [ "string", "null" ], "description": "Creator display name when available." } } }, "CreateKeyRequest": { "type": "object", "additionalProperties": false, "required": [ "name" ], "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 80 } } }, "UsageRow": { "type": "object", "additionalProperties": false, "required": [ "id", "key_id", "model", "status", "input_tokens", "latency_ms", "created_at", "output_tokens", "estimated_cost_usd" ], "properties": { "id": { "type": "string" }, "key_id": { "type": "string" }, "model": { "type": "string" }, "status": { "type": "integer" }, "input_tokens": { "type": "integer", "minimum": 0 }, "latency_ms": { "type": "integer", "minimum": 0 }, "created_at": { "type": "string" }, "output_tokens": { "type": "integer", "const": 0 }, "estimated_cost_usd": { "type": "number", "minimum": 0 } } }, "NoulRequest": { "type": "object", "additionalProperties": false, "required": [ "model", "question", "type" ], "properties": { "model": { "type": "string", "pattern": "^ren-(?:\\d+\\.\\d+\\.\\d+|latest|preview)$" }, "situation": { "type": "string", "minLength": 1, "description": "Plain-text situation. Send exactly one of situation or state." }, "state": { "description": "Structured situation as any non-null JSON value; a string must be nonblank. Send exactly one of situation or state.", "not": { "type": "null" } }, "question": { "type": "string", "minLength": 1, "description": "The single question the model answers about the situation." }, "type": { "const": "noul", "type": "string" }, "threshold": { "type": "number", "minimum": 0, "maximum": 1, "description": "Routing threshold; defaults to 0.9 unless configured by the operator." } }, "oneOf": [ { "required": [ "situation" ] }, { "required": [ "state" ] } ] }, "Usage": { "type": "object", "additionalProperties": false, "required": [ "input_tokens", "output_tokens", "model" ], "properties": { "input_tokens": { "type": "integer", "minimum": 1, "maximum": 2048 }, "output_tokens": { "type": "integer", "const": 0 }, "model": { "type": "string", "pattern": "^ren-\\d+\\.\\d+\\.\\d+$" } } }, "UsageSummary": { "type": "object", "required": [ "requests", "input_tokens", "estimated_cost_usd" ], "properties": { "requests": { "type": "integer", "minimum": 0 }, "input_tokens": { "type": "integer", "minimum": 0 }, "estimated_cost_usd": { "type": "number", "minimum": 0 } } }, "AccountUsage": { "type": "object", "required": [ "usage", "summary", "by_key", "by_model", "daily", "limits", "price_per_million_input_tokens_usd" ], "properties": { "usage": { "type": "array", "items": { "$ref": "#/components/schemas/UsageRow" } }, "summary": { "$ref": "#/components/schemas/UsageSummary" }, "by_key": { "type": "array", "items": { "type": "object", "required": [ "requests", "input_tokens", "estimated_cost_usd", "key_id" ], "properties": { "requests": { "type": "integer", "minimum": 0 }, "input_tokens": { "type": "integer", "minimum": 0 }, "estimated_cost_usd": { "type": "number", "minimum": 0 }, "key_id": { "type": "string" } } } }, "by_model": { "type": "array", "items": { "type": "object", "required": [ "requests", "input_tokens", "estimated_cost_usd", "model" ], "properties": { "requests": { "type": "integer", "minimum": 0 }, "input_tokens": { "type": "integer", "minimum": 0 }, "estimated_cost_usd": { "type": "number", "minimum": 0 }, "model": { "type": "string" } } } }, "daily": { "type": "array", "items": { "type": "object", "required": [ "requests", "input_tokens", "estimated_cost_usd", "day" ], "properties": { "requests": { "type": "integer", "minimum": 0 }, "input_tokens": { "type": "integer", "minimum": 0 }, "estimated_cost_usd": { "type": "number", "minimum": 0 }, "day": { "type": "string" } } } }, "limits": { "type": "object", "required": [ "per_key_requests_per_minute", "per_account_requests_per_minute" ], "properties": { "per_key_requests_per_minute": { "type": "integer" }, "per_account_requests_per_minute": { "type": "integer" } } }, "price_per_million_input_tokens_usd": { "type": "number", "minimum": 0 } } }, "ChoiceQuestion": { "type": "object", "additionalProperties": false, "required": [ "question", "type", "options" ], "properties": { "question": { "type": "string", "minLength": 1, "description": "The single question the model answers about the situation." }, "type": { "const": "choice", "type": "string" }, "options": { "type": "array", "minItems": 2, "maxItems": 255, "items": { "$ref": "#/components/schemas/Option" } }, "threshold": { "type": "number", "minimum": 0, "maximum": 1, "description": "Routing threshold; defaults to 0.9 unless configured by the operator." } } }, "ScoreQuestion": { "type": "object", "additionalProperties": false, "required": [ "question", "type", "levels" ], "properties": { "question": { "type": "string", "minLength": 1, "description": "The single question the model answers about the situation." }, "type": { "const": "score", "type": "string" }, "levels": { "type": "array", "minItems": 2, "maxItems": 10, "items": { "$ref": "#/components/schemas/Option" } }, "threshold": { "type": "number", "minimum": 0, "maximum": 1, "description": "Routing threshold; defaults to 0.9 unless configured by the operator." } } }, "NoulQuestion": { "type": "object", "additionalProperties": false, "required": [ "question", "type" ], "properties": { "question": { "type": "string", "minLength": 1, "description": "The single question the model answers about the situation." }, "type": { "const": "noul", "type": "string" }, "threshold": { "type": "number", "minimum": 0, "maximum": 1, "description": "Routing threshold; defaults to 0.9 unless configured by the operator." } } }, "Question": { "oneOf": [ { "$ref": "#/components/schemas/ChoiceQuestion" }, { "$ref": "#/components/schemas/ScoreQuestion" }, { "$ref": "#/components/schemas/NoulQuestion" } ] }, "MultiDecideRequest": { "type": "object", "additionalProperties": false, "required": [ "model", "questions" ], "description": "Design only: not served. Behind the gateway flag MULTI_QUESTION_ENABLED (off); while off, a questions field is an unknown field (422). One situation or state shared by up to 64 named questions. The state is encoded and billed once; each question adds only its own text and options.", "properties": { "model": { "type": "string", "pattern": "^ren-(?:\\d+\\.\\d+\\.\\d+|latest|preview)$" }, "situation": { "type": "string", "minLength": 1, "description": "Plain-text situation. Send exactly one of situation or state." }, "state": { "description": "Structured situation as any non-null JSON value; a string must be nonblank. Send exactly one of situation or state.", "not": { "type": "null" } }, "questions": { "type": "object", "minProperties": 1, "maxProperties": 64, "propertyNames": { "type": "string", "pattern": "^[A-Za-z0-9_-]{1,64}$" }, "additionalProperties": { "$ref": "#/components/schemas/Question" } } }, "oneOf": [ { "required": [ "situation" ] }, { "required": [ "state" ] } ] }, "Answer": { "type": "object", "additionalProperties": false, "required": [ "choice", "probability", "probabilities", "action", "gate", "type", "threshold" ], "properties": { "choice": { "type": "string" }, "probabilities": { "type": "object", "minProperties": 2, "maxProperties": 255, "additionalProperties": { "type": "number", "minimum": 0, "maximum": 1 } }, "action": { "type": "string", "enum": [ "act", "ask_human" ] }, "gate": { "type": "object", "additionalProperties": false, "required": [ "threshold" ], "properties": { "threshold": { "type": "number", "minimum": 0, "maximum": 1 } } }, "type": { "type": "string", "enum": [ "choice", "score", "noul" ] }, "threshold": { "type": "number", "minimum": 0, "maximum": 1 }, "expected_score": { "type": "number", "minimum": 1, "maximum": 10 }, "probability": { "type": "number", "minimum": 0, "maximum": 1, "description": "Calibrated probability of the chosen option. The gate acts when probability >= threshold. For noul, the probability of true is probabilities.true." } } }, "MultiDecideResponse": { "type": "object", "additionalProperties": false, "required": [ "id", "model", "answers", "usage" ], "description": "Design only: not served. Behind the gateway flag MULTI_QUESTION_ENABLED (off); while off, a questions field is an unknown field (422). Answers are keyed by question name; usage counts the shared state once.", "properties": { "id": { "type": "string" }, "model": { "type": "string", "pattern": "^ren-\\d+\\.\\d+\\.\\d+$" }, "answers": { "type": "object", "minProperties": 1, "maxProperties": 64, "propertyNames": { "type": "string", "pattern": "^[A-Za-z0-9_-]{1,64}$" }, "additionalProperties": { "$ref": "#/components/schemas/Answer" } }, "usage": { "type": "object", "additionalProperties": false, "required": [ "input_tokens", "output_tokens", "model" ], "properties": { "input_tokens": { "type": "integer", "minimum": 1 }, "output_tokens": { "type": "integer", "const": 0 }, "model": { "type": "string", "pattern": "^ren-\\d+\\.\\d+\\.\\d+$" } }, "description": "State counted once; each question adds its own text and options. Each question is limited to 2048 tokens with the state." } } } }, "headers": { "RequestId": { "description": "Unique request ID; quote it when contacting support.", "schema": { "type": "string", "pattern": "^req_[0-9a-f]{32}$" } }, "RetryAfter": { "description": "Whole seconds to wait before retrying.", "schema": { "type": "integer", "minimum": 1 } }, "RetryAfterMs": { "description": "Milliseconds to wait before retrying.", "schema": { "type": "integer", "minimum": 0 } }, "RateLimitLimit": { "description": "Requests allowed in the current one-minute window by the tightest limit (key or account).", "schema": { "type": "integer", "minimum": 1 } }, "RateLimitRemaining": { "description": "Requests left in the current window by the tightest limit.", "schema": { "type": "integer", "minimum": 0 } }, "RateLimitReset": { "description": "Seconds until the current window resets.", "schema": { "type": "integer", "minimum": 0 } } } } ```