AI
AI Passport

API reference

The advanced spec for Qwen3.8-Flash-Next: every endpoint, every parameter this lane really accepts, and copy-paste setups for five agent harnesses.

OpenAI + Anthropic wire262,144 ctx32,768 max outputthinking on · medium16 images/prompttools · JSON schemano rate limits

1 · Base URL and auth

Wire formatBase URLCredential
OpenAI
/chat/completions /completions /responses
https://aipassport.trirat.co/v1
with the /v1
Authorization: Bearer sk-…
Anthropic
/messages /messages/count_tokens
https://aipassport.trirat.co
no /v1 — the client appends it
x-api-key: sk-… or Bearer
+ anthropic-version: 2023-06-01

One model id: qwen3.8-flash-next. Precision GGUF UD-IQ4_XS, speculative decoding MTP (Thai draft vocabulary). Keys carry no requests-per-minute, tokens-per-minute or concurrency limit — and they are scoped to this id, so calling any other name returns an auth-scope error rather than a 404. The previous id qwen3.8-27b still works on every key and reaches the same model, so existing configs need no change.

2 · Endpoints carried

The rows are previous-model measurements, except /v1/completions. Endpoint availability depends on the engine behind the router lane, not only on the lane: /v1/completions returned 200 on the previous model and returns 404 on this one (re-measured 2026-10-23). The other rows have not been re-exercised since 2026-09-05.

