Docs
One script tag and one function, onetag.ask. The same reference, written for AI coding tools, is at llms.txt.
Install
Get a key, then add two script tags to your page:
<script src="https://onetagai.com/v1.js" data-key="YOUR_ONETAG_KEY"></script>
<script type="module">
const summary = await onetag.ask("Summarize in 3 bullets", document.body);
</script>
The SDK tag is a classic script, so onetag is ready before any module script runs. Top-level await only works inside <script type="module">, so put your calls there (or inside an async function).
onetag.ask
onetag.ask(prompt, input?, options?) → Promise<string | object>
prompt is the instruction. input is what it works on. The promise resolves with the answer as text, or as a parsed object when you pass json: true.
Input
| You pass | OneTag AI sends |
|---|---|
| a string | the text |
| an HTML element | its innerText |
a <form> | each field as a name: value line, except password fields |
<input>, <textarea>, <select> | its .value |
an <img> | the image, downscaled to a 1,600 px JPEG (a cross-origin image needs crossorigin="anonymous") |
an image File or Blob | the image, downscaled the same way |
| a plain object | it as JSON |
an array, NodeList or other iterable of any of these | all of them, in order |
Options
| Option | Type | Default | What it does |
|---|---|---|---|
stream | function | none | Called with each piece of text as it arrives: stream(delta). The promise still resolves with the full text. |
json | boolean | false | Asks for a JSON object and resolves with it parsed. Throws bad_json if the answer is not valid JSON. |
system | string | none | Extra instructions for the model: a persona, a tone, rules. |
maxTokens | number | 500 | Maximum length of the answer in tokens, up to 1000. |
history | array | none | Earlier turns of a chat: [{ role: "user" | "assistant", content: "..." }]. |
signal | AbortSignal | none | Cancels the request, streaming included, when it fires: pass controller.signal from an AbortController. The promise rejects with aborted. |
<script type="module">
const out = document.querySelector("#answer");
await onetag.ask("Explain this table to a beginner", document.querySelector("table"), {
stream: (delta) => { out.textContent += delta; },
maxTokens: 300,
});
</script>
onetag.configure
onetag.configure({ key: "YOUR_ONETAG_KEY", endpoint: "https://onetagai.com/v1/ask" });
Sets the key in code instead of the data-key attribute. endpoint defaults to /v1/ask on the server the SDK was loaded from; you only need it to point the SDK somewhere else.
Handling errors
Every failure throws onetag.OneTagError with .code (from the table below), .message (a sentence you can show to people) and .status (the HTTP status, or 0 when there was no response).
<script type="module">
const out = document.querySelector("#out");
try {
out.textContent = await onetag.ask("Translate to French", document.querySelector("#text"));
} catch (e) {
out.textContent = e.code === "quota_exceeded" ? "The AI is resting until next month." : e.message;
}
</script>
Error codes
| Code | HTTP | Meaning |
|---|---|---|
bad_ | 400 | The body is not JSON, the prompt is empty, or an image is not a base64 data:image/ URL. |
bad_ | 401 | Unknown or disabled key. |
domain_ | 403 | The key is locked to another domain, or the request has no Origin or Referer. See Domain lock. |
too_ | 413 | More than 32,000 characters of text, more than 50 input parts or 100 history messages, more than one image, or an image over 2 MB. |
quota_ | 429 | The key has used its 1,000,000 tokens for this month. |
rate_ | 429 | More than 20 requests a minute from one IP, or 60 a minute on one key. |
upstream | 502 | The AI provider returned an error. Try again. |
paused | 503 | OneTag AI reached its daily budget and is paused until 00:00 UTC. |
no_ | none | SDK only: no data-key on the script tag and no onetag.configure({ key }). |
bad_ | none | SDK only: an input it cannot read, such as a cross-origin image served without CORS. |
bad_ | none | SDK only: you passed json: true and the answer was not valid JSON. |
network | none | SDK only: the request failed, or a stream was cut off before it finished. |
aborted | none | SDK only: the signal you passed fired before the answer was complete. |
Limits
| Limit | Value |
|---|---|
| Tokens per key | 1,000,000 per calendar month (UTC), input and output together |
| Text per call | 32,000 characters: prompt, system, text input and history together (the SDK trims long input to fit) |
| Input parts per call | 50 |
| History messages per call | 100 |
| Images per call | 1, up to 2 MB (the SDK downscales it to a 1,600 px JPEG before sending) |
| Answer length | 500 tokens by default, up to 1,000 with maxTokens |
| Requests per IP | 20 per minute |
| Requests per key | 60 per minute |
We email you when a key has used 80% and 100% of its monthly tokens.
Domain lock
If you give a domain when you sign up, the key only works from browser pages on that domain or its subdomains, and on localhost or 127.0.0.1 while you build. A key locked to example.com works on www.example.com and blog.example.com, not on example.org. OneTag AI checks the Origin header the browser sends, or the Referer when there is no Origin.
A locked key rejects every request without an Origin or Referer with domain_not_allowed: curl, code running on a server, and pages opened from file://. A locked key is for browser pages on your site.
A key without a domain works from anywhere, curl included. Anyone can copy a key out of your page source, so lock the keys you publish. To lock a key later, or move it to another domain, sign up again with the same email and the new domain: the email carries a confirmation link, valid for 24 hours, and nothing changes until it is opened.
HTTP API
The SDK wraps one endpoint. You can call it directly from any language with a key that is not locked to a domain.
POST https://onetagai.com/v1/ask
Content-Type: application/json
| Field | Type | Notes |
|---|---|---|
key | string | Required. |
prompt | string | Required, not empty. |
input | string or array | A string, or a list of parts: {"type": "text", "text": "..."} and at most one {"type": "image", "data_url": "data:image/jpeg;base64,..."}. |
system | string | Optional extra instructions. |
json | boolean | Default false. When true, text holds a JSON object as a string. |
max_tokens | integer | Default 500, max 1000. |
stream | boolean | Default false. When true, the answer comes as server-sent events. |
history | array | Earlier turns: [{"role": "user" | "assistant", "content": "..."}]. |
Example
curl https://onetagai.com/v1/ask \
-H "Content-Type: application/json" \
-d '{"key": "YOUR_ONETAG_KEY", "prompt": "Summarize in one sentence", "input": "OneTag AI lets any static page call an AI model with one script tag, without a backend and without an API key in the page."}'
The answer, with the tokens it used:
{"text": "OneTag AI adds AI to static pages with a single script tag.", "usage": {"input": 61, "output": 14}}
Streaming
With "stream": true the response is text/event-stream: one data: line per event, with a blank line between events. The text arrives in delta events, and the last event carries done and the usage.
data: {"delta": "OneTag AI adds"}
data: {"delta": " AI to static pages"}
data: {"delta": " with a single script tag."}
data: {"done": true, "usage": {"input": 61, "output": 14}}
If the AI provider fails partway through, the stream ends with an error event instead: data: {"error": {"code": "upstream", "message": "..."}}.
Errors
Errors found before the answer starts come back as JSON with the HTTP status from the error table:
HTTP/1.1 401 Unauthorized
{"error": {"code": "bad_key", "message": "Unknown or disabled key."}}