← Blog
6 de octubre, 2026 · 16 min

Cotizado en caracteres, cobrado en tokens: auditamos nuestro 402

Un precio x402 walk-up tiene que existir antes de que corra el modelo. Nuestro gateway lo calcula con una sola línea: el largo en JSON de los mensajes, dividido entre cuatro. Después el provider cobra en las unidades de su propio tokenizer. Pasamos quince idiomas y un conjunto de payloads de agentes por seis tokenizers públicos para medir la brecha. El inglés se sobrecuenta en cerca de un tercio. El chino, el japonés, los hashes de transacciones y las firmas en base64 se subcotizan entre dos y cuatro veces. Una imagen se sobrecuenta casi dieciocho veces.

Todo endpoint LLM de pago por llamada tiene el mismo problema. El agente firma el precio antes de que ocurra el trabajo, pero la unidad de trabajo, el token, la define un tokenizer que el gateway quizá no tiene. El output tiene un techo: max_tokens. El input no tiene nada más que una estimación.

Esta es la cuarta auditoría de nuestra serie sobre lo que un gateway compatible con OpenAI realmente traduce. La auditoría de reasoning tokens siguió el output oculto. La auditoría de structured outputs y la de tool calling encontraron que nuestra cotización ignora los schemas y las definiciones de tools. Esta pregunta qué hace la cotización con la parte que sí cuenta.

Nuestras fuentes, todas leídas o ejecutadas el 6 de octubre de 2026: la guía de token counting de OpenAI y la página del modelo GPT-5; las páginas de Anthropic sobre token counting, pricing y vision; la guía de tokens de Gemini de Google; la página de usage accounting de OpenRouter; LiteLLM en 18c3118; y x402 en cb0ec5b. Los tokenizers: tiktoken 0.14.0 para o200k_base y cl100k_base; los archivos tokenizer.json publicados de Qwen3-8B, DeepSeek-V3.1 y GLM-4.6; y Tekken de Mistral, desde el archivo tekken_240911.json que trae mistral-common 1.12.0. El corpus es la Declaración Universal de Derechos Humanos de la colección udhr2 de NLTK, un texto paralelo. También sondeamos nuestro propio 402.

Qué cuenta nuestro 402

Un POST sin autenticar a /v1/chat/completions devuelve HTTP 402 con un requisito x402 v2 exact en USDC sobre Base. Fijamos max_tokens en 1 para que la reserva de output fuera despreciable. Luego enviamos un carácter repetido N veces y buscamos el N más chico que movía la cotización de $0.01 a $0.02.

# api.llm4agents.com, POST sin autenticar a /v1/chat/completions, 6 oct 2026
# anthropic/claude-opus-5.5, max_tokens 1, un mensaje de usuario con N copias de un carácter
carácter              bytes UTF-8   la cotización llega a $0.02 con N =
a                     1             8,283
é                     2             8,283
क (ka devanagari)     3             8,283
的 (chino)            3             8,283
😀 (emoji)            4             4,142
" (comilla doble)     1             4,142
\ (backslash)         1             4,142
tab                   1             4,142
salto de línea        1             4,142

Los bytes no importan. Un carácter chino cuesta exactamente lo mismo que una a latina. Lo que importa es el largo del string serializado en JSON, medido en unidades de código UTF-16. Los caracteres que JSON escapa (comillas, backslashes, tabs, saltos de línea) cuentan doble. También un emoji, que ocupa dos unidades UTF-16.

Los umbrales encajan exactamente con una fórmula. La cotización serializa messages, divide el largo entre cuatro, redondea hacia arriba y lo valora a la tarifa de input del modelo. Suma max_tokens a la tarifa de output, aplica un fee de plataforma del 20% y redondea hacia arriba al centavo. El sobre de 30 caracteres [{"role":"user","content":""}] explica por qué el salto cae en 8,283 y no en 8,313. Después leímos el código de nuestro gateway y encontramos la misma línea: Math.ceil(JSON.stringify(messages).length / 4). Cuando falta max_tokens, el código reserva 4,096 tokens de output.

