makemode
developers · api reference

one key. nine endpoints.

Chat models, images, transcription, web search and a page checker, all at https://api.makemode.eu. You get one key and one prepaid balance in euros. Every request runs on EU servers.

eu rails · metered in france
start here

what you need

An account, a balance and a key. Open account.makemode.eu, add credit, and create a key. The key starts with sk-gw- and is shown once, so copy it when it appears. You can revoke it on the same page at any time.

The examples below read the key from an environment variable. Set it once in your terminal:

$ export MAKEMODE_KEY="sk-gw-paste-your-key-here"

Then check that it works. This call is free and shows your balance:

$ curl https://api.makemode.eu/v1/balance \ -H "Authorization: Bearer $MAKEMODE_KEY"

Already using the terminal setup? It created a key for you. The same key works here.

authentication

send the key with every request

Put the key in an Authorization: Bearer header. Clients built for the Anthropic API send it as x-api-key instead, and the gateway accepts that too. Both carry the same sk-gw- key.

To use an OpenAI SDK, set its base URL to https://api.makemode.eu/v1 and its API key to your sk-gw- key. For an Anthropic SDK or Claude Code, set the base URL to https://api.makemode.eu.

Browsers can call the gateway directly, because it answers cross-origin requests. A key in page source is readable by anyone who opens the page, so keep it on a server you run.

what it costs

pay per use from one balance

endpointhow it is charged
chat and messagesPer token. The provider's price in euros times 1.5. A model's input and output tokens have separate rates. For GLM-5.3 that is about €2.70 per million input tokens and €8.25 per million output tokens.
imagesAbout €0.02 per picture.
transcription€0.01 per minute of audio.
lookThe cost of one small vision-model call, around €0.0004. The reply reports the exact amount.
search, read, models, balanceFree.

A call that fails before it produces anything is not charged; a chat answer cut off partway is charged for the tokens it produced. Calls that cost money need a balance above zero, and they answer 402 when it runs out. Top up at account.makemode.eu. Images, look and transcription include a costEur field in the reply, so a script can add up its own spend.

endpoint 01
POST/v1/chat/completionsper token

chat

The OpenAI chat format. Send a model and a list of messages, and set "stream": true to receive the answer as it is written. Get the model names from /v1/models. If you leave out max_tokens, the gateway sets it to the model's own ceiling.

$ curl https://api.makemode.eu/v1/chat/completions \ -H "Authorization: Bearer $MAKEMODE_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"glm-5.3","messages":[{"role":"user","content":"Say hello in Dutch."}]}'

The reply is the standard OpenAI shape: choices[0].message.content holds the text and usage holds the token counts. When your balance is low, the response carries the header x-makemode-balance-low: 1.

endpoint 02
POST/v1/messagesper token

messages (anthropic format)

The Anthropic Messages format, for Anthropic SDKs and for Claude Code. The gateway translates your request and sends it to the same EU models as chat. No request on this path goes to Anthropic. Streaming works.

$ curl https://api.makemode.eu/v1/messages \ -H "x-api-key: $MAKEMODE_KEY" \ -H "anthropic-version: 2023-06-01" \ -H "Content-Type: application/json" \ -d '{"model":"glm-5.3","max_tokens":200,"messages":[{"role":"user","content":"Say hello in Dutch."}]}'

If you ask for a Claude model name, the gateway answers with an EU model instead. When it substitutes one, the response carries x-makemode-served-model with the model that actually ran. Errors on this path use the Anthropic error format.

endpoint 03
GET/v1/modelsfree

models

Lists the models your key can use right now, in the OpenAI list format. Use an id from this list as the model in chat and messages.

$ curl https://api.makemode.eu/v1/models \ -H "Authorization: Bearer $MAKEMODE_KEY"

Each entry has id, object, created and owned_by. We add context_window, the longest input the model accepts, and a makemode object with a display name, a short description and a multimodal flag. A model that is temporarily unavailable is left out of the list.

endpoint 04
GET/v1/balancefree

balance

Your remaining credit in euros, and a short account of recent spending.

$ curl https://api.makemode.eu/v1/balance \ -H "Authorization: Bearer $MAKEMODE_KEY"

The reply has userId, balanceEur, low (true when the balance is nearly spent) and pace. The pace object shows what you spent today, in the current working session, and on your last request. It can be null.

endpoint 06
POST/v1/readfree

read

Fetches one public web page and returns its text with the menus and clutter removed. Send a full http:// or https:// address of up to 2,000 characters.

$ curl https://api.makemode.eu/v1/read \ -H "Authorization: Bearer $MAKEMODE_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://en.wikipedia.org/wiki/Polder"}'

