co/core
Developer docsDocs
Jump to
API reference

Inference API

OpenAI-compatible HTTP endpoints at https://cocore.dev/api/v1. Authenticated routes accept a co/core API key as Authorization: Bearer ….

Base URLhttps://cocore.dev/api/v1
/chat/completionspostauth: required

OpenAI-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).

ParamType
model*string
messages*array
streamboolean
max_tokensinteger
countrystring
min_provider_versionstring
curl
API key
model
user message
max_tokens
country
min_provider_version
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}'
response

Paste an API key above, then run the example.

/systemonepostauth: required

Typed decisions from a System-One model, wire-compatible with the Jev API and with local servers like ollaya — point an existing client at co/core by changing its base URL. Send a state plus a map of named questions, each a noul (yes/no), choice (pick one of criteria), or score (rate against 2–10 ordered levels); every question is answered in one pass with calibrated probabilities. The model jev-latest resolves to whichever System-One model is currently on the network, and the receipt named in x_cocore records the model that actually ran. Decisions are never streamed — the model is non-autoregressive, so there is nothing to stream, and usage reports 0 output tokens. A malformed request is 422; no capacity is 529 (the status Jev SDKs retry with backoff). Because a decision is deterministic, you can check the answer yourself — see Verifying a decision below.

ParamType
model*string
state*string | object | array
questions*object
curl
API key
model
state
noul statement
curl -sS -X POST -H 'Authorization: Bearer $COCORE_API_KEY' 'https://cocore.dev/api/v1/systemone' -H 'Content-Type: application/json' -d '{"model":"jev-latest","state":"Third time this year you've double-charged me. I need this fixed today.","questions":{"is_urgent":{"type":"noul","instructions":"The message conveys urgency or time-sensitivity."}}}'
response

Paste an API key above, then run the example.

/modelsgetauth: none

Public model directory. Default response matches OpenAI's list shape; use view for co/core-specific detail.

ParamType
viewstring
curl
view
curl -sS 'https://cocore.dev/api/v1/models'
response

Click Run example to fetch a live response.

/private/chat/completionspostauth: required

Same request shape as chat/completions, but routing is limited to providers run by DIDs on your friends list. country still narrows by region.

ParamType
model*string
messages*array
streamboolean
max_tokensinteger
countrystring
min_provider_versionstring
curl
API key
model
user message
max_tokens
country
min_provider_version
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}'
response

Paste an API key above, then run the example.

/verified/chat/completionspostauth: required

Same 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.

ParamType
model*string
messages*array
min_truststring
streamboolean
max_tokensinteger
countrystring
min_provider_versionstring
curl
API key
model
user message
min_trust
max_tokens
country
min_provider_version
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}'
response

Paste an API key above, then run the example.

/probono/chat/completionspostauth: required

Same 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.

ParamType
model*string
messages*array
streamboolean
max_tokensinteger
countrystring
min_provider_versionstring
curl
API key
model
user message
max_tokens
country
min_provider_version
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}'
response

Paste an API key above, then run the example.

Verifying a decision

A decision is one deterministic pass with no sampling, so unlike a chat completion you can re-run it and check the receipt yourself — offline, without the provider's cooperation. It does not make the decision confidential.

A chat completion's outputCommitment cannot be checked by anyone: the provider sampled tokens with a random seed, so re-running the job never reproduces the same bytes. A decision is one deterministic encoder pass with no sampler, so the same model over the same state gives the same probabilities — and you already hold the state and questions you sent. That is enough to check the receipt yourself.

node --experimental-strip-types scripts/verify-decision.ts \
  at://did:plc:.../dev.cocore.compute.receipt/abc \
  --request ./what-i-sent.json \
  --server http://127.0.0.1:11435

It recomputes the input commitment from your request, re-runs the decision against a model server you control, and compares the output commitment the provider signed. Point it at your own server — re-running against the provider that issued the receipt proves nothing. The same check is available as a library: verifyDecisionReceipt in @cocore/sdk/decision, and verify_decision_receipt in the Python SDK.

Which model ran

A 4-bit and an f16 build of one repo produce different probabilities, so a receipt may name the exact artifact in params.modelDigest. That is a provider claim, not an attestation — nothing verifies it when the receipt is published. What makes it useful is that it is falsifiable: run the artifact it names and either the commitment matches or you hold a signed receipt that contradicts its own computation.

What this does not prove

Not confidentiality. A decision is served by an engine running outside the provider's measured binary, so the machine operator can read the state you sent. Verification tells you the answer is real; it does not tell you nobody saw the question. Decision models aim to be checkable, not private.

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 be image/* (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 is image/*, 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": "..." } }

// 402 — not enough credits: balance must cover the price ceiling plus the admission floor
{ "error": { "type": "insufficient_credits_error", "code": "insufficient_credits", "message": "Not enough co/core credits: balance 12,000 CC, needs 200,000 CC ..." } }

// 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.