EndpointNotes
GET /v1/models200the ids your key may call
GET /v1/model/info200capability metadata — max_input_tokens, max_output_tokens, supports_reasoning, supports_vision
POST /v1/chat/completions200primary surface · streaming and non-streaming
POST /v1/messages200Anthropic Messages · tools and streaming
POST /v1/messages/count_tokens200token counting
POST /v1/responses200OpenAI Responses · function tools
GET /health200liveness, no auth — portal process, not the model
POST /v1/completions404not served by this model's engine — legacy text completion; use POST /v1/chat/completions (re-measured 2026-10-23)
/v1/embeddings /v1/rerank
/v1/moderations /v1/audio/*
404not on this lane — one chat model only. Worse than useless: see trap 1
/v1/key/* /v1/user/*
/v1/spend/* /v1/team/*
404management plane is not exposed

Each endpoint links to its own shareable page — /docs/<endpoint> — which serves this same document with that endpoint's link preview and jumps to its row.

3 · Thinking — read this one

The lane runs thinking ON at medium effort by default. Two consequences bite people immediately:

Accepted effort values

ValueWhy
low medium xhigh200the recommended ladder: accepted here and on the previous model
high max200accepted here as the deepest tier, the same as xhigh; a 400 on the previous model
none off minimal200accepted here: thinking off
banana400"unknown reasoning effort 'banana': use none, low, medium or high"

⚠️ Effort vocabularies are model-specific and have changed with every model behind this endpoint. Every row above was measured on this deployment on 2026-10-12. A config that has to survive a model change should stick to low / medium / xhigh and use chat_template_kwargs for off.

Turning thinking off

no thinking
curl https://aipassport.trirat.co/v1/chat/completions \
  -H "Authorization: Bearer sk-YOUR_KEY" -H "Content-Type: application/json" \
  -d '{"model":"qwen3.8-flash-next",
       "chat_template_kwargs":{"enable_thinking":false},
       "messages":[{"role":"user","content":"say ok"}]}'

enable_thinking: false is the portable off switch; on this deployment reasoning_effort: "none" works too. A client-supplied chat_template_kwargs replaces the lane default wholesale, so include enable_thinking: true yourself if you send the object and still want thinking. carried That it also overrides a top-level effort was measured on the previous model.

Where the reasoning text goes

WireReasoning textCounted
OpenAI · chatchoices[0].message.reasoning_content (streamed: delta.reasoning_content) — measured; reasoning absentstreamed: usage.completion_tokens_details.reasoning_tokens; non-streamed: usage.completion_tokens total only
OpenAI · responsesa reasoning output item before the message — measuredusage carried
Anthropicdiscarded — only a text block comes back — measuredstill counted in output_tokens carried

Anthropic-format thinking controls measured inert on the previous model: thinking:{type:"disabled"}, budget_tokens and output_config.effort all returned 200 and all left the model thinking at the lane default. The proof they were dropped rather than honoured was that output_config:{effort:"banana"} returned 200 while reasoning_effort:"banana" returned 400. This is a router-translation behaviour, not a weights behaviour, so it is expected to hold — but if you need depth control or thinking off, use the OpenAI wire, which is unambiguous either way.

4 · Parameters

ParametersNotes
workstemperature top_p top_k min_p presence_penalty repetition_penalty seed stop logprobs top_logprobs max_tokens max_completion_tokens stream stream_options; frequency_penalty carriedaccepted (200) on this deployment, 2026-10-12. Lane defaults: temperature 1.0, top_p 0.5, presence_penalty 0.0; a value you send replaces the default. For Thai, keep top_p at 0.5 or lower: measured 2026-10-12 at temperature 1.0, replies containing Chinese characters fell from 16/180 at top_p 0.95 to 2/180 at 0.5, with HumanEval, tools and vision unchanged and single-stream decode ~5 % faster. min_p now works at nonzero temperature. Per the engine's documentation the penalties are not applied while several requests decode together, so treat them as best-effort
ignorednaccepted and ignored: n: 2 returned one choice on this deployment
400logit_bias"logit_bias: continuous batching is not supported; run without --batch." Measured on this deployment. Remove it from client plumbing rather than catching the error
worksresponse_format {type:"json_object"} response_format {type:"json_schema"}measured on this deployment. A JSON schema is validated, not enforced while generating: the model is shown the schema and its answer is checked afterwards, so a non-conforming answer comes back as an error (structured_output_failed, 502 through the router) rather than a corrected answer. Describe the schema in the prompt too, and retry on that error. Structured output cannot be combined with tools
unverifiedguided_regexaccepted (200); whether it is enforced was not established on this deployment. Do not rely on it
workstools tool_choice (auto · required · named) parallel_tool_callsthe tool call is correct in every form tested on this deployment, and a named forced choice now reports finish_reason: "tool_calls" (see trap 7)
worksimage_url (data URL or https)17 images in one prompt were accepted and counted correctly on this deployment; declare 16 in clients
ignoredguided_choice carriedaccepted and not enforced — it fails open to free prose. Use a JSON schema
400reasoning_effort outside none / off / minimal / low / medium / high / xhigh / maxmeasured; see §3

5 · Limits

LimitValueHow it fails
Context window262,144 tokens, prompt + max_tokens combined400 "request exceeds the context window: prompt (…) + max tokens (…) exceeds the context (262144); requests are never truncated" — measured
Declared max input228,352 tokens1,024 below window − max output on purpose: the chat template adds tokens your tokenizer cannot see. carried A 228,338-token prompt with thinking on and a full 32,768 reservation was accepted; 229,376 at that reservation 400s. Client configs below therefore declare a 261,120 window
Deep-context recallnot guaranteed near the windowthis is an acceptance limit, not a recall promise. On this deployment a code word planted at ~8 % depth was found in a 259,996-token prompt, and in each of four ~250K prompts read at once (direct route, one sample each). Budget accordingly for retrieval-style prompts
Public-path depth ceilingnot re-measured over aipassport.trirat.coprefill is silent through the router, and Cloudflare cuts at ~125 s. This deployment reads prompts at ~4,400 tok/s (direct: 260K tokens in 59 s), so a request that does not queue behind other long prompts should fit the full 228,352 inside that timer. Prompts are read one at a time: behind other long prompts the wait adds up, so stream and keep prompts lean. The full window is always reachable on a direct/LAN route
Declared max output32,768declare 32,768 in clients; the server enforces the window arithmetic above
Images per promptno engine cap (17 accepted)declare 16 in clients — measured
Image size≤ 1 Mpx longest edgeresized server-side, no error
Concurrency4 batch slots on the GPU host, shared by every laneexcess queues for a slot, it does not 429 (8 concurrent long agent conversations were all served in testing). The router lane also caps concurrency and queues the rest carried
Long requests carried~125 s of silenceCloudflare 524. Stream anything slow: on the previous model the same 30k-token prompt gave 524 at 125 s non-streaming vs 200 at 224 s streaming. The silence timer is Cloudflare's, so it is model-independent; the timings are not

Prompt caching is on server-side, and usage now reports it: a repeated 2,660-token prompt came back with usage.prompt_tokens_details.cached_tokens: 2653 (measured 2026-10-12). An agent conversation is kept for its next turn, so a follow-up re-reads only what was added.

6 · Errors

StatusMeaningWhat to do
400parameter or context-window rejection; the upstream message is preserved verbatimread the message — it names the field
401missing key, or a key not starting with sk-check the credential header for your wire format
429 carriedNo deployments available … Try again in 30 secondsa 30 s router cooldown, not a rate limit — see trap 1
502structured_output_failedthe answer did not match your JSON schema (§4) — retry
503code: "model_offline" + retry-after: 1800the GPU box is mid-maintenance or training. Back off; it usually serves 07:00-18:00 ICT (Bangkok)
524Cloudflare silence timerstream the request

The 429 shape quoted here is a previous-model observation; the status codes and their meanings are router behaviour and are expected to be identical. The 400 and 502 messages were measured on this deployment.

7 · Traps

  1. Calling an unsupported endpoint poisons the lane for 30 s. carried One POST /v1/embeddings (404 — there is no embedding model) puts the chat deployment into the router's cooldown, and the next /v1/chat/completions returns 429 for ~30 s before recovering on its own. Do not probe endpoints you do not use, and wait the cooldown out.
  2. Thinking is on, so cheap probes look broken. Measured on this deployment: max_tokens: 8 returned content: null (§3). Disable thinking for smoke tests, and expect reasoning-token counts only in streamed usage (§3).
  3. Effort values change with the model. high and max are accepted here and were a hard 400 on the previous model (§3). Stick to low / medium / xhigh if a config must survive the next change, and re-read §3 whenever the model behind this endpoint changes.
  4. Anthropic callers cannot steer thinking and never receive the reasoning text, though it is counted in output_tokens (§3). carried
  5. Anthropic streaming emits duplicate framing carried — a measured stream carried message_start twice and content_block_start/content_block_stop twice around a single delta. This is an upstream router defect. Real SDKs and Claude Code cope; a strict hand-rolled parser that asserts one message_start will not.
  6. guided_choice is a no-op (§4) carried — it fails open instead of erroring.
  7. A named forced tool_choice used to report finish_reason: "stop". On the previous model, with tool_choice:{type:"function",function:{name:…}}, the tool call came back correct but the finish reason was stop. Measured on this deployment, the same request returns finish_reason: "tool_calls". Branch on the presence of tool_calls in the message rather than on the finish reason anyway: it is the form that works on both.

8 · Agent harness setup

Claude Code, opencode, pi and Hermes were each run end-to-end against this endpoint through a request-capturing proxy on 2026-09-05, so the wire shapes below are observed rather than inferred — but they were captured against the previous model. The transport is unchanged, so the configs stay valid; what changed is the effort vocabulary, and each config below is already corrected for it. OpenClaw is verified against its pinned schema and a live production config, and was not executed here.

Claude Code

Anthropic /v1/messages ✅ run + wire captured
~/.claude/settings.json
{
  "env": {
    "ANTHROPIC_BASE_URL": "https://aipassport.trirat.co",
    "ANTHROPIC_AUTH_TOKEN": "sk-YOUR_KEY",
    "ANTHROPIC_MODEL": "qwen3.8-flash-next",
    "ANTHROPIC_DEFAULT_OPUS_MODEL": "qwen3.8-flash-next",
    "ANTHROPIC_DEFAULT_SONNET_MODEL": "qwen3.8-flash-next",
    "ANTHROPIC_DEFAULT_HAIKU_MODEL": "qwen3.8-flash-next",
    "ANTHROPIC_CUSTOM_MODEL_OPTION": "qwen3.8-flash-next",
    "CLAUDE_CODE_MAX_CONTEXT_TOKENS": "261120",
    "CLAUDE_CODE_MAX_OUTPUT_TOKENS": "32768",
    "CLAUDE_CODE_EFFORT_LEVEL": "medium",
    "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
  }
}

opencode

OpenAI /v1/chat/completions ✅ run + wire captured
~/.config/opencode/opencode.json
{
  "$schema": "https://opencode.ai/config.json",
  "model": "aipassport/qwen3.8-flash-next",
  "small_model": "aipassport/qwen3.8-flash-next",
  "provider": {
    "aipassport": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "AI Passport",
      "options": {
        "baseURL": "https://aipassport.trirat.co/v1",
        "apiKey": "{env:AI_PASSPORT_API_KEY}"
      },
      "models": {
        "qwen3.8-flash-next": {
          "name": "Qwen3.8-Flash-Next (AI Passport)",
          "attachment": true, "reasoning": true, "tool_call": true,
          "limit": { "context": 261120, "output": 32768 },
          "modalities": { "input": ["text", "image"], "output": ["text"] },
          "options": { "reasoningEffort": "medium" },
          "variants": {
            "off":    { "reasoningEffort": "medium",
                        "chat_template_kwargs": { "enable_thinking": false } },
            "low":    { "reasoningEffort": "low" },
            "medium": { "reasoningEffort": "medium" },
            "high":   { "disabled": true },
            "xhigh":  { "reasoningEffort": "xhigh" }
          }
        }
      }
    }
  }
}

pi

OpenAI /v1/chat/completions ✅ run + every level captured
~/.pi/agent/models.json
{
  "providers": {
    "aipassport": {
      "baseUrl": "https://aipassport.trirat.co/v1",
      "api": "openai-completions",
      "apiKey": "$AIPASSPORT_API_KEY",
      "models": [{
        "id": "qwen3.8-flash-next",
        "name": "AI Passport - Qwen3.8-Flash-Next",
        "reasoning": true,
        "thinkingLevelMap": {
          "off": "off", "minimal": null, "low": "low", "medium": "medium",
          "high": null, "xhigh": "xhigh", "max": null
        },
        "input": ["text", "image"],
        "contextWindow": 261120,
        "maxTokens": 32768,
        "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 },
        "compat": {
          "thinkingFormat": "chat-template",
          "supportsReasoningEffort": true,
          "supportsUsageInStreaming": true,
          "supportsDeveloperRole": false,
          "chatTemplateKwargs": {
            "enable_thinking": { "$var": "thinking.enabled" },
            "reasoning_effort": { "$var": "thinking.effort" }
          }
        }
      }]
    }
  }
}

# then:  pi --provider aipassport --model qwen3.8-flash-next --thinking medium

Hermes Agent

OpenAI /v1/chat/completions ✅ run + wire captured
~/.hermes/config.yaml + .env
# ~/.hermes/.env        (%LOCALAPPDATA%\hermes\.env on native Windows)
AIPASSPORT_API_KEY=sk-YOUR_KEY

# ~/.hermes/config.yaml
providers:
  ai-passport:
    api: https://aipassport.trirat.co/v1
    key_env: AIPASSPORT_API_KEY
    transport: chat_completions
    default_model: qwen3.8-flash-next
    extra_body:
      reasoning_effort: medium

model:
  provider: custom:ai-passport
  default: qwen3.8-flash-next
  context_length: 261120
  max_tokens: 32768
  supports_vision: true

OpenClaw

OpenAI /v1/chat/completions ⚠ source-verified, not run
~/.openclaw/openclaw.json
{
  models: {
    mode: "merge",
    providers: {
      aipassport: {
        baseUrl: "https://aipassport.trirat.co/v1",
        apiKey: "${AI_PASSPORT_API_KEY}",
        api: "openai-completions",
        models: [{
          id: "qwen3.8-flash-next",
          name: "Qwen3.8-Flash-Next (AI Passport)",
          reasoning: true,
          input: ["text", "image"],
          contextWindow: 261120,
          maxTokens: 32768,
          thinkingLevelMap: {
            off: null, minimal: null, low: "low", medium: "medium",
            high: null, xhigh: "xhigh", max: null,
          },
          compat: {
            supportsReasoningEffort: true,
            supportedReasoningEfforts: ["low","medium","xhigh"],
          },
        }],
      },
    },
  },
  agents: {
    defaults: {
      model: { primary: "aipassport/qwen3.8-flash-next", fallbacks: [] },
      thinkingDefault: "medium",
    },
  },
}

// then:  openclaw config validate   (strict config — an unknown key blocks startup)

Something wrong or missing?

This is a home lab. The serving contract above is proven for the model deployed on 2026-10-12; rows marked carried were measured on 2026-09-05 against the previous model over the same path and are being re-verified. If anything here disagrees with what you observe, tell me — that is a bug in the docs or the endpoint, and I want to know which.