---
title: Tokenization and Parsing APIs
---
# Tokenization and Parsing APIs
SMG exposes utility endpoints for tokenization, detokenization, function-call parsing, and reasoning separation. They use the gateway's own tokenizers and the same parser registries as the [gRPC pipeline](../concepts/architecture/grpc-pipeline.md). You can use them to see exactly how SMG tokenizes a prompt or splits a model's raw output.
#### Before you begin
- Completed the [Getting Started](index.md) guide
- For `/v1/tokenize` and `/v1/detokenize`, at least one loaded tokenizer: from `--model-path` or `--tokenizer-path` at startup, from a registered gRPC worker, or added with `POST /v1/tokenizers`
| Method | Path | Purpose | Auth |
|--------|------|---------|------|
| `POST` | `/v1/tokenize` | Convert text to token IDs | Serving API key |
| `POST` | `/v1/detokenize` | Convert token IDs to text | Serving API key |
| `POST` | `/parse/function_call` | Extract tool calls from model output | Control plane |
| `POST` | `/parse/reasoning` | Separate reasoning from the final answer | Control plane |
---
## Tokenize
`POST /v1/tokenize`
| Field | Type | Description |
|-------|------|-------------|
| `model` | string | Name of a loaded tokenizer (required) |
| `prompt` | string or array of strings | Text to tokenize (required) |
`model` is the name a tokenizer was loaded under: a worker's model ID, the `--model-path` or `--tokenizer-path` value the gateway started with, or the `name` given to `POST /v1/tokenizers`. A tokenizer ID also works, and an empty string or `"unknown"` picks the loaded tokenizer whose name sorts first. The endpoint doesn't add special tokens such as BOS.
Single input:
```bash
curl http://localhost:30000/v1/tokenize \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen/Qwen3-4B-Instruct-2507",
"prompt": "Hello world"
}'
```
```json
{"tokens": [9707, 1879], "count": 2, "char_count": 11}
```
Batch input:
```bash
curl http://localhost:30000/v1/tokenize \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen/Qwen3-4B-Instruct-2507",
"prompt": ["Hello", "World"]
}'
```
```json
{"tokens": [[9707], [10134]], "count": [1, 1], "char_count": [5, 5]}
```
| Response field | Description |
|----------------|-------------|
| `tokens` | Token IDs; one array per input for a batch |
| `count` | Number of tokens; an array for a batch |
| `char_count` | Number of characters in the input; an array for a batch |
---
## Detokenize
`POST /v1/detokenize`
| Field | Type | Description |
|-------|------|-------------|
| `model` | string | Name of a loaded tokenizer (required) |
| `tokens` | array of IDs, or array of arrays for a batch | Token IDs to decode (required) |
| `skip_special_tokens` | boolean | Drop special tokens from the output. Default `true` |
```bash
curl http://localhost:30000/v1/detokenize \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen/Qwen3-4B-Instruct-2507",
"tokens": [9707, 1879],
"skip_special_tokens": true
}'
```
```json
{"text": "Hello world"}
```
A batch request returns `text` as an array of strings. For tiktoken-based tokenizers such as Kimi's, `skip_special_tokens` drops only the tokens marked `"special": true` in `tokenizer_config.json`. Control markers without that flag stay in the text (smg-project/smg#2421).
### Errors
Errors use the body `{"error": {"message": "...", "type": "..."}}`:
| Status | `type` | Cause |
|--------|--------|-------|
| 400 | `tokenizer_not_found` | No tokenizer is loaded under that name (the message lists the loaded names), or none is loaded at all |
| 500 | `tokenization_error` / `detokenization_error` | The tokenizer failed on the input |
---
## Parse Function Calls
`POST /parse/function_call`
Runs one tool-call parser over complete model output, the same way the gateway parses a non-streaming response.
| Field | Type | Description |
|-------|------|-------------|
| `text` | string | Raw model output (required) |
| `tool_call_parser` | string | Registered parser name (required) |
| `tools` | array | Tool definitions in OpenAI format (required; may be empty). Schema-aware parsers use them to convert argument types |
`tool_call_parser` accepts the 24 registered names: `passthrough`, `json`, `mistral`, `qwen`, `qwen_xml`, `qwen_coder`, `nemotron`, `pythonic`, `llama`, `deepseek`, `deepseek31`, `deepseek32`, `deepseek_v4`, `deepseek_v41`, `glm45_moe`, `glm47_moe`, `step3`, `sarashina`, `kimik2`, `kimi_k3`, `inkling`, `minimax_m2`, `minimax_m3`, `cohere`. The [gRPC Pipeline](../concepts/architecture/grpc-pipeline.md#tool-call-parsers) reference shows each parser's format.
If control-plane auth is configured, include an admin bearer token.
```bash
curl http://localhost:30000/parse/function_call \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-d '{
"text": "{\"name\":\"get_weather\",\"arguments\":{\"city\":\"SF\"}}",
"tool_call_parser": "json",
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string"}
},
"required": ["city"]
}
}
}
]
}'
```
```json
{
"remaining_text": "",
"tool_calls": [
{"function": {"name": "get_weather", "arguments": "{\"city\":\"SF\"}"}}
],
"success": true
}
```
`remaining_text` is the output with the tool calls taken out, and each `arguments` value is a JSON string. When the text holds no tool call, `tool_calls` is empty and `remaining_text` is the whole text. An unknown parser name returns 400 with `{"error": "Unknown tool parser: ", "success": false}`, and a parser failure returns 400 with a `Failed to parse function calls` message.
---
## Parse Reasoning
`POST /parse/reasoning`
| Field | Type | Description |
|-------|------|-------------|
| `text` | string | Raw model output (required) |
| `reasoning_parser` | string | Registered parser name (required) |
`reasoning_parser` accepts the 19 registered names: `base`, `passthrough`, `deepseek_r1`, `deepseek_v31`, `deepseek_v4`, `deepseek_v41`, `qwen3`, `qwen3_thinking`, `glm45`, `step3`, `kimi`, `kimi_k25`, `kimi_thinking`, `kimi_k3`, `minimax`, `minimax_m3`, `cohere_cmd`, `nano_v3`, `inkling`.
If control-plane auth is configured, include an admin bearer token.
```bash
curl http://localhost:30000/parse/reasoning \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-d '{
"text": "internal reasoningFinal answer",
"reasoning_parser": "deepseek_r1"
}'
```
```json
{"normal_text": "Final answer", "reasoning_text": "internal reasoning", "success": true}
```
Each request gets a fresh parser in its default state, with no prompt behind it. Parsers that start in reasoning (`deepseek_r1`, `qwen3_thinking`, `kimi_thinking`, `step3`, `minimax`) treat text before the end marker as reasoning even without a start marker. The others need the start marker, such as ``, in `text`. The serving pipeline differs: there, a request with thinking on starts the parser in reasoning mode. An unknown parser name returns 400 with `{"error": "Unknown reasoning parser: ", "success": false}`.
---
## Auth Notes
- `/v1/tokenize` and `/v1/detokenize` are serving routes. When `--api-key` or `--tenant-api-key` is set, send one of those keys as a bearer token. The same admission control as inference requests applies (concurrency limit or priority scheduler).
- `/parse/function_call` and `/parse/reasoning` are control-plane routes. With [control-plane auth](control-plane-auth.md) configured (`--control-plane-api-keys`, or `--jwt-issuer` with `--jwt-audience`), they need a bearer credential with the admin role. Without it, they accept only the shared `--api-key`, never a tenant key. If only `--tenant-api-key` is set, they reject every request with 401, and with no keys configured at all they're open.
---
## Next Steps
- [gRPC Pipeline](../concepts/architecture/grpc-pipeline.md) — Parser formats, auto-detection, and per-model parser overrides
- [Gateway Extensions API](../reference/api/extensions.md) — Route groups for tokenizer management and parser utilities
- [Admin API Reference](../reference/api/admin.md) — Add, list, and remove tokenizers with `/v1/tokenizers`