# Providers

> Discover public offers and manage your account's provider profile, avatar, and offer publication.

Public discovery needs no bearer token and lives outside `/v1`. Profile and offer management use a signed-in Network account bearer token; Router application API keys cannot manage these resources. Keep account tokens private and use the dashboard for interactive editing.

## Discover offers for a model

**GET** `/public/provider-offers`: Public · no token required

| Query    | Type              | Meaning                            |
| -------- | ----------------- | ---------------------------------- |
| `model`  | string · required | Exact model ID, 1–160 characters.  |
| `cursor` | UUID · optional   | Previous response's `next_cursor`. |

```bash
curl --get "${ROUTER_BASE_URL%/v1}/public/provider-offers" \
  --data-urlencode 'model=gemma3:1b'
```

Returns `{ offers, has_more, next_cursor }`. Only offers with both profile and offer publication enabled appear. Busy and offline offers can appear alongside available offers.

## Read one offer

**GET** `/public/provider-offers/{offer_id}`: Public · no token required

`offer_id` is a UUID. Returns one public offer object, or `404 provider_offer_unavailable` when it is no longer public. Refresh this endpoint to review a selected offer's current pricing revision.

### Public offer fields

| Field                         | Type                | Meaning                                                               |
| ----------------------------- | ------------------- | --------------------------------------------------------------------- |
| `offer_id`                    | UUID                | Opaque identifier for the saved computer/model offer.                 |
| `model`                       | string              | Exact model ID.                                                       |
| `model_revision`              | string              | Saved model artifact revision.                                        |
| `pricing_revision`            | integer, at least 1 | Current price revision to include in manual selection.                |
| `public_name`                 | string              | Provider-chosen public computer name.                                 |
| `rates.input`, `rates.output` | decimal strings     | Separate USD rates per 1M input/output tokens. Zero is valid.         |
| `availability`                | string              | `available`, `busy`, or `offline`; an observation, not a reservation. |
| `provider`                    | profile object      | Published provider identity, described below.                         |

Account IDs, device IDs, and private computer names are excluded. Identity is self-described, and no verified reputation or performance score is supplied.

## Read a public profile

**GET** `/public/providers/{handle}`: Public · no token required

Accepts an optional UUID `cursor`. Returns `{ profile, offers, has_more, next_cursor }`, including published offers that are offline. A hidden or missing profile returns `404 provider_not_found`.

**GET** `/public/providers/{handle}/avatar`: Public · no token required

Returns raster image bytes with their image content type while the profile is published. A hidden profile or missing image returns `404 avatar_not_found`.

### Pagination

Public model discovery, public profile offers, and owner offers return up to 200 offers per page. Pass `next_cursor` as the next request's `cursor`; null means the final page. `has_more` indicates another page. Cursors advance by immutable offer ID; available, busy, and offline offers are sorted in that order within each public page. This is not a price or performance ranking across the full result set.

## Read or save your profile

**GET** `/v1/provider-profile`: Bearer token required

Returns `{ "profile": null }` before a profile is saved, otherwise `{ profile }`.

**PUT** `/v1/provider-profile`: Bearer token required

Supply **all** editable fields; this is not a partial update.

| Field          | Type           | Validation                                                                      |
| -------------- | -------------- | ------------------------------------------------------------------------------- |
| `display_name` | string         | 1–80 characters; trimmed, nonblank, no control characters.                      |
| `handle`       | string         | Unique; matches `^[a-z0-9][a-z0-9-]{2,39}$`.                                    |
| `description`  | string         | Up to 160 characters; trimmed, no control characters.                           |
| `website`      | string or null | Optional HTTPS URL up to 300 characters, without credentials; use null to omit. |
| `published`    | boolean        | Whether the profile is public.                                                  |

```bash
curl -X PUT "$ROUTER_BASE_URL/provider-profile" \
  -H "Authorization: Bearer $NETWORK_ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "display_name": "Example Provider",
    "handle": "example-provider",
    "description": "A self-described provider profile.",
    "website": null,
    "published": false
  }'
```

This example saves a synthetic draft. Returns `{ profile }`. The profile object includes every editable field plus `avatar_url` (string or null). Owner avatar URLs point to `/v1/provider-profile/avatar`; public URLs point to `/public/providers/{handle}/avatar`.

## Manage your avatar

**POST** `/v1/provider-profile/avatar`: Bearer token required

Send `{ "data_url": "data:image/png;base64,..." }` with canonical base64 for PNG, JPEG, or WebP. The abbreviated string illustrates the shape; it is not a valid upload. Decoded size is at most 262,144 bytes (256 KiB), and the MIME type must match the image signature. Save a profile first. Send `{ "data_url": null }` to remove the avatar. Returns `{ profile }`.

**GET** `/v1/provider-profile/avatar`: Bearer token required

Returns your image bytes even when the profile is unpublished. Both image-read endpoints send `X-Content-Type-Options: nosniff`; a missing avatar returns `404 avatar_not_found`.

## List or publish your offers

**GET** `/v1/provider-offers`: Bearer token required

Accepts an optional UUID `cursor`. Returns `{ offers, has_more, next_cursor }`, including unpublished saved-price offers. Owner offers include `offer_id`, `device_id`, `model`, `model_revision`, `pricing_revision`, `public_name`, `published`, `rates`, `availability`, and `needs_review`. `needs_review` indicates that the saved model revision no longer matches an enabled edition. There is no nested public `provider` object in owner offers.

**PUT** `/v1/provider-offers/{offer_id}`: Bearer token required

Send `{ "public_name": "Example computer", "published": true }`. `public_name` is required, 1–60 characters, trimmed, nonblank, without control characters. The UUID must identify your own saved offer. Returns the refreshed **first page** of owner offers, not one offer object. Save a profile before publishing an offer. New offers start unpublished.

Both the profile and individual offer must be published for public discovery and new manual requests. Unpublishing the profile retains individual publication flags; republishing restores flagged offers. Publication does not change Serving or Auto eligibility. Already accepted requests retain their saved quotes.

## Errors and selection

| Status | Code                                                                            | Meaning                                                                                              |
| ------ | ------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| 400    | `invalid_profile`, `invalid_website`, `invalid_offer_name`, or `invalid_avatar` | Correct the submitted details or image. Schema validation can also reject missing or unknown fields. |
| 401    | `auth_no_token` or another authentication error                                 | Supply a valid signed-in Network account bearer token.                                               |
| 403    | `api_key_scope`                                                                 | Use your signed-in Network account for owner management.                                             |
| 409    | `provider_handle_taken`                                                         | Choose another unique handle.                                                                        |
| 409    | `profile_required`                                                              | Save a profile before publishing an offer or managing its avatar.                                    |
| 404    | `offer_not_found`                                                               | No owned saved offer matches the supplied ID.                                                        |
| 404    | `provider_not_found`, `provider_offer_unavailable`, or `avatar_not_found`       | The public resource is hidden or missing, or no avatar is saved.                                     |
| 429    | Rate limit                                                                      | Respect retry guidance and bound polling or writes.                                                  |
| 503    | `provider_profiles_unavailable`                                                 | Profile storage is temporarily unavailable; do not treat this as an empty result.                    |

Use `{ offer_id, pricing_revision }` in the chat body's `provider_selection` to choose an offer. See [choosing a provider](https://router.optimai.network/docs/choosing-a-provider.md) for Auto, manual selection, spending limits, and recovery, and [chat completions](https://router.optimai.network/docs/api/chat-completions.md#select-a-specific-provider) for request headers and selection errors.
