Errors and handling

Understand connection errors, error events, stop reasons, fallback and spend limits, and retry safely.

Things go wrong in a few places: the connection, a single turn, a provider, or your spend limits. This guide shows what each looks like and what to do.

Connection errors

If the WebSocket cannot open, you get a normal HTTP status instead of an upgrade.

StatusMeaningWhat to do
401Key missing, unknown or revokedCheck the key at vaktex.com/app
404Reattach only: no such session for this keyStart a new session
426The request was not a WebSocket upgradeUse a WebSocket client
503Key verification is briefly unavailableRetry after a short wait

Error events

During a connection, errors arrive as events and the socket stays open.

{ "type": "error", "code": "rate_limited", "message": "Monthly spend limit reached", "retryable": false, "seq": 3, "...": "..." }
  • An error that happens during a turn has a seq and is followed by turn.complete.
  • An error about the connection itself, such as a malformed frame, has no seq.
  • retryable says whether trying the same thing again might work.

The helper from the quickstart collects them in result.errors:

const result = await gateway.turn(request);

if (result.errors.length > 0) {
	const { code, message, retryable } = result.errors[0];
	console.error(`${code}: ${message}`);
}

Error codes

The codes are the same for every provider, so you only handle one set.

CodeRetryableMeaning
invalid_requestnoA frame or request failed validation. Fix the request
unauthorizednoNot authorized
model_not_foundnoThe provider and model pair does not exist
no_capable_modelnoNo model can serve this request, for example the images, tools or length do not fit
refusednoThe models declined to answer
session_busynoA turn is already running on this session
session_not_foundnoNo such session
client_disconnectnoThe client went away
provider_misconfigurednoA provider is not set up correctly
provider_unavailableyesA provider is down
rate_limitedyesA rate or spend limit was hit
upstream_timeoutyesThe provider timed out
upstream_erroryesThe provider returned an error
interruptedyesThe session restarted mid-turn. Send the turn again
internalnoUnexpected gateway error

Spend limits use rate_limited but are marked not retryable, since waiting a few seconds will not help. See Spend limits.

Stop reasons

Check turn.complete.stop_reason before you trust a result. stop, length, stop_sequence and tool_use are normal endings. error, refused, rate_limited, disconnect and interrupted mean the answer may be missing or cut short. The full list is in Text inputs and outputs.

Fallback

When a model errors, is unavailable or refuses, the gateway can finish the turn on another capable model. It retries brief failures a couple of times first. If it switches, it tells you:

{ "type": "model.switched", "from": { "provider": "anthropic", "model": "claude-sonnet-5-5" }, "to": { "provider": "openai", "model": "gpt-6-sol" }, "reason": "error", "seq": 5, "...": "..." }

reason is refusal, error or unavailable. Later events carry the new provider and model. Events you already received are never rewritten. If the first model had already written part of the answer, the next model continues from it.

turn.complete.attempts lists every model that was tried, in order, with an outcome:

OutcomeMeaning
completedThe model finished the turn
retriedA brief failure, tried again on the same model
errorThe model failed
refusedThe model declined
stoppedYou stopped the turn, or a limit did

A turn that has already delivered a tool call to you does not switch models. See Function calling.

If every candidate fails, you receive an error and the turn ends. When all of them refuse, the code is refused.

Spend limits

Your account can have limits on spend, measured in US dollars at model cost.

LimitWhat happens when you reach it
Monthly (calendar month, UTC)Every running turn on the account stops
Weekly (Monday to Sunday, UTC)Every running turn on the account stops
Per providerTurns using that provider stop
4 hoursNew turns are blocked. Running turns finish

A turn ended by a limit sends an error with code rate_limited and completes with stop_reason: "rate_limited". To stay ahead of limits, read budget on the stream: units_remaining is what is left for the tightest limit, and limit_window says which one.

If you have no weekly limit but do have a monthly one, the weekly limit defaults to a quarter of the monthly amount.

Retry safely

Retry only when retryable is true, wait between attempts, and give up after a few tries. A simple pattern is exponential backoff with a little randomness:

async function turnWithRetry(gateway, request, attempts = 4) {
	for (let attempt = 0; ; attempt++) {
		const result = await gateway.turn(request);
		const error = result.errors[0];

		if (!error || !error.retryable || attempt === attempts - 1) return result;

		const delay = 500 * 2 ** attempt + Math.random() * 250;
		await new Promise((resolve) => setTimeout(resolve, delay));
	}
}

Two rules:

  • Do not retry non-retryable errors. invalid_request fails the same way every time.
  • Do not repeat tool work. If a failed turn already delivered a tool call that you ran, send the result in the next turn instead of starting over.

Dropped connections

If the socket closes in the middle of a turn, reconnect and resume instead of starting over. See Reconnect and resume.

Check the service

GET https://gateway.vaktex.com/health returns { "healthy": true } and needs no key. /health/live and /health/ready are also available.

Get help

For anything not covered here, the full protocol reference is at gateway.vaktex.com/openapi.

enesfrdenbsvfiplarhe