---
title: "API"
description: "The Frazer API on api.frazer.ai, REST, gRPC and Connect from one set of protobuf contracts, with resource names, plans, long-running operations, idempotent retries, pagination, rate limits and one error model."
url: https://frazer.ai/docs/api
index: https://frazer.ai/docs/api/llms.txt
tokens: 4100
---

> For agents: this page is indexed at https://frazer.ai/docs/api/llms.txt, and every page at https://frazer.ai/llms.txt. Copy commands from this file exactly.

# API

Every action in Frazer is an API method first. The console, the [CLI](https://frazer.ai/docs/cli), the [MCP server](https://frazer.ai/docs/mcp) and the [Terraform provider](https://frazer.ai/docs/terraform) call the same handlers, so permissions, plans, quotas and audit records are the same whichever you use. The API is generated from protobuf contracts that follow the API Improvement Proposals (AIP), and the REST, gRPC and Connect surfaces, the CLI, the MCP tools, the provider and the SDKs all come from those contracts.

## Base URLs

| Host | Serves |
| --- | --- |
| `https://api.frazer.ai` | gRPC, Connect and gRPC-Web, and REST under `/<service>/v1/…` |
| `https://mcp.frazer.ai` | The [MCP server](https://frazer.ai/docs/mcp) for AI agents |
| `<service>.<region>.frazer.ai` | Regional data planes, such as `registry.us-virginia1.frazer.ai` for [Registry](https://frazer.ai/docs/registry) |

Each product is a service with a one-word ID, for example `resourcemanager` (organizations and projects), `iam`, `approvals`, `kubernetes`, `serverless`, `databases`, `keyvalue` (the in-memory store), `storage`, `registry`, `secrets`, `network`, `dns`, `monitoring`, `ai`, `llm`, `agents`, `devboxes`, `runners`, `billing` and `quota`. A service’s REST paths start with its ID, and its gRPC package is `frazer.<service>.v1`.

## Authentication

Send a token in the `Authorization` header:

```sh
curl https://api.frazer.ai/resourcemanager/v1/projects/acme-staging \
  -H "Authorization: Bearer $FRAZER_TOKEN"
```

| Credential | Use it for | Lifetime |
| --- | --- | --- |
| Personal access token (`frz_pat_…`) | Scripts and tools you run | Expiry required: up to 365 days, 90 by default; an organization can cap it |
| Workload identity federation | CI jobs and workloads, with no stored key | 15-minute tokens |
| Service account key | Systems that cannot federate; off by default | Up to 90 days when the organization allows keys |
| CLI sign-in (`frazer login`) | Your terminal | 15-minute access tokens, refreshed for up to 90 days |
| OAuth 2.1 (MCP clients and apps) | Agents and connected apps | 15-minute access tokens, refreshed for up to 30 days |

### Personal access tokens

Create one under **Account › Personal access tokens and CLI tokens** in the console. Creating a token needs a passkey or second factor from the last 10 minutes, and a token can never create another token. Frazer shows the token once and stores only its hash.

-   A token is bound to one organization. A call on another organization’s resource fails with `ORG_RESTRICTED`.
-   A token carries scopes, and it never allows more than you hold yourself.
-   `allowed_networks` limits a token to up to 20 IPv4 or IPv6 prefixes; a call from anywhere else is refused before any permission check.
-   The token page shows when and from which address each token was last used. Frazer emails you 7 days and 1 day before a token expires.

### Scopes

| Scope | Allows |
| --- | --- |
| `<service>:read` | The service’s read and list permissions, for example `kubernetes:read` |
| `<service>:write` | Every permission of the service except IAM changes and data access |
| `cloud-platform` | Every permission you hold |

A permission outside a token’s scopes is denied with `deniedBy: token_scope`.

### Audiences and bound tokens

Every access token names one audience: `https://api.frazer.ai` or `https://mcp.frazer.ai`. A token minted for one is refused by the other. Access tokens are signed JWTs.

Tokens the CLI holds are bound to the device’s key (DPoP): they travel as `Authorization: DPoP` with a fresh proof on each request, and the same token sent as `Bearer` is refused. For scripts, use a personal access token or workload identity federation.

### Workload identity federation

A GitHub Actions or GitLab job, or a workload in a Frazer cluster, exchanges its own signed identity token for a 15-minute Frazer token. A condition on the token’s claims decides which jobs may, for example `repository == "acme/platform-envs" && ref == "refs/heads/main"`. Nothing secret is stored in CI. See [Identity and access](https://frazer.ai/docs/iam).

## REST, gRPC and Connect

All protocols are served on `api.frazer.ai`, on one port, by the same handlers.

### REST

```sh
curl https://api.frazer.ai/resourcemanager/v1/projects/acme-staging \
  -H "Authorization: Bearer $FRAZER_TOKEN"
```

### Connect

```sh
curl https://api.frazer.ai/frazer.resourcemanager.v1.ProjectService/GetProject \
  -H "Authorization: Bearer $FRAZER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "projects/acme-staging"}'
```

-   **REST** paths are `https://api.frazer.ai/<service>/v1/<resource name>`. Custom methods end in `:verb`, for example `POST https://api.frazer.ai/iam/v1/organizations/acme/decisions:troubleshootAccess`.
-   **gRPC and Connect** use the service names of the contracts, such as `frazer.resourcemanager.v1.ProjectService` or `frazer.approvals.v1.ApprovalService`. gRPC-Web is served too.
-   **Streams** such as `WatchOperation` exist on gRPC and Connect only. REST clients poll instead.
-   **OpenAPI 3.1** descriptions of the REST surface and the **Go** (`go.frazer.ai/sdk`) and **TypeScript** (`@frazer-ai` on npm) SDKs are generated from the same contracts.

## Resource names

Every resource has a name that is also its path:

```text
organizations/acme
projects/acme-production
projects/acme-production/locations/us-virginia1/clusters/acme-production-1
projects/acme-production/managedZones/apps
organizations/acme/decisions/<decision>
```

-   **Projects first.** Project IDs are unique across Frazer, so a project’s resources are named from the project. The organization is the project’s parent, not part of the name, so moving a project to another organization renames nothing. Only a small set of collections, such as decisions and verified domains, live under `organizations/<organization>`.
-   **Locations.** Regional resources carry `locations/<location>`, a region such as `us-virginia1` (Virginia, USA). `global` is a location too.
-   **IDs** you choose are lowercase letters, digits and hyphens, start with a letter and are at most 63 characters. Project IDs are 6 to 30 characters and never reused; organization IDs are 4 to 24. IDs starting with `frazer`, `goog` or `system` are reserved. Every resource also has a `uid` that is never reused.
-   **Full resource names** add the service: `//kubernetes.frazer.ai/projects/acme-production/locations/us-virginia1/clusters/acme-production-1`. Events and Terraform imports use them, and `https://console.frazer.ai/r/<full resource name>` opens any resource in the console.

Resources share standard fields:

| Field | Meaning |
| --- | --- |
| `name`, `uid` | The resource name and the system ID |
| `display_name`, `labels`, `annotations` | Your descriptions. Labels are metadata only and never used in policy; tags are |
| `create_time`, `update_time`, `delete_time`, `purge_time` | When it was created, changed, deleted and will be purged |
| `etag` | The version, for safe updates |
| `state`, `reconciling` | Its state, and whether Frazer is still converging it |
| `generation`, `observed_generation` | The version of the spec, and the one Frazer has applied |

Labels: up to 64 per resource; keys and values up to 63 characters of `a-z`, `0-9`, `_` and `-`.

## Methods

| Method | REST | Notes |
| --- | --- | --- |
| Get | `GET` on the name |  |
| List | `GET` on the collection | [Pagination](#pagination) and [filtering](#filtering-and-ordering) |
| Create | `POST` on the collection | You choose the ID |
| Update | `PATCH` on the name | Only the fields in `update_mask` change |
| Delete | `DELETE` on the name | A soft delete; see [Soft delete](#soft-delete) |
| Undelete | `POST` on `:undelete` | Before `purge_time` |
| Custom | `POST` on `:verb` | Such as `:upgrade` or `:troubleshootAccess` |

Every mutation takes `validate_only` and `request_id`; every mutable resource has an `etag`.

## Long-running operations

A mutation that can take more than a second returns an operation:

-   `GetOperation` and `ListOperations` read operations; done operations stay readable for at least 30 days.
-   `WatchOperation` streams progress on gRPC and Connect, and a watch that breaks resumes where it stopped. Over REST, poll `GetOperation`.
-   `CancelOperation` cancels where a method supports it, and answers `CANCEL_NOT_SUPPORTED` where it does not.
-   An operation’s name is `projects/<project>/locations/<location>/operations/<operation>`, or `organizations/<organization>/operations/<operation>` for an organization-level resource.

## Plans: validate\_only

Every mutation accepts `validate_only`. Frazer runs every check of the real call (permissions, organization policy, quota and cost) and changes nothing; no audit record is written. The answer is the plan:

-   A long-running method returns a done operation with no name: its `metadata.plan` is the plan, and its `response` is the resource as it would be.
-   A method that answers at once returns the resource as it would be.

| Plan field | Meaning |
| --- | --- |
| `changes` | Each resource created, changed or deleted: the action, the resource and its type |
| `quota_impacts` | Per quota counter: the metric, scope, location, limit, usage and the amount requested |
| `policy_violations` | Organization policy the change would break |
| `estimated_cost` | The cost per month, as a currency amount |
| `warnings` | For example, a counter at 80 % or more of its limit after the change |

A plan over a quota fails with the same `QUOTA_EXCEEDED` error the call itself would return, with the numbers and a link to request more. `frazer … --dry-run`, the console’s **Plan** step, MCP write tools and `terraform plan` all use `validate_only`.

## Approvals

When a rule holds a call for a person’s approval, the call changes nothing and answers `FAILED_PRECONDITION` with reason `APPROVAL_REQUIRED` in domain `approvals.frazer.ai`:

-   metadata `approval` (its name), `planHash`, `expireTime`, `rule` and `state`;
-   a `Help` link to the approval’s page, `https://console.frazer.ai/approvals/<approval name>`;
-   the plan, and the `X-Frazer-Approval` response header.

Retrying with the same `request_id` returns the same approval. Once approved, Frazer applies the call itself, exactly once, as the caller, and with the plan it showed: if the target changed in the meantime, the approval fails with `PLAN_CHANGED`. Approvals are resources of the `approvals` service (`GetApproval`, `ListApprovals`; `:approve`, `:reject`, `:cancel`), and approving needs a person with a passkey or second factor from the last 10 minutes. See [Identity and access](https://frazer.ai/docs/iam).

## Pagination

List methods take `page_size` and `page_token` and return `next_page_token`. Pass the token back unchanged to get the next page; it is opaque and signed. An empty `next_page_token` means the last page. Pages are read by key, never by offset.

Where a method allows it, `projects/-` as the parent lists across every project you can read. Results from places Frazer could not reach are named in `unreachable` rather than silently left out.

## Filtering and ordering

List methods take a `filter` in the AIP-160 syntax and an `order_by` on the fields the method allows:

```text
can_decide = true AND state = PENDING
```

A list returns what you may see: everything when you may list the collection, otherwise the resources you were granted directly.

## Idempotency

Every mutation takes a `request_id`, a UUID you generate. A retry with the same ID and the same request returns the first answer for 7 days, so a create that timed out never runs twice. The same ID with a different request fails with `REQUEST_ID_REUSED`. The CLI sets a request ID on every invocation, and the Terraform provider keeps one per create in its state.

## Concurrent changes: etags

Every mutable resource has an `etag`. Send it with an update or delete; if the resource changed since you read it, the call fails with `ABORTED` and reason `ETAG_MISMATCH`. Read the resource again, reapply your change and retry. Some methods require an etag, such as setting a whole IAM policy.

## Soft delete

Delete is soft: the resource gets a `delete_time` and a `purge_time`, and `Undelete` restores it until then. A deleted project, for example, stops billing and suspends its resources at once, and can be restored for 30 days; after that it is purged and its ID stays reserved. A lien on a project blocks its deletion with `LIEN_PRESENT`.

## Rate limits

Frazer admits every request before it does any work:

-   **Rate limits** per principal and per organization, weighted by each method’s cost; large pages cost more.
-   **Fair queues** per organization, then per project, so one organization’s burst does not starve another.
-   **Priorities**: interactive calls from the console and CLI first, then automation (MCP, Terraform, SDKs and service accounts), then batch work such as exports and large lists.

A refused request gets `RESOURCE_EXHAUSTED` (HTTP 429) with reason `RATE_LIMITED` or `CONCURRENCY_LIMITED`, or `OVERLOADED` when Frazer is shedding load, each with a `RetryInfo` delay. Wait at least that long before you retry, and retry a mutation only with the same `request_id`.

## Versioning and deprecation

| Version | Promise |
| --- | --- |
| `v1` | Stable: no breaking changes. A new major version comes with at least 12 months’ notice |
| `v1beta1` | At least 90 days’ notice before a breaking change |
| `v1alpha1` | May change; an organization opts in per feature |

A deprecated method or field answers with a `Deprecation` response header, the CLI warns, MCP tools are marked, and a `deprecation.announced` event goes out. Error reasons and metadata keys are added but never removed or reused; treat a value you do not know as the generic case.

## Errors

Errors follow one model on every protocol: a status code, an `ErrorInfo` with a `reason`, the `domain` of the service that refused (`<service>.frazer.ai`) and `metadata`, plus details such as field violations, `QuotaFailure`, `RetryInfo` and `Help` links.

```json title="403 over REST"
{
  "error": {
    "code": 403,
    "status": "PERMISSION_DENIED",
    "message": "The caller does not have the permission this method needs on the resource (or it might not exist).",
    "details": [
      {
        "@type": "type.googleapis.com/google.rpc.ErrorInfo",
        "reason": "PERMISSION_DENIED",
        "domain": "iam.frazer.ai",
        "metadata": {
          "permission": "kubernetes.clusters.get",
          "resource": "projects/acme-production/locations/us-virginia1/clusters/acme-production-1",
          "deniedBy": "allow",
          "rolesGranting": "roles/kubernetes.viewer",
          "decisionId": "0192f3a1-7c2e-7b4d-9e10-3f6a2b8c5d47"
        }
      }
    ]
  }
}
```

[Errors](https://frazer.ai/docs/errors) explains every field, how to replay a denial by its decision ID, and every reason with its fix.

### Response headers

| Header | When |
| --- | --- |
| `X-Frazer-Authz-Decision` | Every response: the ID of the permission decision, allowed or not |
| `X-Frazer-Approval` | The call is held: the approval’s name |
| `X-Frazer-Iam-Revision` | After an IAM change: the organization’s IAM revision |
| `X-Frazer-Access-Change` | After an IAM change: `EQUIVALENT`, `MORE_PERMISSIVE`, `LESS_PERMISSIVE` or `INCOMPARABLE` |
| `Deprecation` | The method or a field you used is deprecated |

> **Tip:**
> 
> `frazer api` calls any method by name with your CLI sign-in, which is the quickest way to try a call before you script it.

## Related

-   [CLI](https://frazer.ai/docs/cli): every method as a command
-   [MCP](https://frazer.ai/docs/mcp): the API as tools for AI agents
-   [Errors](https://frazer.ai/docs/errors): the error model and every reason
-   [Terraform and OpenTofu](https://frazer.ai/docs/terraform): the API’s resources as code
-   [Webhooks and events](https://frazer.ai/docs/webhooks): changes pushed to you
-   [Identity and access](https://frazer.ai/docs/iam): roles, tokens and approvals

## Pages in Connect

- [Integrations](https://frazer.ai/docs/integrations.md)
- [Webhooks and events](https://frazer.ai/docs/webhooks.md)
- [Terraform and OpenTofu](https://frazer.ai/docs/terraform.md)
- [API, CLI and MCP](https://frazer.ai/docs/api.md) (this page)
