> ## Documentation Index
> Fetch the complete documentation index at: https://bifrost-dev.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Update project

> Applies a patch to the stored project. Fields the body omits are left as
stored, and child lists are paired with their stored rows by `id`, so a
cap keeps its accumulated spend and its window across an edit while a
child the body stops naming is deleted.

Changing the roster or any divided cap on a project with
`split_policy: equal` recalculates every member's slice. That work is
queued and its id is returned as `redivision_job_id`; the project's own
write has already committed by then, so a failure to queue is reported as
a server error rather than rolling the edit back.


<Note>
  This endpoint is available in [Bifrost Enterprise](https://www.getmaxim.ai/bifrost/enterprise) only.
</Note>


## OpenAPI

````yaml /openapi/openapi.json put /api/governance/projects/{project_id}
openapi: 3.1.0
info:
  title: Bifrost API
  description: >
    Bifrost HTTP Transport API for AI model inference and gateway management.


    This API provides a unified interface for interacting with multiple AI
    providers

    including OpenAI, Anthropic, Bedrock, Gemini, and more through a single API,

    along with comprehensive management APIs for configuring and monitoring the
    gateway.


    ## API Structure


    ### Unified Inference API (`/v1/*`)

    The primary API using Bifrost's unified format. Model parameters use the
    format

    `provider/model` (e.g., `openai/gpt-4`, `anthropic/claude-3-opus`).


    ### Async Inference API (`/v1/async/*`)

    Submit inference requests for asynchronous execution. Returns a job ID
    immediately

    and allows polling for results. Supports all inference types except batches,
    files,

    and containers.


    ### Provider Integration APIs

    Native provider-format APIs for drop-in compatibility:

    - `/openai/*` - OpenAI-compatible API

    - `/anthropic/*` - Anthropic-compatible API

    - `/genai/*` - Google GenAI (Gemini) compatible API

    - `/bedrock/*` - AWS Bedrock compatible API

    - `/cohere/*` - Cohere compatible API


    ### Framework Integration APIs

    Multi-provider proxy endpoints for AI frameworks:

    - `/litellm/*` - LiteLLM proxy with all provider formats

    - `/langchain/*` - LangChain compatible endpoints

    - `/pydanticai/*` - PydanticAI compatible endpoints


    ### Management APIs (`/api/*`)

    APIs for managing and monitoring the Bifrost gateway:

    - `/api/config` - Configuration management

    - `/api/providers` - Provider and API key management

    - `/api/plugins` - Plugin management

    - `/api/governance/*` - Virtual keys, teams, customers, budgets, rate
    limits, routing rules, and pricing overrides

    - `/api/logs` - Log search and analytics

    - `/api/mcp/*` - MCP (Model Context Protocol) client management

    - `/api/session/*` - Authentication and session management

    - `/api/cache/*` - Cache management

    - `/health` - Health check endpoint


    ## Fallbacks

    Requests can include fallback models that will be tried if the primary model
    fails.
  version: 1.0.0
  contact:
    name: Contact Us
    url: https://getmaxim.ai/bifrost
  license:
    name: Apache 2.0
    url: https://opensource.org/licenses/Apache-2.0
servers:
  - url: '{baseUrl}'
    description: Your Bifrost instance
    variables:
      baseUrl:
        default: http://localhost:8080
        description: Base URL of your Bifrost instance (e.g. https://bifrost.mycompany.com)
security:
  - BearerAuth: []
  - BasicAuth: []
  - ApiKeyAuth: []
tags:
  - name: Models
    description: Model listing and information
  - name: Chat Completions
    description: Chat-based text generation
  - name: Text Completions
    description: Text completion generation
  - name: Responses
    description: OpenAI Responses API compatible endpoints
  - name: OCR
    description: Optical character recognition for documents and images
  - name: Rerank
    description: Document reranking by relevance to a query
  - name: Embeddings
    description: Text embedding generation
  - name: Images
    description: Image generations, editing, and variations
  - name: Videos
    description: Video generation and management
  - name: Audio
    description: Speech synthesis and transcription
  - name: Count Tokens
    description: Token counting utilities
  - name: Batch
    description: Batch processing operations
  - name: Files
    description: File management operations
  - name: Containers
    description: Container management operations
  - name: Async Jobs
    description: Asynchronous job submission and retrieval endpoints
  - name: Realtime
    description: Realtime WebSocket and WebRTC endpoints
  - name: OpenAI Integration
    description: OpenAI-compatible API endpoints (/openai/*)
  - name: Azure Integration
    description: Azure OpenAI integration endpoints
  - name: Anthropic Integration
    description: Anthropic-compatible API endpoints (/anthropic/*)
  - name: GenAI Integration
    description: Google GenAI (Gemini) compatible API endpoints (/genai/*)
  - name: Bedrock Integration
    description: AWS Bedrock compatible API endpoints (/bedrock/*)
  - name: Cohere Integration
    description: Cohere compatible API endpoints (/cohere/*)
  - name: LiteLLM Integration
    description: LiteLLM proxy endpoints with multi-provider support (/litellm/*)
  - name: LangChain Integration
    description: LangChain compatible endpoints with multi-provider support (/langchain/*)
  - name: PydanticAI Integration
    description: >-
      PydanticAI compatible endpoints with multi-provider support
      (/pydanticai/*)
  - name: Health
    description: Health check endpoints
  - name: Configuration
    description: Configuration management endpoints
  - name: Session
    description: Session and authentication endpoints
  - name: Providers
    description: Provider management endpoints
  - name: Plugins
    description: Plugin management endpoints
  - name: MCP
    description: Model Context Protocol endpoints
  - name: Governance
    description: Virtual keys, teams, and customers management
  - name: Routing
    description: Routing rules and complexity analyzer configuration
  - name: Logging
    description: Log search and management endpoints
  - name: Cache
    description: Cache management endpoints
  - name: Vault
    description: Vault secret management endpoints
  - name: Skills
    description: Skills Repository management, marketplace, and download endpoints
  - name: Audit Logs
    description: >-
      CADF-compliant audit log search, export, and signature verification
      endpoints
  - name: Webhooks
    description: Webhook endpoint management and signed async-job delivery history
  - name: Notifications
    description: >-
      Role-targeted dashboard notifications, delivered over the dashboard
      WebSocket
paths:
  /api/governance/projects/{project_id}:
    put:
      tags:
        - Projects
      summary: Update project
      description: >
        Applies a patch to the stored project. Fields the body omits are left as

        stored, and child lists are paired with their stored rows by `id`, so a

        cap keeps its accumulated spend and its window across an edit while a

        child the body stops naming is deleted.


        Changing the roster or any divided cap on a project with

        `split_policy: equal` recalculates every member's slice. That work is

        queued and its id is returned as `redivision_job_id`; the project's own

        write has already committed by then, so a failure to queue is reported
        as

        a server error rather than rolling the edit back.
      operationId: updateProject
      parameters:
        - name: project_id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: >-
                Read-then-patch. A field this body omits is left as stored. For
                `description`, `expires_at`, `rate_limit`, `budgets`,
                `provider_configs`, `mcp_configs` and `virtual_mcps`, an
                explicit `null` clears what is stored, and for the four list
                fields that means deleting every stored child. The remaining
                fields cannot be cleared: `null` reads the same as omitted.
                Child lists are matched to their stored rows by `id`, so restate
                the ids you read; a stored child the body does not name is
                deleted. A `members` field is ignored rather than refused.
              properties:
                name:
                  type: string
                description:
                  type: string
                  nullable: true
                is_active:
                  type: boolean
                expires_at:
                  type: string
                  format: date-time
                  nullable: true
                access_rule:
                  type: string
                  enum:
                    - intersect
                    - union
                  description: >-
                    How the project's own access composes with the access the
                    caller already holds. `intersect` permits only what both
                    allow, so a project with no provider config permits nothing.
                    `union` leaves the caller's access untouched and adds to it,
                    so a project with no provider or MCP config only attributes
                    and caps spend.
                membership_mode:
                  type: string
                  enum:
                    - explicit
                    - open
                  default: explicit
                  description: >-
                    `explicit` admits only the users on the roster. `open`
                    admits every authenticated caller and has no membership
                    rows, which is why it cannot divide a budget between
                    members.
                accounting_mode:
                  type: string
                  enum:
                    - both
                    - project_only
                    - principal_only
                  default: both
                  description: >-
                    Which ledgers a request's spend lands on. `both` charges the
                    project and the caller. `project_only` takes the request off
                    everything the caller funds, including the teams and
                    business units above them. `principal_only` leaves the
                    project's caps on record but checks and charges none of
                    them. The deployment's own global caps always apply.
                split_policy:
                  type: string
                  enum:
                    - none
                    - equal
                  default: none
                  description: >-
                    `none` keeps the project's caps shared, and individual
                    members can still be capped by hand. `equal` gives every
                    member an equal slice of every budget and rate limit the
                    project holds, at every tier, as a second cap within it.
                calendar_aligned:
                  type: boolean
                budgets:
                  type: array
                  nullable: true
                  items:
                    type: object
                    description: >-
                      A spend cap on a project, on a provider inside it, or on a
                      model under that provider. On an update, restating `id`
                      pairs the declaration with its stored row so accumulated
                      spend and the window anchor carry over; omit `id` to
                      create a new cap.
                    required:
                      - max_limit
                      - reset_duration
                    properties:
                      id:
                        type: string
                        description: >-
                          Omit to create. Restate to update in place. An id the
                          project does not hold is refused.
                      max_limit:
                        type: number
                        minimum: 0
                        description: Cap in dollars.
                      reset_duration:
                        type: string
                        description: >-
                          Reset window as a number and a unit, for example
                          `30s`, `5m`, `1h`, `1d`, `1w`, `1M`, `1Q`, `1Y`. Only
                          day-or-longer windows can be calendar-aligned.
                        example: 1M
                      reset_config:
                        type: object
                        description: >-
                          Window settings the reset duration cannot express.
                          Only valid when `reset_duration` is quarterly (`1Q`).
                        properties:
                          quarter_start_month:
                            type: integer
                            minimum: 1
                            maximum: 12
                            description: >-
                              First month of Q1 as 1-12. Omitted means January.
                              Only the value modulo 3 changes the reset dates,
                              so January, April, July and October all reset on
                              calendar quarters.
                        additionalProperties: false
                      current_usage:
                        type: number
                        readOnly: true
                        description: Spend recorded in the current window.
                      last_reset:
                        type: string
                        format: date-time
                        readOnly: true
                      override_amount:
                        type: number
                        readOnly: true
                        description: >-
                          Operator top-up added to `max_limit` while active.
                          Projects have no endpoint that sets one; the field is
                          reported for completeness.
                      override_mode:
                        type: string
                        readOnly: true
                      override_cycles_remaining:
                        type: integer
                        readOnly: true
                      override_cycles_total:
                        type: integer
                        readOnly: true
                      override_anchor_reset:
                        type: string
                        format: date-time
                        readOnly: true
                      config_hash:
                        type: string
                        readOnly: true
                      created_at:
                        type: string
                        format: date-time
                        readOnly: true
                      updated_at:
                        type: string
                        format: date-time
                        readOnly: true
                rate_limit:
                  oneOf:
                    - type: object
                      description: >-
                        A token and request rate limit on a project, on a
                        provider inside it, or on a model under that provider.
                        The two dimensions run independent windows, and a cap is
                        enforced only together with its window.
                      properties:
                        id:
                          type: string
                          description: Omit to create. Restate to update in place.
                        token_max_limit:
                          type: integer
                          minimum: 0
                          description: >-
                            Maximum tokens per window. Requires
                            `token_reset_duration`. On a project with
                            `split_policy: equal`, a cap below the member count
                            is refused, since a slice is a whole number of
                            tokens.
                        token_reset_duration:
                          type: string
                          example: 1h
                        request_max_limit:
                          type: integer
                          minimum: 0
                          description: >-
                            Maximum requests per window. Requires
                            `request_reset_duration`. Subject to the same lower
                            bound under `split_policy: equal`.
                        request_reset_duration:
                          type: string
                          example: 1h
                        token_current_usage:
                          type: integer
                          readOnly: true
                        token_last_reset:
                          type: string
                          format: date-time
                          readOnly: true
                        request_current_usage:
                          type: integer
                          readOnly: true
                        request_last_reset:
                          type: string
                          format: date-time
                          readOnly: true
                        config_hash:
                          type: string
                          readOnly: true
                        created_at:
                          type: string
                          format: date-time
                          readOnly: true
                        updated_at:
                          type: string
                          format: date-time
                          readOnly: true
                    - type: 'null'
                provider_configs:
                  type: array
                  nullable: true
                  items:
                    type: object
                    description: >-
                      What the project allows on one provider, and the money
                      that provider's use answers to. A project names each
                      provider at most once.
                    required:
                      - provider_name
                    properties:
                      id:
                        type: integer
                        description: >-
                          Omit or send `0` to create. Restate to update in
                          place. An id the project does not hold is refused.
                      provider_name:
                        type: string
                        example: openai
                      all_models_allowed:
                        type: boolean
                        default: false
                        description: Allows every model the provider offers.
                      allowed_models:
                        type: array
                        nullable: true
                        items:
                          type: string
                        description: >-
                          Allowlist. `["*"]` allows every model. A list may not
                          mix `*` with named entries.
                      blacklisted_models:
                        type: array
                        nullable: true
                        items:
                          type: string
                        description: >-
                          Denylist, which wins over the allowlist. `["*"]`
                          blocks everything.
                      key_ids:
                        type: array
                        nullable: true
                        items:
                          type: string
                        description: >-
                          Provider keys that may serve the request. `["*"]`
                          allows all; an empty list allows none. Each id must
                          exist and belong to this provider.
                      weight:
                        type: number
                        nullable: true
                        description: >-
                          Load-balancing preference for this provider inside the
                          project. Null leaves the caller's own preference
                          standing. Negative values are refused.
                      budgets:
                        type: array
                        nullable: true
                        items:
                          type: object
                          description: >-
                            A spend cap on a project, on a provider inside it,
                            or on a model under that provider. On an update,
                            restating `id` pairs the declaration with its stored
                            row so accumulated spend and the window anchor carry
                            over; omit `id` to create a new cap.
                          required:
                            - max_limit
                            - reset_duration
                          properties:
                            id:
                              type: string
                              description: >-
                                Omit to create. Restate to update in place. An
                                id the project does not hold is refused.
                            max_limit:
                              type: number
                              minimum: 0
                              description: Cap in dollars.
                            reset_duration:
                              type: string
                              description: >-
                                Reset window as a number and a unit, for example
                                `30s`, `5m`, `1h`, `1d`, `1w`, `1M`, `1Q`, `1Y`.
                                Only day-or-longer windows can be
                                calendar-aligned.
                              example: 1M
                            reset_config:
                              type: object
                              description: >-
                                Window settings the reset duration cannot
                                express. Only valid when `reset_duration` is
                                quarterly (`1Q`).
                              properties:
                                quarter_start_month:
                                  type: integer
                                  minimum: 1
                                  maximum: 12
                                  description: >-
                                    First month of Q1 as 1-12. Omitted means
                                    January. Only the value modulo 3 changes the
                                    reset dates, so January, April, July and
                                    October all reset on calendar quarters.
                              additionalProperties: false
                            current_usage:
                              type: number
                              readOnly: true
                              description: Spend recorded in the current window.
                            last_reset:
                              type: string
                              format: date-time
                              readOnly: true
                            override_amount:
                              type: number
                              readOnly: true
                              description: >-
                                Operator top-up added to `max_limit` while
                                active. Projects have no endpoint that sets one;
                                the field is reported for completeness.
                            override_mode:
                              type: string
                              readOnly: true
                            override_cycles_remaining:
                              type: integer
                              readOnly: true
                            override_cycles_total:
                              type: integer
                              readOnly: true
                            override_anchor_reset:
                              type: string
                              format: date-time
                              readOnly: true
                            config_hash:
                              type: string
                              readOnly: true
                            created_at:
                              type: string
                              format: date-time
                              readOnly: true
                            updated_at:
                              type: string
                              format: date-time
                              readOnly: true
                      rate_limit:
                        type: object
                        description: >-
                          A token and request rate limit on a project, on a
                          provider inside it, or on a model under that provider.
                          The two dimensions run independent windows, and a cap
                          is enforced only together with its window.
                        properties:
                          id:
                            type: string
                            description: Omit to create. Restate to update in place.
                          token_max_limit:
                            type: integer
                            minimum: 0
                            description: >-
                              Maximum tokens per window. Requires
                              `token_reset_duration`. On a project with
                              `split_policy: equal`, a cap below the member
                              count is refused, since a slice is a whole number
                              of tokens.
                          token_reset_duration:
                            type: string
                            example: 1h
                          request_max_limit:
                            type: integer
                            minimum: 0
                            description: >-
                              Maximum requests per window. Requires
                              `request_reset_duration`. Subject to the same
                              lower bound under `split_policy: equal`.
                          request_reset_duration:
                            type: string
                            example: 1h
                          token_current_usage:
                            type: integer
                            readOnly: true
                          token_last_reset:
                            type: string
                            format: date-time
                            readOnly: true
                          request_current_usage:
                            type: integer
                            readOnly: true
                          request_last_reset:
                            type: string
                            format: date-time
                            readOnly: true
                          config_hash:
                            type: string
                            readOnly: true
                          created_at:
                            type: string
                            format: date-time
                            readOnly: true
                          updated_at:
                            type: string
                            format: date-time
                            readOnly: true
                      model_budgets:
                        type: array
                        items:
                          type: object
                          description: >-
                            Caps for one model under a provider configuration. A
                            provider may carry up to 100 of these, and the `*`
                            tier is not accepted here because the provider
                            configuration's own caps cover it.
                          required:
                            - model_name
                          properties:
                            model_name:
                              type: string
                              description: Concrete model name. `*` is refused.
                            budgets:
                              type: array
                              items:
                                type: object
                                description: >-
                                  A spend cap on a project, on a provider inside
                                  it, or on a model under that provider. On an
                                  update, restating `id` pairs the declaration
                                  with its stored row so accumulated spend and
                                  the window anchor carry over; omit `id` to
                                  create a new cap.
                                required:
                                  - max_limit
                                  - reset_duration
                                properties:
                                  id:
                                    type: string
                                    description: >-
                                      Omit to create. Restate to update in
                                      place. An id the project does not hold is
                                      refused.
                                  max_limit:
                                    type: number
                                    minimum: 0
                                    description: Cap in dollars.
                                  reset_duration:
                                    type: string
                                    description: >-
                                      Reset window as a number and a unit, for
                                      example `30s`, `5m`, `1h`, `1d`, `1w`,
                                      `1M`, `1Q`, `1Y`. Only day-or-longer
                                      windows can be calendar-aligned.
                                    example: 1M
                                  reset_config:
                                    type: object
                                    description: >-
                                      Window settings the reset duration cannot
                                      express. Only valid when `reset_duration`
                                      is quarterly (`1Q`).
                                    properties:
                                      quarter_start_month:
                                        type: integer
                                        minimum: 1
                                        maximum: 12
                                        description: >-
                                          First month of Q1 as 1-12. Omitted means
                                          January. Only the value modulo 3 changes
                                          the reset dates, so January, April, July
                                          and October all reset on calendar
                                          quarters.
                                    additionalProperties: false
                                  current_usage:
                                    type: number
                                    readOnly: true
                                    description: Spend recorded in the current window.
                                  last_reset:
                                    type: string
                                    format: date-time
                                    readOnly: true
                                  override_amount:
                                    type: number
                                    readOnly: true
                                    description: >-
                                      Operator top-up added to `max_limit` while
                                      active. Projects have no endpoint that
                                      sets one; the field is reported for
                                      completeness.
                                  override_mode:
                                    type: string
                                    readOnly: true
                                  override_cycles_remaining:
                                    type: integer
                                    readOnly: true
                                  override_cycles_total:
                                    type: integer
                                    readOnly: true
                                  override_anchor_reset:
                                    type: string
                                    format: date-time
                                    readOnly: true
                                  config_hash:
                                    type: string
                                    readOnly: true
                                  created_at:
                                    type: string
                                    format: date-time
                                    readOnly: true
                                  updated_at:
                                    type: string
                                    format: date-time
                                    readOnly: true
                            rate_limit:
                              type: object
                              description: >-
                                A token and request rate limit on a project, on
                                a provider inside it, or on a model under that
                                provider. The two dimensions run independent
                                windows, and a cap is enforced only together
                                with its window.
                              properties:
                                id:
                                  type: string
                                  description: Omit to create. Restate to update in place.
                                token_max_limit:
                                  type: integer
                                  minimum: 0
                                  description: >-
                                    Maximum tokens per window. Requires
                                    `token_reset_duration`. On a project with
                                    `split_policy: equal`, a cap below the
                                    member count is refused, since a slice is a
                                    whole number of tokens.
                                token_reset_duration:
                                  type: string
                                  example: 1h
                                request_max_limit:
                                  type: integer
                                  minimum: 0
                                  description: >-
                                    Maximum requests per window. Requires
                                    `request_reset_duration`. Subject to the
                                    same lower bound under `split_policy:
                                    equal`.
                                request_reset_duration:
                                  type: string
                                  example: 1h
                                token_current_usage:
                                  type: integer
                                  readOnly: true
                                token_last_reset:
                                  type: string
                                  format: date-time
                                  readOnly: true
                                request_current_usage:
                                  type: integer
                                  readOnly: true
                                request_last_reset:
                                  type: string
                                  format: date-time
                                  readOnly: true
                                config_hash:
                                  type: string
                                  readOnly: true
                                created_at:
                                  type: string
                                  format: date-time
                                  readOnly: true
                                updated_at:
                                  type: string
                                  format: date-time
                                  readOnly: true
                          additionalProperties: false
                        description: >-
                          Per-model caps under this provider. Stored as
                          project-scoped model configurations and reassembled on
                          read, so this field is absent from `GET
                          /api/governance/users/{user_id}/projects`.
                mcp_configs:
                  type: array
                  nullable: true
                  items:
                    type: object
                    description: >-
                      The tools of one MCP client the project may execute.
                      Naming a client with an empty tool list still counts as
                      the project having named it, so a client the project
                      closes off cannot be reopened by a default-allowed rule.
                    properties:
                      id:
                        type: integer
                        description: >-
                          Omit or send `0` to create. Restate to update in
                          place.
                      mcp_client_id:
                        type: integer
                        description: >-
                          The stored MCP client. May be omitted when
                          `mcp_client_name` is given, which is resolved to this
                          id on write.
                      mcp_client_name:
                        type: string
                        description: >-
                          Write-only convenience. Resolved to `mcp_client_id`; a
                          name that matches no client is refused. Never returned
                          on a read.
                      mcp_client:
                        readOnly: true
                        allOf:
                          - $ref: '#/components/schemas/MCPClient'
                        description: The resolved client, included on reads.
                      tools_to_execute:
                        type: array
                        nullable: true
                        items:
                          type: string
                        description: >-
                          `["*"]` allows every tool the client exposes,
                          including ones added later. An empty list allows none.
                          A list may not mix `*` with named entries.
                virtual_mcps:
                  type: array
                  nullable: true
                  items:
                    type: object
                    description: >-
                      A virtual MCP assigned to the project. A scoped request
                      can reach each one at its `/mcp/<slug>` endpoint.
                      Assignments are a set, so a repeated id is ignored rather
                      than doubled, and the list is replaced wholesale on update
                      rather than diffed.
                    required:
                      - virtual_mcp_id
                    properties:
                      virtual_mcp_id:
                        type: integer
                        minimum: 1
                    additionalProperties: false
                reset_budget_usage:
                  type: boolean
                  description: >-
                    Clears recorded spend on the budgets this body restates that
                    the project already held, and on each member's slice of
                    them. A budget the edit introduces has no spend to clear,
                    and one the body does not name is left alone.
              additionalProperties: false
      responses:
        '200':
          description: Project updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  project:
                    type: object
                    description: >-
                      A per-request access scope and accounting ledger that
                      callers opt into with a header.
                    properties:
                      id:
                        type: string
                        readOnly: true
                      name:
                        type: string
                        description: >-
                          Globally unique. Requests reference it with the
                          `x-bf-project-name` header.
                      description:
                        type: string
                        nullable: true
                      is_active:
                        type: boolean
                        default: true
                        description: >-
                          A disabled project refuses every request that names
                          it.
                      expires_at:
                        type: string
                        format: date-time
                        nullable: true
                        description: >-
                          An expired project refuses every request that names
                          it. The comparison is inclusive.
                      access_rule:
                        type: string
                        enum:
                          - intersect
                          - union
                        description: >-
                          How the project's own access composes with the access
                          the caller already holds. `intersect` permits only
                          what both allow, so a project with no provider config
                          permits nothing. `union` leaves the caller's access
                          untouched and adds to it, so a project with no
                          provider or MCP config only attributes and caps spend.
                      membership_mode:
                        type: string
                        enum:
                          - explicit
                          - open
                        default: explicit
                        description: >-
                          `explicit` admits only the users on the roster. `open`
                          admits every authenticated caller and has no
                          membership rows, which is why it cannot divide a
                          budget between members.
                      accounting_mode:
                        type: string
                        enum:
                          - both
                          - project_only
                          - principal_only
                        default: both
                        description: >-
                          Which ledgers a request's spend lands on. `both`
                          charges the project and the caller. `project_only`
                          takes the request off everything the caller funds,
                          including the teams and business units above them.
                          `principal_only` leaves the project's caps on record
                          but checks and charges none of them. The deployment's
                          own global caps always apply.
                      split_policy:
                        type: string
                        enum:
                          - none
                          - equal
                        default: none
                        description: >-
                          `none` keeps the project's caps shared, and individual
                          members can still be capped by hand. `equal` gives
                          every member an equal slice of every budget and rate
                          limit the project holds, at every tier, as a second
                          cap within it.
                      calendar_aligned:
                        type: boolean
                        default: false
                        description: >-
                          Snaps every window the project holds, including each
                          member's derived slice, onto calendar boundaries
                          instead of running from creation.
                      budgets:
                        type: array
                        nullable: true
                        items:
                          type: object
                          description: >-
                            A spend cap on a project, on a provider inside it,
                            or on a model under that provider. On an update,
                            restating `id` pairs the declaration with its stored
                            row so accumulated spend and the window anchor carry
                            over; omit `id` to create a new cap.
                          required:
                            - max_limit
                            - reset_duration
                          properties:
                            id:
                              type: string
                              description: >-
                                Omit to create. Restate to update in place. An
                                id the project does not hold is refused.
                            max_limit:
                              type: number
                              minimum: 0
                              description: Cap in dollars.
                            reset_duration:
                              type: string
                              description: >-
                                Reset window as a number and a unit, for example
                                `30s`, `5m`, `1h`, `1d`, `1w`, `1M`, `1Q`, `1Y`.
                                Only day-or-longer windows can be
                                calendar-aligned.
                              example: 1M
                            reset_config:
                              type: object
                              description: >-
                                Window settings the reset duration cannot
                                express. Only valid when `reset_duration` is
                                quarterly (`1Q`).
                              properties:
                                quarter_start_month:
                                  type: integer
                                  minimum: 1
                                  maximum: 12
                                  description: >-
                                    First month of Q1 as 1-12. Omitted means
                                    January. Only the value modulo 3 changes the
                                    reset dates, so January, April, July and
                                    October all reset on calendar quarters.
                              additionalProperties: false
                            current_usage:
                              type: number
                              readOnly: true
                              description: Spend recorded in the current window.
                            last_reset:
                              type: string
                              format: date-time
                              readOnly: true
                            override_amount:
                              type: number
                              readOnly: true
                              description: >-
                                Operator top-up added to `max_limit` while
                                active. Projects have no endpoint that sets one;
                                the field is reported for completeness.
                            override_mode:
                              type: string
                              readOnly: true
                            override_cycles_remaining:
                              type: integer
                              readOnly: true
                            override_cycles_total:
                              type: integer
                              readOnly: true
                            override_anchor_reset:
                              type: string
                              format: date-time
                              readOnly: true
                            config_hash:
                              type: string
                              readOnly: true
                            created_at:
                              type: string
                              format: date-time
                              readOnly: true
                            updated_at:
                              type: string
                              format: date-time
                              readOnly: true
                      rate_limit:
                        type: object
                        description: >-
                          A token and request rate limit on a project, on a
                          provider inside it, or on a model under that provider.
                          The two dimensions run independent windows, and a cap
                          is enforced only together with its window.
                        properties:
                          id:
                            type: string
                            description: Omit to create. Restate to update in place.
                          token_max_limit:
                            type: integer
                            minimum: 0
                            description: >-
                              Maximum tokens per window. Requires
                              `token_reset_duration`. On a project with
                              `split_policy: equal`, a cap below the member
                              count is refused, since a slice is a whole number
                              of tokens.
                          token_reset_duration:
                            type: string
                            example: 1h
                          request_max_limit:
                            type: integer
                            minimum: 0
                            description: >-
                              Maximum requests per window. Requires
                              `request_reset_duration`. Subject to the same
                              lower bound under `split_policy: equal`.
                          request_reset_duration:
                            type: string
                            example: 1h
                          token_current_usage:
                            type: integer
                            readOnly: true
                          token_last_reset:
                            type: string
                            format: date-time
                            readOnly: true
                          request_current_usage:
                            type: integer
                            readOnly: true
                          request_last_reset:
                            type: string
                            format: date-time
                            readOnly: true
                          config_hash:
                            type: string
                            readOnly: true
                          created_at:
                            type: string
                            format: date-time
                            readOnly: true
                          updated_at:
                            type: string
                            format: date-time
                            readOnly: true
                      provider_configs:
                        type: array
                        items:
                          type: object
                          description: >-
                            What the project allows on one provider, and the
                            money that provider's use answers to. A project
                            names each provider at most once.
                          required:
                            - provider_name
                          properties:
                            id:
                              type: integer
                              description: >-
                                Omit or send `0` to create. Restate to update in
                                place. An id the project does not hold is
                                refused.
                            provider_name:
                              type: string
                              example: openai
                            all_models_allowed:
                              type: boolean
                              default: false
                              description: Allows every model the provider offers.
                            allowed_models:
                              type: array
                              nullable: true
                              items:
                                type: string
                              description: >-
                                Allowlist. `["*"]` allows every model. A list
                                may not mix `*` with named entries.
                            blacklisted_models:
                              type: array
                              nullable: true
                              items:
                                type: string
                              description: >-
                                Denylist, which wins over the allowlist. `["*"]`
                                blocks everything.
                            key_ids:
                              type: array
                              nullable: true
                              items:
                                type: string
                              description: >-
                                Provider keys that may serve the request.
                                `["*"]` allows all; an empty list allows none.
                                Each id must exist and belong to this provider.
                            weight:
                              type: number
                              nullable: true
                              description: >-
                                Load-balancing preference for this provider
                                inside the project. Null leaves the caller's own
                                preference standing. Negative values are
                                refused.
                            budgets:
                              type: array
                              nullable: true
                              items:
                                type: object
                                description: >-
                                  A spend cap on a project, on a provider inside
                                  it, or on a model under that provider. On an
                                  update, restating `id` pairs the declaration
                                  with its stored row so accumulated spend and
                                  the window anchor carry over; omit `id` to
                                  create a new cap.
                                required:
                                  - max_limit
                                  - reset_duration
                                properties:
                                  id:
                                    type: string
                                    description: >-
                                      Omit to create. Restate to update in
                                      place. An id the project does not hold is
                                      refused.
                                  max_limit:
                                    type: number
                                    minimum: 0
                                    description: Cap in dollars.
                                  reset_duration:
                                    type: string
                                    description: >-
                                      Reset window as a number and a unit, for
                                      example `30s`, `5m`, `1h`, `1d`, `1w`,
                                      `1M`, `1Q`, `1Y`. Only day-or-longer
                                      windows can be calendar-aligned.
                                    example: 1M
                                  reset_config:
                                    type: object
                                    description: >-
                                      Window settings the reset duration cannot
                                      express. Only valid when `reset_duration`
                                      is quarterly (`1Q`).
                                    properties:
                                      quarter_start_month:
                                        type: integer
                                        minimum: 1
                                        maximum: 12
                                        description: >-
                                          First month of Q1 as 1-12. Omitted means
                                          January. Only the value modulo 3 changes
                                          the reset dates, so January, April, July
                                          and October all reset on calendar
                                          quarters.
                                    additionalProperties: false
                                  current_usage:
                                    type: number
                                    readOnly: true
                                    description: Spend recorded in the current window.
                                  last_reset:
                                    type: string
                                    format: date-time
                                    readOnly: true
                                  override_amount:
                                    type: number
                                    readOnly: true
                                    description: >-
                                      Operator top-up added to `max_limit` while
                                      active. Projects have no endpoint that
                                      sets one; the field is reported for
                                      completeness.
                                  override_mode:
                                    type: string
                                    readOnly: true
                                  override_cycles_remaining:
                                    type: integer
                                    readOnly: true
                                  override_cycles_total:
                                    type: integer
                                    readOnly: true
                                  override_anchor_reset:
                                    type: string
                                    format: date-time
                                    readOnly: true
                                  config_hash:
                                    type: string
                                    readOnly: true
                                  created_at:
                                    type: string
                                    format: date-time
                                    readOnly: true
                                  updated_at:
                                    type: string
                                    format: date-time
                                    readOnly: true
                            rate_limit:
                              type: object
                              description: >-
                                A token and request rate limit on a project, on
                                a provider inside it, or on a model under that
                                provider. The two dimensions run independent
                                windows, and a cap is enforced only together
                                with its window.
                              properties:
                                id:
                                  type: string
                                  description: Omit to create. Restate to update in place.
                                token_max_limit:
                                  type: integer
                                  minimum: 0
                                  description: >-
                                    Maximum tokens per window. Requires
                                    `token_reset_duration`. On a project with
                                    `split_policy: equal`, a cap below the
                                    member count is refused, since a slice is a
                                    whole number of tokens.
                                token_reset_duration:
                                  type: string
                                  example: 1h
                                request_max_limit:
                                  type: integer
                                  minimum: 0
                                  description: >-
                                    Maximum requests per window. Requires
                                    `request_reset_duration`. Subject to the
                                    same lower bound under `split_policy:
                                    equal`.
                                request_reset_duration:
                                  type: string
                                  example: 1h
                                token_current_usage:
                                  type: integer
                                  readOnly: true
                                token_last_reset:
                                  type: string
                                  format: date-time
                                  readOnly: true
                                request_current_usage:
                                  type: integer
                                  readOnly: true
                                request_last_reset:
                                  type: string
                                  format: date-time
                                  readOnly: true
                                config_hash:
                                  type: string
                                  readOnly: true
                                created_at:
                                  type: string
                                  format: date-time
                                  readOnly: true
                                updated_at:
                                  type: string
                                  format: date-time
                                  readOnly: true
                            model_budgets:
                              type: array
                              items:
                                type: object
                                description: >-
                                  Caps for one model under a provider
                                  configuration. A provider may carry up to 100
                                  of these, and the `*` tier is not accepted
                                  here because the provider configuration's own
                                  caps cover it.
                                required:
                                  - model_name
                                properties:
                                  model_name:
                                    type: string
                                    description: Concrete model name. `*` is refused.
                                  budgets:
                                    type: array
                                    items:
                                      type: object
                                      description: >-
                                        A spend cap on a project, on a provider
                                        inside it, or on a model under that
                                        provider. On an update, restating `id`
                                        pairs the declaration with its stored
                                        row so accumulated spend and the window
                                        anchor carry over; omit `id` to create a
                                        new cap.
                                      required:
                                        - max_limit
                                        - reset_duration
                                      properties:
                                        id:
                                          type: string
                                          description: >-
                                            Omit to create. Restate to update in
                                            place. An id the project does not hold
                                            is refused.
                                        max_limit:
                                          type: number
                                          minimum: 0
                                          description: Cap in dollars.
                                        reset_duration:
                                          type: string
                                          description: >-
                                            Reset window as a number and a unit, for
                                            example `30s`, `5m`, `1h`, `1d`, `1w`,
                                            `1M`, `1Q`, `1Y`. Only day-or-longer
                                            windows can be calendar-aligned.
                                          example: 1M
                                        reset_config:
                                          type: object
                                          description: >-
                                            Window settings the reset duration
                                            cannot express. Only valid when
                                            `reset_duration` is quarterly (`1Q`).
                                          properties:
                                            quarter_start_month:
                                              type: integer
                                              minimum: 1
                                              maximum: 12
                                              description: >-
                                                First month of Q1 as 1-12. Omitted means
                                                January. Only the value modulo 3 changes
                                                the reset dates, so January, April, July
                                                and October all reset on calendar
                                                quarters.
                                          additionalProperties: false
                                        current_usage:
                                          type: number
                                          readOnly: true
                                          description: Spend recorded in the current window.
                                        last_reset:
                                          type: string
                                          format: date-time
                                          readOnly: true
                                        override_amount:
                                          type: number
                                          readOnly: true
                                          description: >-
                                            Operator top-up added to `max_limit`
                                            while active. Projects have no endpoint
                                            that sets one; the field is reported for
                                            completeness.
                                        override_mode:
                                          type: string
                                          readOnly: true
                                        override_cycles_remaining:
                                          type: integer
                                          readOnly: true
                                        override_cycles_total:
                                          type: integer
                                          readOnly: true
                                        override_anchor_reset:
                                          type: string
                                          format: date-time
                                          readOnly: true
                                        config_hash:
                                          type: string
                                          readOnly: true
                                        created_at:
                                          type: string
                                          format: date-time
                                          readOnly: true
                                        updated_at:
                                          type: string
                                          format: date-time
                                          readOnly: true
                                  rate_limit:
                                    type: object
                                    description: >-
                                      A token and request rate limit on a
                                      project, on a provider inside it, or on a
                                      model under that provider. The two
                                      dimensions run independent windows, and a
                                      cap is enforced only together with its
                                      window.
                                    properties:
                                      id:
                                        type: string
                                        description: >-
                                          Omit to create. Restate to update in
                                          place.
                                      token_max_limit:
                                        type: integer
                                        minimum: 0
                                        description: >-
                                          Maximum tokens per window. Requires
                                          `token_reset_duration`. On a project
                                          with `split_policy: equal`, a cap below
                                          the member count is refused, since a
                                          slice is a whole number of tokens.
                                      token_reset_duration:
                                        type: string
                                        example: 1h
                                      request_max_limit:
                                        type: integer
                                        minimum: 0
                                        description: >-
                                          Maximum requests per window. Requires
                                          `request_reset_duration`. Subject to the
                                          same lower bound under `split_policy:
                                          equal`.
                                      request_reset_duration:
                                        type: string
                                        example: 1h
                                      token_current_usage:
                                        type: integer
                                        readOnly: true
                                      token_last_reset:
                                        type: string
                                        format: date-time
                                        readOnly: true
                                      request_current_usage:
                                        type: integer
                                        readOnly: true
                                      request_last_reset:
                                        type: string
                                        format: date-time
                                        readOnly: true
                                      config_hash:
                                        type: string
                                        readOnly: true
                                      created_at:
                                        type: string
                                        format: date-time
                                        readOnly: true
                                      updated_at:
                                        type: string
                                        format: date-time
                                        readOnly: true
                                additionalProperties: false
                              description: >-
                                Per-model caps under this provider. Stored as
                                project-scoped model configurations and
                                reassembled on read, so this field is absent
                                from `GET
                                /api/governance/users/{user_id}/projects`.
                      mcp_configs:
                        type: array
                        items:
                          type: object
                          description: >-
                            The tools of one MCP client the project may execute.
                            Naming a client with an empty tool list still counts
                            as the project having named it, so a client the
                            project closes off cannot be reopened by a
                            default-allowed rule.
                          properties:
                            id:
                              type: integer
                              description: >-
                                Omit or send `0` to create. Restate to update in
                                place.
                            mcp_client_id:
                              type: integer
                              description: >-
                                The stored MCP client. May be omitted when
                                `mcp_client_name` is given, which is resolved to
                                this id on write.
                            mcp_client_name:
                              type: string
                              description: >-
                                Write-only convenience. Resolved to
                                `mcp_client_id`; a name that matches no client
                                is refused. Never returned on a read.
                            mcp_client:
                              readOnly: true
                              allOf:
                                - $ref: '#/components/schemas/MCPClient'
                              description: The resolved client, included on reads.
                            tools_to_execute:
                              type: array
                              nullable: true
                              items:
                                type: string
                              description: >-
                                `["*"]` allows every tool the client exposes,
                                including ones added later. An empty list allows
                                none. A list may not mix `*` with named entries.
                      virtual_mcps:
                        type: array
                        items:
                          type: object
                          description: >-
                            A virtual MCP assigned to the project. A scoped
                            request can reach each one at its `/mcp/<slug>`
                            endpoint. Assignments are a set, so a repeated id is
                            ignored rather than doubled, and the list is
                            replaced wholesale on update rather than diffed.
                          required:
                            - virtual_mcp_id
                          properties:
                            virtual_mcp_id:
                              type: integer
                              minimum: 1
                          additionalProperties: false
                      members:
                        type: array
                        readOnly: true
                        items:
                          type: object
                          description: >-
                            A user on a project's roster. Members are users,
                            never keys or teams: a key authenticates a request,
                            and the user behind it is who belongs to a project.
                          required:
                            - user_id
                          properties:
                            id:
                              type: string
                              readOnly: true
                              description: >-
                                The membership row id, which is the
                                `{member_id}` of the member routes.
                            user_id:
                              type: string
                              description: >-
                                Immutable after creation. Changing it is a
                                remove and an add.
                            budget_shares:
                              type: array
                              items:
                                type: object
                                description: >-
                                  One member's cap on a budget the project
                                  holds. The share is a second cap, not a
                                  replacement: the budget it slices still caps
                                  the whole, and whichever binds first refuses
                                  the request.
                                required:
                                  - source_budget_id
                                properties:
                                  id:
                                    type: string
                                    readOnly: true
                                  project_id:
                                    type: string
                                    readOnly: true
                                  user_id:
                                    type: string
                                    readOnly: true
                                  source_budget_id:
                                    type: string
                                    description: >-
                                      The budget being sliced. Must be one the
                                      project holds, at any tier.
                                  budget_id:
                                    type: string
                                    readOnly: true
                                    description: >-
                                      The derived governance budget that
                                      enforces the slice.
                                  share_value:
                                    type: number
                                    description: >-
                                      The authored figure. A percent share must
                                      be greater than 0 and at most 100; an
                                      amount share must be greater than 0. Null
                                      on every slice derived by an equal split.
                                  share_type:
                                    type: string
                                    enum:
                                      - percent
                                      - amount
                                    description: >-
                                      Whether a member's authored share is a
                                      percentage of the source cap or an
                                      absolute figure. Budget shares must state
                                      this explicitly; a rate-limit share left
                                      blank settles to `amount`.
                                  budget:
                                    readOnly: true
                                    allOf:
                                      - type: object
                                        description: >-
                                          A spend cap on a project, on a provider
                                          inside it, or on a model under that
                                          provider. On an update, restating `id`
                                          pairs the declaration with its stored
                                          row so accumulated spend and the window
                                          anchor carry over; omit `id` to create a
                                          new cap.
                                        required:
                                          - max_limit
                                          - reset_duration
                                        properties:
                                          id:
                                            type: string
                                            description: >-
                                              Omit to create. Restate to update in
                                              place. An id the project does not hold
                                              is refused.
                                          max_limit:
                                            type: number
                                            minimum: 0
                                            description: Cap in dollars.
                                          reset_duration:
                                            type: string
                                            description: >-
                                              Reset window as a number and a unit, for
                                              example `30s`, `5m`, `1h`, `1d`, `1w`,
                                              `1M`, `1Q`, `1Y`. Only day-or-longer
                                              windows can be calendar-aligned.
                                            example: 1M
                                          reset_config:
                                            type: object
                                            description: >-
                                              Window settings the reset duration
                                              cannot express. Only valid when
                                              `reset_duration` is quarterly (`1Q`).
                                            properties:
                                              quarter_start_month:
                                                type: integer
                                                minimum: 1
                                                maximum: 12
                                                description: >-
                                                  First month of Q1 as 1-12. Omitted means
                                                  January. Only the value modulo 3 changes
                                                  the reset dates, so January, April, July
                                                  and October all reset on calendar
                                                  quarters.
                                            additionalProperties: false
                                          current_usage:
                                            type: number
                                            readOnly: true
                                            description: Spend recorded in the current window.
                                          last_reset:
                                            type: string
                                            format: date-time
                                            readOnly: true
                                          override_amount:
                                            type: number
                                            readOnly: true
                                            description: >-
                                              Operator top-up added to `max_limit`
                                              while active. Projects have no endpoint
                                              that sets one; the field is reported for
                                              completeness.
                                          override_mode:
                                            type: string
                                            readOnly: true
                                          override_cycles_remaining:
                                            type: integer
                                            readOnly: true
                                          override_cycles_total:
                                            type: integer
                                            readOnly: true
                                          override_anchor_reset:
                                            type: string
                                            format: date-time
                                            readOnly: true
                                          config_hash:
                                            type: string
                                            readOnly: true
                                          created_at:
                                            type: string
                                            format: date-time
                                            readOnly: true
                                          updated_at:
                                            type: string
                                            format: date-time
                                            readOnly: true
                                    description: >-
                                      The derived budget row, with its own
                                      usage.
                                  created_at:
                                    type: string
                                    format: date-time
                                    readOnly: true
                                  updated_at:
                                    type: string
                                    format: date-time
                                    readOnly: true
                            rate_limit_shares:
                              type: array
                              items:
                                type: object
                                description: >-
                                  One member's cap on a rate limit the project
                                  holds. The token and request dimensions are
                                  authored independently, so a member may hold a
                                  percentage of tokens and a flat count of
                                  requests.
                                required:
                                  - source_rate_limit_id
                                properties:
                                  id:
                                    type: string
                                    readOnly: true
                                  project_id:
                                    type: string
                                    readOnly: true
                                  user_id:
                                    type: string
                                    readOnly: true
                                  source_rate_limit_id:
                                    type: string
                                    description: >-
                                      The rate limit being sliced. Must be one
                                      the project holds, at any tier.
                                  rate_limit_id:
                                    type: string
                                    readOnly: true
                                    description: >-
                                      The derived governance rate limit that
                                      enforces the slice.
                                  token_share:
                                    type: integer
                                    description: >-
                                      Authored token share. Requires the source
                                      to cap tokens. A percent share that comes
                                      to no whole tokens is refused.
                                  token_share_type:
                                    allOf:
                                      - type: string
                                        enum:
                                          - percent
                                          - amount
                                        description: >-
                                          Whether a member's authored share is a
                                          percentage of the source cap or an
                                          absolute figure. Budget shares must
                                          state this explicitly; a rate-limit
                                          share left blank settles to `amount`.
                                    description: >-
                                      Settles to `amount` when a token share is
                                      given without one.
                                  request_share:
                                    type: integer
                                    description: >-
                                      Authored request share. Requires the
                                      source to cap requests. A percent share
                                      that comes to no whole requests is
                                      refused.
                                  request_share_type:
                                    allOf:
                                      - type: string
                                        enum:
                                          - percent
                                          - amount
                                        description: >-
                                          Whether a member's authored share is a
                                          percentage of the source cap or an
                                          absolute figure. Budget shares must
                                          state this explicitly; a rate-limit
                                          share left blank settles to `amount`.
                                    description: >-
                                      Settles to `amount` when a request share
                                      is given without one.
                                  rate_limit:
                                    readOnly: true
                                    allOf:
                                      - type: object
                                        description: >-
                                          A token and request rate limit on a
                                          project, on a provider inside it, or on
                                          a model under that provider. The two
                                          dimensions run independent windows, and
                                          a cap is enforced only together with its
                                          window.
                                        properties:
                                          id:
                                            type: string
                                            description: >-
                                              Omit to create. Restate to update in
                                              place.
                                          token_max_limit:
                                            type: integer
                                            minimum: 0
                                            description: >-
                                              Maximum tokens per window. Requires
                                              `token_reset_duration`. On a project
                                              with `split_policy: equal`, a cap below
                                              the member count is refused, since a
                                              slice is a whole number of tokens.
                                          token_reset_duration:
                                            type: string
                                            example: 1h
                                          request_max_limit:
                                            type: integer
                                            minimum: 0
                                            description: >-
                                              Maximum requests per window. Requires
                                              `request_reset_duration`. Subject to the
                                              same lower bound under `split_policy:
                                              equal`.
                                          request_reset_duration:
                                            type: string
                                            example: 1h
                                          token_current_usage:
                                            type: integer
                                            readOnly: true
                                          token_last_reset:
                                            type: string
                                            format: date-time
                                            readOnly: true
                                          request_current_usage:
                                            type: integer
                                            readOnly: true
                                          request_last_reset:
                                            type: string
                                            format: date-time
                                            readOnly: true
                                          config_hash:
                                            type: string
                                            readOnly: true
                                          created_at:
                                            type: string
                                            format: date-time
                                            readOnly: true
                                          updated_at:
                                            type: string
                                            format: date-time
                                            readOnly: true
                                    description: >-
                                      The derived rate limit row, with its own
                                      counters.
                                  created_at:
                                    type: string
                                    format: date-time
                                    readOnly: true
                                  updated_at:
                                    type: string
                                    format: date-time
                                    readOnly: true
                            user_name:
                              type: string
                              readOnly: true
                            user_email:
                              type: string
                              readOnly: true
                            created_at:
                              type: string
                              format: date-time
                              readOnly: true
                            updated_at:
                              type: string
                              format: date-time
                              readOnly: true
                        description: >-
                          Narrowed to what the caller's data access permits, so
                          two operators can read the same project and see
                          different rosters. Managed through the member routes,
                          never through the project body.
                      created_by_user_id:
                        type: string
                        nullable: true
                        readOnly: true
                        description: Taken from the calling identity, never from the body.
                      created_at:
                        type: string
                        format: date-time
                        readOnly: true
                      updated_at:
                        type: string
                        format: date-time
                        readOnly: true
                  redivision_job_id:
                    type: string
                    description: >-
                      Present only when the edit queued a redivision, which
                      happens on a project with `split_policy: equal` whose
                      roster or divided caps changed. A pending job for the same
                      project is reused rather than queued behind, so this may
                      name a job an earlier request created. Progress is
                      reported on the `redivision_progress` WebSocket channel
                      under this id.
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BifrostError'
        '404':
          description: Project not found
        '409':
          description: A project with the same name already exists.
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BifrostError'
      security:
        - ManagementBearerAuth: []
components:
  schemas:
    MCPClient:
      type: object
      description: Connected MCP client with its tools
      properties:
        config:
          $ref: '#/components/schemas/MCPClientConfig'
        tools:
          type: array
          items:
            type: object
            description: Tool function definition
            properties:
              name:
                type: string
              description:
                type: string
              parameters:
                type: object
                additionalProperties: true
              strict:
                type: boolean
        state:
          type: string
          enum:
            - healthy
            - unstable
            - error
            - pending_verification
            - needs_reauth
            - disabled
            - degraded
          description: >
            Connection state of an MCP client:

            - healthy: Bifrost's own periodic connection check (ping/list_tools
            for
              sticky clients, list_tools for per-call clients) most recently
              succeeded.
            - unstable: The periodic connection check most recently failed with
            a
              transient-classified error. Purely informational — unlike
              needs_reauth, this never gates execution; tool calls are still
              attempted normally. Self-heals to healthy on the next successful
              check, no human action required.
            - error: A data-consistency fallback used only when a client is
              registered in the config store but missing from the runtime manager
              entirely — a deeper anomaly than anything in the normal
              connect/health-check lifecycle, which never assigns this value itself.
            - pending_verification: Declared (typically via config.json) but the
            one-time
              admin verification has not been completed yet. Complete it via
              POST /api/mcp/client/{id}/initiate-verification (auth_type oauth /
              per_user_oauth) or POST /api/mcp/client/{id}/verify-headers
              (auth_type per_user_headers).
            - needs_reauth: Setup completed at least once, but a credential an
            admin is
              responsible for has permanently died and needs a human to repair it.
              For shared OAuth clients the connection credential itself was
              rejected/expired with no silent recovery; the state is sticky (the health
              monitor will not clobber it) until an admin runs
              POST /api/mcp/client/{id}/reauthorize. For per-user clients this is a
              response-only projection — computed at list-time, never stored in the
              runtime manager — meaning the retained admin discovery credential
              needs repair; end-user credentials and tool calls keep working, only
              periodic tool-list refresh pauses. Overlays onto both healthy and
              unstable runtime readings, never onto disabled or
              pending_verification. Repair via reauthorize (per_user_oauth),
              verify-headers with fresh sample values (per_user_headers), or
              verify-exchange (token_exchange, which re-exchanges the signed-in
              admin's own identity token — no sample values to supply).
            - disabled: Client has been intentionally disabled; no connection or
            workers are active

            - degraded: A read-time cluster aggregate, never a single node's own
              local state — multiple instances of a distributed deployment each
              currently hold a different self-reported state for the same client.
              Only meaningful for states that can genuinely vary per instance
              (healthy, unstable, pending_verification); needs_reauth/disabled are
              config-sourced facts expected to already agree everywhere. Never
              appears in a single-instance deployment.
        last_failure:
          $ref: '#/components/schemas/MCPConnectionFailure'
        node_states:
          type: object
          additionalProperties:
            $ref: '#/components/schemas/MCPInstanceState'
          description: >
            Per-instance breakdown behind `state` in a distributed deployment:

            instance ID -> that instance's own self-reported state and

            last_failure. Present when instances disagree (`state` is then

            `degraded`) and when they all agree on `unstable`, so each
            instance's

            own reason is visible. Never present in a single-instance
            deployment.
        vk_configs:
          type: array
          items:
            type: object
            description: >-
              Per-virtual-key tool access configuration as returned in list/get
              responses
            properties:
              virtual_key_id:
                type: string
                description: ID of the virtual key
              virtual_key_name:
                type: string
                description: Display name of the virtual key
              tools_to_execute:
                type: array
                items:
                  type: string
                description: |
                  Tools this virtual key is allowed to call on this MCP client.
                  ["*"] => all tools allowed
                  ["tool1", "tool2"] => only the specified tools
          description: Virtual key assignments for this MCP client
    BifrostError:
      type: object
      description: Error response from Bifrost
      properties:
        event_id:
          type: string
        type:
          type: string
        is_bifrost_error:
          type: boolean
        status_code:
          type: integer
        error:
          $ref: '#/components/schemas/ErrorField'
        extra_fields:
          $ref: '#/components/schemas/BifrostErrorExtraFields'
    MCPClientConfig:
      type: object
      description: Full MCP client configuration (used in responses)
      properties:
        client_id:
          type: string
          description: Unique identifier for the MCP client
        name:
          type: string
          description: Display name for the MCP client
        is_code_mode_client:
          type: boolean
          description: Whether this client is available in code mode
        connection_type:
          type: string
          enum:
            - http
            - stdio
            - sse
            - inprocess
          description: Connection type for MCP client
        connection_string:
          type: string
          description: HTTP or SSE URL (required for HTTP or SSE connections)
        stdio_config:
          type: object
          description: STDIO configuration for MCP client
          properties:
            command:
              type: string
              description: Executable command to run
            args:
              type: array
              items:
                type: string
              description: Command line arguments
            envs:
              type: array
              items:
                type: string
              description: Environment variables required
        tls_config:
          type: object
          description: TLS configuration for HTTP and SSE connections.
          properties:
            insecure_skip_verify:
              type: boolean
              description: Disable TLS certificate verification. Development/testing only.
            ca_cert_pem:
              type: string
              description: >
                PEM-encoded CA certificate. Supports env.VAR_NAME syntax for
                input.

                Responses return a redacted placeholder rather than the raw PEM
                value.
        auth_type:
          type: string
          enum:
            - none
            - headers
            - oauth
            - per_user_oauth
            - per_user_headers
            - token_exchange
          description: Authentication type for the MCP connection
        oauth_config_id:
          type: string
          description: |
            OAuth config ID for OAuth authentication.
            References the oauth_configs table.
            Only set when auth_type is "oauth".
        headers:
          type: object
          additionalProperties:
            type: string
          description: |
            Custom headers to include in requests.
            Only used when auth_type is "headers".
        tools_to_execute:
          type: array
          items:
            type: string
          description: |
            Include-only list for tools.
            ["*"] => all tools are included
            [] => no tools are included
            ["tool1", "tool2"] => include only the specified tools
        tools_to_auto_execute:
          type: array
          items:
            type: string
          description: |
            List of tools that can be auto-executed without user approval.
            Must be a subset of tools_to_execute.
            ["*"] => all executable tools can be auto-executed
            [] => no tools are auto-executed
            ["tool1", "tool2"] => only specified tools can be auto-executed
        tool_pricing:
          type: object
          additionalProperties:
            type: number
            format: double
          description: |
            Per-tool cost in USD for execution.
            Key is the tool name, value is the cost per execution.
            Example: {"read_file": 0.001, "write_file": 0.002}
        allow_on_all_virtual_keys:
          type: boolean
          default: false
          description: >
            When true, this MCP client's tools are accessible to all virtual
            keys without requiring

            explicit per-key assignment. All tools are allowed by default. If a
            virtual key has an

            explicit MCP config for this client, that config takes precedence
            and overrides this behaviour.
        per_user_header_keys:
          type: array
          items:
            type: string
          description: >
            For `per_user_headers` clients only. The list of header names each
            end-user

            must supply via the inline-401 flow. Header values themselves are
            stored

            per-user in a separate table (surfaced on `/api/mcp/sessions`).
        token_exchange:
          type: object
          required:
            - audience
          properties:
            audience:
              type: string
              minLength: 1
              description: >-
                Resource identifier this server is scoped to at the identity
                provider (e.g. "api://jira-mcp")
            use_idp_credentials:
              type: boolean
              description: >
                When true, performs the exchange as the SSO login application
                itself

                instead of client_id/client_secret below, which are then
                ignored.

                Some providers require this: Microsoft Entra ID's on-behalf-of
                grant

                only accepts an assertion audienced to the exchanging
                application,

                and the SSO login flow always requests a token self-audienced to
                the

                SSO application, so a separate exchange application can never

                receive a usable one.
            client_id:
              type: string
              minLength: 1
              description: >
                The identity-provider application authorized to perform
                exchanges for

                this audience — typically a dedicated registration carrying the

                token-exchange (or on-behalf-of) grant, separate from the SSO
                login

                application. Required unless use_idp_credentials is true.
                Supports

                env.VAR_NAME and vault.path references. Redacted in API
                responses.
            client_secret:
              type: string
              description: >
                Secret for the exchange application; omit for public clients.
                Ignored

                when use_idp_credentials is true. Supports env.VAR_NAME and

                vault.path references. Redacted in API responses.
            scopes:
              type: array
              items:
                type: string
              description: >
                Optional scopes to request on the exchanged token. Include

                "offline_access" (where the identity provider supports it) so
                the

                retained admin discovery credential gets a refresh token and
                stays

                self-renewing.
            authorization_server_url:
              type: string
              description: >
                Overrides the Authorization Server the exchange request is sent
                to.

                Only needed when the audience is registered on a different

                Authorization Server than the one used for SSO login (e.g.
                Okta's

                per-resource Custom Authorization Servers). Leave unset to use
                the

                deployment's SSO login issuer, which is correct for providers
                with a

                single tenant-wide token endpoint (Entra, Auth0).
          allOf:
            - if:
                not:
                  properties:
                    use_idp_credentials:
                      const: true
                  required:
                    - use_idp_credentials
              then:
                required:
                  - client_id
                description: >
                  client_id is required unless use_idp_credentials is true, in
                  which

                  case the exchange uses the SSO login application's own
                  credentials

                  instead.
          additionalProperties: false
          description: >
            For `token_exchange` clients only. `client_id` and `client_secret`
            are

            redacted in this response.
        disabled:
          type: boolean
          default: false
          description: >
            Whether the client is intentionally disabled.

            When true, the client has no active connection or workers and its
            tools are not available for inference.
    MCPConnectionFailure:
      type: object
      description: |
        The latest failure recorded by the instance serving the request for its
        own connection handling of this client: which step failed, the error it
        failed with, when that last happened, and when the current run of
        failures began. Present only after that instance has attempted a connect
        or check that failed. Absent while `healthy`, cleared the moment a check
        passes, and absent when a state changed without such an attempt, for
        example `needs_reauth` projected from a credential row that died before
        the next scheduled check ran. Describes only Bifrost's own connection
        checks and connect attempts, never the outcome of real tool calls made
        through the client.
      required:
        - stage
        - message
        - at
        - since
      properties:
        stage:
          type: string
          enum:
            - connect
            - ping
            - list_tools
            - tool_discovery
            - transport_lost
            - credential
          description: >
            The step that failed:

            - connect: establishing the shared connection (dial, initialize,
              connect gate, or the initial list_tools a new connection must pass)
            - ping: the periodic check's ping over an existing sticky connection

            - list_tools: the periodic check's list_tools over an existing
            sticky
              connection
            - tool_discovery: the periodic check's ephemeral
            connect-discover-close
              cycle for per-call auth types
            - transport_lost: a live SSE connection dropped outside any check

            - credential: a credential the connection depends on was rejected
              upstream or rotated by an admin
        message:
          type: string
          description: >-
            The error the step failed with, whitespace-collapsed and capped at
            512 characters.
        at:
          type: string
          format: date-time
          description: |
            The most recent failed attempt. While a client is unstable the check
            retries every 10 seconds, so this keeps moving for as long as the
            outage lasts.
        since:
          type: string
          format: date-time
          description: The first failed attempt of the current unhealthy run.
    MCPInstanceState:
      type: object
      description: One instance's own view of an MCP client in a distributed deployment.
      required:
        - state
      properties:
        state:
          type: string
          enum:
            - healthy
            - unstable
            - error
            - pending_verification
            - needs_reauth
            - disabled
          description: >
            That instance's own self-reported connection state. Never
            `degraded`,

            which only ever exists as the aggregate above this map.
        last_failure:
          $ref: '#/components/schemas/MCPConnectionFailure'
    ErrorField:
      type: object
      properties:
        type:
          type: string
        code:
          type: string
        message:
          type: string
        param:
          type: string
        event_id:
          type: string
    BifrostErrorExtraFields:
      type: object
      properties:
        provider:
          $ref: '#/components/schemas/ModelProvider'
        model_requested:
          type: string
        request_type:
          type: string
    ModelProvider:
      type: string
      description: AI model provider identifier
      enum:
        - anthropic
        - azure
        - bedrock
        - bedrock_mantle
        - cerebras
        - cohere
        - deepseek
        - gemini
        - groq
        - mistral
        - ollama
        - opencode-go
        - opencode-zen
        - openai
        - parasail
        - perplexity
        - sgl
        - vertex
        - openrouter
        - elevenlabs
        - huggingface
        - nebius
        - xai
        - replicate
        - vllm
        - runway
        - runware
        - fireworks
        - sarvam
        - wafer
        - databricks
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: >
        Bearer token authentication. Use your provider API key or Bifrost
        authentication token.

        Virtual keys (prefixed with `sk-bf-`) can also be passed here.
    BasicAuth:
      type: http
      scheme: basic
      description: |
        Basic authentication using username and password.
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: |
        API key authentication via the `x-api-key` header.
        Virtual keys (prefixed with `sk-bf-`) can also be passed here.
    ManagementBearerAuth:
      type: http
      scheme: bearer
      description: >
        Management API authentication for `/api/*` endpoints. Use the
        `Authorization` header with `Bearer <API key>`.

        Virtual keys, dashboard/user/session tokens, and `x-api-key` headers are
        not supported on management APIs.

````