Ultri Platform
OverviewAPI keysUsageDocs
Reference Open Ultri
OverviewAPI keysUsageDocs

Build with Ultri

Sign in with your Ultri account to make API keys and see what each call cost. The same credits pay for the app and the API.

Forgot your password?

Overview

Every request is charged to your credits at the model's listed price, to the token, and recorded with the key that made it.

Credits left

–

API spend this month

–

Requests this month

–

Active keys

–
Manage keys
Add creditsRead the docsAPI reference

Models

First call

API keys

A key acts as your account: it can call the models and read your balance and usage, and nothing else. Keys are shown once. Give each integration its own key and a monthly limit, so a leak or a bug cannot spend more than you meant.

NameKeyCreatedLast usedRequestsThis monthLimit

Usage

What you were charged, grouped how you like. "Cost" is what the call cost us; "charged" is what came off your credits. Prices that changed are dated on each model's page, and every row keeps the price it was charged at.

Download CSV

Recent requests

WhenProductModelKeyInOutChargedRequest id

Documentation

Ultri's own writing models over HTTP. The chat endpoint speaks the OpenAI wire format, so the OpenAI SDKs work with a different base URL. Everything is priced per token or per word, charged to your credits as it happens.

AuthenticationQuickstartChat completionsHumanizeDetectionBalance and usagePricingErrorsLimitsModel versions

Authentication

Make a key under API keys and send it as a bearer token on every request:

Authorization: Bearer ultri_sk_…

A key is tied to the account that made it and spends that account's credits. It cannot change the account, make other keys or read conversations from the app. Revoke a key any time; the usage it generated stays on the books under its name.

Quickstart

The base URL is . With the OpenAI SDKs set base_url to it and api_key to your key; nothing else changes.

Chat completions

POST /v1/chat/completions. Send the conversation, get the next assistant message. Ultri's writing rules (plain words, no stock phrases, no em dashes) are prepended to your system prompt unless you pass "ultri_style": false.

FieldTypeWhat it does
modelstringA chat model from GET /v1/models. Default ultri-writer.
messagesarrayRoles system, user, assistant (developer counts as system). Content is a string or an array of {"type":"text"} parts.
streamboolServer-sent events, one chat.completion.chunk per delta, then data: [DONE].
stream_optionsobject{"include_usage": true} adds a last chunk with usage and ultri (cost) before [DONE].
max_tokensintCap on the reply. Default 1024, at most the model's max_output_tokens. max_completion_tokens is accepted too.
temperature, top_p, stopAs in OpenAI. Defaults 0.7, 0.95, none. Up to 4 stop strings.
userstringYour own id for the end user; stored on the usage row so you can split your bill.
ultri_styleboolDefault true. Set false to run the model on your system prompt alone.

Response

{
  "id": "chatcmpl-5f1c…", "object": "chat.completion", "created": 1794220000, "model": "ultri-writer",
  "choices": [{"index": 0, "message": {"role": "assistant", "content": "Hi Mr. Patel, …"}, "finish_reason": "stop"}],
  "usage": {"prompt_tokens": 412, "completion_tokens": 86, "total_tokens": 498},
  "ultri": {"cost_usd": 0.001272, "balance_usd": 4.9871, "model_version": "ultri-writer@dpo6",
            "request_id": "chatcmpl-5f1c…", "estimated": false, "price_per_1m_tokens": {"input": 1.0, "output": 10.0}}
}

The ultri object is the bill for this call: what came off your credits, your balance after it, and exactly which weights answered. A reply you cut off mid-stream is still paid for (the prompt was read); its output count is estimated from the text you received and the row is marked estimated.

Streaming

data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"Hi Mr. Patel,"},"finish_reason":null}]}
…
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[],"usage":{…},"ultri":{…}}     ← with include_usage
data: [DONE]

Ultri's model runs on a machine that sleeps when nobody is using it. If your call wakes it, the stream sends comment lines (: waking the model…) while it starts, which SDKs ignore; a non-streaming call waits up to five minutes and then answers 503 with Retry-After. Keep a retry in your client for that case.

