# platform/openapi — OpenAPI 3.1 (generated)
---
openapi: 3.1.0
info:
  title: Accoil Tools Platform API
  version: '1'
paths:
  "/users/sign_out":
    delete:
      operationId: sign_out
      summary: Sign out (end the browser session)
      description: Bearer-token requests have no session to end; this is for cookie
        sessions.
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/invites/{token}":
    patch:
      operationId: accept_invite
      summary: Accept an account invite (you become a member)
      description: Requires being signed in; the token in the path is the authorization.
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/invites/{token}/setup":
    post:
      operationId: invite_setup
      summary: Set up an email + password login at Accoil for an invited address
      description: 'Provisions an Auth0 database-connection user for the invite''s
        address and emails a link to set a password — it does not sign anyone in.
        A 409-equivalent refusal means the address already has a login, OR that an
        earlier attempt created one and timed out before recording it. The two are
        indistinguishable here, so the advice is the same: sign in instead, using
        Accoil''s own password reset if no password was ever set. Rate limited per
        invite token.'
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/accounts":
    post:
      operationId: create_account
      summary: Create a team account (you become its owner)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
              required:
              - name
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/{account_slug}/members/{id}":
    patch:
      operationId: change_member_role
      summary: Change a member's account role
      description: Owner-only, and you may not grant a role above your own.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                account_member:
                  type: object
                  properties:
                    role:
                      type: string
                      enum: &1
                      - owner
                      - admin
                      - member
                      description: 'roles are inclusive: owner ⊃ admin ⊃ member'
                  required:
                  - role
              required:
              - account_member
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
    delete:
      operationId: remove_member
      summary: Remove a member from the account
      description: The last owner cannot be removed.
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/{account_slug}/member_invites":
    post:
      operationId: invite_member
      summary: Invite someone to the account by email
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                email:
                  type: string
                  format: email
                role:
                  type: string
                  enum: *1
                  default: member
                  description: you may not grant a role above your own
              required:
              - email
              - role
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/{account_slug}/member_invites/{token}":
    delete:
      operationId: revoke_invite
      summary: Revoke a pending invite
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/{account_slug}/settings/general":
    patch:
      operationId: update_account
      summary: Rename the account (and manage its logo)
      description: The logo itself is a file upload (multipart form field account[logo])
        — not a JSON field.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                account:
                  type: object
                  properties:
                    name:
                      type: string
                remove_logo:
                  type: string
                  enum:
                  - '1'
                  description: optional; removes the current logo
              required:
              - account
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/{account_slug}/settings/accoil_connection":
    post:
      operationId: save_accoil_connection
      summary: Save or replace the account's Accoil API key
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                account_integration:
                  type: object
                  properties:
                    api_key:
                      type: string
                      format: password
                      description: write-only; it is never returned by the settings
                        page
                    workspace_id:
                      type: string
                  required:
                  - api_key
              required:
              - account_integration
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
    delete:
      operationId: disconnect_accoil
      summary: Delete the account's stored Accoil credential
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/{account_slug}/settings/intercom_authorization":
    post:
      operationId: connect_intercom
      summary: Start Intercom MCP OAuth discovery and PKCE in a browser
      description: Intercom dynamically registers this client; one-shot state binds
        consent to the initiating account.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                region:
                  type: string
                  enum:
                  - us
                  - eu
                  description: Intercom MCP data region; AU workspaces are not currently
                    supported by Intercom MCP
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/{account_slug}/settings/intercom_connection":
    delete:
      operationId: disconnect_intercom
      summary: Remove the locally stored Intercom MCP credential
      description: Revoke the connected-app grant in Intercom as well when remote
        authority must be removed.
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/{account_slug}/settings/hubspot_authorization":
    post:
      operationId: connect_hubspot
      summary: Start HubSpot MCP OAuth with PKCE in a browser
      description: Redirects to HubSpot. One-shot state binds the callback to the
        initiating user and account.
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/{account_slug}/settings/hubspot_connection":
    delete:
      operationId: disconnect_hubspot
      summary: Remove the locally stored HubSpot MCP credential
      description: Remote revocation remains available from the HubSpot MCP Auth Apps
        surface.
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/{account_slug}/settings/attio_authorization":
    post:
      operationId: connect_attio
      summary: Start Attio MCP OAuth discovery and PKCE in a browser
      description: Attio dynamically registers this client; one-shot state binds the
        callback to the initiating user and account.
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/{account_slug}/settings/attio_connection":
    delete:
      operationId: disconnect_attio
      summary: Remove the locally stored Attio MCP credential
      description: Remove the grant in Attio as well when remote revocation is required.
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/{account_slug}/settings/userpilot_authorization":
    post:
      operationId: connect_userpilot
      summary: Start UserPilot MCP OAuth discovery and PKCE in a browser
      description: UserPilot dynamically registers this client and lets the installing
        user choose permissions.
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/{account_slug}/settings/userpilot_connection":
    delete:
      operationId: disconnect_userpilot
      summary: Remove the locally stored UserPilot MCP credential
      description: Revoke the connected-app grant in UserPilot as well when remote
        revocation is required.
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/{account_slug}/settings/posthog_authorization":
    post:
      operationId: connect_posthog
      summary: Start PostHog MCP OAuth discovery and PKCE in a browser
      description: PostHog dynamically registers this client and routes the login
        to the account's US or EU region.
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/{account_slug}/settings/posthog_connection":
    delete:
      operationId: disconnect_posthog
      summary: Remove the locally stored PostHog MCP credential
      description: Revoke the connected application in PostHog as well when remote
        revocation is required.
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/{account_slug}/settings/integrations/{provider}/check":
    post:
      operationId: check_integration
      summary: Test a connected account integration credential
      description: Updates connected, stale, or failed status without returning provider
        data.
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/{account_slug}/settings/profile":
    patch:
      operationId: update_profile
      summary: Update your own name
      description: Your email is not editable here — it follows the verified address
        Accoil asserts when you sign in.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                user:
                  type: object
                  properties:
                    full_name:
                      type: string
              required:
              - user
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/{account_slug}/settings/sessions":
    delete:
      operationId: revoke_sessions
      summary: Sign out everywhere else (revoke your other browser sessions)
      description: Rotates the salt every session cookie carries, so every OTHER browser
        is signed out at once. The browser making the request stays signed in. It
        does not end your Accoil (Auth0) sign-in, so it answers "someone copied my
        session cookie", not "someone has my browser". API tokens are unaffected —
        revoke those individually.
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/{account_slug}/settings/api_tokens":
    post:
      operationId: create_api_token
      summary: Mint a personal API token (shown once, in the response page)
      description: A read-only token cannot call this (or any write) — minting a write
        token requires a session or a write token.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                api_token:
                  type: object
                  properties:
                    name:
                      type: string
                    scope:
                      type: string
                      enum:
                      - read
                      - write
                      default: read
                      description: anything unrecognised becomes read
                    expires_in_days:
                      type: string
                      enum:
                      - ''
                      - '30'
                      - '90'
                      - '365'
                      default: ''
                      description: blank never expires
                  required:
                  - name
              required:
              - api_token
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/{account_slug}/settings/api_tokens/{id}":
    delete:
      operationId: revoke_api_token
      summary: Revoke a token — anything using it stops working immediately
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/{account_slug}/settings/identities/{id}":
    delete:
      operationId: disconnect_identity
      summary: Disconnect a linked social login
      description: Refused for your Accoil login — it is the only way in. A legacy
        Google or Atlassian login from before the cutover can be removed.
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/{account_slug}/settings/billing/checkouts":
    post:
      operationId: create_checkout_session
      summary: Start a Stripe Checkout for a tool plan
      description: Answers with a redirect to a stripe.com URL; payment happens there
        and the subscription lands via webhook — poll the billing page for it. Refused
        when billing is not configured on the deployment, the plan is not purchasable,
        or the tool already has a live subscription.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                tool_plan_id:
                  type: integer
                  description: an active plan's id — plans are listed on the billing
                    page
              required:
              - tool_plan_id
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/{account_slug}/settings/billing/portal":
    post:
      operationId: open_billing_portal
      summary: Open Stripe's Billing Portal (card, plan changes, invoices)
      description: Answers with a redirect to a stripe.com URL. Requires a prior purchase
        — the first checkout creates the account's Stripe customer.
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/system/accounts":
    post:
      operationId: system_provision_account
      summary: Provision a customer account (no members yet) and invite its owner
      description: Creates the account with zero members — this does not make you
        a member of it. Nothing is tracked yet; the account's first analytics event
        is the owner's own MEMBER_JOINED, once they accept.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                owner_email:
                  type: string
                  format: email
              required:
              - name
              - owner_email
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/system/accounts/{slug}/suspend":
    patch:
      operationId: suspend_account
      summary: Suspend an account (it becomes read-only immediately)
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/system/accounts/{slug}/reactivate":
    patch:
      operationId: reactivate_account
      summary: Reactivate a suspended account
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/system/accounts/{slug}":
    delete:
      operationId: delete_account
      summary: Hard-delete a suspended account — irreversible
      description: Refused unless the account is already suspended. Members, invites,
        products and credentials go; dead letters are kept, orphaned.
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/system/users/{id}":
    patch:
      operationId: set_app_role
      summary: Grant or remove the platform-operator role
      description: You cannot demote yourself, and the last app admin cannot be demoted.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                user:
                  type: object
                  properties:
                    app_role:
                      type: string
                      enum:
                      - admin
                      - member
                      description: the PLATFORM axis — not an account role
                  required:
                  - app_role
              required:
              - user
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
    delete:
      operationId: delete_user
      summary: Hard-delete a user — irreversible
      description: Their tokens, identities and personal account go too. Refused for
        yourself, and while they solely own a shared account.
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/system/accounts/{account_slug}/members":
    post:
      operationId: system_add_member
      summary: Add a user to an account, or invite an unknown email
      description: 'The support-access door: works on suspended accounts, and there
        is no grant ceiling — the operator is not a member. An existing user becomes
        a member immediately; an unknown email gets a pending invite.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                email:
                  type: string
                  format: email
                role:
                  type: string
                  enum: *1
              required:
              - email
              - role
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/system/accounts/{account_slug}/invites":
    post:
      operationId: system_resend_invite
      summary: Reissue a pending or expired invite
      description: Supersedes the old invite with a fresh token and expiry, and re-sends
        the mail. Email and role are read from the stored invite, never from the request.
        An already-accepted invite is a 404.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                token:
                  type: string
                  description: the existing invite's token; email and role come from
                    it
              required:
              - token
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/system/accounts/{account_slug}/members/{id}":
    patch:
      operationId: system_change_member_role
      summary: Change a member's account role from the operator side
      description: The last owner cannot be demoted.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                account_member:
                  type: object
                  properties:
                    role:
                      type: string
                      enum: *1
                      description: 'roles are inclusive: owner ⊃ admin ⊃ member'
                  required:
                  - role
              required:
              - account_member
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
    delete:
      operationId: system_remove_member
      summary: Remove a member from the operator side
      description: The last owner cannot be removed.
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/system/accounts/{account_slug}/tools":
    post:
      operationId: grant_tool
      summary: Enable a tool for an account
      description: The tool appears in the account's navigation and its pages start
        answering immediately.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                tool_key:
                  type: string
                  enum:
                  - amp_events
                  - flux
              required:
              - tool_key
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/system/accounts/{account_slug}/tools/{tool_key}":
    patch:
      operationId: update_tool_grant
      summary: Update a grant's per-account tool settings
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                account_tool:
                  type: object
                  properties:
                    settings:
                      type: object
                      properties:
                        free_event_limit:
                          type:
                          - integer
                          - 'null'
                          minimum: 0
                          description: 'Marketplace Events (amp_events) only: account-level
                            default free event limit for the tool''s products; blank/null
                            clears it (platform default applies); a product''s own
                            override still wins'
                        spending_cap_usd:
                          type:
                          - string
                          - 'null'
                          description: 'AI workflows (flux) only: the account''s AI
                            spending cap in USD, a decimal string (e.g. "5.00") —
                            overrides the platform default of $0.10; blank/null clears
                            back to the default; non-negative, at most $1000'
              required:
              - account_tool
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
    delete:
      operationId: revoke_tool
      summary: Disable a tool for an account
      description: The tool's pages 404 for the account immediately. Its data is kept;
        re-granting restores access.
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/system/tool_plans":
    post:
      operationId: create_tool_plan
      summary: Add a plan to a tool's pricing catalog
      description: A plan is offered to accounts only while active AND carrying every
        Stripe price its billing model needs.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                tool_plan:
                  type: object
                  properties:
                    tool_key:
                      type: string
                      enum:
                      - amp_events
                      - flux
                    name:
                      type: string
                    billing_model:
                      type: string
                      enum: &2
                      - flat
                      - metered
                      - flat_plus_metered
                      description: flat = fixed recurring price; metered = usage-billed
                        via a Stripe Billing Meter; flat_plus_metered = both
                    stripe_product_id:
                      type: string
                      description: prod_… — authored in the Stripe dashboard
                    stripe_price_ids:
                      type: object
                      properties:
                        flat:
                          type: string
                          description: price_…; blank clears
                        metered:
                          type: string
                          description: price_…; blank clears
                    config:
                      type: object
                      properties:
                        included_events:
                          type: integer
                          description: 'informational: events the flat fee covers'
                        stripe_meter_event_name:
                          type: string
                          description: the Billing Meter's event_name; required before
                            metered usage reports
                    active:
                      type: boolean
                      description: inactive plans stay in the catalog but are never
                        offered
                    position:
                      type: integer
                      description: sort order on the billing page, lowest first
                  required:
                  - tool_key
                  - name
                  - billing_model
              required:
              - tool_plan
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/system/tool_plans/{id}":
    patch:
      operationId: update_tool_plan
      summary: Update a catalog plan (rename, reprice, retire via active:false)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                tool_plan:
                  type: object
                  properties:
                    tool_key:
                      type: string
                      enum:
                      - amp_events
                      - flux
                    name:
                      type: string
                    billing_model:
                      type: string
                      enum: *2
                      description: flat = fixed recurring price; metered = usage-billed
                        via a Stripe Billing Meter; flat_plus_metered = both
                    stripe_product_id:
                      type: string
                      description: prod_… — authored in the Stripe dashboard
                    stripe_price_ids:
                      type: object
                      properties:
                        flat:
                          type: string
                          description: price_…; blank clears
                        metered:
                          type: string
                          description: price_…; blank clears
                    config:
                      type: object
                      properties:
                        included_events:
                          type: integer
                          description: 'informational: events the flat fee covers'
                        stripe_meter_event_name:
                          type: string
                          description: the Billing Meter's event_name; required before
                            metered usage reports
                    active:
                      type: boolean
                      description: inactive plans stay in the catalog but are never
                        offered
                    position:
                      type: integer
                      description: sort order on the billing page, lowest first
                  required:
                  - tool_key
                  - name
                  - billing_model
              required:
              - tool_plan
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
    delete:
      operationId: delete_tool_plan
      summary: Delete a catalog plan
      description: Refused while any subscription points at the plan — deactivate
        instead to retire it.
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/system/settings":
    patch:
      operationId: update_platform_settings
      summary: Update platform-wide settings
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                app_setting:
                  type: object
                  properties:
                    allow_registration:
                      type: boolean
                      description: off = invite-only signup
                    amp_events_free_limit:
                      type: integer
                      description: default free event limit; a product can override
                        it
                    flux_draft_run_billing_account_id:
                      type:
                      - integer
                      - 'null'
                      description: the account every Flux draft run is charged to;
                        it must have Flux turned on. null/blank refuses every draft
                        run.
              required:
              - app_setting
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/system/amp_events/products/{id}":
    patch:
      operationId: system_update_product
      summary: Pause/resume a product's sync, or set its free-limit override
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                product:
                  type: object
                  properties:
                    sync_enabled:
                      type: boolean
                    free_event_limit:
                      type:
                      - integer
                      - 'null'
                      description: null/blank = follow the platform default; 0 = pause
                        on the next event
              required:
              - product
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/system/amp_events/products/{id}/syncs":
    post:
      operationId: system_start_sync
      summary: Start one sync run for any product
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/system/amp_events/products/{id}/sync_state":
    delete:
      operationId: reset_sync_state
      summary: Delete a product's sync-state snapshot; the next run rebuilds a clean
        baseline
      description: Licences, transactions, events, runs and dead letters are all kept;
        no event is re-sent.
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/system/amp_events/dead_letters/{id}/resolve":
    patch:
      operationId: system_resolve_dead_letter
      summary: Mark a dead letter resolved (forensics kept)
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/system/amp_events/anomalies/{id}/acknowledge":
    patch:
      operationId: system_acknowledge_anomaly
      summary: Acknowledge a contract anomaly
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/system/queue/failed_executions/{failed_execution_id}/retries":
    post:
      operationId: queue_retry_failure
      summary: Retry one failed job — it goes back on its queue and runs again
      description: The failure row is deleted and the job's attempt counters are reset.
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/system/queue/retries":
    post:
      operationId: queue_retry_failures
      summary: Retry every failed job the filter selects
      description: Selects by ?job_class=, ?exception_class= and ?q= — the same query
        the failed-jobs list uses, so an agent can read the list, then act on exactly
        what it read. Bounded to 1000 failures per request; repeat the call to work
        through more.
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/system/queue/failed_executions/{id}":
    delete:
      operationId: queue_discard_failure
      summary: Discard one failed job — irreversible
      description: Deletes the job itself, not just the failure record. The work is
        not retried and cannot be recovered.
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/system/queue/failed_executions":
    delete:
      operationId: queue_discard_failures
      summary: Discard every failed job the filter selects — irreversible
      description: Deletes the jobs themselves. The work is not retried and cannot
        be recovered. Selects by ?job_class=, ?exception_class= and ?q= — the same
        query the failed-jobs list uses, so an agent can read the list, then act on
        exactly what it read. Bounded to 1000 failures per request; repeat the call
        to work through more.
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/system/flux/processes/{name}":
    patch:
      operationId: system_flux_set_process_active
      summary: Activate or deactivate a Flux process
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                process:
                  type: object
                  properties:
                    is_active:
                      type: boolean
                      description: inactive processes refuse generation; history and
                        prompts are kept
                  required:
                  - is_active
              required:
              - process
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/system/flux/processes/{process_name}/revisions":
    post:
      operationId: system_flux_save_draft
      summary: Save process YAML to the process's draft
      description: 'Refused with 409 whenever draft_version/lock_version disagree
        with the store: a stale lock; a draft discarded and replaced since (a different
        draft_version, even at the same lock); identity omitted while a draft exists
        (omission means ''I read no draft'', and a draft you never read is one you
        must not replace); or identity sent while NO draft exists — the draft you
        read was published or discarded under you, and re-creating it from your copy
        would revert that publish. So: send the identity the editor (GET .../revisions/new)
        gave you, empty only when it gave you none. Re-read the editor after any 409;
        the refusal says which case it was. Rolling back (prefilled_from set) while
        a draft is open REPLACES that draft. It is refused with 409 unless replace_draft
        names the open draft — read the draft first; the editor''s JSON names it as
        replaces_draft. Writes the DRAFT and nothing else. Nothing about the live
        definition moves until the draft is published, so every ordinary run keeps
        executing the current version. There is at most one draft per process: saving
        again edits it in place rather than opening another. Validator errors refuse
        the save (422) — including raw provider:model ids under model:/default_model:,
        which must be aliases from /system/flux/model_prefs; warnings save and are
        reported back. Roll back by opening the editor with ?from=<published version>,
        saving that text to the draft, then publishing.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                revision:
                  type: object
                  properties:
                    process_yaml:
                      type: string
                      description: the full process.yaml; validated before saving
                    draft_version:
                      type: string
                      description: the open draft's version (e.g. draft:3) exactly
                        as the editor gave it; empty when there was no draft
                    lock_version:
                      type: integer
                      description: the open draft's lock_version exactly as the editor
                        gave it; empty when there was no draft. Together with draft_version
                        it states WHICH draft you are writing over — see the 409 note
                        below.
                    prefilled_from:
                      type: string
                      description: the published revision the editor was prefilled
                        from (?from=), when it was; empty when the text started from
                        the draft or from live
                    replace_draft:
                      type: string
                      description: 'required when prefilled_from is set AND a draft
                        is open: the open draft''s version, stating that you mean
                        to replace that draft''s unpublished work with this text'
                  required:
                  - process_yaml
              required:
              - revision
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/system/flux/processes/{process_name}/draft/publication":
    post:
      operationId: system_flux_publish_draft
      summary: Publish the draft — the only thing that moves the live pointer
      description: 'Refused with a concurrent_change refusal when draft_version or
        lock_version no longer match the open draft — someone saved, replaced, published
        or discarded it since you read it. Re-read the process page, then decide again.
        Publishing COPIES: a new published revision takes a version slot allocated
        from the day you publish, the live pointer moves to it, and the draft row
        is archived naming what it became. Nothing is rewritten and nothing is deleted.
        A draft that fails VALIDATION is refused outright and no acknowledgement gets
        past it — a different refusal from publishing an untested or stale draft,
        which is a judgement call you are allowed to make. Runs already in flight
        keep replaying the revision they started with.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                draft_version:
                  type: string
                  description: the draft's version (e.g. draft:3) exactly as you read
                    it from the process page
                lock_version:
                  type: integer
                  description: the draft's lock_version exactly as you read it
                acknowledge:
                  type: string
                  description: 'required when the process page shows a publish caveat
                    (never run, last run failed or still generating, edited since
                    its last run): send the caveat text you read, or "true". A different
                    caveat text is refused, because the warning has changed since
                    you read it.'
              required:
              - draft_version
              - lock_version
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/system/flux/processes/{process_name}/draft":
    delete:
      operationId: system_flux_discard_draft
      summary: Discard the draft
      description: Refused when either no longer matches the open draft, exactly as
        for publishing. Archives rather than deletes, so the draft's lineage stays
        readable and its runs keep showing exactly what they ran.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                draft_version:
                  type: string
                  description: the draft's version exactly as you read it
                lock_version:
                  type: integer
                  description: the draft's lock_version exactly as you read it
              required:
              - draft_version
              - lock_version
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/system/flux/processes/{process_name}/draft_runs":
    post:
      operationId: system_flux_run_draft
      summary: Run the process's draft against any product
      description: 'A real run — real models, real spend — billed to the draft-run
        billing account set at /system/settings, never to the product''s account.
        Refused when none is set. Writes NOTHING to the product or its reports, and
        the product''s account never sees the run. The product''s current report is
        saved on the run as the original to compare against. Each run is its own permanent
        row: read it at /system/flux/processes/{process_name}/draft_runs/{id}, and
        compare it at .../draft_runs/{id}/comparison?with=baseline|control|<run id>.
        Refused as concurrent_change when draft_version or lock_version no longer
        match the open draft — it was edited, replaced, published or discarded since
        you read it — so you never pay for a run of YAML you did not see. Also refused
        when the process has no draft, the draft does not validate, no report type
        maps to the process, or the billing account''s spending cap is reached. The
        refusal names which.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                draft_run:
                  type: object
                  properties:
                    product_id:
                      type: integer
                      description: any Flux product, on any account — find one at
                        /system/flux/products or with ?product_q= on the process page
                    with_control:
                      type: boolean
                      description: also run the LIVE version on the same product at
                        the same time, so the comparison isolates what the draft changed;
                        doubles the cost
                    draft_version:
                      type: string
                      description: the open draft's version exactly as you read it
                        from the process page
                    lock_version:
                      type: integer
                      description: the open draft's lock_version exactly as you read
                        it
                  required:
                  - product_id
                  - draft_version
                  - lock_version
              required:
              - draft_run
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/system/flux/processes/{process_name}/prompts":
    post:
      operationId: system_flux_append_prompt
      summary: Append a new version of a prompt (editing IS appending)
      description: Old versions are kept forever (deliverable provenance resolves
        against them). The YAML pins name@version refs, so nothing runs the new version
        until a revision re-points to it.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                prompt:
                  type: object
                  properties:
                    name:
                      type: string
                      description: the prompt's name; an existing name gets its next
                        version slot, a new name starts at version one
                    prompt_type:
                      type: string
                      enum:
                      - system
                      - user
                      - evaluation
                      - security
                    description:
                      type: string
                    inputs:
                      type: string
                      description: comma-separated variable names this prompt consumes
                    output:
                      type: string
                      description: the step-output key this prompt fills
                    content:
                      type: string
                      description: the prompt body; {{ variables }} are validated
                        against declared inputs when a revision references it
                  required:
                  - name
                  - prompt_type
                  - content
              required:
              - prompt
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/system/flux/model_prefs":
    post:
      operationId: system_flux_create_model_pref
      summary: Add a model alias (a role processes can reference)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                model_pref:
                  type: object
                  properties:
                    name:
                      type: string
                      description: lowercase words-and-dashes, no colon
                    model_id:
                      type: string
                      description: exact provider:model id (the colon is required
                        — aliases are colon-less)
                    notes:
                      type: string
                      description: why this mapping; heals overwrite it with their
                        own story
                  required:
                  - name
                  - model_id
              required:
              - model_pref
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/system/flux/model_prefs/{id}":
    patch:
      operationId: system_flux_update_model_pref
      summary: Repoint a model alias — every referencing process follows on its next
        run
      description: 'The alias name itself is immutable: it is the contract process
        YAML hangs on.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                model_pref:
                  type: object
                  properties:
                    model_id:
                      type: string
                      description: exact provider:model id (the colon is required
                        — aliases are colon-less)
                    notes:
                      type: string
                      description: why this mapping; heals overwrite it with their
                        own story
                  required:
                  - model_id
              required:
              - model_pref
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
    delete:
      operationId: system_flux_delete_model_pref
      summary: Delete a model alias
      description: Refused while any process's current definition still references
        the alias — the alert names them.
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/system/flux/products/{id}":
    delete:
      operationId: system_flux_delete_product
      summary: Delete a lead product — the operator call the account surface lacks
      description: Its deliverables, share links and operator draft runs are deleted
        with it; usage-ledger rows are KEPT with the product reference cleared, because
        spend accounting outlives what was spent on.
      requestBody:
        description: No body — an empty request.
        required: false
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
  "/system/flux/products/{product_id}/deliverables":
    post:
      operationId: system_flux_generate_deliverable
      summary: Generate (or regenerate) a deliverable as the operator — skips the
        24h cooldown
      description: 'The operator exemption covers ONLY the cooldown: entitlement,
        suspension and the account''s spending cap still refuse the run. Costs real
        AI spend against the product''s account.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                deliverable:
                  type: object
                  properties:
                    deliverable_type:
                      type: string
                      enum:
                      - product_analysis
                      - business_case
                      - tracking_plan
                      - customer_discovery
                      - engagement_playbook
                      - engagement_playbook_deep
                      - engagement_playbook_from_config
                      - whats_next
                  required:
                  - deliverable_type
              required:
              - deliverable
      responses:
        '200':
          description: OK
        '403':
          description: Read-only token, or not permitted
