~/writing/mistral-vibe-devex-audit
Mistral · DevEx-Audit
Was Mistrals Free Tier wirklich hergibt.
Von einem gescheiterten Vibe-Login zur empirischen Karte von Modellen, Fähigkeiten und Zugriffsgrenzen.
Ich begann diese Untersuchung mit einer breiten 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.
Daraus ergab sich eine engere Frage:
Was kann ich im Free Tier tatsächlich nutzen?
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.
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 diagnosisVon einer Person, die einen CLI-Agenten verwendet, kann man vernünftigerweise erwarten, dass sie Umgebungsvariablen, HTTP-Fehler und API-Keys versteht. Das war aber nicht die Untersuchung, die ich durchführen wollte: Ich wollte herausfinden, was Mistrals Free Tier kann, musste dafür aber 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.
„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. Die Anfrage scheiterte weiterhin.
Der zweite Fehler war unabhängig davon
Dasselbe Default-Modell konnte weiterhin keine Anfrage abschließen.
Vibe wiederholte die Anfrage nun ständig, statt Invalid API key zu melden.
Die Debug-Ausgabe zeigte:
Retrying request category=rate_limited detail=HTTP 429 Das Problem mit den Zugangsdaten war real gewesen. Seine Behebung machte ein zweites Problem sichtbar.
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 Header zeigten:
x-ratelimit-limit-req-minute: 0
x-ratelimit-remaining-req-minute: 0 Für dieses Konto lag das effektive Anfrage-Limit von Medium 3.5 genau bei:
0 RPM 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 Vibes Default-Modell hatte kein Kontingent. Ich musste herausfinden, welche Modelle der Free-Account tatsächlich nutzen konnte.
/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 EndpunktDer 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 Für Developer-Tooling ergibt sich daraus ein praktischer Unterschied:
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 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 $HEADERSDer Probe erhält den Unterschied zwischen diesen Zuständen:
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:
| 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 | — |
Ich verwendete auch zwei dokumentierte Modelle als Kontrolltests, die 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 known model + direct 403 tier_not_allowed
≠
model returned as available by authenticated /v1/models
≠
model with non-zero operational allowance Der Katalog ordnete die Vibe-spezifischen IDs 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
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 bedeutet:
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 weiteren Probes folgten dieser Fähigkeitskarte:
- 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
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 Ich verwendete ein gültiges Testdokument. 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.
Labs war eine weitere Zustandsdimension
Zwei Leanstral-IDs lieferten zunächst:
403 labs_not_enabled Dieser Fehler verweist auf eine Kontoeinstellung.
- 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, aber der Discovery-Pfad für Entwickler kommunizierte sie 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.
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 |
Neuer Befund: Mistral Large 4 ist bekannt, aber im Free-Tier gesperrt
Mistrals Ankündigung von Mistral Large 4 stellt das Modell vor; der folgende Probe prüft separat, ob es im getesteten Free-Tier tatsächlich nutzbar ist.
Ein späterer Probe für mistral-large-4 lieferte HTTP 403 mit type: tier_not_allowed und Code 1910: „This model is not available in
your subscription tier“. Modell-Slug und Endpoint wurden erkannt, der API-Key
war gültig. Der Befund weist auf eine Entitlement-Sperre des getesteten
Subscription-Tiers hin – nicht auf fehlende Authentifizierung, einen ungültigen
Modellnamen oder ein Rate-Limit. Die 403-Antwort enthielt keine
Rate-Limit-Header; der Request erreichte kein nutzbares Modellkontingent.
mistral-large-4
→ HTTP 403 · tier_not_allowed · code 1910
→ Modell und Endpoint erkannt, API-Key gültig
→ kein Zugriff im getesteten Subscription-Tier Ein eigener Mistral-Large-4-Eintrag in Vibe mit dem normalen API-Provider trifft auf dieselbe Berechtigungsgrenze. Der Befund beschreibt das getestete Konto und dessen Tier, nicht andere Konten oder Tiers.
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 erfordert mehr, als /model hübscher zu machen. Der Client muss anderen
Zustand an Entwicklerinnen und Entwickler weitergeben.
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 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. Aus Discovery keinen zusätzlichen API-Traffic machen
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.
Ablauf:
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.
Die Komponenten konsumieren normalisierten Zustand, statt Provider-Fehler jeweils selbst abzubilden.
Warum die Korrektur meines eigenen Probes wichtig ist
Mein erster Test stellte die falsche Frage.
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.
Für Developer-Tooling:
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 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.
Ein anderer Entwickler 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.
Eine Person fasste den gewünschten nächsten Schritt schlicht zusammen:
„Let’s hope Mistral incorporates these findings.“
Da Vibe Open Source ist, liegt der nächste Schritt nahe: 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ß. Dafür 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.
Ein Developer-Tool muss die zugrunde liegende Plattform nicht verstecken. Es sollte ihren Zustand so verständlich machen, dass Entwicklerinnen und Entwickler die nächste 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.