Humanize

POST /v1/humanize rewrites text that reads like a machine wrote it. A run takes one to several minutes and is billed at metered cost as it goes (model calls, GPU seconds, GPTZero reads), so the usual flow is: start the job, follow it, read the result.

POST /v1/humanize
{"text": "…at least 15 words…", "wait": false, "voice": "auto", "candidates": 2, "length": "natural", "fidelity": "natural",
 "style_sample": "optional: your own writing to match", "notes": "optional facts the writer may use", "verify": true}

→ {"job_id": "a1b2c3d4e5f6", "status": "running", "estimate": {"usd": 0.84, "needed": 0.84, "balance": 12.1, "enough": true},
   "status_url": "/v1/jobs/a1b2c3d4e5f6", "events_url": "/v1/jobs/a1b2c3d4e5f6/events"}

GET /v1/jobs/{job_id}                    status running | paused | done | error | cancelled, the log, the running cost, then result
GET /v1/jobs/{job_id}/events             server-sent events: log, cost (every time it moves), paused, running, done
DELETE /v1/jobs/{job_id}                 stop it (what it already spent stays charged)
POST /v1/credits/estimate {"words": 800} what a run of this length is likely to cost, before you start it

"wait": true (the default) holds the HTTP request open and returns the finished job instead; fine for scripts, not for a web server. The result has output, before and after (detector scores and statistics), meaning (how faithfully the meaning survived), verify (the GPTZero verdict when on) and cost. Not enough credits for the estimate is a 402 with the estimate in the body. A run that spends the balance to zero mid-way pauses and carries on when credits are added; your keys' monthly limits also apply here.

GET /v1/voices lists the voice ids you can pass instead of auto.

Detection

POST /v1/check {"text": "…"} runs Ultri's own detector of machine writing habits: a 0 to 100 score, a verdict, the flagged phrases with what a person would say instead. Free.

POST /v1/score {"text": "…", "detectors": ["gptzero"]} reads the text with GPTZero and returns the probability it is machine-written, per sentence too. Priced per word at our cost (see Pricing); the balance must cover the whole read before it starts. Leave detectors out for the free local detectors only.

Balance and usage

GET /v1/platform/balance                 credits left, this month's API spend, this key's month to date and limit
GET /v1/platform/usage?days=30&group=day  rows by day | model | key | product; ?key=… for one key; ?api_only=1; &format=csv
GET /v1/usage                            the account's full ledger view (the same one the app shows)
GET /v1/credits                          balance, free credits and when they expire, purchases, per-run subtotals

Each usage row carries the model, the token counts, our cost, what you were charged, the key, your user id, and the model version. Nothing is ever edited after the fact: a price change applies to calls after it.

Pricing

Prices come from GET /v1/models, so this table is live. Credits are prepaid in dollars and never expire when bought; every account also gets a small free allowance each month.

ModelEndpointUnitInputOutputSince

Errors

Errors are JSON: {"error": {"message", "type", "code", "param"}}, in the OpenAI shape, with the HTTP status that matters.

StatusCodeMeaning
401No key, an unknown or revoked key, or a route keys may not use.
402insufficient_credits, key_spend_limitNot enough credits for the call, or this key hit its monthly limit. The body says how much is needed.
404model_not_foundNo such model, or not for this endpoint. The message lists what you can use.
400 / 413invalid_request_errorA bad field (param names it) or too much text.
429rate_limit_exceeded, concurrencyToo many requests this minute on this key, or too many in flight. Honour Retry-After.
502 / 503model_starting, server_errorThe model is waking up or failed; retry after Retry-After.

Limits

    Model versions

    Ultri keeps training its models. A model id like ultri-writer always points at the weights in production; the exact version answering is in every response's ultri.model_version and on every usage row, so you can see when a change happened and compare. A new version or a new price is dated under GET /v1/models/ultri-writer (version, price_history). Models move through preview, active, deprecated and retired; a deprecated model keeps working until it is retired, and the list says which is which.

    The full machine-readable reference is at /reference (openapi.json).