# la cotización walk-up, reconstruida con sondas y confirmada en nuestro código
est_input  = ceil( JSON.stringify(messages).length / 4 )
quote_usd  = 1.20 * ( est_input * input_price + (max_tokens ?? 4096) * output_price )
quote      = max( $0.01, ceil_to_cent(quote_usd) )

Cuatro caracteres por token no es invento nuestro. La guía de Gemini de Google dice que "a token is equivalent to about 4 characters". El problema es la palabra "about", y los idiomas y payloads para los que nunca se pensó.

Cómo cuentan los providers

OpenAI ofrece un endpoint para contar tokens de input, POST /v1/responses/input_tokens. Acepta el mismo input que la Responses API y "returns the exact count the model will receive". Ese conteo incluye "formatting tokens used to represent request structure, such as message roles and boundaries", que un tokenizer local no ve. La guía dice que tiktoken sirve para texto plano, pero que para imágenes y archivos "estimates like characters / 4 are inaccurate". tiktoken 0.14.0 asigna gpt-5 a o200k_base.

El endpoint count_tokens de Anthropic es gratis. Tiene sus propios rate limits, separados de la creación de mensajes: 5,000, 10,000 y 20,000 requests por minuto en los tiers Start, Build y Scale. El resultado es "an estimate", y puede incluir tokens agregados por el sistema que no se cobran. La línea importante es sobre generaciones. Claude 4.7 y los modelos posteriores "use a newer tokenizer" que produce "approximately 30 percent more tokens" para el mismo texto. Fable 5.1, Mythos 5.1, Fable 5 y Mythos 5 lo comparten. La documentación pide no "reuse token counts measured on the older model to estimate costs". Ofrece el endpoint, no un tokenizer local.

Google documenta countTokens y la regla de los cuatro caracteres, más tarifas fijas para media: 258 tokens para una imagen de hasta 384 píxeles por lado, 258 por cada tile de 768x768 por encima de eso, 263 tokens por segundo de video y 32 por segundo de audio.

Las capas intermedias eligen un lado. OpenRouter reporta el uso "using the model's native tokenizer", y ahora lo incluye en cada respuesta; stream_options.include_usage está deprecado y no tiene efecto. El token_counter de LiteLLM elige un tokenizer de Hugging Face para cuatro familias y, si no, usa tiktoken según el nombre del modelo, con cl100k_base como fallback para los nombres que tiktoken no conoce (utils.py, token_counter.py). Para todo modelo de Anthropic cuyo nombre no contenga claude-3, carga un anthropic_tokenizer.json empaquetado con un vocabulario de 65,000 entradas. Su mapa de modelos lista claude-opus-5-5 y claude-fable-5-1 como modelos de Anthropic, así que les toca ese archivo. Anthropic dice que el tokenizer detrás de esos modelos es nuevo.

Así que un gateway queda entre tres hechos. El precio tiene que fijarse antes de la llamada. El conteo exacto vive detrás de un endpoint del provider, o en un archivo de tokenizer público para OpenAI y las familias open-weight. La factura usa el conteo nativo.

Una declaración, quince idiomas

La Declaración Universal de Derechos Humanos es el texto paralelo clásico: el mismo contenido en cientos de idiomas. Normalizamos cada versión a NFC, la contamos con seis tokenizers y dividimos entre lo que nuestra cotización asume para el mismo texto. Por encima de 1.00, la cotización cuenta de menos.

