Skip to content
KeylayerDocs
Docs menu
PricingGet started

Use Mistral with a scoped key and a spend cap

Point the Mistral SDK at https://relay.keylayer.dev with a Keylayer key to put a hard or estimated spend cap on Mistral per UTC hour, day, week or month. Only the server URL and the API key change: Keylayer swaps in your Mistral key and forwards each request to api.mistral.ai with the same method, path and query. A request can run for up to 900 seconds.

Set up

Add your Mistral provider key as a credential, then mint a Keylayer key from it; the quickstart walks through both. Mistral keys can't be limited to endpoints, so create the provider key with an expiry in a workspace that only Keylayer uses.

Put the Keylayer key in an environment variable:

Shell
export KEYLAYER_KEY=kl_k_YOUR_KEY

The SDKs add /v1 to the server URL themselves, so the server URL is the bare relay host. In version 3 of the Python SDK, the client is imported from mistralai.client:

Python (mistralai SDK)
import os
from mistralai.client import Mistral

client = Mistral(
    server_url="https://relay.keylayer.dev",
    api_key=os.environ["KEYLAYER_KEY"],
)

response = client.chat.complete(
    model="mistral-small-latest",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Write a haiku about rate limits."}],
)
print(response.choices[0].message.content)
Node.js (@mistralai/mistralai)
import { Mistral } from "@mistralai/mistralai";

const client = new Mistral({
  serverURL: "https://relay.keylayer.dev",
  apiKey: process.env.KEYLAYER_KEY,
});

const response = await client.chat.complete({
  model: "mistral-small-latest",
  maxTokens: 1024,
  messages: [{ role: "user", content: "Write a haiku about rate limits." }],
});
console.log(response.choices[0].message?.content);
curl
curl https://relay.keylayer.dev/v1/chat/completions \
  -H "Authorization: Bearer $KEYLAYER_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "mistral-small-latest", "max_tokens": 1024, "messages": [{"role": "user", "content": "Write a haiku about rate limits."}]}'

Neither SDK reads a server URL from the environment. Both read the API key from MISTRAL_API_KEY when you pass none, but the server URL always goes in your code, as server_url in Python or serverURL in Node.js. Streams work as usual: Keylayer passes server-sent events through as they arrive.

Spend caps on Mistral

Keylayer prices Mistral Large, Medium and Small, Ministral, Codestral, the zai-glm models, mistral-embed, codestral-embed, Voxtral speech and transcription, and Mistral OCR at Mistral's standard prices, and Priority Tier at 1.75 times them. What a request reserves depends on its route:

Request Hard cap Estimated cap
POST /v1/chat/completions or /v1/fim/completions Worst-case cost Worst-case cost
POST /v1/embeddings Input cost Input cost
POST /v1/audio/speech with a Voxtral TTS model Input cost Input cost
POST /v1/ocr with pages Page cost Page cost
POST /v1/ocr without pages, or past 1,000 Refused (403) 50-page estimate
POST /v1/audio/transcriptions with a Voxtral transcription model Refused (403) Upload-size estimate
POST /v1/moderations or /v1/chat/moderations, and any GET, HEAD, DELETE or OPTIONS $0.000001 $0.000001
POST /v1/batch/jobs or /v1/fine_tuning/jobs Refused (403) Refused (403)
POST cancels of batch and fine-tuning jobs, and workflow cancels, terminations, stops and pauses Refused (403) $0.000001
Any other POST, such as agents, conversations or files Refused (403) Refused (403)

The worst case counts about one token per byte of the request body, plus max_tokens. Without it, Keylayer reserves the model's maximum output: for mistral-small-latest that's 262,144 tokens, about $0.16 per request in flight, where the example above reserves about $0.0006. When the response ends, Keylayer charges the cost from the usage Mistral reports and frees the rest. A cancelled request, or a 504 or 524 timeout with no usage, is charged the full reservation; any other 5xx error with no usage is free.

