~/writing/mistral-vibe-devex-audit
10. September 2026 · 31 Min. Lesezeit · DevEx · AI tooling · API design · Debugging
Was bietet Mistrals Free Tier Entwicklerinnen und Entwicklern tatsächlich?
Eine Untersuchung von Mistrals Free Tier führte zu API-Instrumentierung, einer Karte von Modellen und Fähigkeiten und schließlich zu einem DevEx-Audit von Mistral Vibe.
Ich habe diese Untersuchung nicht begonnen, weil ich Mistral Vibe debuggen wollte.
Ich begann mit einer viel breiteren Frage:
Was bietet Mistral Entwicklerinnen und Entwicklern heute tatsächlich?
Mistrals Plattform war längst über eine einzelne Familie von Chat-Modellen hinausgewachsen: Coding-Modelle, kleinere lokal gut nutzbare Modelle, Audio, OCR, Moderation, Embeddings, Labs-Modelle und ein CLI-Coding-Agent. Dann fiel mir auf, dass es ein Free Tier gab.
Das führte direkt zur nützlicheren Frage:
Was kann ich im Free Tier tatsächlich nutzen?
Nicht, was im Katalog existiert. Nicht, was in der Dokumentation steht. Nicht, was eine Preiszeile hat. Was kann ein authentifizierter Free-Account tatsächlich aufrufen, über welche Schnittstelle und mit welchen Limits?
Vibe schien der naheliegende Einstieg aus Entwicklersicht zu sein. Es ist ein lokaler CLI-Coding-Agent, unterstützt von Mistral gehostete Modelle, und der Free-Account bot Vibe-spezifische Nutzung an. Statt mit synthetischen API-Tests zu beginnen, probierte ich das Produkt so aus, wie es eine Entwicklerin oder ein Entwickler vernünftigerweise tun würde.
An dieser Stelle begann die eigentliche Recherche.
Von der Plattformübersicht zum DevEx-Audit
Die Untersuchung sah schließlich so aus:
- 01 Mistral-Plattform
- 02 Free Tier
- 03 Vibe als Einstiegspunkt
- 04 Browser-Login
- 05 Fehler bei Zugangsdaten
- 06 HTTP 429
- 07 0 RPM
- 08 46 model IDs
- 09 Fähigkeitsprobes
- 10 Labs-Status
- 11 Empirische Karte
- 12 DevEx-Audit
investigation-path.txt text Quelltext anzeigen
What does Mistral offer today?
↓
there is a Free tier
↓
what is actually included?
↓
Vibe is a developer-facing entry point
↓
install Vibe
↓
browser login
↓
login succeeds
↓
try default Medium 3.5
↓
API / authentication error
↓
check Mistral account online
↓
API / Vibe key exists
↓
check local credential state
↓
~/.vibe/.env DOES NOT EXIST
↓
create a new Vibe API key manually
↓
manually create ~/.vibe/.env
↓
authentication works
↓
request STILL doesn't work ಠ_ಠ
↓
debug console reveals HTTP 429
↓
direct API request + response headers
↓
Medium 3.5 = 0 RPM
↓
"okay, what CAN I use?"
↓
/model does not answer that
↓
GET /v1/models
↓
46 IDs ಠ_ಠ
↓
build instrumentation
↓
map aliases, capabilities and limits
↓
discover first generic probe is incomplete
↓
build capability-specific probes
↓
discover Labs activation state
↓
discover two different Privacy pages
↓
Leanstral = 38 RPM / 5M TPM
↓
test embeddings / moderation / OCR
↓
test transcription / TTS / realtime
↓
build an empirical Free-tier capability map
↓
compare what the platform knows
with what Vibe communicates
↓
DevEx diagnosisDer interessante Teil ist nicht, dass ich das Problem debuggen konnte.
Von einer Person, die einen CLI-Agenten verwendet, kann man vernünftigerweise erwarten, dass sie Umgebungsvariablen, HTTP-Fehler und API-Keys versteht. Das Problem ist: Nichts davon war die Untersuchung, die ich durchführen wollte.
Ich wollte herausfinden, was Mistrals Free Tier kann.
Stattdessen musste ich zunächst den Zustand der Plattform rekonstruieren.
Der erste Fehler war real: Zugangsdaten wurden nicht eingerichtet
Vibe v2.25.1 startete auf meinem Mistral-Free-EU-Konto mit ausgewähltem Medium 3.5.
Ich nutzte Vibes Browser-Login. Der Browser-Ablauf wurde erfolgreich abgeschlossen. Dann schickte ich einen trivialen Prompt und erhielt einen Authentifizierungs-/API-Fehler.
Zu diesem Zeitpunkt lag ein Authentifizierungsproblem als naheliegende Hypothese nahe.
- 01 Vibe installieren
- 02 Browser-Login erfolgreich
- 03 Anfrage an Medium 3.5 scheitert
- 04 API-Key online vorhanden
- 05 ~/.vibe/.env fehlt
- 06 Key manuell einrichten
- 07 Authentifizierung funktioniert
- 08 Anfrage scheitert weiterhin
- 09 Debug-Konsole: 429
- 10 Direkter API-Test
- 11 Medium 3.5: 0 RPM
Ich führte das Setup erneut aus:
vibe --setup Der Browser-/Setup-Ablauf meldete Erfolg.
Ich prüfte das Mistral-Konto online. Dort existierte ein API-/Vibe-Credential. Von der Service-Seite aus sah das Konto eingerichtet aus.
Dann prüfte ich, was Vibe lokal tatsächlich vorfand:
test -f ~/.vibe/.env; and echo exists; or echo missing Ergebnis:
missing Der Browser-Login war erfolgreich, aber ~/.vibe/.env war nicht angelegt worden.
Dieser Unterschied ist entscheidend. „Authentifizierung im Browser erfolgreich“ und „die lokale CLI hat verwendbare Zugangsdaten“ sind zwei verschiedene Zustände. Der Setup-Ablauf hatte sie jedoch als einen erfolgreichen Vorgang dargestellt.
Ich legte manuell einen neuen Vibe-API-Key an und richtete den lokalen Zustand selbst ein:
~/.vibe/.env
MISTRAL_API_KEY=... Für das API-Debugging auf Shell-Ebene konnte ich ihn laden, ohne das Secret auszugeben:
set -lx MISTRAL_API_KEY
(string replace 'MISTRAL_API_KEY=' '' < ~/.vibe/.env)
echo "MISTRAL_API_KEY loaded, length:"
(string length -- $MISTRAL_API_KEY) Die Authentifizierung funktionierte nun.
Damit hätte das Debugging enden können.
Tat es aber nicht.
Der zweite Fehler war unabhängig davon
Dasselbe Default-Modell konnte weiterhin keine Anfrage abschließen.
Statt Invalid API key wiederholte Vibe die Anfrage nun ständig. Der entscheidende
Hinweis tauchte in der Debug-Ausgabe auf:
Retrying request category=rate_limited detail=HTTP 429 Das Problem mit den Zugangsdaten war also real gewesen. Seine Behebung hatte jedoch nur ein zweites Problem sichtbar gemacht.
Ich nahm Vibe aus der Gleichung und rief die API direkt auf.
Eine minimale Diagnose-Anfrage sah so aus:
set MODEL mistral-medium-latest
curl -sS -D /tmp/mistral-headers
https://api.mistral.ai/v1/chat/completions
-H "Authorization: Bearer $MISTRAL_API_KEY"
-H "Content-Type: application/json"
-d (jq -nc --arg model "$MODEL" '{
model: $model,
messages: [{role:"user", content:"hi"}],
max_tokens: 1
}')
-o /tmp/mistral-body
cat /tmp/mistral-headers
jq . /tmp/mistral-body Der Response-Body sagte:
{
"object": "error",
"message": "Rate limit exceeded",
"type": "rate_limited",
"code": "1300",
"raw_status_code": 429
}Die interessantere Evidenz stand in den Headern:
x-ratelimit-limit-req-minute: 0
x-ratelimit-remaining-req-minute: 0 Das war kein vorübergehend ausgeschöpftes Minutenkontingent.
Für dieses Konto lag das effektive Anfrage-Limit von Medium 3.5 genau bei:
0 RPM Das ist ein grundlegend anderer Zustand als „du wirst rate-limited, weil du zu viele Anfragen geschickt hast“.
Einen Kontrolltest einrichten
Bevor ich schloss, dass der Free-Account oder die API grundsätzlich kaputt war, brauchte ich einen Kontrolltest.
Ich schickte dieselbe Anfrage mit demselben API-Key an:
ministral-3b-latest Das lieferte HTTP 200.
Die Response-Header enthielten:
x-ratelimit-limit-tokens-minute: 1300000
x-ratelimit-remaining-tokens-minute: 1299995
x-ratelimit-tokens-query-cost: 5
x-ratelimit-limit-req-minute: 750
x-ratelimit-remaining-req-minute: 749 Damit ließ sich der Zustand sauber isolieren:
credential valid yes
API reachable yes
chat completions working yes
Free account usable yes
Medium 3.5 usable no: effective 0 RPM An diesem Punkt wurde die ursprüngliche Frage zur Plattformübersicht deutlich interessanter.
Wenn Vibes Default-Modell kein Kontingent hatte, welche Modelle konnte der Free-Account dann tatsächlich nutzen?
/model konnte die Frage nicht beantworten
Der naheliegende nächste Schritt war, das Modell zu wechseln.
Vibes /model-Picker gab mir keinen operativen Blick auf die Verfügbarkeit des
Kontos. Er zeigte eine kleine Auswahl, beantwortete aber nicht:
Welche gehosteten Modelle sind für dieses Konto verfügbar?
Welche davon sind Chat-/Coding-Modelle?
Welche sind Aliase?
Welche haben ein Limit größer als null?
Welche benötigen eine weitere Kontoeinstellung? Also ging ich erneut unter Vibe:
curl -sS
https://api.mistral.ai/v1/models
-H "Authorization: Bearer $MISTRAL_API_KEY"
| jq . Der authentifizierte Endpunkt lieferte:
Katalog-Discovery
46 model IDs Roher Provider-BestandOperatives Ergebnis
Empirical capability map Nutzbarkeit erfordert Berechtigung, Limits und den korrekten EndpunktDas waren mehr Informationen, aber noch keine Antwort.
Der Modellkatalog ist strukturierte Daten, kein Picker
Die Antwort von /v1/models war nützlich, weil sie mehr als nur Namen enthielt.
Sie legte maschinenlesbare Informationen zu Fähigkeiten offen, etwa:
{
"completion_chat": true,
"function_calling": true,
"reasoning": true,
"completion_fim": false,
"vision": true,
"ocr": false,
"classification": false,
"moderation": false,
"audio": false,
"audio_transcription": false,
"audio_transcription_realtime": false,
"audio_speech": false
} Außerdem zeigte die Antwort Beziehungen zwischen den IDs.
Mehrere der 46 Einträge waren Aliase und keine eigenständigen Modellfamilien:
Mistral Medium 3.5
├─ mistral-medium-latest
├─ mistral-medium
├─ mistral-medium-3
├─ mistral-medium-3-5
├─ mistral-medium-3.5
├─ mistral-medium-2604
├─ mistral-vibe-cli-latest
├─ mistral-vibe-cli-with-tools
└─ magistral-medium-latest
Mistral Small 4
├─ mistral-small-latest
├─ mistral-vibe-cli-fast
└─ magistral-small-latest
Codestral 2508
├─ codestral-latest
├─ mistral-code-latest
└─ mistral-code-fim-latest Das legt für Developer-Tooling sofort einen wichtigen Unterschied nahe:
Ein Provider-Katalog ist nicht dieselbe Abstraktion wie eine Model-Selection-UI.
Der Katalog enthält den rohen Provider-Zustand. Ein brauchbarer Picker muss diesen Zustand in konkrete Entscheidungen übersetzen.
Bevor ich jedoch etwas entwarf, wollte ich empirische Daten.
Das Konto instrumentieren statt zu raten
46 IDs manuell zu testen wäre mühsam und schwer reproduzierbar gewesen.
Also schrieb ich einen kleinen Fish-Probe, der:
- den authentifizierten Modellkatalog abruft;
- IDs dynamisch ermittelt;
- die kleinstmögliche sinnvolle Chat-Anfrage sendet;
- den HTTP-Status protokolliert;
- Rate-Limit-Header erfasst;
- strukturierte API-Fehlertypen erhält;
- zwischen Anfragen wartet, damit der Probe selbst nicht zum Rate-Limit-Problem wird.
Der zentrale Probe:
probe-free-models.fish fish Quelltext anzeigen
#!/usr/bin/env fish
if not set -q MISTRAL_API_KEY
if test -f ~/.vibe/.env
set -lx MISTRAL_API_KEY
(string replace 'MISTRAL_API_KEY=' '' < ~/.vibe/.env)
end
end
if not set -q MISTRAL_API_KEY
echo "MISTRAL_API_KEY is not set."
exit 1
end
set MODEL_BODY /tmp/mistral-models.json
set BODY /tmp/mistral-probe-body
set HEADERS /tmp/mistral-probe-headers
curl -sS
https://api.mistral.ai/v1/models
-H "Authorization: Bearer $MISTRAL_API_KEY"
> $MODEL_BODY
set MODELS (jq -r '.data[].id' $MODEL_BODY | sort)
echo "Found "(count $MODELS)" models"
for MODEL in $MODELS
echo
echo "=== $MODEL ==="
set HTTP (curl -sS
-o $BODY
-D $HEADERS
-w '%{http_code}'
https://api.mistral.ai/v1/chat/completions
-H "Authorization: Bearer $MISTRAL_API_KEY"
-H "Content-Type: application/json"
-d (jq -nc --arg model "$MODEL" '{
model: $model,
messages: [{role:"user", content:"hi"}],
max_tokens: 1
}'))
echo "HTTP: $HTTP"
set RPM (grep -i '^x-ratelimit-limit-req-minute:' $HEADERS
| string replace -ri '^.*: *' '')
set REM_RPM (grep -i '^x-ratelimit-remaining-req-minute:' $HEADERS
| string replace -ri '^.*: *' '')
set TPM (grep -i '^x-ratelimit-limit-tokens-minute:' $HEADERS
| string replace -ri '^.*: *' '')
set REM_TPM (grep -i '^x-ratelimit-remaining-tokens-minute:' $HEADERS
| string replace -ri '^.*: *' '')
test -n "$RPM"; and echo "RPM: $RPM"
test -n "$REM_RPM"; and echo "Remaining RPM: $REM_RPM"
test -n "$TPM"; and echo "TPM: $TPM"
test -n "$REM_TPM"; and echo "Remaining TPM: $REM_TPM"
jq '{
error_type: .type,
error_code: .code,
message: .message,
model: .model,
usage: .usage
}' $BODY 2>/dev/null; or cat $BODY
sleep 1.1
end
rm -f $MODEL_BODY $BODY $HEADERSDie wichtige Designentscheidung ist, den Unterschied zwischen diesen Zuständen zu erhalten:
200
400 invalid_model
403 tier_not_allowed
403 labs_not_enabled
429 rate_limited Alle diese Zustände in available = false zusammenzufassen würde genau die
Information wegwerfen, die ich verstehen wollte.
Erste empirische Karte
Der erste Chat-orientierte Durchlauf lieferte diese nützliche Teilmenge:
| Model family | Observed state | RPM | TPM |
|---|---|---|---|
| Ministral 3B | Available | 750 | 1,300,000 |
| Ministral 8B | Available | 188 | 625,000 |
| Ministral 14B | Available | 30 | 937,500 |
| Codestral / Mistral Code | Available | 125 | 625,000 |
| Voxtral Small | Available | 60 | 50,000 |
| Medium 3.5 aliases | Effective 0 RPM | 0 | — |
| Small 4 aliases | Effective 0 RPM | 0 | — |
| Vibe CLI aliases | Effective 0 RPM | 0 | — |
Zwei weitere dokumentierte Modelle waren als Kontrolltests nützlich, obwohl sie nicht im zurückgegebenen 46-ID-Katalog des Kontos enthalten waren:
zai-glm-5-2
→ 403 tier_not_allowed
mistral-large-latest
→ 403 tier_not_allowed Dieser Unterschied ist nützlich:
known model + direct 403 tier_not_allowed
≠
model returned as available by authenticated /v1/models
≠
model with non-zero operational allowance Die Vibe-spezifischen IDs waren besonders interessant.
Der Katalog ordnete sie so zu:
mistral-vibe-cli-fast
→ Mistral Small 4
mistral-vibe-cli-latest
→ Mistral Medium 3.5
mistral-vibe-cli-with-tools
→ Mistral Medium 3.5 Es waren also keine drei mysteriösen versteckten Vibe-Modelle. Es waren Aliase auf dieselben Familien, die bereits null RPM zurückgaben.
Dann war mein Probe-Skript falsch
Das ist einer der wichtigsten Teile der Untersuchung.
Der generische Probe schickte jede ID über:
POST /v1/chat/completions Mehrere Modelle lieferten:
400 invalid_model Es wäre einfach gewesen, daraus eine sauber aussehende Tabelle zu machen:
400 = unavailable on Free Sie wäre trotzdem falsch gewesen.
Die Capability-Metadaten von /v1/models enthielten den Hinweis bereits. Einige
dieser IDs gehörten zu Embeddings, OCR, Moderation, Transkription,
Text-to-Speech oder Realtime-Transkription.
Die Anfrage war ungültig, weil ich die falsche API-Frage stellte.
Das ändert die Interpretation:
400 invalid_model on /chat/completions means:
this model cannot be used through this endpoint und nicht:
this account cannot use this model Statt den ersten Probe zu verteidigen, änderte ich deshalb die Teststrategie.
Erste Interpretation
400 invalid_model Free kann dieses Modell nicht verwendenKorrekte Interpretation
Falscher Fähigkeits-Endpunkt Das Modell kann über eine andere API nutzbar seinVon Modell-Probes zu Fähigkeits-Probes
Die bessere Abstraktion ist:
- completion_chat /v1/chat/completions Chat und Coding
- embeddings /v1/embeddings Vektorrepräsentationen
- moderation /v1/moderations Klassifizierung
- ocr /v1/ocr Dokumente und Bilder
- audio_transcription /v1/audio/transcriptions Sprache zu Text
- audio_speech /v1/audio/speech Text zu Sprache
Das ist sowohl für Tests als auch für Produktdesign ein deutlich nützlicheres Modell.
Embeddings
Der Embedding-Probe verwendete /v1/embeddings:
for MODEL in
codestral-embed
codestral-embed-2505
mistral-embed
mistral-embed-2312
echo
echo "=== $MODEL ==="
set HTTP (curl -sS
-o /tmp/mistral-body
-D /tmp/mistral-headers
-w '%{http_code}'
https://api.mistral.ai/v1/embeddings
-H "Authorization: Bearer $MISTRAL_API_KEY"
-H "Content-Type: application/json"
-d (jq -nc --arg model "$MODEL" '{
model: $model,
input: ["hello"]
}'))
echo "HTTP: $HTTP"
grep -i '^x-ratelimit-' /tmp/mistral-headers; or true
jq '{model, usage, error: .message}' /tmp/mistral-body
end Alle vier funktionierten:
codestral-embed → 200 · 60 RPM
codestral-embed-2505 → 200 · 60 RPM
mistral-embed → 200 · 60 RPM
mistral-embed-2312 → 200 · 60 RPM Moderation
curl -sS
-o /tmp/mistral-body
-D /tmp/mistral-headers
https://api.mistral.ai/v1/moderations
-H "Authorization: Bearer $MISTRAL_API_KEY"
-H "Content-Type: application/json"
-d '{
"model": "mistral-moderation-2603",
"input": ["hello"]
}'
grep -i '^x-ratelimit-' /tmp/mistral-headers
jq . /tmp/mistral-body Result:
mistral-moderation-2603
→ 200
→ 100 RPM OCR
Die OCR-Familie wurde über /v1/ocr getestet.
Eine beispielhafte Anfrage:
set MODEL mistral-ocr-latest
curl -sS
-o /tmp/mistral-body
-D /tmp/mistral-headers
https://api.mistral.ai/v1/ocr
-H "Authorization: Bearer $MISTRAL_API_KEY"
-H "Content-Type: application/json"
-d (jq -nc --arg model "$MODEL" '{
model: $model,
document: {
type: "document_url",
document_url: "https://example.com/test.pdf"
}
}')
jq . /tmp/mistral-body Für die eigentliche Untersuchung verwendete ich ein gültiges Testdokument. Das
wichtige Ergebnis: Alle getesteten OCR-IDs lieferten HTTP 200:
mistral-ocr-2512
mistral-ocr-3
mistral-ocr-3-0
mistral-ocr-4
mistral-ocr-4-0
mistral-ocr-4-1
mistral-ocr-latest Für OCR beobachtete ich nicht dieselben RPM-/TPM-Header, daher erfand ich keinen Quota-Wert.
Das klingt offensichtlich, ist aber ein wichtiges Testprinzip:
Das Fehlen eines Messwerts ist keine Erlaubnis, einen Wert zu unterstellen.
Labs war eine weitere Zustandsdimension
Zwei Leanstral-IDs lieferten zunächst:
403 labs_not_enabled Das ist ein besserer Fehler als ein generisches Scheitern, weil er einen konkreten, bearbeitbaren Zustand ausdrückt.
- 01 403 labs_not_enabled
- 02 Fehler verweist auf Privacy
- 03 Vibe Privacy: kein Schalter
- 04 API Privacy: Schalter vorhanden
- 05 Labs aktivieren
- 06 Leanstral funktioniert
Das verbleibende Problem war, die Einstellung zu finden.
Es gab zwei relevante Privacy-Seiten:
Vibe → Privacy
API → Privacy Der Labs-Schalter befand sich unter:
API → Privacy Nach der Aktivierung von Labs führte ich einen minimalen Probe erneut aus:
for MODEL in labs-leanstral-1-5 labs-leanstral-1-5-1
echo
echo "=== $MODEL ==="
curl -sS -D /tmp/mistral-labs-headers
https://api.mistral.ai/v1/chat/completions
-H "Authorization: Bearer $MISTRAL_API_KEY"
-H "Content-Type: application/json"
-d (jq -nc --arg model "$MODEL" '{
model: $model,
messages: [{role:"user", content:"hi"}],
max_tokens: 1
}')
| jq
echo "--- limits ---"
grep -i '^x-ratelimit-' /tmp/mistral-labs-headers
end Beide funktionierten sofort:
labs-leanstral-1-5
38 RPM
5,000,000 TPM
labs-leanstral-1-5-1
38 RPM
5,000,000 TPM Zum Zeitpunkt dieses Tests hatte Leanstral das höchste TPM-Limit aller Chat-Modelle, die ich auf diesem Free-Account gemessen hatte.
Labs vorher
403 labs_not_enabled Fähigkeit ist hinter einer Kontoeinstellung verborgenLabs nachher
Leanstral · 5M TPM 38 RPM nach der Aktivierung von Labs unter API → PrivacyDas ergab einen ziemlich absurden Kontrast:
Vibe default:
Mistral Medium 3.5
→ 0 RPM
coding-focused Labs model:
Leanstral 1.5
→ 38 RPM
→ 5,000,000 TPM Die Fähigkeit war vorhanden.
Der Discovery-Pfad für Entwickler kommunizierte sie jedoch nicht.
Audio ließ das Free Tier noch besser aussehen
Unter den verbleibenden 400 invalid_model-Ergebnissen waren Voxtral-Modelle.
Auch hier zeigten die Capability-Metadaten des Katalogs, welche APIs zu verwenden waren.
Für Offline-Transkription und TTS erzeugte ich eine lokale WAV-Datei von einer Sekunde. So hing der Probe nicht von einer externen Audioquelle ab:
ffmpeg -loglevel error -y
-f lavfi -i "sine=frequency=440:duration=1"
-ar 16000 -ac 1
/tmp/mistral-probe.wav Offline-Transkription
for MODEL in voxtral-mini-2602 voxtral-mini-latest
echo
echo "=== $MODEL ==="
set HTTP (curl -sS
-o /tmp/mistral-audio-body
-D /tmp/mistral-audio-headers
-w '%{http_code}'
https://api.mistral.ai/v1/audio/transcriptions
-H "Authorization: Bearer $MISTRAL_API_KEY"
-F "model=$MODEL"
-F "file=@/tmp/mistral-probe.wav")
echo "HTTP: $HTTP"
grep -i '^x-ratelimit-' /tmp/mistral-audio-headers; or true
jq . /tmp/mistral-audio-body
end Beide Aliase lieferten 200.
Die Header zeigten eine andere Quota-Dimension:
x-ratelimit-limit-audio-seconds-minute: 3600
x-ratelimit-limit-req-minute: 60 Das beobachtete Free-Kontingent war also:
60 RPM
3,600 audio seconds/minute Die Sinuswelle von einer Sekunde wurde tatsächlich verarbeitet:
prompt_audio_seconds: 1
audio_tokens: 375 Die leere Transkription war erwartbar, denn die Eingabe enthielt keine Sprache.
TTS und Voice-Erkennung
Statt eine Voice-ID fest einzubauen, fragte ich zuerst die Voice-API ab:
curl -sS
"https://api.mistral.ai/v1/audio/voices?type=preset&limit=5"
-H "Authorization: Bearer $MISTRAL_API_KEY"
| jq . Das Konto stellte Folgendes bereit:
30 preset voices Dann verwendete ich eine der zurückgegebenen Voice-IDs für den TTS-Probe:
probe-tts.fish fish Quelltext anzeigen
set VOICE_ID (curl -sS
"https://api.mistral.ai/v1/audio/voices?type=preset&limit=1"
-H "Authorization: Bearer $MISTRAL_API_KEY"
| jq -r '.items[0].id')
for MODEL in voxtral-mini-tts-2603 voxtral-mini-tts-latest
echo
echo "=== $MODEL ==="
set HTTP (curl -sS
-o /tmp/mistral-tts-body
-D /tmp/mistral-tts-headers
-w '%{http_code}'
https://api.mistral.ai/v1/audio/speech
-H "Authorization: Bearer $MISTRAL_API_KEY"
-H "Content-Type: application/json"
-d (jq -nc
--arg model "$MODEL"
--arg voice "$VOICE_ID"
'{
model: $model,
input: "Hello.",
voice_id: $voice,
response_format: "wav",
stream: false
}'))
echo "HTTP: $HTTP"
grep -i '^x-ratelimit-' /tmp/mistral-tts-headers; or true
jq 'if .audio_data then {
success: true,
audio_bytes_approx:
((.audio_data | length) * 3 / 4 | floor)
} else . end' /tmp/mistral-tts-body
endBeide TTS-IDs lieferten 200.
Beobachtetes Limit:
12,000 input characters/minute Auch das ursprüngliche 400 invalid_model war keine Einschränkung des
Abonnements gewesen. Es war eine falsche Endpunktwahl in meinem ersten Probe.
Realtime benötigte ein vollständig anderes Protokoll
Die letzte spezialisierte Familie war Realtime-Transkription.
Das ließ sich nicht sinnvoll durch eine weitere REST-Anfrage testen. Stattdessen verwendete ich Mistrals Realtime-Python-Client.
Temporäre Umgebung:
python -m venv /tmp/mistral-realtime-probe
source /tmp/mistral-realtime-probe/bin/activate.fish
pip install 'mistralai[realtime]' Rohes PCM erzeugen:
ffmpeg -loglevel error -y
-f lavfi -i "sine=frequency=440:duration=1"
-f s16le -acodec pcm_s16le
-ar 16000 -ac 1
/tmp/mistral-realtime.pcm Der Probe:
probe-realtime.py python Quelltext anzeigen
import asyncio
import os
from pathlib import Path
from mistralai.client import Mistral
from mistralai.client.models import AudioFormat
MODELS = [
"voxtral-mini-transcribe-realtime-2602",
"voxtral-mini-realtime-2602",
"voxtral-mini-realtime-latest",
]
PCM = Path("/tmp/mistral-realtime.pcm")
CHUNK_BYTES = 7680 # ~240 ms @ 16 kHz mono s16le
async def audio_stream():
data = PCM.read_bytes()
for pos in range(0, len(data), CHUNK_BYTES):
yield data[pos : pos + CHUNK_BYTES]
await asyncio.sleep(0.24)
async def probe(model):
print()
print(f"=== {model} ===")
client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
audio_format = AudioFormat(
encoding="pcm_s16le",
sample_rate=16000,
)
try:
got_event = False
async for event in client.audio.realtime.transcribe_stream(
audio_stream=audio_stream(),
model=model,
audio_format=audio_format,
):
got_event = True
event_type = getattr(event, "type", type(event).__name__)
print(f"EVENT: {event_type}")
for field in (
"text",
"error",
"message",
"code",
"usage",
):
value = getattr(event, field, None)
if value not in (None, "", [], {}):
print(f" {field}: {value}")
if got_event:
print("RESULT: stream completed")
else:
print("RESULT: stream completed without events")
except Exception as exc:
print("RESULT: ERROR")
print(f"TYPE: {type(exc).__name__}")
print(f"ERROR: {exc}")
async def main():
for model in MODELS:
await probe(model)
asyncio.run(main())Alle drei IDs schlossen eine echte Streaming-Session ab:
session.created
session.updated
transcription.done
RESULT: stream completed Auch die Nutzung wurde gemeldet:
prompt_tokens=23
completion_tokens=23
total_tokens=46
prompt_audio_seconds=2 Zu diesem Zeitpunkt funktionierte jede spezialisierte Modellfamilie, die im
generischen Chat-Probe 400 invalid_model geliefert hatte, über die korrekte
API oder das korrekte Protokoll.
Das ist ein deutlich nützlicheres Ergebnis als die ursprünglichen 400er.
Die daraus entstandene empirische Fähigkeitskarte
Für diese konkrete Mistral-Free-EU-Organisation am 10.09.2026 ergab sich folgender beobachteter Zustand:
Chat und Coding
| Model family | Observed state | RPM | TPM |
|---|---|---|---|
| Leanstral 1.5 | Available after Labs opt-in | 38 | 5,000,000 |
| Ministral 3B | Available | 750 | 1,300,000 |
| Ministral 14B | Available | 30 | 937,500 |
| Ministral 8B | Available | 188 | 625,000 |
| Codestral / Mistral Code | Available | 125 | 625,000 |
| Voxtral Small | Available | 60 | 50,000 |
| Medium 3.5 aliases | Effective 0 RPM | 0 | — |
| Small 4 aliases | Effective 0 RPM | 0 | — |
| Vibe CLI aliases | Effective 0 RPM | 0 | — |
| GLM 5.2 | Tier denied | — | — |
| Large 3 | Tier denied | — | — |
Spezialisierte APIs
| Capability | Observed state | Observed limit |
|---|---|---|
| Embeddings | Available | 60 RPM |
| Moderation | Available | 100 RPM |
| OCR | Available | No equivalent RPM/TPM header observed |
| Offline transcription | Available | 60 RPM + 3,600 audio seconds/min |
| TTS | Available | 12,000 input characters/min |
| Realtime transcription | Available | Successful streaming sessions |
| Preset voice discovery | Available | 30 voices observed |
Diese Zahlen sind Beobachtungen, keine Versprechen.
Sie beschreiben ein Konto, eine Organisation, eine Region und einen Zeitpunkt.
Dieser Unterschied ist für jede empirische Plattformuntersuchung wichtig.
Was das Experiment tatsächlich über Mistral Free sagt
Die erste Vibe-Erfahrung ließ die Plattform schlechter aussehen, als sie war.
Der tiefere Probe zeigte fast das Gegenteil.
Der Free-Account hatte Zugriff auf:
- mehrere nutzbare gehostete Chat-/Coding-Modelle;
- ein Coding-orientiertes Labs-Modell mit 5M TPM;
- Embeddings;
- Moderation;
- mehrere OCR-Aliase;
- Offline-Audio-Transkription;
- Realtime-Transkription;
- TTS;
- globale Preset-Voices.
Das Problem war vor allem keine Knappheit an Fähigkeiten.
Es war die Verständlichkeit der Fähigkeiten.
An diesem Punkt war dies kein Free-Tier-Benchmark mehr, sondern ein DevEx-Audit.
Die fehlende Abstraktion
Das wiederkehrende Fehlermuster war: Der rohe Plattformzustand war vorhanden, aber die Entwicklerin oder der Entwickler musste ihn manuell übersetzen.
- Entdeckt ≠
- Berechtigt ≠
- Limit größer als null ≠
- Korrekte Fähigkeit ≠
- In Vibe nutzbar
Bei einer einzigen Aufgabe zur Modellauswahl musste ich klären:
Hat die Browser-Authentifizierung die CLI tatsächlich eingerichtet?
Ist der API-Key gültig?
Ist das ein globaler API-Fehler oder betrifft er nur dieses Modell?
Bedeutet 429 ein erschöpftes Kontingent oder ein konfiguriertes Limit von null?
Wird dieses Modell für mein Konto zurückgegeben?
Ist diese ID kanonisch oder ein Alias?
Ist das ein Chat-Modell?
Welche Fähigkeit stellt es bereit?
Welcher Endpunkt implementiert diese Fähigkeit?
Benötigt es Labs?
Bedeutet 403, dass Labs deaktiviert ist, oder dass der Subscription-Tier den
Zugriff verweigert?
Welches alternative Modell kann ich jetzt verwenden? Die meisten Bausteine existierten bereits irgendwo:
/v1/models
→ IDs
→ aliases
→ capabilities
API errors
→ labs_not_enabled
→ tier_not_allowed
→ rate_limited
response headers
→ RPM
→ TPM
→ audio-seconds/min
→ input-characters/min
Vibe
→ provider
→ selected model
→ authentication/setup state Was fehlte, war eine normalisierte Repräsentation zwischen Provider-Zustand und Entscheidungen für Entwickler.
Konzeptionell:
raw provider state
↓
discovery
↓
normalization
├── canonical model
├── aliases
├── capabilities
└── provider
↓
availability enrichment
├── tier state
├── Labs state
├── observed limits
└── last relevant error
↓
cached ModelAvailability
↓
consumers
├── /model
├── setup
├── retry/error handling
└── fallback suggestions Das ist eine größere Änderung, als /model hübscher zu machen.
Es ist eine UX- und DX-Korrektur.
Was aus /model werden könnte
Sobald der Client den Provider-Zustand normalisiert hat, wird der Picker vergleichsweise einfach.
Zum Beispiel:
Select model
Hosted · Available
● Codestral 2508 125 RPM · 625K TPM
● Ministral 3B 750 RPM · 1.3M TPM
● Ministral 8B 188 RPM · 625K TPM
● Ministral 14B 30 RPM · 937K TPM
● Leanstral 1.5 38 RPM · 5M TPM Labs
Hosted · Unavailable
○ Mistral Medium 3.5 0 RPM Rate limit
○ Mistral Small 4 0 RPM Rate limit
Local
● Devstral Die konkrete UI ist weniger wichtig als das Zustandsmodell dahinter.
Aus einem Labs-Fehler kann werden:
Leanstral 1.5 is disabled for this organization.
Enable Labs under:
API → Privacy Aus einer Anfrage mit null RPM kann werden:
Mistral Medium 3.5 is currently unavailable for this account.
Observed request limit:
0 requests/minute
Available alternatives:
Codestral 2508
Ministral 3B
Ministral 8B
Ministral 14B
Use /model to switch. Das ist deutlich nützlicher, als eine Anfrage mit konfiguriertem Limit von null immer wieder zu wiederholen.
Aus Discovery keinen zusätzlichen API-Traffic machen
Dabei gibt es eine wichtige Implementierungsgrenze.
Ein besserer Picker sollte nicht bei jedem Öffnen von /model jedes Modell
testen.
Der generische Probe mit 46 IDs war für eine Untersuchung angemessen. Als normales Interaktionsmuster wäre er schrecklich.
Instead:
setup
→ fetch catalog
→ normalize metadata
→ cache
successful API calls
→ update observed limits
relevant API errors
→ update availability state
explicit refresh / cache expiry
→ refresh provider state
/model
→ render cached normalized state Damit sind die Verantwortlichkeiten außerdem sauber getrennt.
/model sollte eine Model-Availability-Abstraktion verwenden. Es sollte nicht
zu einem Haufen Mistral-spezifischer API-Regeln werden.
Eine mögliche Repräsentation
Die konkrete Implementierung sollte Vibes bestehender Architektur folgen. Als Konzept stelle ich mir jedoch etwas in dieser Art vor:
- 01 Provider
- 02 Erkennung
- 03 Fähigkeiten normalisieren
- 04 Berechtigung
- 05 Rate-Limit-Status
- 06 ModelAvailability
- 07 Picker · Setup · Fallback · Diagnose
ModelAvailability(
model_id="codestral-2508",
canonical_id="codestral-2508",
aliases=[
"codestral-latest",
"mistral-code-latest",
"mistral-code-fim-latest",
],
capabilities={
"completion_chat": True,
"completion_fim": True,
"function_calling": True,
},
status="available",
limits=RateLimits(
rpm=125,
tpm=625_000,
),
) versus:
ModelAvailability(
model_id="mistral-medium-latest",
canonical_id="mistral-medium-latest",
aliases=[
"mistral-medium",
"mistral-medium-3.5",
"mistral-vibe-cli-latest",
"mistral-vibe-cli-with-tools",
],
status="zero_rate_limit",
limits=RateLimits(
rpm=0,
),
) Und möglicherweise:
ModelAvailability(
model_id="labs-leanstral-1-5",
status="labs_disabled",
action=EnableLabs(
location="API → Privacy",
),
) Die entwicklernahen Komponenten müssen dann nicht jeden Provider-Fehler unabhängig verstehen.
Sie konsumieren normalisierten Zustand.
Warum die Korrektur meines eigenen Probes wichtig ist
Der wertvollste technische Moment dieser Untersuchung war nicht die Entdeckung von 0 RPM.
Es war die Erkenntnis, dass mein erster Test die falsche Frage stellte.
Ein generischer Chat-Probe erzeugte eine Reihe sauberer 400 invalid_model-
Antworten. Diese Ergebnisse schienen leicht einzuordnen zu sein. Die
Modell-Metadaten widersprachen dieser Interpretation.
Also änderte ich den Test.
Jede spezialisierte Familie, die ich anschließend korrekt testete, funktionierte auf dem Free-Account.
Das ist eine nützliche Erinnerung für Developer-Tooling im Allgemeinen:
Ein Fehler ist Evidenz über die Anfrage, die ihn erzeugt hat, nicht automatisch Evidenz für die weitergehende Schlussfolgerung, die du ziehen willst.
Dasselbe Prinzip zeigte sich hier wiederholt:
browser login succeeded
≠ local credential provisioned
429 rate_limited
≠ quota temporarily exhausted
model returned by /v1/models
≠ non-zero operational allowance
400 invalid_model on chat endpoint
≠ unavailable model
403
≠ one universal access failure Diese Unterschiede zu bewahren machte aus dem Debugging ein nützliches Plattformmodell.
Externe Validierung
Ich veröffentlichte die ersten Ergebnisse auf r/MistralAI, solange die
Untersuchung noch frisch war. Nach den Labs-Tests und der Korrektur der
Ergebnisse für spezialisierte Modelle aktualisierte ich den Thread.
Reddit’s own insights showed the post briefly reaching roughly:
#3 post of all time in r/MistralAI
~1.4k views
10 shares within the first ~40 minutes.
Nützlicher als das Ranking selbst war der Kommentar eines anderen Entwicklers. Er beschrieb unabhängig, dass er zu viel Zeit mit dem Debugging der Vibe- Authentifizierung verbracht hatte, und stimmte zu, dass der Picker die Modellverfügbarkeit direkt kommunizieren sollte.
Das ist qualitative Validierung.
Der öffentliche Write-up lieferte außerdem ein kleines, aber nützliches Signal, dass die Ergebnisse umsetzbar und nicht nur interessant waren. Eine Person fasste es schlicht zusammen:
„Let’s hope Mistral incorporates these findings.“
Da Vibe Open Source ist, liegt der nächste Schritt auf der Hand: das Audit in einen Beitrag zu verwandeln.
Die stärkere Evidenz bleibt der reproduzierbare technische Zustand:
HTTP responses
rate-limit headers
/v1/models metadata
capability-specific API calls
realtime protocol events Aus der Übersicht wurde eine Fallstudie
Die ursprüngliche Frage lautete:
Was bietet Mistrals Free Tier Entwicklerinnen und Entwicklern tatsächlich?
Die empirische Antwort lautet: deutlich mehr, als die erste Vibe-Erfahrung vermuten ließ.
Um diese Antwort zu bekommen, musste ich:
- einen fehlgeschlagenen lokalen Zustand für Zugangsdaten reparieren;
- Authentifizierung von Modellverfügbarkeit trennen;
- rohe Rate-Limit-Header untersuchen;
- den authentifizierten Modellkatalog finden;
- Aliase normalisieren;
- 46 zurückgegebene IDs instrumentieren;
- einen Fehler in meinem eigenen generischen Probe erkennen;
- von modellbasierten zu fähigkeitsbasierten Tests wechseln;
- die Labs-Aktivierung auf Organisationsebene finden;
- REST- und Realtime-Audio-Schnittstellen testen;
- und schließlich eine nutzbare Fähigkeitskarte des Kontos rekonstruieren.
Das ist das DevEx-Problem.
Ein Developer-Tool muss die zugrunde liegende Plattform nicht verstecken.
Es sollte den Plattformzustand so verständlich machen, dass Entwicklerinnen und Entwickler die nächste richtige Entscheidung treffen können, ohne ihn zuerst reverse-engineeren zu müssen.
Ein Developer-Tool sollte nicht verlangen, dass Entwicklerinnen und Entwickler Informationen reverse-engineeren, die die eigene Plattform bereits programmatisch bereitstellen kann.