# tokens reales / estimación de la cotización, solo texto de la DUDH, NFC, 6 oct 2026
# quote = ceil(largo UTF-16 con escapes JSON / 4); DSV3.1 = DeepSeek-V3.1
idioma             chars   quote   o200k cl100k  Qwen3 DSV3.1 GLM4.6 Tekken
Inglés            10,637   2,683    0.75   0.75   0.76   0.75   0.75   0.77
Español           11,964   3,015    0.82   0.99   0.99   0.94   0.92   0.88
Portugués (BR)    11,280   2,844    0.84   1.06   1.04   0.96   0.98   0.90
Francés           11,901   2,999    0.88   1.04   1.04   1.00   0.98   0.89
Alemán            11,936   3,008    0.85   1.10   1.09   1.00   0.97   0.87
Turco             10,278   2,593    1.15   1.54   1.33   1.59   1.32   1.25
Suajili           10,452   2,637    1.14   1.55   1.56   1.53   1.54   1.49
Vietnamita        11,059   2,789    1.11   1.96   1.09   1.69   1.10   1.09
Ruso              11,805   2,975    0.95   1.73   1.18   1.03   0.98   1.04
Árabe              7,645   1,935    1.24   2.74   1.45   1.43   1.56   1.17
Hindi             11,500   2,899    1.16   3.89   3.66   2.12   3.89   1.36
Bengalí            9,711   2,452    1.37   4.85   4.20   1.67   4.85   1.63
Chino (simpl.)     2,988     771    3.07   4.48   2.36   2.16   2.21   3.44
Japonés            4,182   1,069    3.33   4.51   2.73   2.53   2.80   3.05
Coreano            4,715   1,202    2.28   3.88   2.44   2.53   3.02   2.04

Cinco lecturas.

El inglés se sobrecuenta en todos los tokenizers: la estimación queda entre 30 y 33% por encima del conteo real. Los seis caen entre 5.2 y 5.3 caracteres por token en este texto, no cuatro. Un agente que habla inglés firma por input que no envió, antes del fee.

El español, nuestro segundo idioma, está cerca del punto de equilibrio, entre 0.82 y 0.99. El mismo contenido cuesta entre 23 y 48% más tokens en español que en inglés. El estimador ve solo 12% más caracteres.

La subcotización empieza con el turco y el suajili (1.14 a 1.59) y crece con la distancia al alfabeto latino. El árabe va de 1.17 a 2.74, el hindi de 1.16 a 3.89, el bengalí de 1.37 a 4.85.

El CJK se subcotiza en todos los tokenizers. El chino va de 2.16 a 4.48, el japonés de 2.53 a 4.51, el coreano de 2.04 a 3.88. El estimador cree que la declaración en chino cuesta 0.29 veces la inglesa. Los tokenizers dicen entre 0.83 y 1.71 veces.

Los tokenizers discrepan entre sí más que con el estimador. El hindi son 3,376 tokens en o200k_base y 11,284 en cl100k_base: una brecha de 3.3x entre dos encodings de OpenAI. Un gateway que cuenta "con tiktoken" sin elegir el encoding por modelo hereda esa brecha.

El mismo texto, otros code points

El archivo vietnamita del corpus llegó descompuesto. Sus acentos están guardados como marcas combinantes después de cada letra base. Componerlo a NFC lo reduce de 13,012 a 11,059 code points. En pantalla, las dos versiones se ven idénticas.

# DUDH en vietnamita, tal como viene (descompuesta) vs NFC, 6 oct 2026
contador          descompuesta     NFC
nuestra quote            3,277   2,789
o200k                    6,950   3,093
cl100k                   8,659   5,468
Qwen3                    3,032   3,032
DeepSeek-V3.1            7,905   4,706
GLM-4.6                  7,844   3,074
Tekken                   8,966   3,033

El tokenizer.json de Qwen3 declara un normalizador NFC, así que cuenta igual ambas formas. Los otros cinco no normalizan y cuentan el texto descompuesto entre 1.6 y 3.0 veces. Ninguna de las documentaciones de providers que leímos dice si la API normaliza el texto antes de cobrarlo. Si no lo hace, un agente puede duplicar su factura de input sin cambiar un solo carácter visible.

Los payloads que cargan los agentes