Under a hard cap, Keylayer refuses with 403 unpriced_operation any request it can't bound up front: models missing from its price table, images, a service_tier other than standard_only, prompt_mode, guardrails that name a model other than a mistral-moderation one, and thinking parts with a signature. It also sets service_tier to standard_only on chat completions that name no tier, since auto can move a request to Priority Tier at 1.75 times the price. If Mistral still reports a tier it can't price, Keylayer suspends the key.

Under an estimated cap, those requests go through, except a model missing from the price table and a body Keylayer can't read, such as one with a duplicate key, which get 403 unpriced_operation under either cap. Keylayer reserves a quarter token per byte of the body at the model's input price, plus $0.01, then charges the reported usage when it can price it, or else that reservation, so spend can pass the cap. A request with built-in tools, such as image generation, is charged its reported usage plus the reservation, which stands in for the tool fees. With escape hatches on, a model missing from the table goes through at the highest input price in it, $1.50 per million tokens.

With either cap, Keylayer adds stream_options.include_usage to streamed chat completions so it can read the cost, and removes that extra usage chunk unless you asked for it.

Speech and OCR

Speech costs Mistral's price per million characters, counted per byte of input and of the JSON in phoneme_hints: $13 on voxtral-mini-tts-3-0 and voxtral-mini-tts-3, and $16 on voxtral-mini-tts-2603 and voxtral-mini-tts-latest. Accented and non-Latin characters take two to four bytes, so they cost more here than at Mistral. A voice cloned from ref_audio costs the same. 200 English characters on voxtral-mini-tts-latest cost $0.0032, streamed or not, charged when Mistral returns the audio.

OCR costs $4 per 1,000 pages on mistral-ocr-4-1, mistral-ocr-4 and mistral-ocr-latest, and $2 on mistral-ocr-2512. With bbox_annotation_format or document_annotation_format, Keylayer charges the page price plus Mistral's annotated-page price: $9 per 1,000 pages, or $5 on mistral-ocr-2512. With both, it adds the annotated-page price for each: $14 per 1,000 pages, or $8 on mistral-ocr-2512. A request that lists its pages, such as "pages": [0, 1, 2] or "pages": "0-2,5", reserves those pages, and Keylayer charges the pages Mistral reports in usage_info.pages_processed. Without pages, the document's length isn't known up front: a hard cap refuses the request, and an estimated cap reserves 50 pages, $0.20 on mistral-ocr-4-1 without annotations, then charges the pages Mistral reports, so spend can pass the cap. If such a request doesn't finish, such as a response cut short after your client hangs up, or Mistral reports no page count, Keylayer charges the reservation plus 1,000 pages, Mistral's limit for one request, or plus the pages a cut response reported if that's more: $4.20 on mistral-ocr-4-1 without annotations. Speech and OCR are charged at the full price even when Mistral bills cached input for less.

Other models, such as the realtime TTS models, and fields outside Mistral's reference, such as conversation_history and instruction, get 403 unpriced_operation, unless the cap is estimated and the key has escape hatches. Those are charged what Keylayer reserves for them, whatever Mistral reports: the price above, at the highest listed price for another model, on the whole request body for speech with a field outside the reference, and for OCR on the pages the request lists, or 50 pages without pages. A failed speech or OCR request is free, except a 504 or 524 timeout, which is charged the reservation, as is an OCR response cut off before its page count, plus the 1,000 pages above for OCR without pages that doesn't need escape hatches.

Transcripts

Transcripts cost Mistral's price per minute of audio: $0.0035 on voxtral-mini-transcribe-3-0, voxtral-mini-transcribe-3 and voxtral-mini-transcribe-latest, $0.004 on voxtral-small-transcribe-3-0, voxtral-small-transcribe-3 and voxtral-small-transcribe-latest, and $0.003 on voxtral-mini-2602. voxtral-mini-latest costs $0.0035, the Mini Transcribe 3 price, since Mistral can move the alias to a newer model.

