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 passOneTag AI sends
a stringthe text
an HTML elementits 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 Blobthe image, downscaled the same way
a plain objectit as JSON
an array, NodeList or other iterable of any of theseall of them, in order

Options

OptionTypeDefaultWhat it does
streamfunctionnoneCalled with each piece of text as it arrives: stream(delta). The promise still resolves with the full text.
jsonbooleanfalseAsks for a JSON object and resolves with it parsed. Throws bad_json if the answer is not valid JSON.
systemstringnoneExtra instructions for the model: a persona, a tone, rules.
maxTokensnumber500Maximum length of the answer in tokens, up to 1000.
historyarraynoneEarlier turns of a chat: [{ role: "user" | "assistant", content: "..." }].
signalAbortSignalnoneCancels 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

CodeHTTPMeaning
bad_request400The body is not JSON, the prompt is empty, or an image is not a base64 data:image/ URL.
bad_key401Unknown or disabled key.
domain_not_allowed403The key is locked to another domain, or the request has no Origin or Referer. See Domain lock.
too_large413More 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_exceeded429The key has used its 1,000,000 tokens for this month.
rate_limited429More than 20 requests a minute from one IP, or 60 a minute on one key.
upstream502The AI provider returned an error. Try again.
paused503OneTag AI reached its daily budget and is paused until 00:00 UTC.
no_keynoneSDK only: no data-key on the script tag and no onetag.configure({ key }).
bad_inputnoneSDK only: an input it cannot read, such as a cross-origin image served without CORS.
bad_jsonnoneSDK only: you passed json: true and the answer was not valid JSON.
networknoneSDK only: the request failed, or a stream was cut off before it finished.
abortednoneSDK only: the signal you passed fired before the answer was complete.

Limits

LimitValue
Tokens per key1,000,000 per calendar month (UTC), input and output together
Text per call32,000 characters: prompt, system, text input and history together (the SDK trims long input to fit)
Input parts per call50
History messages per call100
Images per call1, up to 2 MB (the SDK downscales it to a 1,600 px JPEG before sending)
Answer length500 tokens by default, up to 1,000 with maxTokens
Requests per IP20 per minute
Requests per key60 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
FieldTypeNotes
keystringRequired.
promptstringRequired, not empty.
inputstring or arrayA string, or a list of parts: {"type": "text", "text": "..."} and at most one {"type": "image", "data_url": "data:image/jpeg;base64,..."}.
systemstringOptional extra instructions.
jsonbooleanDefault false. When true, text holds a JSON object as a string.
max_tokensintegerDefault 500, max 1000.
streambooleanDefault false. When true, the answer comes as server-sent events.
historyarrayEarlier 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."}}