Los agentes sobre un rail de pagos no envían sobre todo prosa. Envían tool results: recibos, hashes, direcciones, firmas, feeds de precios. También los medimos. Las filas reales son un archivo Python de la biblioteca estándar, un archivo JavaScript de npm, nuestro propio documento OpenAPI, una de nuestras páginas del blog, diez recibos de transacciones de Base y el header PAYMENT-REQUIRED que devolvió nuestro gateway. Las filas de hashes, direcciones, base64, UUIDs, CSV y emojis son sintéticas, generadas con una semilla fija.

# tokens reales / estimación de la cotización, mismo método, 6 oct 2026
payload                                  chars   quote   o200k cl100k  Qwen3 DSV3.1 GLM4.6 Tekken
Código Python (json/encoder.py)         16,074   4,162    0.83   0.82   0.83   0.89   0.82   0.88
Código JavaScript (npm install.js)       5,383   1,393    0.94   0.94   0.94   0.98   0.94   0.96
JSON minificado (nuestro openapi.json)  62,176  16,855    0.94   0.93   0.97   1.00   0.94   1.02
JSON con indentación de 2 (mismo doc)   98,589  26,636    0.83   0.83   0.85   0.86   0.84   0.87
HTML (una de nuestras páginas del blog) 36,474   9,396    1.02   1.02   1.05   1.08   1.02   1.09
10 recibos de Base, JSON minificado     24,268   6,486    1.57   1.57   2.67   1.62   1.70   2.71
nuestro header PAYMENT-REQUIRED (b64)      552     139    2.55   2.79   2.85   2.61   2.79   3.03
Hashes de tx en hex (0x + 64), sint.    20,099   5,100    2.32   2.31   3.48   2.35   2.68   3.52
Direcciones EVM (0x + 40), sint.        12,899   3,300    2.35   2.35   3.47   2.39   2.69   3.51
Firmas base64 de 65 bytes, sint.        26,699   6,750    2.70   2.83   2.92   2.76   2.83   3.01
Lista de UUIDv4, sint.                  11,099   2,850    2.49   2.49   3.44   2.51   2.77   3.46
Feed de precios CSV, sint.              11,573   2,994    2.01   2.01   3.86   2.01   2.42   3.86
Línea de emojis, sint.                   3,999   1,426    2.67   3.45   3.44   3.03   3.45   4.14

El código, el JSON y el HTML quedan cerca o por debajo del equilibrio, entre 0.82 y 1.09. El JSON indentado es más barato de lo que parece al estimador. El escape JSON cuenta doble cada comilla interna, mientras que los tokenizers pliegan las rachas de espacios en tokens únicos.

Todo lo que maneja un agente de pagos se subcotiza entre dos y cuatro veces. Los diez recibos vienen del bloque 52,244,264 de Base vía eth_getTransactionReceipt, y van de 1.57 a 2.71. Nuestro propio header 402 va de 2.55 a 3.03 en base64. Decodificado, el mismo header son 147 tokens en o200k_base en vez de 355. El base64 más que duplica los tokens del JSON que envuelve.

Los dígitos explican la dispersión entre tokenizers. Los patrones de pre-tokenización de Qwen3 y Tekken separan cada dígito por su cuenta (\p{N}). o200k_base, cl100k_base, DeepSeek-V3.1 y GLM-4.6 agrupan hasta tres (\p{N}{1,3}). El hex y los feeds de precios cuestan entre 3.5 y 3.9 veces la estimación en el primer par, y entre 2.0 y 2.7 en el resto.

Imágenes: el error en la otra dirección

El estimador trata una imagen enviada como data URL en base64 como si fuera texto. Generamos un JPEG de 1000x1000 de 69,433 bytes. Como data URL son 92,603 caracteres, y la cotización estima el mensaje en 23,181 tokens de input. Anthropic documenta el costo de imagen como ⌈width / 28⌉ × ⌈height / 28⌉ visual tokens, que son 1,296 para este tamaño en todos los tiers de Claude. La cotización cuenta 17.9 veces lo que cobra Claude.

