Skip to main content

AI Providers

The credentialed model vendors a project generates through — and the models and prices each one carries.

Overview​

An AI provider is a project-scoped record naming a vendor (naturali, openai, anthropic, bedrock, ollama, …), a default_model, and — for a vendor of your own — the secret holding the credential to authenticate with. An agent either pins one provider directly (ai_provider_id) or generates through a model route whose targets each name one.

There are two kinds:

  • provider: "naturali" — the managed offering, and the only way to turn it on. It runs on naturali's own model access, so it takes no credential at all: name it, give it a default_model from the catalog, and it serves every model that catalog lists for the same vendor. Usage is metered at the catalog's pricing.
  • Everything else — bring your own key. Register your credential as a secret and the provider bills you directly; naturali adds no LLM cost.

This module is otherwise a verbatim mirror of the runtime: every field, method, status code and error shape is the runtime's own, re-rooted under the project in the path. naturali is the one value that is naturali's own.

See the OpenAPI spec for the full endpoint and schema reference, or browse it rendered under API Reference → AI Providers.

Data Model​

AiProvider​

FieldTypeDescription
idstringPublic provider ID (aip_ prefix).
project_idstringThe owning project.
namestringHuman-readable name. Required on create.
providerstringnaturali for the managed offering, or a vendor slug you bring a credential for: openai, anthropic, google, xai, groq, ollama, azure, bedrock, vertex, gateway, custom. Required on create.
default_modelstringModel used when an agent names none. Required on create. For naturali, a catalog model.
secret_idstring, nullableThe secret holding the credential. Absent on a naturali provider.
base_urlstring, nullableOverride the vendor's base URL — a self-hosted or gateway endpoint. Absent on a naturali provider.
configobject, nullableVendor-specific configuration (a region for bedrock, for instance). Absent on a naturali provider.
created_atstring (date-time)
updated_atstring (date-time)

Key Concepts​

The naturali provider, in one call​

provider: "naturali" is the whole configuration. There is no credential to register, no region to pick and no vendor to choose — the model you name decides what serves it. Enable naturali models creates one and reads the price book it gets.

This is the only way to provision the offering. It used to also have a route of its own on Models — POST /v1/projects/{project_id}/models/providers — which asked for the vendor that the default_model already implies, and answered a repeat call with the existing provider while this create makes a new one. One resource, two doors, two behaviours; the slug is the door that remains.

naturali create-ai-provider --project-id proj_V1StGXR8Z5jdHi6B \
--name oneclick-ai-provider \
--provider naturali \
--default-model nova-lite-v1

The provider is priced for the whole catalog before the call returns, so every model GET /v1/models lists is usable from an agent's model from the first generation — and metered from it.

Sending secret_id, config or base_url alongside provider: "naturali" is a 400: the offering owns all three, and silently ignoring a credential you sent would be worse than refusing it. So is a default_model that is not a catalog model the listing shows as available — including the vendor's own invocation string for that same model, which the runtime would accept and which is exactly why it is refused here.

Three answers this create has that no other provider create does, none of them expressed in the generated spec:

  • 503 catalog_not_ready — the catalog has no models to price the provider for, so nothing is created. Not your mistake and not a wrong default_model: the daily sync fills the catalog, and a retry shortly after succeeds.
  • 503 managed_source_unavailable — the model you named is real and offered, and the source that serves it is not configured on this deployment. Also not a wrong default_model: no value from that source would work, so there is nothing to retype. Name a model from another source, or wait for the deployment to gain the one you want.
  • 502 upstream_unavailable after a pricing failure — the provider is rolled back rather than left in place. A managed provider that was never priced would meter nothing, so none is ever handed back.
One provider, one source

A provider serves the models of one vendor, so the default_model you name decides which vendor's models this provider can run. A project that wants models from more than one creates more than one naturali provider — each with a default_model from that set — and points each agent at the right one.

An agent or a model-route target naming a model from another source than its naturali provider's is refused with 400 bad_request; details names the model, its source and the provider_source.

