Skip to main content

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"])