# api.llm4agents.com, anthropic/claude-opus-5.5, max_tokens 500, 6 oct 2026
request                                          monto del 402
"Describe this image." (solo texto)              $0.02
  + un JPEG de 1000x1000 como data URL           $0.13
  + cinco copias                                 $0.57
  + diez copias                                  $1.13

Con el conteo documentado de Claude, diez imágenes así son 12,960 tokens de input. Al precio de lista de Opus 5.5, $4 por millón, eso es $0.052. Suma los 500 tokens completos de output y nuestro fee, y la llamada cuesta $0.08 después de redondear. La cotización pide $1.13.

Ese número cruza una línea que el agente no trazó. Desde que se lanzaron los spend controls, el SDK de TypeScript de x402 limita cada pago a $1 por defecto, como registra su changelog. Nuestra auditoría de spend controls cubre ese límite. Un agente cuidadoso con la configuración por defecto se niega a pagar un request que cuesta ocho centavos.

Quién paga el error

Una estimación equivocada no siempre produce una factura equivocada. La cotización también reserva el max_tokens completo de output, y el agente rara vez lo usa entero. El costo real supera la cotización cuando el input no contado, valorado a la tarifa de input, es mayor que la reserva de output sin usar valorada a la tarifa de output, más lo que deje el redondeo al centavo. Las llamadas chicas tienen un centavo de holgura. Las grandes no.

Medimos una grande. Treinta y cuatro copias de la declaración en chino suman 101,658 caracteres. Enviadas a openai/gpt-5 con max_tokens 1,000, la cotización es $0.06, construida sobre una estimación de 26,212 tokens de input. En o200k_base, el encoding que tiktoken asigna a GPT-5, el texto son 80,478 tokens: 3.07 veces la estimación. Al precio de lista de GPT-5, $1.25 por millón, más nuestro fee, solo el input cuesta $0.12. Sin output la llamada cuesta $0.13. Con los 1,000 tokens completos de output cuesta $0.14. Las dos cifras superan el doble de la cotización, antes de los formatting tokens de OpenAI.

Lo que pasa después depende del camino. En el código actual de nuestro gateway:

Así que una subcotización es o un subsidio para el input denso en tokens o una falla en el camino que con más probabilidad usa un agente walk-up honesto.

Una sobrecotización cuesta algo distinto en cada rail. En el rail prepago la reserva se devuelve al liquidar, así que la sobrecotización del inglés cuesta float, no dinero. En el rail walk-up el requisito es exact, y el agente firma el monto inflado. Nuestro handler después calcula el costo real y se lo pasa al middleware de x402 como settlement override. Ese es el mecanismo que x402 construyó para upto: el doc del esquema upto dice fijar el precio "to the maximum authorized amount" y "use settlement overrides to charge the actual amount". El resource server del core de x402 es explícito sobre el otro caso. Sobrescribir el monto "is only valid in schemes that support partial settlement", y con exact "will likely cause settlement verification to fail".

Qué significa para LLM4Agents

El monto del 402 es la señal de precio que leen los agentes autónomos. Un agente que compara dos modelos, o dos gateways, compara montos de 402. El nuestro acierta para prosa en inglés, con un margen de cerca de un tercio a nuestro favor. Se equivoca en nuestra contra con texto árabe, índico y CJK en todos los tokenizers que corrimos. Y se equivoca con los payloads que cargan nuestros usuarios principales.

Ese último punto es el incómodo. LLM4Agents está hecho para agentes que pagan. Su contexto está lleno de hashes, direcciones, headers de pago en base64 y recibos, justo donde más falla el estimador. Un agente en español recibe un precio casi justo. A un agente en japonés se le cotiza entre el 22 y el 40% de su input real.

La amenaza corre en ambos sentidos. Una subcotización sistemática es un subsidio que cualquiera puede encontrar sondeando 402s, como acabamos de hacer, y un costo que cargamos en cada request denso en tokens. Una sobrecotización sistemática nos hace ver caros al lado de un competidor que cuente mejor. Con imágenes, empuja a los agentes cuidadosos por encima de sus propios límites de gasto.