The reply has url, text and chars. The text stops after 20,000 characters. If the page is private, needs JavaScript or turns readers away, text is empty and a note explains. Search and read share one allowance of 30 requests per minute for each key.

endpoint 07
POST/v1/lookabout €0.0004

look

Checks a web page you built. Send its files. The gateway opens them in a real browser on an EU server, takes a screenshot, and a small vision model reports anything visibly broken: a blank page, raw markup, unreadable text, errors in the console.

$ curl https://api.makemode.eu/v1/look \ -H "Authorization: Bearer $MAKEMODE_KEY" \ -H "Content-Type: application/json" \ -d '{"files":{"index.html":"<!doctype html><h1>Hello</h1>"}}'

files maps a file path to its text. It must include index.html. Send at most 200 files and 2 MB of text in total, with relative paths. Each key may make 6 looks per minute.

The reply has ok (true when nothing is wrong), rendered (the page opened in a browser), judged (the vision check answered), problems (a list), a plain-language summary, signals and costEur. If judged is false, the page loaded but nobody checked how it looks, and uncheckedBecause gives the reason. Look again before you trust it.

endpoint 08
POST/v1/images/generationsabout €0.02 per image

images

Makes a picture from a text description, in the OpenAI images format. Ask for one picture at a time.

$ curl https://api.makemode.eu/v1/images/generations \ -H "Authorization: Bearer $MAKEMODE_KEY" \ -H "Content-Type: application/json" \ -d '{"prompt":"A windmill on a flat green polder, flat illustration","ratio":"16:9"}'
fieldmeaning
promptRequired. Cut off after 2,000 characters.
nOptional. Must be 1 if you send it.
ratio1:1 (default), 16:9, 3:2 or 4:5.
sizeThe OpenAI way to pick a shape: 1024x1024, 1792x1024 or 1024x1792. If you send both, ratio wins.
response_formaturl (default) or b64_json.

The reply is {"created": …, "data": [ … ], "costEur": …}. With url, the data entry has a url. We store the picture on our EU storage at made.makemode.app, under a long random address. Nobody can guess it, but anyone you give it to can open it. With b64_json, the entry has b64_json (the image, base64 encoded) and content_type, and we store nothing.

endpoint 09
POST/v1/audio/transcriptions€0.01 per minute

transcription

Turns speech into text, in the OpenAI transcriptions format. Upload the audio as a multipart form.

$ curl https://api.makemode.eu/v1/audio/transcriptions \ -H "Authorization: Bearer $MAKEMODE_KEY" \ -F file=@interview.mp3 \ -F language=nl
fieldmeaning
fileRequired. The audio. Keep it under about 15 MB.
languageOptional. A language code such as nl or en. Leave it out to let the model detect the language.
response_formatjson (default) returns text and costEur. text returns plain text. verbose_json adds language, duration in seconds and segments.

The audio is held in memory while it is transcribed and is never stored. There is a daily limit of 8 hours of audio for each account.

errors and limits

when a call fails

Errors come back with an HTTP status and a body like {"error":{"code":"insufficient_credits","message":"…"}}. Read code in a script and show message to a person.

statuscodewhat to do
400bad_requestA field is missing or wrong. The message names it.
401missing_credential, invalid_api_keySend the key, or check you copied all of it. Create a new key if you lost it.
402insufficient_creditsYour balance is empty. Top up in your account. Image and transcription calls answer 402 with their own codes (image_failed, transcription_failed).
413too_large, transcription_failedSend fewer or smaller files (look), or a shorter recording (transcription).
429rate_limitedWait a minute and try again. Images and transcription also stop at a daily limit.
451non_sovereign_blockedThe request would have left the EU, so the gateway refused it.
502, 503upstream_unreachable, model_unavailable, image_failedA provider is down or the model is paused. Try again shortly or choose another model. You are not charged.
limitvalue
search and read30 requests per minute, shared, for each key
look6 per minute, up to 200 files and 2 MB
imagesone per request, prompt up to 2,000 characters, daily limit
transcriptionabout 15 MB per file, 8 hours of audio per day
what stays in the eu

where your requests go

The gateway runs on Scaleway in France. Chat and messages go to models hosted by Scaleway or by Mistral in the EU. Transcription runs on a Whisper model at Scaleway. Images are generated on the EU endpoint of the image provider.

Two things reach beyond the gateway, because they are their job. Search sends your query to the public sources you choose, such as Wikipedia or Europe PMC. Read fetches the web page you name. Everything else stays on EU servers.

Pictures made with url are stored on EU storage and are public to anyone with the address. Pictures made with b64_json and all audio are not stored. More on the sovereignty and your data pages.