Skip to main content

Create a provider

By the end of this tutorial you will have a working, validated provider — the model credentials every agent in your project runs against.

Three steps:

  1. Create a project — the tenancy boundary everything else lives in.
  2. Register a providernaturali models (no credential of your own) or BYOK (your key).
  3. Validate it with one smoke-test generation.

Every step is one API call, shown for all three clients. The ids in the responses are examples — copy the ones your own calls return.

Prerequisites

You need a nat_sk_… API key (or a session JWT from Auth) exported as NATURALI_TOKEN:

export NATURALI_TOKEN=nat_sk_...

Then set your client up:

pnpm add -g @naturali/cli

The CLI reads NATURALI_TOKEN from the environment. See the CLI guide.

1. Create a project

A project is the tenancy boundary: providers, agents, generations and the usage meter all belong to one.

naturali create-project --name my-first-project
{
"id": "proj_V1StGXR8Z5jdHi6B",
"name": "my-first-project",
"status": "active",
"created_at": "2026-07-18T00:00:00.000Z"
}

Keep that id — every call from here takes it. The CLI can hold it for you, so the rest of the CLI examples omit --project-id:

export NATURALI_PROJECT=proj_V1StGXR8Z5jdHi6B

For the SDK and curl examples, assume:

export PROJECT=proj_V1StGXR8Z5jdHi6B

2. Register a provider

A provider is the model credentials an agent runs against. There are two kinds, and this is the one real decision in the tutorial:

managed — naturali modelsbyok — your own key
SetupPick a catalog model. No credential.Register your provider key.
Runs onnaturali's provider accountyour provider account
Billingmetered and priced by naturaliyou pay your provider directly
Generation costa dollar figuretokens only, cost_usd is null

Pick one below; the tabs remember your choice for the rest of the page.

Managed providers use naturali's own model access, so you supply nothing but a model id from the catalog. Ask for the models that can actually be managed — managed=true is exactly the set this step accepts:

naturali list-models --managed true --vendor amazon --status available --limit 5
{
"data": [
{
"id": "amazon.nova-lite-v1:0",
"display_name": "Nova Lite",
"vendor": "amazon",
"provider": "bedrock",
"provider_model": "us.amazon.nova-lite-v1:0",
"input_modalities": ["text", "image"],
"output_modalities": ["text"],
"streaming": true,
"status": "available",
"managed": true
}
],
"next_cursor": null
}
status and managed are different questions

status: "available" means the model is in the catalog. managed: true means it can back a managed provider — a managed provider is priced at naturali's own cost, so only models with an authoritative published price qualify. The Anthropic Claude family has none today: it is available in the catalog but BYOK-only.

Drop the filter and you may pick a model this step cannot use, which fails fast rather than provisioning something unpriced:

{
"error": {
"code": "bad_request",
"message": "`model` is not available as a managed model."
}
}

If you want a model that reports managed: false, register it as BYOK with your own key instead.

Use the id to create the provider — model on a managed provider takes the catalog id, not provider_model:

naturali create-provider \
--kind managed \
--model amazon.nova-lite-v1:0 \
--name nova-lite-managed
{
"id": "aip_V1StGXR8Z5jdHi6B",
"project_id": "proj_V1StGXR8Z5jdHi6B",
"name": "nova-lite-managed",
"provider": "bedrock",
"default_model": "amazon.nova-lite-v1:0",
"kind": "managed",
"status": "active",
"has_secret": false,
"base_url": null
}

has_secret is false and stays that way: a managed provider has no credential of yours to hold.

Either way you now have a provider id:

export PROVIDER=aip_V1StGXR8Z5jdHi6B

3. Validate it with one generation

status: "active" says the provider was accepted, not that its credential and model actually work. The proof is one round trip through it: a minimal agent bound to the provider, and a single generation. This is a smoke test — everything agents and generations can do is the subject of Your first agent generation, not this step.

First the agent — provider_id is the only required field:

naturali create-agent \
--provider-id aip_V1StGXR8Z5jdHi6B \
--name provider-smoke-test
export AGENT=agent_V1StGXR8Z5jdHi6B

Then one generation through it:

naturali create-generation \
--agent-id agent_V1StGXR8Z5jdHi6B \
--wait \
--messages '[{"role":"user","content":"Reply with the single word: ok"}]'
{
"id": "gen_V1StGXR8Z5jdHi6B",
"agent_id": "agent_V1StGXR8Z5jdHi6B",
"status": "completed",
"text": "ok",
"object": null,
"tool_calls": null
}

status: "completed" is the validation: the credential resolved, the model answered, the provider works. If the call fails here, the error names the layer that rejected it — a bad BYOK key fails at the provider, a wrong model string at the model.

That's the value delivered: a project with a working provider in it, ready for any agent to run against.

What's next

  • Bind a real agent to this provider, run generations and read exactly what each one cost with Your first agent generation.
  • Everything a provider can be — kinds, health, rotation — is on the Providers module page.