Projects
Projects are isolated workspaces, available on every plan. Each project has its own provider allow-list, routing rules, budgets, environments, and API keys. Use them to separate dev/staging/production or different products.
Managing projects
# List
projects = client.projects.list()
# Create
project = client.projects.create(name="My App", slug="my-app", description="Production AI")
# Update
client.projects.update(project["id"], name="My App v2")
# Delete
client.projects.delete(project["id"])
With a project-scoped API key
A client created with a project-scoped API key works inside that project only:
client.projects.list() returns just the key's project, and client.project(id) works for that project alone (any other
project is not found, 404). The key can read the project's settings, but creating, updating or
deleting projects, and changing a project's settings, needs an owner's or admin's session (403).
Organization-level calls such as client.providers, client.routing.get_config() and
client.keys are refused with 403. client.requests.list(), client.routing.list_history() and client.usage
cover the key's project only, and client.requests.get() or client.routing.get_decision() on a request of another
project (or of no project) is not found, 404. See
Project-scoped API keys.
Project-scoped sub-client
Once you have a project ID, use client.project(id) to get a sub-client for all
project-level operations. Unlike the JavaScript SDK's nested namespaces
(proj.connections.add()), the Python client exposes these as flat, prefixed methods:
proj = client.project("proj_abc123")
Connections (project provider allow-list)
Restricts which providers this project's requests may use. There's no API key here, just a
provider_id. This is a different concept from the org-level
client.providers namespace (see Providers), which is where you actually
connect your own key (BYOK); a project's allow-list applies on top of whatever providers your
org already has access to, whether platform-connected or BYOK. Only enabled connections count,
and a request none of them can serve is refused with 422 no_available_model. allowed_models
is saved with the connection but not enforced yet; to limit the project's models, use its
policy's allowed_models. See
JavaScript: Connections
for the full explanation.
# Restrict this project to a platform-connected provider
proj.add_connection("openai", enabled=True)
# List this project's allow-list
connections = proj.list_connections()
# Toggle
proj.disable_connection("openai")
proj.enable_connection("openai")
# Remove it from the list; with no connections left, the project may use every provider again
proj.remove_connection("openai")
Project routing
Covers mode, strategy, weights, provider order/affinity, aliases, regions and the Speed setting only. The governance
policy block (blocked providers, cost/latency ceilings, fallbackBehavior, etc.) is
org-level only and has no project override today: the project's allowed_regions and its policy can only
narrow it. What each setting does: Cost, latency and quality controls.
Once a project has a routing config, its mode and strategy replace the organization's for that project's requests:
those made with a project-scoped API key, or that name the project with the X-NuRoute-Project-ID header. Unset
fields fall through to the organization: router_speed="", an empty provider_order or provider_affinity, and
weights_json="". router_speed is Artemis Task's Speed setting. weights_json holds
the balanced weights, which rank the providers of a model you name, and the choice fallback-router:apollo makes
inside its tier when Artemis Task cannot decide; Artemis Task does not read them.
get_routing_config() and set_routing_config() use the API's snake_case keys as they are. set_routing_config()
keeps every key you leave out, so you can send only your change (if the stored config cannot be read at that moment, a
call that leaves a key out fails with 503 and saves nothing). A project without a config of its own reads back the
defaults, not the organization's settings.
cfg = proj.get_routing_config()
cfg.update(strategy="cheapest")
cfg.update(router_speed="faster") # "off" | "faster" | "fastest", or "" to follow the organization
proj.set_routing_config(**cfg)
# Per-project model aliases
proj.set_aliases({"smart": "gpt-4o-mini"})
proj.delete_alias("smart")
Policy
The policy applies to every request of the project: see
Policy enforcement. set_policy() replaces the whole policy, so a key
you leave out is cleared; start from get_policy().
policy = proj.get_policy()
policy.update(max_tokens=4096, allowed_models=["gpt-4o", "gpt-4o-mini"])
proj.set_policy(**policy)
Budget
set_budget() replaces the whole budget: pass all three, since a missing hard_limit turns the hard limit off. See
Budget enforcement.
# hard_limit: refuse the project's requests (402 budget_exceeded) once this month's (UTC) spend reaches 500.
# alert_pct is stored; budget alerts are not sent yet.
proj.set_budget(monthly_usd=500, alert_pct=80, hard_limit=True)
budget = proj.get_budget()
print(budget["current_spend"], budget["monthly_usd"])
Environments
environments = proj.list_environments()
env = proj.create_environment("Staging", slug="staging", is_production=False)
proj.delete_environment(env["id"])