Inference API
OpenAI-compatible HTTP endpoints at https://cocore.dev/api/v1. Authenticated routes accept a co/core API key as Authorization: Bearer ….
/chat/completionspostauth: requiredOpenAI-compatible chat completion. Routes to an attested provider serving the requested model. Set country to an ISO 3166-1 alpha-2 code (e.g. US) to route only to providers advertising that region — an advisory provider self-claim — failing closed with no_providers_for_country when none match. Set min_provider_version (e.g. 0.9.32) to require a minimum provider release, failing closed with no_providers_for_version when none qualify (see Provider version below).
| Param | Type |
|---|---|
| model* | string |
| messages* | array |
| stream | boolean |
| max_tokens | integer |
| country | string |
| min_provider_version | string |
curl -sS -X POST -H 'Authorization: Bearer $COCORE_API_KEY' 'https://cocore.dev/api/v1/chat/completions' -H 'Content-Type: application/json' -d '{"model":"stub","messages":[{"role":"user","content":"Hello"}],"stream":false,"max_tokens":256}'
Paste an API key above, then run the example.
/modelsgetauth: nonePublic model directory. Default response matches OpenAI's list shape; use view for co/core-specific detail.
| Param | Type |
|---|---|
| view | string |
curl -sS 'https://cocore.dev/api/v1/models'
Click Run example to fetch a live response.
/private/chat/completionspostauth: requiredSame request shape as chat/completions, but routing is limited to providers run by DIDs on your friends list. country still narrows by region.
| Param | Type |
|---|---|
| model* | string |
| messages* | array |
| stream | boolean |
| max_tokens | integer |
| country | string |
| min_provider_version | string |
curl -sS -X POST -H 'Authorization: Bearer $COCORE_API_KEY' 'https://cocore.dev/api/v1/private/chat/completions' -H 'Content-Type: application/json' -d '{"model":"stub","messages":[{"role":"user","content":"Hello"}],"stream":false,"max_tokens":256}'
Paste an API key above, then run the example.
/verified/chat/completionspostauth: requiredSame request shape as chat/completions, but routing is limited to providers whose attestation is cryptographically verified (recomputed from the signed Apple-rooted attestation, not the self-asserted label). Set min_trust to "hardware-attested" (default) or "confidential" to pick the floor. Fails closed with no_verified_providers when none qualify. country still narrows by region.
| Param | Type |
|---|---|
| model* | string |
| messages* | array |
| min_trust | string |
| stream | boolean |
| max_tokens | integer |
| country | string |
| min_provider_version | string |
curl -sS -X POST -H 'Authorization: Bearer $COCORE_API_KEY' 'https://cocore.dev/api/v1/verified/chat/completions' -H 'Content-Type: application/json' -d '{"model":"stub","messages":[{"role":"user","content":"Hello"}],"stream":false,"max_tokens":256}'
Paste an API key above, then run the example.
/probono/chat/completionspostauth: requiredSame request shape as chat/completions, but routing is limited to providers whose proBono policy elects to serve YOU for free (mode any, or mode direct with your DID listed). A matched job is unmetered, zero-price, and takes no exchange cut, so a balance-less requester can still get a completion. Fails closed with no_pro_bono_providers (503) when no connected provider currently offers you pro bono, or pro_bono_lookup_failed (502) when the provider lookup itself fails. country still narrows by region.
| Param | Type |
|---|---|
| model* | string |
| messages* | array |
| stream | boolean |
| max_tokens | integer |
| country | string |
| min_provider_version | string |
curl -sS -X POST -H 'Authorization: Bearer $COCORE_API_KEY' 'https://cocore.dev/api/v1/probono/chat/completions' -H 'Content-Type: application/json' -d '{"model":"stub","messages":[{"role":"user","content":"Hello"}],"stream":false,"max_tokens":256}'
Paste an API key above, then run the example.
Image input
Vision-capable models accept images alongside text using OpenAI's multimodal content parts. Carried inline as base64 or fetched from a URL; no separate upload step.
Any chat/completions route (open, private, and verified) accepts the OpenAI array-of-parts content shape. A message is either a plain string (text only) or an ordered list of text and image_url parts. There is no separate upload endpoint — images travel inside the request body.
{
"model": "your-vision-model",
"messages": [
{
"role": "user",
"content": [
{ "type": "text", "text": "What's in this image?" },
{
"type": "image_url",
"image_url": {
"url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg..."
}
}
]
}
]
}The image_url.url field takes one of two forms:
- Inline base64 data URI —
data:<mime>;base64,<payload>. The MIME type must beimage/*(e.g.image/png,image/jpeg). The bytes are committed directly into the signed job, so the receipt verifies offline with no extra fetch. - Remote URL — an
http(s)://link. co/core fetches it server-side, verifies the response isimage/*, and inlines it as base64 before sealing, so the input stays self-contained.
Limits
- Up to 20 MiB of decoded image bytes per request, budgeted separately from the 1 MiB text limit.
- Up to 256 messages per request; images may be spread across them.
- Images only. Non-image MIME types and arbitrary file attachments (PDFs, documents, audio) are rejected — there is no general file-upload part today.
Model support
Image input is not gated per model: any model id you can route to will accept an image-bearing request. Whether the image is actually understood depends on the model — send images to a vision/multimodal model (the models directory flags vision-capable models). A bad or unparseable image_url with no accompanying text returns 400 invalid_request_error.
Tool calling
Send standard OpenAI tools and receive structured tool-call intents from canary-verified provider/model pairs; requester clients execute every tool.
Pass OpenAI-compatible tools and optional tool_choice. A capable model may return tool_calls with finish_reason: "tool_calls". The provider generates only the structured intent; it never executes a tool. Your client executes it and sends the result back as a tool-role message. Upgraded eligible machines with no stored preference attempt this by default; owners can opt out per machine without disabling ordinary inference.
{
"model": "mlx-community/Qwen3.5-4B-MLX-4bit",
"messages": [{ "role": "user", "content": "Check Amsterdam weather" }],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"parameters": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"]
}
}
}]
}Support is live and per model, not inferred from catalog metadata. The models directory reports tools: true only when a connected machine passed a forced-tool startup canary for that exact model. If none did, the request fails with 400 tool_calls_not_supported; ordinary requests still route normally.
Provider version
Pin a minimum tray-provider release so your request only reaches machines new enough to honor a feature, failing closed when none qualify.
Every chat/completions route (open, private, verified, and probono) accepts an optional min_provider_version body field. Set it to a dotted-numeric release (e.g. 0.9.32; an optional leading v is tolerated) to route only to providers running that release or newer. Each tray provider reports its binaryVersion when it registers with the matchmaker; only machines reporting a version greater than or equal to your floor are eligible.
{
"model": "your-model",
"messages": [{ "role": "user", "content": "Hello" }],
"min_provider_version": "0.9.32"
}Automatic floor for multimodal requests
A request that carries images or tool messages (the messages-v1 envelope) already derives a floor on its own — the first release that both reports its version and parses that envelope (currently 0.9.32) — so an image request never reaches a provider that can't read it. When you also pass min_provider_version, the effective floor is whichever is higher: your explicit pin is never relaxed below the multimodal floor, and the multimodal floor is never relaxed below your pin.
When none qualify
If the model is served but no connected provider runs a new-enough binary, the request fails closed with 503 no_providers_for_version (see dispatch errors). It is capacity-shaped and retryable — capable machines may come online as the fleet updates. A provider that reports no version is treated as below every floor and excluded.
Dispatch errors
Returned when the exchange cannot place your request with a provider.
// 404 — no provider is serving this model
{ "error": { "type": "invalid_request_error", "code": "model_not_found", "message": "..." } }
// 503 — no providers are connected
{ "error": { "type": "service_unavailable_error", "code": "no_providers_connected", "message": "..." } }
// 503 — friends-only, but no friends are online
{ "error": { "type": "service_unavailable_error", "code": "no_friends_available", "message": "..." } }
// 404 — friends-only, but no friend serves this model
{ "error": { "type": "invalid_request_error", "code": "no_friends_for_model", "message": "..." } }
// 503 — country set, but no provider in that region serves this model
{ "error": { "type": "service_unavailable_error", "code": "no_providers_for_country", "message": "..." } }
// 503 — min_provider_version (or a multimodal floor) set, but no connected
// provider runs a new-enough binary. Retryable as the fleet updates.
{ "error": { "type": "service_unavailable_error", "code": "no_providers_for_version", "message": "..." } }
// 503 — pro-bono route, but no connected provider currently serves you free
{ "error": { "type": "service_unavailable_error", "code": "no_pro_bono_providers", "message": "..." } }
// 502 — pro-bono route, the provider lookup itself failed (try again)
{ "error": { "type": "server_error", "code": "pro_bono_lookup_failed", "message": "..." } }HTTP errors
Authentication, validation, and upstream failure responses.
{
"error": {
"message": "Missing Authorization: Bearer header",
"type": "authentication_error",
"code": null,
"param": null
}
}401 authentication_error— missing or invalid API key. Create a new key on /account.400 invalid_request_error— malformed body (missing model, messages, etc.).502 server_error— provider disconnected mid-stream. Retrying usually succeeds.