# NeuroForge API

30 September 2026 · Research preview. [OpenAPI](/openapi.json).

## Make your first request

Find the available model and send it a message. You'll need `curl` and `jq` to run this example; your own app can use any HTTP client.

Base: `https://neuroforge.io/v1`. Keyless models, chat and search: omit Authorization. Valid private keys work; invalid keys are rejected. Anonymous CORS is credential-free.

```bash
set -e
model_id=$(curl --fail --max-time 15 -sS https://neuroforge.io/v1/models | jq -er '.data[0].id')
jq -n --arg model "$model_id" '{model:$model,messages:[{role:"user",content:"Hello"}],max_tokens:64,stream:false}' |
  curl --fail --max-time 120 https://neuroforge.io/v1/chat/completions -H 'Content-Type: application/json' --data-binary @-
```

## Models and availability

[`GET /models`](/v1/models): check availability and choose the request profile by the returned ID: `erais-fracture-gemma` (hybrid), `erais-dense-gemma4-e4b` (dense) or `erais-native-qwen3` (native). Use the `erais` metadata when present; it may be absent for the legacy hybrid profile. Dense and native discovery require it. Recorded native API/Discord selections share the World owner and completed a public response. Signed Discord delivery, assistant quality, sparse savings and paid readiness remain unqualified.

## Chat

`POST /chat/completions`: text only. Resend alternating user/assistant history ending with the new user question. Token-context limits also apply. Limits depend on the model returned by discovery:

| Profile | Request and history | Output |
| --- | --- | --- |
| Native ERAIS | Final question 2,048 UTF-8 bytes; at most three prior exchanges totalling 4,096 bytes; JSON 32,768 bytes | `max_tokens` 1–128, default 128; `stream:false`; `n:1` |
| Hybrid or dense | Optional system message up to 8,192 UTF-8 bytes; at most 64 messages; other text 49,152 bytes total; JSON 98,304 bytes | `max_tokens` 32–2048, default 512; dense generation caps at 512; JSON or SSE |

Native instructions/sampling are deployment-owned: omit `temperature` and system messages. Hybrid and dense profiles allow only `temperature:0`, `n:1`. Unknown fields, tools, media, embeddings and Responses API are unsupported. Camera/microphone use the separate World visitor interface.

The composed native prompt must fit 512 tokenizer tokens; oldest complete exchanges may be retired. Byte limits do not guarantee token fit. Token-budget preflight is unavailable. Discovery exposes text only; World image perception does not enable API media. Its `runtime_id` identifies the owner; completions return model and usage without a runtime field. Rediscover after identity/unavailable errors. No silent fallback, token IDs or prompt echo.

JSON: `choices[0].message.content` and usage. Native output: ≤8,192 UTF-8 bytes. Hybrid/dense SSE: `chat.completion.chunk` deltas, `finish_reason`, then `[DONE]`. Interrupted output is incomplete; disconnect cancels cooperatively. Never auto-replay.

## Search

`POST /search`: exactly `{"query":"heat pumps"}`; the raw query must be ≤500 UTF-16 code units. After NFKC normalization and cleanup, it must be 3–160 UTF-16 code units and ≤24 words; the UTF-8 JSON body must be ≤2,048 bytes. Queries containing URL-like or private-looking text may be rejected. The screening is a bounded heuristic, not a guarantee that private information will be detected. Up to three title/URL/content results; `retrieved_at` is lookup time. Untrusted `search_snippets`, not full pages or verified answers. `source_status` distinguishes results, partial results, no candidates and filtered candidates; a 502 may include an unavailable status and bounded diagnostic counts. An empty response does not establish that no sources exist.

Dense chat: start with `Search the web for…` or `Look up…`. Only the screened topic leaves. History/system text never triggers search. Retrieved sources are snippets; failed/empty lookups are labelled. Notices/links count towards bytes, not model tokens.

## Capacity and deadlines

Hybrid: 16 jobs. Desktop dense: two slots, one API/Discord/World and one Minecraft; serialized generation. Native shares the World owner: eight visitor leases, two model jobs, one exclusive model turn and a non-model reserve. Leases do not mean simultaneous model execution. Unified learning and serving quality remain unqualified.

| Request | Origin | Edge | Client |
| --- | --- | --- | --- |
| SSE | hybrid 600s / dense 90s | 615s | 660s total, 30s idle |
| JSON | hybrid 105s / dense 90s | 115s | 120s |
| Native JSON | 45s including queueing | 50s including request body | 55s or longer |
| Search (8 concurrent) | 18s reader / 20s proxy | 22s | 30s |

Deadlines include queue/search/generation. SSE keep-alives every 10s add no tokens or time. Desktop Discord: 120s HTTP / 125s handler. Sinter's native path uses a 55s client deadline; its legacy path uses 660s Python / 700s browser.

Per minute: anonymous inference 24/network, 60 generations/edge; search 30/network, 120/edge; keys 60; shared guard 180/route namespace. Anonymous counters are separate. Edge-local rates are approximate, not global spending guarantees.

429/Retry-After: bounded backoff. 503: unavailable, not missing credentials. No transcript database or training added. Avoid sensitive inputs; verify answers.

## Need help connecting?

[Get help with the API](https://neuroforge.io/contact/?intent=general&topic=Help+using+the+NeuroForge+API#contact-builder). Tell us what you’re trying to connect, which endpoint you used and the public error code you saw. Leave out keys, prompts and private data.
