Chat Prompt Token Estimator
ai.prompt_token_estimate
Overview
You send the messages, tool schemas and model profile you are about to use, and we tell you how many tokens the whole request comes to, how much of your context window is left, and what it would cost at a price you supply. The content half is exact: we run the real tokenizer over your message text, the same one behind text.count_tokens, so the two can never disagree. The framing half is an estimate, and we say so in the response with a literal estimate flag — a chat request costs more than the sum of its message bodies, because each message carries role and delimiter framing and the reply is primed, and providers can change that wire format without notice. We ship no price list and no context-window list: both are yours to supply, because a table baked into a skill is wrong the week a provider changes it and a confidently wrong cost is worse than no cost. If all you want is the token count of one string, text.count_tokens is the smaller, cheaper answer.
Token surprises are expensive and late. A conversation that fits in testing overflows in production once a tool schema and a few turns of history join it, and the first sign is a truncated answer or a rejected call. Getting the number locally, with the envelope included and the arithmetic published, turns that into a decision you make before you pay for it.
Reliability
Deterministic for a given tokenizer library version: the same request always returns the same numbers, and tokenizer_version tells you which library produced them so a count can be explained later. The content bucket is exact; the framing bucket is an estimate of a provider wire format that can change without notice, which is why every answer carries estimate true. Under NONE framing there is no envelope at all, so content equals what text.count_tokens returns for each message body added up. Failures, all of them fixed by changing the request rather than retrying it: PROMPT_MESSAGES_INVALID means messages arrived empty, an entry in it was not an object, or a message carried a property or a value shape we do not accept — fix the message named in the error. A missing messages, or one that is not an array at all, is a broken request rather than a policy outcome and comes back as the platform's TOOL_INPUT_INVALID; the same is true of any field sent with the wrong type. PROMPT_ROLE_UNSUPPORTED means a role was not one of system, user, assistant or tool in lower case — correct it, we do not map unfamiliar roles onto familiar ones; PROMPT_MESSAGE_LIMIT_EXCEEDED means more than 2,000 messages arrived — estimate the conversation in sections; PROMPT_TOOLS_INVALID means a tool entry was not an object, was missing its name, or carried an unknown property; PROMPT_TOOL_LIMIT_EXCEEDED means more than 128 tools arrived; PROMPT_FRAMING_UNSUPPORTED means framing named something outside CHATML_V1, CHATML_TOOLS_V1 and NONE, and we refuse rather than quietly use the default; PROMPT_TOKENIZER_UNSUPPORTED means the encoding is not one we carry — send cl100k_base or o200k_base; PRICING_INVALID means the pricing object arrived with no rate in it, with a rate outside 0 to 1,000,000, or with an unknown property; PRICING_OUTPUT_TOKENS_REQUIRED means you priced the output side without saying how long you expect the answer to be — add expectedOutputTokens; CURRENCY_UNKNOWN means currency was not a three-letter ISO-4217 code with a minor unit.
Context footprint
The answer holds counts, one small object per message, and decimal amounts — never your prompt text. A 2,000-message request answers with 2,000 four-field entries, so expect the response to grow with your turn count rather than with your prompt length. Message content is accepted up to 1,000,000 characters each and tool parameter schemas up to 100,000, inside a 4 MiB request. A successful call costs 1 execution credit.
When to use it
Before sending a chat request whose size you are not certain of, especially one carrying tool schemas or a growing history, and whenever you want a cost figure at your own rate.
When you only need the token count of a single string — reach for text.count_tokens — or when you need a figure you can bill against, which no estimate should ever be.
Powered by JTokkit, the open-source tokenizer that reproduces OpenAI's tiktoken byte-pair encodings, so the content count is exact rather than approximated.
Scope
Non-goals
- Does not call a model, a tokenizer service or any inference API
- Does not ship a price list — every rate comes from you
- Does not ship context-window sizes — that number is an input, not a lookup
- Does not count a plain string: text.count_tokens is the skill for that
- Does not bill anything; the estimate flag in the answer says so out loud
Properties
Capabilities
Cost & billing
Flat rate per invocation
File handling
How we handle your data
What this means
This skill may receive sensitive content, but it does not target sensitive data and returns only a structured non-sensitive result.
Squillify guarantees
- Runs inside Squillify
- This skill runs inside Squillify.
- Payloads are not sent to an external service by this skill.
- Squillify does not permanently store submitted payloads.
- Telemetry records metadata only, not submitted content.
- Submitted payloads are not used for training.
- Submitted payloads are not reviewed by humans.
- This skill does not target sensitive data.
- The response does not contain submitted content.
Your responsibility
Squillify does not determine whether submitted content is sensitive. Your application is responsible for deciding what data it is allowed to submit. Your application is responsible for securing the response.
Input schema
| Field | Type | Required | Description |
|---|---|---|---|
| messages | array | Yes | Closed chat turns in caller order. The skill refuses more than 2,000 messages with PROMPT_MESSAGE_LIMIT_EXCEEDED. |
| tools | array | No | Closed tool schemas; omit it for a zero tools bucket. The skill refuses more than 128 tools with PROMPT_TOOL_LIMIT_EXCEEDED. |
| modelProfile | object | No | Closed optional model profile; omit the object for CHATML_V1 on cl100k_base with no headroom. |
| pricing | object | No | Closed optional pricing block; omit the object to leave the cost block out of the answer. |
Output schema
| Field | Type | Required | Description |
|---|---|---|---|
| tokens | object | Yes | Closed token buckets for content, framing, tools, and their sum. |
| perMessage | array | Yes | Bounded array of 1 through 2000 per-turn breakdowns in request order. |
| headroom | object | No | Closed headroom block present only when modelProfile.contextWindow was supplied. |
| cost | object | No | Closed cost block present only when pricing was supplied, with optional currency rounding. |
| framing | string | Yes | Framing convention actually applied. |
| tokenizer | string | Yes | Encoding actually used to count content. |
| tokenizer_version | string | Yes | Pinned tokenizer library version behind that encoding. |
| estimate | boolean | Yes | Always true so this answer cannot be mistaken for a metered bill. |
API contract
POST /v1/executecurl -sS -X POST "$BASE/v1/execute" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' -d '{"skillKey":"ai.prompt_token_estimate","input":{"messages":[{"role":"system","content":"You are a support agent."},{"role":"user","content":"Where is order NW-84120?"}],"modelProfile":{"contextWindow":8192}}}'import { Squillify } from "@squillify/sdk";
const sq = new Squillify({ apiKey: process.env.SQUILLIFY_API_KEY });
const { output } = await sq.execute("ai.prompt_token_estimate", {"messages":[{"role":"system","content":"You are a support agent for Northwind Supplies. Answer in British English and never promise a delivery date."},{"role":"user","content":"Order NW-84120 has not arrived. What should I tell the customer?"}],"modelProfile":{"contextWindow":8192,"reservedOutputTokens":1000},"pricing":{"inputPricePerMillion":"2.50","outputPricePerMillion":"10.00","currency":"EUR","expectedOutputTokens":400}});Errors
| Code | HTTP | Retryable | Description |
|---|---|---|---|
| VALIDATION_ERROR | 400 | No | The request payload failed schema validation. |
| UNAUTHORIZED | 401 | No | Missing or invalid API key. |
| SKILL_NOT_ACCESSIBLE | 403 | No | The API key is not granted access to this skill. |
| SKILL_NOT_AVAILABLE_FOR_PLAN | 403 | No | This skill is not included in your current plan. |
| PLAN_ELIGIBILITY_UNAVAILABLE | 503 | Yes | Plan eligibility could not be decided. Retry after backoff. |
| SKILL_NOT_FOUND | 404 | No | The requested skill or version does not exist. |
| TENANT_RATE_LIMITED | 429 | Yes | Request rate limit exceeded. Retry after backoff. |
| QUOTA_EXCEEDED | 429 | Yes | Plan quota exhausted. Retryable once the quota period resets. |
| EXECUTION_TIMEOUT | 504 | Yes | Execution exceeded the allowed time budget. |
| PROMPT_MESSAGES_INVALID | 400 | No | The request payload failed this skill's input validation. |
| PROMPT_ROLE_UNSUPPORTED | 400 | No | The skill does not support the requested variant. |
| PROMPT_MESSAGE_LIMIT_EXCEEDED | 429 | No | A size, quota, rate, or time limit was exceeded. |
| PROMPT_TOOLS_INVALID | 400 | No | The request payload failed this skill's input validation. |
| PROMPT_TOOL_LIMIT_EXCEEDED | 429 | No | A size, quota, rate, or time limit was exceeded. |
| PROMPT_FRAMING_UNSUPPORTED | 400 | No | The skill does not support the requested variant. |
| PROMPT_TOKENIZER_UNSUPPORTED | 400 | No | The skill does not support the requested variant. |
| PRICING_INVALID | 400 | No | The request payload failed this skill's input validation. |
| PRICING_OUTPUT_TOKENS_REQUIRED | 400 | No | The request payload failed this skill's input validation. |
| CURRENCY_UNKNOWN | 400 | No | The request payload failed this skill's input validation. |
Other limits
| Limit | Value |
|---|---|
| Recommended inline bytes | 32 KiB |
Limitations
- Does not call a model, a tokenizer service or any inference API
- Does not ship a price list — every rate comes from you
- Does not ship context-window sizes — that number is an input, not a lookup
- Does not count a plain string: text.count_tokens is the skill for that
- Does not bill anything; the estimate flag in the answer says so out loud
- Counting the tokens of one plain string, which text.count_tokens does directly
- Producing a billable figure — this is a planning number, never an invoice
- Models whose tokenizer is not cl100k_base or o200k_base
How to call
POST /v1/execute (requires authentication)
{
"skillKey": "ai.prompt_token_estimate",
"input": {
"messages": [
{
"role": "system",
"content": "You are a support agent for Northwind Supplies. Answer in British English and never promise a delivery date."
},
{
"role": "user",
"content": "Order NW-84120 has not arrived. What should I tell the customer?"
}
],
"modelProfile": {
"contextWindow": 8192,
"reservedOutputTokens": 1000
},
"pricing": {
"inputPricePerMillion": "2.50",
"outputPricePerMillion": "10.00",
"currency": "EUR",
"expectedOutputTokens": 400
}
}
}Examples
A two-turn support prompt with headroom and a euro cost
{
"messages": [
{
"role": "system",
"content": "You are a support agent for Northwind Supplies. Answer in British English and never promise a delivery date."
},
{
"role": "user",
"content": "Order NW-84120 has not arrived. What should I tell the customer?"
}
],
"modelProfile": {
"contextWindow": 8192,
"reservedOutputTokens": 1000
},
"pricing": {
"inputPricePerMillion": "2.50",
"outputPricePerMillion": "10.00",
"currency": "EUR",
"expectedOutputTokens": 400
}
}{
"estimate": true,
"tokens": {
"content": 37,
"framing": 11,
"tools": 0,
"total": 48
},
"perMessage": [
{
"index": 0,
"role": "system",
"contentTokens": 21,
"framingTokens": 4
},
{
"index": 1,
"role": "user",
"contentTokens": 16,
"framingTokens": 4
}
],
"headroom": {
"contextWindow": 8192,
"reservedOutputTokens": 1000,
"availableTokens": 7144,
"fits": true,
"overflowTokens": 0
},
"cost": {
"inputCost": 0.00012,
"outputCost": 0.004,
"totalCost": 0.00412,
"currency": "EUR",
"rounded": 0
},
"framing": "CHATML_V1",
"tokenizer_version": "jtokkit-1.1.0",
"tokenizer": "cl100k_base"
}A tool schema counted under CHATML_TOOLS_V1, priced in yen
{
"messages": [
{
"role": "user",
"content": "Find the shipping status for order NW-84120."
}
],
"pricing": {
"currency": "JPY",
"inputPricePerMillion": "150",
"expectedOutputTokens": 0
},
"modelProfile": {
"tokenizer": "o200k_base",
"framing": "CHATML_TOOLS_V1",
"contextWindow": 128000
},
"tools": [
{
"name": "lookup_order",
"parametersJson": "{\"type\":\"object\",\"properties\":{\"orderId\":{\"type\":\"string\"}},\"required\":[\"orderId\"]}",
"description": "Look up an order by its reference and return its shipping status."
}
]
}{
"estimate": true,
"tokens": {
"content": 11,
"framing": 7,
"tools": 43,
"total": 61
},
"perMessage": [
{
"index": 0,
"role": "user",
"contentTokens": 11,
"framingTokens": 4
}
],
"headroom": {
"contextWindow": 128000,
"reservedOutputTokens": 0,
"availableTokens": 127939,
"fits": true,
"overflowTokens": 0
},
"cost": {
"inputCost": 0.00915,
"outputCost": 0,
"totalCost": 0.00915,
"currency": "JPY",
"rounded": 0
},
"framing": "CHATML_TOOLS_V1",
"tokenizer_version": "jtokkit-1.1.0",
"tokenizer": "o200k_base"
}Recommended use cases
- Decide whether a conversation still fits before you send it, and how much room is left
- Compare what a tool schema adds to every turn against what it buys you
- Put a euro figure beside a prompt design review, at your own negotiated rate
Avoid for
- Counting the tokens of one plain string, which text.count_tokens does directly
- Producing a billable figure — this is a planning number, never an invoice
- Models whose tokenizer is not cl100k_base or o200k_base
Related skills
Composable with
Safety & privacy
- Runs without calling any external service.
- Your payloads are never stored permanently.
- Telemetry captures metadata only — never your payload contents.
- We never train models on your payloads.
- No human reviews your payloads.