La oportunidad es que el arreglo es en buena parte conocimiento público. OpenAI y las familias open-weight publican sus tokenizers. Anthropic, OpenAI y Google ofrecen endpoints de conteo. Los costos de imagen son fórmulas documentadas. Un gateway que cotiza con pocos puntos de error puede ofrecer exact sin pudor, o upto con un techo ajustado. "El 402 es la factura" es una característica de producto, y pocos gateways pueden decirlo.

Cómo mantenerse en la frontera

En orden de costo y urgencia.

1. Contar con el tokenizer real donde es público. Usar o200k_base para las familias GPT-4o y GPT-5, como las asigna tiktoken, y el tokenizer.json publicado para familias open-weight como Qwen, DeepSeek, GLM y Mistral. Elegir el encoding por modelo y nunca caer en uno por defecto: cl100k_base y o200k_base difieren 3.3x en hindi. Planificar el tamaño. El archivo de o200k_base pesa 3.6 MB, el tokenizer de Qwen3 11.4 MB y el de GLM-4.6 20 MB.

2. Calibrar las familias cerradas contra sus endpoints. El count_tokens de Anthropic es gratis y tiene rate limits propios. Usarlo, junto con los contadores de Google y OpenAI, offline para construir ratios por modelo y por escritura, en vez de llamarlos en cada request. Repetir la calibración cada vez que un modelo cambia de generación. El tokenizer de Claude 4.7 movió los conteos cerca de 30% en un solo release.

3. Lanzar ya una heurística de respaldo mejor. Contar bytes UTF-8 en vez de unidades UTF-16 es un cambio de una línea. Entre nuestros seis tokenizers, reduce la dispersión por idioma de 0.75–4.85 a 0.45–1.82, y solo en o200k_base, de 0.75–3.33 a 0.45–1.16. Dejar de contar los escapes JSON. Agregar una regla para rachas largas de hex, base64 y dígitos, que siguen entre dos y cuatro veces por debajo porque son ASCII y los bytes no ayudan.

4. Cotizar las imágenes por sus dimensiones, no por su base64. Leer ancho y alto del header de la imagen y aplicar la regla documentada: ⌈w/28⌉ × ⌈h/28⌉ para Claude, 258 tokens por tile para Gemini, el endpoint de conteo de OpenAI para sus modelos. Solo eso elimina la sobrecuenta de 17.9x y mantiene los requests con imágenes debajo de los límites de gasto por defecto.

5. Contar lo que la cotización se salta. Las definiciones de tools y response_format siguen siendo gratis en la estimación, como encontraron las auditorías anteriores. Sumarlas, más el overhead documentado de cada provider para roles y formato.

6. Mover el walk-up de LLM a upto. Con un techo preciso dentro de un margen conocido y la liquidación sobre el conteo nativo, una sobrecotización deja de costarle dinero al agente y el settlement override pasa a ser correcto según el protocolo. Nuestro post sobre upto cubre el esquema. Dimensionar el margen de seguridad por modelo y por grupo de escritura. Cuando la estimación es demasiado incierta para acotarla, rechazar antes de la llamada, no con un 500 después.

7. Medir la deriva y mostrarla. Registrar la estimación contra el uso reportado por el provider en cada llamada, por modelo y por escritura. Alertar cuando el ratio de un modelo se mueve, porque un release de tokenizer puede moverlo de un día para otro. Devolver los tokens de input estimados con el 402 y el conteo nativo con el recibo, para que un agente pueda auditar sus propias facturas.

Los pasos uno a cuatro hacen que el número sea correcto. El cinco y el seis lo hacen seguro. El siete lo mantiene correcto.

Una cotización que es la factura

Compatible con OpenAI, pagado por llamada en USDC sobre x402.

Registra tu agente