Mistral bills the length of the audio, which Keylayer can't know up front, so a hard cap refuses transcripts. An estimated cap reserves an upload as 16 kbit/s audio, at least 10 seconds, and an hour for audio sent as file_url or file_id: $0.21 on voxtral-mini-transcribe-3-0. Keylayer then charges the seconds Mistral reports in usage.prompt_audio_seconds, streamed or not, at the full price even for audio Mistral bills at its cached rate: a 30-second voice note on voxtral-mini-transcribe-latest costs $0.00175. A response without the seconds, a 504 or 524 and one your client hangs up on that Mistral hasn't finished 5 seconds later are charged the reservation plus three hours, the longest Mistral documents: $0.54 to $0.72. Uploads over 16 MiB get 413, so send longer recordings as file_url.

Other models, such as voxtral-small-2507 and the realtime models, speaker_references and other fields outside Mistral's API reference, and a query string get 403 unpriced_operation; an escape hatch admits them under an estimated cap at the full reservation, at $0.008 a minute for another model. Keylayer reads forms the way both Mistral SDKs send them, and refuses a form another reader could split differently, such as one that sends model twice, under either cap, even with escape hatches. Other failed transcripts are free; through an escape hatch, a timeout costs the reservation alone.

Blocked routes

These Mistral routes return 403 path_not_allowed:

  • /v1/admin/**, /v1/api-keys/** and /v1/api_keys/**
  • /v1/service_accounts/**, /v1/users/** and /v1/workspaces/**
  • /v1/libraries/*/accesses/**, which shares a document library
  • /v1/client/sessions/** and /v1/audio/sessions/**, which mint session credentials that work against Mistral directly

Path blocks are a backstop: the workspace that only Keylayer uses and the key's expiry are what limit your provider key. Escape hatches lift the block for one key; the key limits page explains how.

Errors

When Keylayer refuses a request, it answers in Mistral's error format, with the code in code and in a keylayer-error header:

Response
HTTP/1.1 403 Forbidden
content-type: application/json
cache-control: no-store
keylayer-error: budget_exceeded
retry-after: 3600

{
  "object": "error",
  "message": "Keylayer budget_exceeded: this key's spend cap is reached; retry after 60m.",
  "type": "invalid_request_error",
  "param": null,
  "code": "budget_exceeded"
}

Refusals before Keylayer knows the provider, such as 401 missing_token and invalid_token, have a plain body like {"error": "invalid_token"} instead, with the same keylayer-error header.

The SDKs raise SDKError, with the status in status_code in Python or statusCode in Node.js, and the response headers in headers. Errors from Mistral itself pass through with no keylayer-error header, so check for it:

Python (mistralai SDK)
from mistralai.client import errors

try:
    response = client.chat.complete(
        model="mistral-small-latest",
        max_tokens=1024,
        messages=[{"role": "user", "content": "Write a haiku about rate limits."}],
    )
except errors.MistralError as error:
    reason = error.headers.get("keylayer-error")
    if reason is None:
        raise
    print(f"Keylayer refused the request: {reason} (HTTP {error.status_code})")

The error reference lists every code.

What doesn't work

  • The EU and US endpoints, api.eu.mistral.ai and api.us.mistral.ai. Keylayer sends Mistral requests only to api.mistral.ai.
  • Images on a key with a hard cap. Use an estimated cap for those.
  • Agents and conversations on a key with a spend cap, and transcripts and OCR without pages on a key with a hard cap, since Keylayer can't price them up front.
  • Realtime transcription. It runs over a WebSocket, which the relay doesn't carry.
  • SDK telemetry, if you turn it on with MISTRAL_SDK_TELEMETRY. The Python SDK sends traces, with your Keylayer key, straight to api.mistral.ai, where that key doesn't work, and the Node.js SDK sends them through the relay, where a spend cap refuses them.
  • Request bodies over 16 MiB on a key with a spend cap. Keylayer reads the whole body to price it, so it answers 413 body_too_large_for_budget, and 415 content_encoding_unsupported to a compressed body. Keys without a spend cap stream the body straight through.
  • Requests that run past 900 seconds. Keylayer ends them with 504 deadline_exceeded, or cuts off a started response.

Next steps

Updated

Create your first scoped key

Add a provider key once, then mint Keylayer keys with their own limits for each app, agent or teammate.

Get started