Projects
Projects are isolated workspaces within your organisation, available on every plan. They exist to give different teams or products their own:
When to use projects
| Scenario | Recommendation |
|---|---|
| Multiple products sharing one NuRoute account | One project per product |
| Different teams with different provider access | One project per team |
| Dev / staging / production separation | Environments within one project |
| Cost isolation between workloads | One project per workload with its own budget |
Project-scoped API keys
A key created for a project can only access that project's resources, so you can give a team an API key without granting access to other projects.
Chat requests made with the key belong to its project: the project's routing and provider settings apply, with no extra header. A request that names a different project with the X-NuRoute-Project-ID header is refused with 403, as is a header naming a project outside your organization. Project IDs are matched without regard to letter case.
What a project-scoped key can use:
| Endpoint | With a project-scoped key |
|---|---|
POST /v1/chat/completions | Requests run under the key's project |
GET /v1/models, GET /v1/models/catalog, GET /v1/providers/catalog, GET /v1/routing/speed | As for any key |
GET /projects | Lists only the key's project |
GET /projects/{id} and /v1/projects/{id}/... | The key's project only. Its settings, usage, budget and environments can be read; changing them is refused with 403, as it needs an owner's or admin's session |
GET /v1/usage/... | The key's project's usage. Leave out project_id or pass the key's project |
GET /v1/requests, GET /v1/routing/history | Only the key's project's requests |
GET /v1/requests/{id}, its replay, GET /v1/routing/history/{id} | Only a request of the key's project |
Everything else is refused with 403 forbidden: your organization's routing configuration, model aliases and policy preview, billing, members, API keys, provider connections, the audit log and organization settings. An organization-wide key can read the routing configuration, aliases, policy preview, usage and requests. Changing any setting, and managing API keys and members, needs an owner's or admin's session (the dashboard, or in the SDKs a token from auth.login()): no API key has a role, so even an organization-wide key gets 403 there.
Another project's data, even one in your organization, answers 404, exactly as if it did not exist: another project's settings, a project_id of another project (or none) on the usage endpoints, and a request made under another project or under no project. A 403 means the endpoint (or, for a project's settings, the change) is closed to project-scoped keys; a 404 means an endpoint the key may use was asked about something outside its project.
A project-scoped key can be created only for an active project of your organization; any other project_id is refused with 400. Once its project is deleted, the key stops working: requests made with it are refused with 401, as for a revoked key.
Environments
Environments let you run dev and production workloads in the same project without spinning up separate projects. Each environment gets its own slug (e.g. dev, staging, prod) and can be marked as production.
Policy enforcement
A project's policy (GET / PUT /v1/projects/{id}/policy) applies to every request of the project: those made with
its project-scoped API key, and those that name it with the X-NuRoute-Project-ID header. It narrows your
organization's policy and never loosens it. Your organization's fallbackBehavior cannot relax it either: when the
organization's rules rule out every provider, a relaxing fallback still picks only among what the project allows.
| Field | Effect when set |
|---|---|
allowed_models | Only these models. A request that names another model is refused with 403 policy_violation |
blocked_models | Never these models, even if allowed_models lists them. Naming one is a 403 |
allowed_providers | Only these providers. A request that pins another with provider is a 403 |
blocked_providers | Never these providers. Pinning one is a 403 |
allowed_regions | Only providers that serve one of these regions, written like the organization's allowed regions (EU, US, APAC, Global, or an exact region such as eu-west-1; not case-sensitive; how regions match). A provider that declares no region is left out unless you allow Global. It applies on top of the organization's regions |
max_tokens | The most output tokens a request may ask for. A larger max_tokens is refused with 400 invalid_request_error. A request that sets no max_tokens is sent to the provider with this limit, or with the model's own output budget if that is smaller. 0 sets no limit |
Empty lists, and max_tokens: 0, set no rule. Model names are matched exactly against catalogue ids; model aliases are
not resolved first. A PUT replaces the whole policy: a field you leave out is cleared, so read the policy first and
send it back with your change.
With model: "auto", only models, providers and regions the policy allows are chosen: Artemis Task weighs only the
allowed models, and so does fallback-router:apollo when it decides a request Artemis Task cannot. When nothing the
policy allows can serve the request, the gateway answers 422 no_available_model, for example with the message
this project's policy leaves no provider that can serve this request.
HTTP/1.1 403 Forbidden
Content-Type: application/json
{ "error": { "type": "policy_violation", "message": "model \"gpt-4o\" is not in this project's allowed models" } }
HTTP/1.1 400 Bad Request
Content-Type: application/json
{ "error": { "type": "invalid_request_error", "message": "max_tokens 8000 exceeds this project's limit of 4096" } }
Budget enforcement
A project's budget (GET / PUT /v1/projects/{id}/budget: monthly_usd, alert_pct, hard_limit) covers the
calendar month in UTC. alert_pct defaults to 80 when a PUT leaves it out. current_spend is the project's spend since the first of the month (UTC), the same figure the
hard limit reads. Spend is the provider cost of the project's requests at NuRoute's catalogue prices, before any NuRoute
fee. A running request counts at its estimate, and at its real token count once it completes. A failed
request counts nothing.
With hard_limit: true and a monthly_usd above 0, once the month's spend reaches monthly_usd, new requests of the
project are refused with 402 Payment Required, type budget_exceeded. Requests already running finish. Requests are
accepted again once you raise the budget, or on the first of the next month (UTC). Without hard_limit, nothing is
refused. A PUT replaces the whole budget: a field you leave out is reset (a missing hard_limit turns the hard
limit off), so send all three.
HTTP/1.1 402 Payment Required
Content-Type: application/json
{ "error": { "type": "budget_exceeded", "message": "this project has spent its monthly budget of $500.00 ($500.12 this month, UTC); requests are refused until the budget is raised or the month ends" } }
- The check can lag by 30 seconds. NuRoute re-reads a project's spend at most every 30 seconds, and a request's cost is known only once it completes. A busy project can pass its budget by that much traffic. The budget itself is read on every request, so a raised budget applies from the next request.
- It fails closed. If the spend cannot be read, requests of a project with a hard limit are refused with
503(routing_error) until it can be read again. Projects without a hard limit are not affected. - It is not your credit balance.
402 insufficient_creditsmeans your organization's prepaid credits are used up;402 budget_exceededmeans this project's own budget is spent. - Alerts are not sent yet.
alert_pctis saved and returned, but nothing reads it: no email, webhook or event fires when spend crosses it or reaches the budget.
Relationship to org-level config
Project settings override org-level settings for the project's requests: those made with a project-scoped API key, or that name the project with the X-NuRoute-Project-ID header:
Org routing strategy: fastest
Project routing strategy: cheapest ← this wins for the project's requests
Requests authenticated with an org-level key use org-level settings unless explicitly routed to a project.
Once a project has a routing configuration of its own, its mode and strategy replace the organization's for the
project's requests. The Speed setting, the provider order, the provider affinity and the balanced
weights follow the organization while the project leaves them unset ("", an empty list or object, or never sent); the
project's dashboard page sets only its mode (with the pinned provider and model), strategy, Speed and model aliases.
Governance stays the organization's: blocked providers, preferred providers (which come ahead of the project's provider order), the cost and
latency caps, provider types and fallbackBehavior apply to every project, and a project's allowed regions can only
narrow the organization's. The project's policy narrows them further, and so do its provider
connections: a project that has connected providers is served only by them, and a request none of them can serve is
refused with 422 no_available_model (none of this project's connected providers (…) can serve this request). Only
enabled connections count, and a project whose connections are all disabled is refused the same way. A connection's
allowed_models is saved but not enforced yet; to limit a project's models, use its policy's allowed_models.
Cost, latency and quality controls lists where each setting lives.
Strategy setting
Artemis Task decides every model: "auto" request of a project, as it does for the organization; no
router can be chosen (Routing). The project's strategy selects its
preset: balanced → Balanced, highest_quality → Quality-first, cheapest → Cost-first,
fastest → Balanced at speed Fastest on a capability version with a speed table (else Fastest).
manual has no preset: the pinned provider serves the request, recorded with the reason
manual_mode. See Strategies and presets.
Set it in the dashboard under Projects → your project → Routing, or with strategy on
PUT /v1/projects/{id}/routing/config. That call keeps every field you leave out, so you can send
only your change.
Speed setting
A project can also set its own Speed: how much a quicker answer is worth, at the quality target its strategy's preset sets (see Artemis Task: Speed). The fallback router ignores it.
| Value | Dashboard label | API router_speed | Effect |
|---|---|---|---|
| Follow the organization | Follow the organization | "" | The organization's Speed applies; if it has none, Off |
| Off | Off | "off" | The preset alone decides |
| Faster | Faster | "faster" | A quicker answer counts a little; the preset keeps its quality target |
| Fastest | Fastest | "fastest" | A quicker answer counts strongly; the preset keeps its quality target |
Set it in the dashboard with Speed (Artemis Task) under Projects → your project → Routing (on Cloud; the
self-hosted dashboard has no Speed setting), or with router_speed on PUT /v1/projects/{id}/routing/config. A PUT that leaves router_speed out
keeps it (or, if the stored configuration cannot be read at that moment, is refused with 503 and
saves nothing), and any other value is a 400. The JavaScript SDK calls the field speed and uses null
to follow the organization. No request header or model name sets the speed.