Skip to main content

Squillify development is paused indefinitely. Read why, and discover what this experiment achieved. Read the story

MicroSkills Reference

Chat Prompt Token Estimator

ai.prompt_token_estimate

ProductionBounded custom (Class B)Deterministicstrictfastv1.0.0
No network

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

Note
Estimates the token cost of a chat-shaped request — roles, message framing, tool schemas and reply priming — and turns it into headroom against a caller-supplied window and cost at a caller-supplied rate.

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

IdempotentRetry safeStreaming supportedFile input supportedRequires external service

Capabilities

analysistext-processing

Cost & billing

Flat rate per invocation

MetercallsBillable onsuccessCost per call1 credit

File handling

Accepts file_refProduces file_refMax input2 MB

How we handle your data

Sensitive input, safe output
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.

Read the Microskill Data Handling Policy

Input schema

FieldTypeRequiredDescription
messagesarrayYesClosed chat turns in caller order. The skill refuses more than 2,000 messages with PROMPT_MESSAGE_LIMIT_EXCEEDED.
toolsarrayNoClosed tool schemas; omit it for a zero tools bucket. The skill refuses more than 128 tools with PROMPT_TOOL_LIMIT_EXCEEDED.
modelProfileobjectNoClosed optional model profile; omit the object for CHATML_V1 on cl100k_base with no headroom.
pricingobjectNoClosed optional pricing block; omit the object to leave the cost block out of the answer.

Output schema

FieldTypeRequiredDescription
tokensobjectYesClosed token buckets for content, framing, tools, and their sum.
perMessagearrayYesBounded array of 1 through 2000 per-turn breakdowns in request order.
headroomobjectNoClosed headroom block present only when modelProfile.contextWindow was supplied.
costobjectNoClosed cost block present only when pricing was supplied, with optional currency rounding.
framingstringYesFraming convention actually applied.
tokenizerstringYesEncoding actually used to count content.
tokenizer_versionstringYesPinned tokenizer library version behind that encoding.
estimatebooleanYesAlways true so this answer cannot be mistaken for a metered bill.

API contract

POST /v1/execute
curl
curl -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}}}'
Integrations
TypeScript SDK
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

CodeHTTPRetryableDescription
VALIDATION_ERROR400NoThe request payload failed schema validation.
UNAUTHORIZED401NoMissing or invalid API key.
SKILL_NOT_ACCESSIBLE403NoThe API key is not granted access to this skill.
SKILL_NOT_AVAILABLE_FOR_PLAN403NoThis skill is not included in your current plan.
PLAN_ELIGIBILITY_UNAVAILABLE503YesPlan eligibility could not be decided. Retry after backoff.
SKILL_NOT_FOUND404NoThe requested skill or version does not exist.
TENANT_RATE_LIMITED429YesRequest rate limit exceeded. Retry after backoff.
QUOTA_EXCEEDED429YesPlan quota exhausted. Retryable once the quota period resets.
EXECUTION_TIMEOUT504YesExecution exceeded the allowed time budget.
PROMPT_MESSAGES_INVALID400NoThe request payload failed this skill's input validation.
PROMPT_ROLE_UNSUPPORTED400NoThe skill does not support the requested variant.
PROMPT_MESSAGE_LIMIT_EXCEEDED429NoA size, quota, rate, or time limit was exceeded.
PROMPT_TOOLS_INVALID400NoThe request payload failed this skill's input validation.
PROMPT_TOOL_LIMIT_EXCEEDED429NoA size, quota, rate, or time limit was exceeded.
PROMPT_FRAMING_UNSUPPORTED400NoThe skill does not support the requested variant.
PROMPT_TOKENIZER_UNSUPPORTED400NoThe skill does not support the requested variant.
PRICING_INVALID400NoThe request payload failed this skill's input validation.
PRICING_OUTPUT_TOKENS_REQUIRED400NoThe request payload failed this skill's input validation.
CURRENCY_UNKNOWN400NoThe request payload failed this skill's input validation.

Other limits

LimitValue
Recommended inline bytes32 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)

Request bodyjson
{
  "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

Inputjson
{
  "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
  }
}
Expected outputjson
{
  "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"
}
Tip
Default CHATML_V1 framing on cl100k_base. Each message carries its own envelope and the reply is primed, which is why framing is not zero even though nothing but two plain messages was sent. The cost adds the input side at your rate to 400 expected answer tokens at your output rate, and rounds to two places because the currency is EUR.

A tool schema counted under CHATML_TOOLS_V1, priced in yen

Inputjson
{
  "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."
    }
  ]
}
Expected outputjson
{
  "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"
}
Tip
The tools bucket is the serialised schema plus the published tool overhead, and it is what you would save by not sending the tool at all. JPY has no minor unit, so rounded comes back whole while totalCost keeps every decimal.
  • 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

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.

Tags

promptanalysisaistatisticstokens