For developers
API documentation
The ModelMesh API is OpenAI-compatible: change the base_url and key, and your code, the OpenAI SDK, Cursor, LangChain or n8n will work with ModelMesh models.
You can import the spec into Postman or Insomnia, or generate a client from it. ModelMesh API 1.0.0, OpenAPI 3.1.0.
Quick start
Base URL for all requests:
https://api.modelmesh.shop/v1- Create a key in your account, under "API keys". The full key is shown only once — save it.
- Set the base_url and your ModelMesh key in the OpenAI SDK.
- Pick a model from GET /models and send a request.
curl https://api.modelmesh.shop/v1/chat/completions \
-H "Authorization: Bearer $MODELMESH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5-mini",
"messages": [{"role": "user", "content": "Hello!"}]
}'Authorization
Pass the key in the Authorization header of every request:
Authorization: Bearer mm_live_...- Live keys start with mm_live_, test keys with mm_test_.
- A test key returns stubs instead of model responses and deducts nothing — handy for debugging your integration.
- A key can be restricted: token limit (total, per day, per month) or a separate balance, a list of models and categories, requests per minute, expiry date.
- Do not publish a key in frontend code or repositories. Revoke a leaked key in your account — it stops working immediately.
Endpoints
All paths are relative to the base URL. Detailed request and response schemas are in openapi.json.
- GET
/modelsList of modelsModels available to your key, with prices in Mesh tokens (MT).
Responses: 200, 401
- POST
/chat/completionsChat (OpenAI chat.completions)Supports
stream: true(SSE in the OpenAI format),tools(function calling) and vision (images incontent). Charges are taken from the prepaid key's wallet or from the account balance within the key's limit. Test keysmm_test_return a stub without charges. Onlyn=1is supported. When streaming, the price arrives in the lastusagechunk (cost_mt); theX-ModelMesh-Balanceheader is the balance before the request.- stream: true — streamed response (SSE in OpenAI format).
- tools — function calling in OpenAI format.
- vision — images in content (type: image_url) for models that understand them.
Responses: 200, 400, 401, 402, 403, 404, 429, 502
- POST
/embeddingsEmbeddings (OpenAI embeddings)Responses: 200, 400, 401, 402, 403, 404, 429, 502
- GET
/balanceKey balanceFor a prepaid key — the key's wallet balance; for a limit key — the minimum of the remaining limit and the account balance.
GET /balance returns balance_mt: for a key with a separate balance — the key balance, for a key with a limit — the smaller of the remaining limit and the account balance.
Responses: 200, 401
- GET
/usageKey usageGET /usage accepts optional from and to (ISO 8601) and returns usage by day: total_cost_mt, total_requests and days[] with date, cost_mt, requests. Defaults to the last 30 days.
Responses: 200, 400, 401
- POST
/images/generationsImage generation (OpenAI images)Models of the
imagecategory fromGET /models.sizeis converted to the model's aspect ratio and resolution,nis the number of images (if the model supports it), other model parameters go inextra(theui_schemakeys fromGET /models). The response arrives when the image is ready;urllinks are signed and valid for one day. Only the price of images actually received is charged; on a provider error nothing is charged (usage.cost_mt).Responses: 200, 202, 400, 401, 402, 403, 404, 429, 502
- POST
/images/editsImage editing (OpenAI images/edits)multipart/form-data:image(one or more PNG/JPEG/WebP/GIF files),prompt,model,n,size,response_format. Or JSON with data-URL images:images: [{ "image_url": "data:image/png;base64,..." }]. External URLs are not accepted,maskis not supported. Models are of theimagecategory with an image field inui_schema.Responses: 200, 202, 400, 401, 402, 403, 404, 429, 502
- POST
/audio/speechText to speech (OpenAI audio/speech)The response is an audio file (
audio/*).voiceis a model voice fromui_schema(standard OpenAI voices are replaced with the model's default voice). The price is per character count.Responses: 200, 202, 400, 401, 402, 403, 404, 429, 502
- POST
/audio/transcriptionsSpeech recognition (OpenAI audio/transcriptions)multipart/form-data:file(MP3, WAV, M4A, OGG, MP4, MOV),model,language,response_format(json,text,verbose_json). The price is per duration, measured from the file.Responses: 200, 202, 400, 401, 402, 403, 404, 429, 502
- POST
/tasksAsynchronous task: video, music, any media modelResponds immediately with
202and a task (status: queued). Tokens are reserved at the price when the task starts and charged only for a finished result; on an error or timeout (2 h) they are returned. You can learn about completion by pollingGET /tasks/{id}or viawebhook_url(https): ModelMesh sends a POST with the eventtask.succeeded,task.failedortask.canceledand the signatureX-ModelMesh-Signature: t=<unix>,v1=<hex>— HMAC-SHA256 of<t>.<body>with the secret fromGET /webhooks/secret. A failed delivery (non-2xx) is retried with a delay for up to ~a day; discard duplicates by thewebhook-idheader.Responses: 202, 400, 401, 402, 403, 404, 409, 429
- GET
/tasks/{id}Task status and resultOnly tasks and synchronous generations created by this key are visible. File links are signed and valid for one day — request the task again to get fresh ones.
Responses: 200, 401, 404
- GET
/webhooks/secretWebhook signature verification secretThe
whsec_...secret for verifyingX-ModelMesh-Signatureon this key's task events.Responses: 200, 401
Streaming
With stream: true the response arrives as text/event-stream: data: {…} chunks in OpenAI format and a final data: [DONE]. Before [DONE] a chunk with usage arrives — it contains the request cost.
curl -N https://api.modelmesh.shop/v1/chat/completions \
-H "Authorization: Bearer $MODELMESH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5-mini",
"stream": true,
"messages": [{"role": "user", "content": "Write a haiku about tokens"}]
}'Images, audio and tasks
Images, speech synthesis and speech recognition work through the usual OpenAI SDK methods (images.generate, images.edit, audio.speech, audio.transcriptions). Video, music and any long generations run as asynchronous /tasks.
- Synchronous methods wait up to 5 minutes for the result. If it is not ready in time, you get a 202 with empty data and the task in modelmesh_task: fetch the result via
GET /tasks/{id}. - Model parameters (aspect ratio, duration, voice…) go in
extraor in the taskinput: the ui_schema keys fromGET /models. - Tokens are reserved at the price at launch and charged only for a finished result; on a provider error or timeout they are returned. The key limit and balance are enforced for media too.
- Result file links are signed and valid for a day; the
Idempotency-Keyheader protects against a repeated launch on network errors.
# Image (the response arrives when it is ready)
curl https://api.modelmesh.shop/v1/images/generations \
-H "Authorization: Bearer $MODELMESH_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "nano-banana-2", "prompt": "Astronaut cat, watercolor", "size": "1792x1024"}'
# Video — a task with a notification to webhook_url
curl https://api.modelmesh.shop/v1/tasks \
-H "Authorization: Bearer $MODELMESH_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "kling-3-pro", "input": {"prompt": "A lighthouse in a storm"}, "webhook_url": "https://example.com/modelmesh"}'
# Task status
curl https://api.modelmesh.shop/v1/tasks/$TASK_ID -H "Authorization: Bearer $MODELMESH_API_KEY"Verifying webhook_url
When a task finishes, ModelMesh sends a POST to webhook_url: body — {"id", "type": "task.succeeded" | "task.failed" | "task.canceled", "data": task}. The signature is in the X-ModelMesh-Signature header: t=<unix>,v1=<hex> — HMAC-SHA256 of "<t>.<body>" with the secret from GET /webhooks/secret. Reply with 2xx; otherwise delivery is retried (for up to ~a day). Retries of the same event arrive with the same webhook-id header.
import crypto from 'node:crypto';
// secret — from GET https://api.modelmesh.shop/v1/webhooks/secret (whsec_...)
export function verifyModelMeshWebhook(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
return fresh && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1 ?? ''));
}Request cost
Model prices are in Mesh tokens (MT), visible in GET /models. The cost of each request is returned:
- in the usage.cost_mt field of the response (a decimal string, MT; usage.cost_mmt — the same in thousandths);
- in the X-ModelMesh-Cost header — charged for the request, MT;
- in the X-ModelMesh-Balance header — the balance after the request, MT.
Only the actual cost is charged. Test keys charge nothing. Plans and token packages
Errors
Errors use the OpenAI format, so SDKs handle them as usual:
{
"error": {
"message": "Your API key does not have access to the model `gpt-5-mini`.",
"type": "permission_error",
"param": "model",
"code": "model_not_allowed"
}
}| Status | code | Meaning |
|---|---|---|
| 401 | invalid_api_key | The key is missing, invalid, paused, revoked or expired. |
| 402 | insufficient_balance | Not enough tokens: the key balance, the account balance or the key limit is used up. |
| 403 | model_not_allowed | This model or category is not allowed for the key (or the model is unavailable on your plan). |
| 429 | rate_limited | The key's requests-per-minute limit is exceeded. Retry later (Retry-After header). |
| 400 | content_policy_violation | The request was rejected by moderation (ours or the model provider's). No tokens charged. |
| 409 | conflict | The Idempotency-Key has already been used by another request. |
| 502 | provider_error | The model provider failed. No tokens charged — retry later. |
Examples
Embeddings, model list, key balance and usage:
# Embeddings
curl https://api.modelmesh.shop/v1/embeddings \
-H "Authorization: Bearer $MODELMESH_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "text-embedding-3-small", "input": "Text to search"}'
# Key balance
curl https://api.modelmesh.shop/v1/balance -H "Authorization: Bearer $MODELMESH_API_KEY"
# Usage by day
curl "https://api.modelmesh.shop/v1/usage?from=2026-10-01T00:00:00Z" \
-H "Authorization: Bearer $MODELMESH_API_KEY"