Bring your own key, in two calls​

Credentials are never passed inline. Store the key as a secret first, then create the provider naming that secret:

SECRET_ID=$(naturali create-secret \
--project-id proj_V1StGXR8Z5jdHi6B \
--name openai-production \
--value "$OPENAI_API_KEY" | jq -r .id)

naturali create-ai-provider \
--project-id proj_V1StGXR8Z5jdHi6B \
--name openai-production \
--provider openai \
--default-model gpt-4o-mini \
--secret-id "$SECRET_ID"

A provider that needs no credential — a local ollama, say — takes a base_url and no secret_id.

bedrock and vertex are the exception: they must carry a credential of their own, either a secret_id or an express-mode config.apiKey. Creating one without either is a 400, as is an update that would strip the last credential from an existing record. Both vendors' SDKs fall back to whatever credentials the serving environment holds when a record links none, which would run your generations on the platform's own cloud access rather than yours — so the API refuses the shape instead of silently doing something neither side intended. To generate without credentials of your own, create a provider: "naturali" provider instead — see above.

The same rule holds for a provider declared in a formation template, where a {"ref": …} to a secret resource in the same template counts as the credential. There provider must also read as a literal or a parameter carrying one, since it is what decides whether the rule applies.

The managed offering has its own resource type in a template — naturali_ai_provider, taking a default_model and no credential at all. See Formations.

Which models a credential can actually run​

GET /v1/projects/{project_id}/ai-providers/{ai_provider_id}/models asks the vendor, using this record's own credential and configuration, and returns provider-native model ids — the same strings default_model and an agent's model carry. Reachability is a property of the credential, not of the vendor: two records for the same vendor can answer differently, so read the list from the record you intend to generate with. Create a provider uses it as the proof a new credential works.

Price overrides​

GET and PUT /v1/projects/{project_id}/ai-providers/{ai_provider_id}/prices carry per-provider price overrides, keyed on (model, effective_from). An override prices this provider record — an enterprise-negotiated rate, or a gateway with markup — and wins over the platform default when cost is computed. effective_from must be in the future for a (model, component) that is already priced, so a rate change never rewrites the cost of calls already made. A first price for a pair nothing prices yet may be dated now, so a new override starts metering immediately rather than after a gap.

A managed provider is the exception: its book is naturali's, priced from the catalog as the record is created and reconciled daily against it, so PUT answers 403 managed_price_book_read_only there and writes nothing. The prices it carries are the ones GET /v1/models publishes for the model. Reading the book back is unaffected, and so is every provider of your own — an override on a credential you pay for is yours to set.

Deleting a provider in use​

DELETE /v1/projects/{project_id}/ai-providers/{ai_provider_id} answers 409 AI_PROVIDER_HAS_DEPENDENTS while an agent or a model route target still names it, and force does not override that — repoint or delete those first. Deleting the provider does not delete the secret it authenticated with.

Price overrides and metered usage also answer 409, but force=true clears them: it deletes the overrides and unlinks the usage history, which is kept — the spend stays on your usage rollup, it simply stops naming a provider that no longer exists. error.meta reports the counts and a forcible flag saying whether a force=true retry would succeed.

A managed provider always carries price overrides — it is priced against the catalog as it is created — so deleting one always takes force=true, even with nothing pointing at it. A formation that owns one does this for you when the formation is deleted.

Examples​

List a project's providers​

naturali list-ai-providers --project-id proj_V1StGXR8Z5jdHi6B

Ask a provider which models it can run​

naturali list-ai-provider-models \
--project-id proj_V1StGXR8Z5jdHi6B \
--ai-provider-id aip_V1StGXR8Z5jdHi6B

Rotate to a new credential​

naturali update-ai-provider \
--project-id proj_V1StGXR8Z5jdHi6B \
--ai-provider-id aip_V1StGXR8Z5jdHi6B \
--secret-id sec_9fJk2LmNpQrStUvW