# Generated from agent-actions.openapi.json. Do not edit this file directly.
openapi: 3.1.1
jsonSchemaDialect: https://spec.openapis.org/oas/3.1/dialect/base
info:
  title: F5ai Agent Actions API
  version: 1.0.0
  termsOfService: https://f5tw.vip/terms.html
  x-privacy-policy: https://f5tw.vip/privacy.html
  description: Use this provider-neutral API with a scoped F5ai client key. A queued action executes through hosted F5ai services. An action_required response is executed by the customer's existing agent inside the customer environment, then completed with only the documented safe result. Never send Discord bot tokens, wallet recovery material, signing keys, witnesses, or transaction CBOR.
externalDocs:
  description: F5ai agent quick-start and client guide
  url: https://f5tw.vip/guide.html#agents
servers:
  - url: https://api.f5tw.vip
    description: F5ai production
security:
  - ClientApiKey: []
tags:
  - name: Actions
    description: Create and inspect tenant-scoped customer actions.
paths:
  /v1/actions:
    post:
      tags:
        - Actions
      operationId: create_action
      summary: Create a customer action
      description: Creates one bounded asynchronous action. Reuse the same outer idempotencyKey only when retrying the exact same action and command. A new action returns 202; an exact replay returns the original action with 200. Read the returned action with get_action until its state is terminal. Consequential calls marked by their command schema require confirm=true.
      x-openai-isConsequential: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateActionRequest"
            examples:
              createAnnouncement:
                summary: Post an announcement
                value:
                  action: announcement.create
                  idempotencyKey: announce-release-20260716
                  command:
                    channelId: "900437959394852944"
                    content: The release is live.
              listChannels:
                summary: List active managed channels
                value:
                  action: channel.list
                  idempotencyKey: list-channels-20260716T220000Z
                  command:
                    status: active
                    limit: 25
              assignRole:
                summary: Assign an existing Discord role
                value:
                  action: role.assign
                  idempotencyKey: assign-member-role-223456789012345678
                  command:
                    guildId: "878250798528745542"
                    discordUserId: "223456789012345678"
                    roleId: "323456789012345678"
                    idempotencyKey: discord-assign-member-role-223456789012345678
      responses:
        "200":
          description: Exact idempotent replay of an existing action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Action"
        "202":
          description: New action accepted. A queued action is ready for hosted execution; action_required means the calling agent must use a capability in the customer environment and return only the documented safe result.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Action"
        "400":
          $ref: "#/components/responses/ValidationFailed"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/IdempotencyConflict"
        "503":
          $ref: "#/components/responses/WalletIdentityUnavailable"
    get:
      tags:
        - Actions
      operationId: list_actions
      summary: List visible actions
      description: Returns tenant-scoped actions whose result-read scope is present on the client API key. Customer executors may request only state=action_required with an optional limit from 1 through 25; the default is 25. Continue a bounded page with only the returned f5ai.pending-actions.v1 cursor. This API-owned cursor is not interchangeable with customer-local resource-list cursors. Use get_action when an action ID is already known.
      parameters:
        - name: state
          in: query
          required: false
          description: Return only pending customer-executed actions.
          schema:
            type: string
            enum:
              - action_required
        - name: limit
          in: query
          required: false
          description: Maximum pending actions to return after tenant, scope, and state filtering.
          schema:
            type: integer
            minimum: 1
            maximum: 25
            default: 25
        - name: cursor
          in: query
          required: false
          description: Opaque tenant-bound f5ai.pending-actions.v1 keyset cursor returned by the preceding pending-action page.
          schema:
            $ref: "#/components/schemas/PendingActionCursor"
      responses:
        "200":
          description: Visible actions.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ActionList"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /v1/actions/{actionId}:
    get:
      tags:
        - Actions
      operationId: get_action
      summary: Get one action
      description: Reads one tenant-scoped action. If state is action_required, the customer's existing agent executes the machine-readable requirement and calls complete_customer_wallet_action. Poll only while state is held or queued. succeeded, failed, cancelled, expired, and revoked are terminal. A missing action or missing result-read scope both return 404.
      parameters:
        - name: actionId
          in: path
          required: true
          description: Action ID returned by create_action.
          schema:
            $ref: "#/components/schemas/Id"
      responses:
        "200":
          description: Action status and safe projected result.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Action"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
  /v1/actions/{actionId}/completion:
    post:
      tags:
        - Actions
      operationId: complete_customer_wallet_action
      summary: Complete customer-environment work
      description: Completes an action_required action using the same scoped client API key that created it. The customer's existing agent executes the requested capability. Return only the exact allowlisted result; never return a Discord token, raw permission overwrite, message content, wallet address registry, winner or payout ledger, recovery material, private key, witness, signed transaction, or transaction CBOR.
      x-openai-isConsequential: true
      parameters:
        - name: actionId
          in: path
          required: true
          description: Action ID whose state is action_required.
          schema:
            $ref: "#/components/schemas/Id"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CustomerActionCompletionRequest"
      responses:
        "200":
          description: Completed action or exact idempotent completion replay.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Action"
        "400":
          $ref: "#/components/responses/ValidationFailed"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/IdempotencyConflict"
components:
  securitySchemes:
    ClientApiKey:
      type: http
      scheme: bearer
      bearerFormat: F5ai client API key
      description: Customer client API key issued by F5ai. Use only the client credential shown during provisioning.
  responses:
    ValidationFailed:
      description: The action name or exact command shape is invalid, including a caller-supplied account, guild, or user field that conflicts with server-derived context.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          examples:
            validationFailed:
              value:
                error:
                  code: validation_failed
            trustedContextMismatch:
              value:
                error:
                  code: trusted_context_mismatch
    Unauthorized:
      description: The client API key is missing, invalid, or lacks the action scope.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            error:
              code: unauthorized
    Forbidden:
      description: The tenant is not entitled, commercial execution is blocked, or the required server-derived account and guild context is unavailable.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          examples:
            notEntitled:
              value:
                error:
                  code: not_entitled
            commercialBlocked:
              value:
                error:
                  code: commercial_access_blocked
            connectorForbidden:
              value:
                error:
                  code: connector_forbidden
            walletContextRequired:
              value:
                error:
                  code: wallet_context_required
    IdempotencyConflict:
      description: The outer idempotencyKey was already used for a different canonical request, or the selected wallet identity source is missing or changed concurrently. Generate a new key only for a genuinely new action.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          examples:
            idempotencyConflict:
              value:
                error:
                  code: idempotency_conflict
            walletIdentitySourceConflict:
              value:
                error:
                  code: wallet_identity_source_conflict
            walletIdentityPrerequisiteRequired:
              value:
                error:
                  code: wallet_identity_prerequisite_required
    WalletIdentityUnavailable:
      description: The hosted execution target or verified wallet identity source is temporarily unavailable. Retry the exact request with the same idempotencyKey.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          examples:
            executionTargetUnavailable:
              value:
                error:
                  code: execution_target_unavailable
            walletIdentityUnavailable:
              value:
                error:
                  code: wallet_identity_unavailable
    NotFound:
      description: The action does not exist for this tenant, or the key lacks its result-read scope.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            error:
              code: not_found
  schemas:
    Id:
      type: string
      pattern: ^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$
    Snowflake:
      type: string
      pattern: ^\d{1,20}$
      description: Discord ID encoded as a string.
    DiscordSnowflake:
      type: string
      pattern: ^\d{17,20}$
      description: A current Discord snowflake encoded as a string.
    IdempotencyKey:
      type: string
      minLength: 1
      maxLength: 255
      description: A caller-generated stable key. The outer key binds the complete request; command-level keys protect Discord-side mutations.
    ActionIdempotencyKey:
      type: string
      pattern: ^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$
      description: The outer request idempotency key. Reuse it only for an exact retry of the same action request.
    PositiveVersion:
      type: integer
      minimum: 1
    ErrorResponse:
      type: object
      additionalProperties: false
      required:
        - error
      properties:
        error:
          type: object
          additionalProperties: false
          required:
            - code
          properties:
            code:
              type: string
              enum:
                - validation_failed
                - trusted_context_mismatch
                - unauthorized
                - not_entitled
                - commercial_access_blocked
                - connector_forbidden
                - wallet_context_required
                - idempotency_conflict
                - wallet_identity_source_conflict
                - wallet_identity_prerequisite_required
                - wallet_identity_unavailable
                - execution_target_unavailable
                - invalid_customer_wallet_completion
                - customer_wallet_completion_conflict
                - invalid_customer_action_completion
                - customer_action_completion_conflict
                - not_found
                - invalid_json
    Action:
      type: object
      additionalProperties: false
      required:
        - id
        - action
        - state
        - createdAt
        - updatedAt
        - safeResult
        - safeExternalIds
      properties:
        id:
          $ref: "#/components/schemas/Id"
        action:
          $ref: "#/components/schemas/AllActionName"
        state:
          type: string
          enum:
            - held
            - queued
            - action_required
            - succeeded
            - failed
            - cancelled
            - expired
            - revoked
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        safeResult:
          oneOf:
            - type: object
              additionalProperties: true
            - type: "null"
          description: A capability-specific, allowlisted result projection. Sensitive provider data is never returned.
        safeExternalIds:
          type: array
          items:
            $ref: "#/components/schemas/Id"
          maxItems: 10
    ActionList:
      type: object
      additionalProperties: false
      required:
        - items
      properties:
        items:
          type: array
          maxItems: 25
          items:
            $ref: "#/components/schemas/Action"
        nextCursor:
          $ref: "#/components/schemas/PendingActionCursor"
          description: Present only when another allowed pending action exists.
    PendingActionCursor:
      type: string
      minLength: 1
      maxLength: 512
      pattern: ^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
      x-f5ai-cursor-type: f5ai.pending-actions.v1
      description: Opaque API-signed pending-action cursor. Contains no tenant identifier, action command, or completion content.
    CustomerActionCompletionRequest:
      oneOf:
        - $ref: "#/components/schemas/CustomerWalletCompletionSucceeded"
        - $ref: "#/components/schemas/CustomerDiscordChannelCompletionSucceeded"
        - $ref: "#/components/schemas/CustomerActionCompletionCommonFailure"
        - $ref: "#/components/schemas/CustomerActionCompletionSpecificFailure"
    CustomerActionCompletionCommonFailure:
      type: object
      additionalProperties: false
      required:
        - intentHash
        - outcome
        - result
      properties:
        intentHash:
          $ref: "#/components/schemas/CustomerActionIntentHash"
        outcome:
          type: string
          enum:
            - unsupported
            - failed
        result:
          type: object
          additionalProperties: false
          required:
            - code
          properties:
            code:
              type: string
              enum:
                - capability_timeout
                - customer_execution_failed
    CustomerActionCompletionSpecificFailure:
      type: object
      additionalProperties: false
      required:
        - intentHash
        - outcome
        - result
      properties:
        intentHash:
          $ref: "#/components/schemas/CustomerActionIntentHash"
        outcome:
          type: string
          enum:
            - unsupported
            - failed
        result:
          type: object
          additionalProperties: false
          required:
            - code
          properties:
            code:
              type: string
              enum:
                - dependency_check_failed
                - discord_api_unavailable
                - discord_permission_denied
                - discord_rate_limited
                - idempotency_conflict
                - not_found
                - private_channel_required
                - protected_resource
                - reconciliation_conflict
                - registry_conflict
                - registry_version_conflict
                - resource_version_conflict
                - stale_page
                - state_persistence_uncertain
                - user_denied
                - validation_failed
                - verification_failed
                - wallet_tool_unavailable
    CustomerDiscordChannelCompletionSucceeded:
      type: object
      additionalProperties: false
      required:
        - intentHash
        - outcome
        - result
      properties:
        intentHash:
          $ref: "#/components/schemas/CustomerActionIntentHash"
        outcome:
          const: succeeded
        result:
          oneOf:
            - $ref: "#/components/schemas/CustomerDiscordAnnouncementResourceResult"
            - $ref: "#/components/schemas/CustomerDiscordAnnouncementListResult"
            - $ref: "#/components/schemas/CustomerDiscordChannelResourceResult"
            - $ref: "#/components/schemas/CustomerDiscordChannelListResult"
            - $ref: "#/components/schemas/CustomerDiscordChannelResult"
            - $ref: "#/components/schemas/CustomerDiscordReactionRoleResourceResult"
            - $ref: "#/components/schemas/CustomerDiscordReactionRoleListResult"
            - $ref: "#/components/schemas/CustomerDiscordRoleAssignedResult"
            - $ref: "#/components/schemas/CustomerDiscordRoleRemovedResult"
            - $ref: "#/components/schemas/CustomerDiscordRoleCurrentResult"
            - $ref: "#/components/schemas/CustomerWalletBindingApplyResult"
            - $ref: "#/components/schemas/CustomerWalletPayoutApplyResult"
            - $ref: "#/components/schemas/CustomerWalletRoleConfigureResult"
            - $ref: "#/components/schemas/CustomerWalletRoleSyncResult"
      x-f5ai-action-result-schemas:
        announcement.create: "#/components/schemas/CustomerDiscordAnnouncementResourceResult"
        announcement.get: "#/components/schemas/CustomerDiscordAnnouncementResourceResult"
        announcement.list: "#/components/schemas/CustomerDiscordAnnouncementListResult"
        announcement.update: "#/components/schemas/CustomerDiscordAnnouncementResourceResult"
        announcement.delete: "#/components/schemas/CustomerDiscordAnnouncementResourceResult"
        channel.create: "#/components/schemas/CustomerDiscordChannelResourceResult"
        channel.get: "#/components/schemas/CustomerDiscordChannelResourceResult"
        channel.list: "#/components/schemas/CustomerDiscordChannelListResult"
        channel.update: "#/components/schemas/CustomerDiscordChannelResourceResult"
        channel.delete: "#/components/schemas/CustomerDiscordChannelResourceResult"
        reaction_role.create: "#/components/schemas/CustomerDiscordReactionRoleResourceResult"
        reaction_role.get: "#/components/schemas/CustomerDiscordReactionRoleResourceResult"
        reaction_role.list: "#/components/schemas/CustomerDiscordReactionRoleListResult"
        reaction_role.delete: "#/components/schemas/CustomerDiscordReactionRoleResourceResult"
        role.assign: "#/components/schemas/CustomerDiscordRoleAssignedResult"
        role.remove: "#/components/schemas/CustomerDiscordRoleRemovedResult"
        role.get: "#/components/schemas/CustomerDiscordRoleCurrentResult"
        wallet.binding.apply: "#/components/schemas/CustomerWalletBindingApplyResult"
        wallet.payout.apply: "#/components/schemas/CustomerWalletPayoutApplyResult"
        wallet_role.configure: "#/components/schemas/CustomerWalletRoleConfigureResult"
        wallet_role.sync: "#/components/schemas/CustomerWalletRoleSyncResult"
        wallet_role.status: "#/components/schemas/CustomerWalletRoleSyncResult"
      description: Exact metadata-only success completion for customer-executed Discord actions. Structurally identical lifecycle results share a non-overlapping schema while retaining an explicit action-to-schema map.
    CustomerDiscordChannelResult:
      type: object
      additionalProperties: false
      required:
        - status
        - key
        - channelName
        - type
        - discordChannelId
        - permissionMode
        - botAccess
        - everyoneAccess
        - authorizedAdminRoleIds
        - createdAt
      properties:
        status:
          const: ready
        key:
          type: string
          enum:
            - wallet_address_registry
            - giveaway_payout_ledger
        channelName:
          type: string
          minLength: 1
          maxLength: 100
        type:
          const: text
        discordChannelId:
          $ref: "#/components/schemas/Snowflake"
        permissionMode:
          const: private_admin_bot
        botAccess:
          const: true
        everyoneAccess:
          const: false
        authorizedAdminRoleIds:
          type: array
          minItems: 1
          maxItems: 20
          uniqueItems: true
          items:
            $ref: "#/components/schemas/Snowflake"
        createdAt:
          type: string
          format: date-time
      description: Jerry reports only the created channel ID and bounded permission readback. F5ai does not receive the Discord token, raw overwrites, messages, wallet addresses, winners, or payout ledger.
    CustomerDiscordAnnouncementResult:
      type: object
      additionalProperties: false
      required:
        - status
        - announcementId
        - channelId
        - messageId
        - version
      properties:
        status:
          const: active
        announcementId:
          $ref: "#/components/schemas/Id"
        channelId:
          $ref: "#/components/schemas/Snowflake"
        messageId:
          $ref: "#/components/schemas/Snowflake"
        version:
          const: 1
    CustomerDiscordTemporaryChannelResult:
      type: object
      additionalProperties: false
      required:
        - status
        - channelResourceId
        - discordChannelId
        - name
        - type
        - version
      properties:
        status:
          const: active
        channelResourceId:
          $ref: "#/components/schemas/Id"
        discordChannelId:
          $ref: "#/components/schemas/Snowflake"
        name:
          $ref: "#/components/schemas/ChannelName"
        type:
          type: string
          enum:
            - text
            - announcement
        version:
          const: 1
    CustomerDiscordReactionRoleResult:
      type: object
      additionalProperties: false
      required:
        - status
        - reactionRoleId
        - channelId
        - messageId
        - version
        - mappingCount
      properties:
        status:
          const: active
        reactionRoleId:
          $ref: "#/components/schemas/Id"
        channelId:
          $ref: "#/components/schemas/Snowflake"
        messageId:
          $ref: "#/components/schemas/Snowflake"
        version:
          const: 1
        mappingCount:
          type: integer
          minimum: 1
          maximum: 20
    CustomerDiscordAnnouncementResourceResult:
      type: object
      additionalProperties: false
      required:
        - status
        - announcementId
        - channelId
        - messageId
        - version
      properties:
        status:
          type: string
          enum:
            - active
            - deleted
        announcementId:
          $ref: "#/components/schemas/Id"
        channelId:
          $ref: "#/components/schemas/Snowflake"
        messageId:
          $ref: "#/components/schemas/Snowflake"
        version:
          $ref: "#/components/schemas/PositiveVersion"
        deleted:
          const: true
      allOf:
        - if:
            properties:
              status:
                const: deleted
            required:
              - status
          then:
            required:
              - deleted
          else:
            not:
              required:
                - deleted
    CustomerDiscordAnnouncementListItem:
      type: object
      additionalProperties: false
      required:
        - id
        - channelId
        - messageId
        - status
        - version
      properties:
        id:
          $ref: "#/components/schemas/Id"
        channelId:
          $ref: "#/components/schemas/Snowflake"
        messageId:
          $ref: "#/components/schemas/Snowflake"
        status:
          type: string
          enum:
            - active
            - deleted
        version:
          $ref: "#/components/schemas/PositiveVersion"
    CustomerDiscordAnnouncementListResult:
      type: object
      additionalProperties: false
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          maxItems: 100
          items:
            $ref: "#/components/schemas/CustomerDiscordAnnouncementListItem"
        nextCursor:
          oneOf:
            - $ref: "#/components/schemas/AnnouncementResourceCursor"
            - type: "null"
    CustomerDiscordChannelResourceResult:
      type: object
      additionalProperties: false
      required:
        - status
        - channelResourceId
        - discordChannelId
        - name
        - type
        - version
      properties:
        status:
          type: string
          enum:
            - active
            - deleted
        channelResourceId:
          $ref: "#/components/schemas/Id"
        discordChannelId:
          $ref: "#/components/schemas/Snowflake"
        name:
          $ref: "#/components/schemas/ChannelName"
        type:
          type: string
          enum:
            - text
            - announcement
        version:
          $ref: "#/components/schemas/PositiveVersion"
        deleted:
          const: true
      allOf:
        - if:
            properties:
              status:
                const: deleted
            required:
              - status
          then:
            required:
              - deleted
          else:
            not:
              required:
                - deleted
    CustomerDiscordChannelListItem:
      type: object
      additionalProperties: false
      required:
        - id
        - discordChannelId
        - name
        - type
        - status
        - version
      properties:
        id:
          $ref: "#/components/schemas/Id"
        discordChannelId:
          $ref: "#/components/schemas/Snowflake"
        name:
          $ref: "#/components/schemas/ChannelName"
        type:
          type: string
          enum:
            - text
            - announcement
        status:
          type: string
          enum:
            - active
            - deleted
        version:
          $ref: "#/components/schemas/PositiveVersion"
    CustomerDiscordChannelListResult:
      type: object
      additionalProperties: false
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          maxItems: 100
          items:
            $ref: "#/components/schemas/CustomerDiscordChannelListItem"
        nextCursor:
          oneOf:
            - $ref: "#/components/schemas/ChannelResourceCursor"
            - type: "null"
    CustomerDiscordReactionRoleResourceResult:
      type: object
      additionalProperties: false
      required:
        - status
        - reactionRoleId
        - channelId
        - messageId
        - version
        - mappingCount
      properties:
        status:
          type: string
          enum:
            - active
            - deleted
        reactionRoleId:
          $ref: "#/components/schemas/Id"
        channelId:
          $ref: "#/components/schemas/Snowflake"
        messageId:
          $ref: "#/components/schemas/Snowflake"
        version:
          $ref: "#/components/schemas/PositiveVersion"
        mappingCount:
          type: integer
          minimum: 0
          maximum: 20
        deleted:
          const: true
      allOf:
        - if:
            properties:
              status:
                const: deleted
            required:
              - status
          then:
            required:
              - deleted
          else:
            not:
              required:
                - deleted
    CustomerDiscordReactionRoleListItem:
      type: object
      additionalProperties: false
      required:
        - id
        - channelId
        - messageId
        - status
        - version
        - mappingCount
      properties:
        id:
          $ref: "#/components/schemas/Id"
        channelId:
          $ref: "#/components/schemas/Snowflake"
        messageId:
          $ref: "#/components/schemas/Snowflake"
        status:
          type: string
          enum:
            - active
            - deleted
        version:
          $ref: "#/components/schemas/PositiveVersion"
        mappingCount:
          type: integer
          minimum: 0
          maximum: 20
    CustomerDiscordReactionRoleListResult:
      type: object
      additionalProperties: false
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          maxItems: 100
          items:
            $ref: "#/components/schemas/CustomerDiscordReactionRoleListItem"
        nextCursor:
          oneOf:
            - $ref: "#/components/schemas/ReactionRoleResourceCursor"
            - type: "null"
    CustomerDiscordRoleAssignedResult:
      type: object
      additionalProperties: false
      required:
        - status
        - guildId
        - discordUserId
        - roleId
        - managedRoleIds
        - applied
      properties:
        status:
          const: assigned
        guildId:
          $ref: "#/components/schemas/Snowflake"
        discordUserId:
          $ref: "#/components/schemas/Snowflake"
        roleId:
          $ref: "#/components/schemas/Snowflake"
        managedRoleIds:
          type: array
          maxItems: 50
          uniqueItems: true
          items:
            $ref: "#/components/schemas/Snowflake"
        applied:
          const: add
    CustomerDiscordRoleRemovedResult:
      type: object
      additionalProperties: false
      required:
        - status
        - guildId
        - discordUserId
        - roleId
        - managedRoleIds
        - applied
      properties:
        status:
          const: removed
        guildId:
          $ref: "#/components/schemas/Snowflake"
        discordUserId:
          $ref: "#/components/schemas/Snowflake"
        roleId:
          $ref: "#/components/schemas/Snowflake"
        managedRoleIds:
          type: array
          maxItems: 50
          uniqueItems: true
          items:
            $ref: "#/components/schemas/Snowflake"
        applied:
          const: remove
    CustomerDiscordRoleCurrentResult:
      type: object
      additionalProperties: false
      required:
        - status
        - guildId
        - discordUserId
        - managedRoleIds
      properties:
        status:
          const: current
        guildId:
          $ref: "#/components/schemas/Snowflake"
        discordUserId:
          $ref: "#/components/schemas/Snowflake"
        managedRoleIds:
          type: array
          maxItems: 50
          uniqueItems: true
          items:
            $ref: "#/components/schemas/Snowflake"
    CustomerWalletPayoutApplyResult:
      type: object
      additionalProperties: false
      required:
        - status
        - registryMessageId
        - recordVersion
        - recordHash
        - addressCommitment
      properties:
        status:
          type: string
          enum:
            - registered
            - replaced
        registryMessageId:
          $ref: "#/components/schemas/DiscordSnowflake"
        recordVersion:
          $ref: "#/components/schemas/PositiveVersion"
        recordHash:
          $ref: "#/components/schemas/Sha256"
        addressCommitment:
          $ref: "#/components/schemas/Sha256"
      description: Address-free readback for one customer-owned Discord wallet-registry record. The payout address and registry payload never enter F5ai.
    CustomerWalletBindingApplyResult:
      type: object
      additionalProperties: false
      required:
        - status
        - registryMessageId
        - recordVersion
        - recordHash
        - identityCommitment
      properties:
        status:
          type: string
          enum:
            - registered
            - replaced
        registryMessageId:
          $ref: "#/components/schemas/DiscordSnowflake"
        recordVersion:
          $ref: "#/components/schemas/PositiveVersion"
        recordHash:
          $ref: "#/components/schemas/Sha256"
        identityCommitment:
          $ref: "#/components/schemas/Sha256"
      description: Address-free readback for one customer-owned Discord holder-binding registry record. Wallet addresses, proofs, and registry payloads never enter F5ai.
    CustomerWalletRoleConfigureResult:
      type: object
      additionalProperties: false
      required:
        - status
        - configVersion
        - managedRoleIds
        - syncedAt
      properties:
        status:
          const: configured
        configVersion:
          $ref: "#/components/schemas/PositiveVersion"
        managedRoleIds:
          type: array
          maxItems: 50
          uniqueItems: true
          items:
            $ref: "#/components/schemas/Snowflake"
        syncedAt:
          $ref: "#/components/schemas/CanonicalTimestamp"
      description: Exact customer-local role-policy configuration readback. Member state, wallet data, and credentials never enter F5ai.
    CustomerWalletRoleSyncResult:
      type: object
      additionalProperties: false
      required:
        - status
        - configVersion
        - discordUserId
        - holdingsCount
        - assignedRoleId
        - assignedRoleName
        - managedRoleIds
        - trackedRoleIds
        - partnerRoleIds
        - failClosed
        - syncedAt
      allOf:
        - if:
            properties:
              status:
                const: synced
            required:
              - status
          then:
            properties:
              failClosed:
                const: false
          else:
            properties:
              failClosed:
                const: true
        - oneOf:
            - properties:
                assignedRoleId:
                  $ref: "#/components/schemas/DiscordSnowflake"
                assignedRoleName:
                  type: string
                  minLength: 1
                  maxLength: 100
            - properties:
                assignedRoleId:
                  type: "null"
                assignedRoleName:
                  type: "null"
      properties:
        status:
          type: string
          enum:
            - synced
            - preserved_incomplete
        configVersion:
          $ref: "#/components/schemas/PositiveVersion"
        discordUserId:
          $ref: "#/components/schemas/DiscordSnowflake"
        holdingsCount:
          type: integer
          minimum: 0
        assignedRoleId:
          oneOf:
            - $ref: "#/components/schemas/DiscordSnowflake"
            - type: "null"
        assignedRoleName:
          oneOf:
            - type: string
              minLength: 1
              maxLength: 100
            - type: "null"
        managedRoleIds:
          type: array
          maxItems: 50
          uniqueItems: true
          items:
            $ref: "#/components/schemas/DiscordSnowflake"
        trackedRoleIds:
          type: array
          maxItems: 50
          uniqueItems: true
          items:
            $ref: "#/components/schemas/DiscordSnowflake"
        partnerRoleIds:
          type: array
          maxItems: 50
          uniqueItems: true
          items:
            $ref: "#/components/schemas/DiscordSnowflake"
        failClosed:
          type: boolean
        syncedAt:
          $ref: "#/components/schemas/CanonicalTimestamp"
      description: Exact customer-local member role reconciliation readback. Role arrays are sorted; tracked roles are a subset of managed roles, partner roles are a subset of tracked roles, and failClosed determines the status.
    CustomerDiscordChannelCompletionFailed:
      type: object
      additionalProperties: false
      required:
        - intentHash
        - outcome
        - result
      properties:
        intentHash:
          $ref: "#/components/schemas/CustomerActionIntentHash"
        outcome:
          type: string
          enum:
            - unsupported
            - failed
        result:
          type: object
          additionalProperties: false
          required:
            - code
          properties:
            code:
              type: string
              enum:
                - capability_timeout
                - customer_execution_failed
                - discord_api_unavailable
                - discord_permission_denied
                - discord_rate_limited
                - reconciliation_conflict
      description: Exact metadata-only unsupported or failed completion shared by the six Phase 1 Discord actions.
    CustomerActionIntentHash:
      type: string
      pattern: ^[a-f0-9]{64}$
      description: Exact intentHash from the action_required requirement.
    CustomerWalletCompletionRequest:
      oneOf:
        - $ref: "#/components/schemas/CustomerWalletCompletionSucceeded"
        - $ref: "#/components/schemas/CustomerWalletCompletionFailed"
    CustomerWalletCompletionSucceeded:
      type: object
      additionalProperties: false
      required:
        - intentHash
        - outcome
        - result
      properties:
        intentHash:
          $ref: "#/components/schemas/CustomerWalletIntentHash"
        outcome:
          const: succeeded
        result:
          oneOf:
            - $ref: "#/components/schemas/CustomerWalletSetupResult"
            - $ref: "#/components/schemas/CustomerWalletBackupAttestationResult"
            - $ref: "#/components/schemas/CustomerWalletStatusResult"
            - $ref: "#/components/schemas/CustomerWalletHoldingsResult"
            - $ref: "#/components/schemas/CustomerWalletPrizeAttachResult"
    CustomerWalletCompletionFailed:
      type: object
      additionalProperties: false
      required:
        - intentHash
        - outcome
        - result
      properties:
        intentHash:
          $ref: "#/components/schemas/CustomerWalletIntentHash"
        outcome:
          type: string
          enum:
            - unsupported
            - failed
        result:
          type: object
          additionalProperties: false
          required:
            - code
          properties:
            code:
              type: string
              enum:
                - capability_timeout
                - customer_execution_failed
                - user_denied
                - wallet_tool_unavailable
    CustomerWalletIntentHash:
      type: string
      pattern: ^[a-f0-9]{64}$
      description: Exact intentHash from the action_required requirement.
    CustomerWalletSetupResult:
      type: object
      additionalProperties: false
      required:
        - status
        - walletId
        - network
        - paymentAddress
        - createdAt
      properties:
        status:
          const: ready
        walletId:
          type: string
          pattern: ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$
        network:
          const: mainnet
        paymentAddress:
          type: string
          pattern: ^addr1[0-9a-z]{20,120}$
        stakeAddress:
          type: string
          pattern: ^stake1[0-9a-z]{20,120}$
        createdAt:
          type: string
          format: date-time
      description: Customer-local wallet creation or idempotent reconciliation. Public addresses appear only in the immediate completion response and exact completion replay; durable action reads retain setup metadata only.
    CustomerWalletPrizeAttachResult:
      type: object
      additionalProperties: false
      required:
        - status
        - walletId
        - network
        - attachedAt
      properties:
        status:
          const: attached
        walletId:
          $ref: "#/components/schemas/WalletId"
        network:
          const: mainnet
        attachedAt:
          $ref: "#/components/schemas/CanonicalTimestamp"
      description: Exact customer-environment prize wallet attachment readback. F5ai durably adds only source=customer_environment.
    CustomerWalletBackupAttestationResult:
      type: object
      additionalProperties: false
      required:
        - status
        - walletId
        - method
        - attestedAt
      properties:
        status:
          const: attested
        walletId:
          type: string
          pattern: ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$
        method:
          const: local_recovery_copies
        attestedAt:
          type: string
          format: date-time
      description: Customer-side attestation only. F5ai does not inspect or independently verify backup contents.
    CustomerWalletStatusResult:
      oneOf:
        - type: object
          additionalProperties: false
          required:
            - status
            - walletId
            - checkedAt
          properties:
            status:
              const: not_configured
            walletId:
              type: string
              pattern: ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$
            checkedAt:
              type: string
              format: date-time
        - type: object
          additionalProperties: false
          required:
            - status
            - walletId
            - checkedAt
            - network
            - paymentAddress
          properties:
            status:
              const: ready
            walletId:
              type: string
              pattern: ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$
            checkedAt:
              type: string
              format: date-time
            network:
              const: mainnet
            paymentAddress:
              type: string
              pattern: ^addr1[0-9a-z]{20,120}$
            stakeAddress:
              type: string
              pattern: ^stake1[0-9a-z]{20,120}$
        - type: object
          additionalProperties: false
          required:
            - status
            - walletId
            - checkedAt
            - network
            - paymentAddress
          properties:
            status:
              const: ready
            walletId:
              type: string
              pattern: ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$
            checkedAt:
              type: string
              format: date-time
            network:
              const: preprod
            paymentAddress:
              type: string
              pattern: ^addr_test1[0-9a-z]{20,120}$
            stakeAddress:
              type: string
              pattern: ^stake_test1[0-9a-z]{20,120}$
    CustomerWalletHoldingsResult:
      type: object
      additionalProperties: false
      required:
        - status
        - walletId
        - network
        - checkedAt
        - assets
        - holdingsComplete
      properties:
        status:
          const: ready
        walletId:
          type: string
          pattern: ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$
        network:
          type: string
          const: mainnet
        checkedAt:
          type: string
          format: date-time
        assets:
          type: array
          maxItems: 500
          items:
            type: object
            additionalProperties: false
            required:
              - unit
              - quantity
            properties:
              unit:
                type: string
                pattern: ^[a-f0-9]{56}([a-f0-9]{2}){0,32}$
              quantity:
                type: string
                pattern: ^[1-9][0-9]{0,39}$
        holdingsComplete:
          type: boolean
      description: Customer-reported, explicitly policy-filtered public holdings. Asset rows appear only in the immediate completion response and exact completion replay; durable action reads retain metadata only.
    PublicActionName:
      type: string
      enum:
        - announcement.create
        - announcement.get
        - announcement.list
        - announcement.update
        - announcement.delete
        - channel.create
        - channel.get
        - channel.list
        - channel.update
        - channel.delete
        - reaction_role.create
        - reaction_role.get
        - reaction_role.list
        - reaction_role.delete
        - role.assign
        - role.remove
        - role.get
        - wallet.binding.apply
        - wallet.payout.apply
        - wallet.prize.attach
        - wallet.setup.create
        - wallet.backup.verify
        - wallet.status
        - wallet.holdings
        - wallet_role.configure
        - wallet_role.sync
        - wallet_role.status
        - giveaway.create
        - giveaway.publish
        - giveaway.open
        - giveaway.end
        - giveaway.cancel
        - giveaway.draw
        - giveaway.close
        - giveaway.list
        - giveaway.winners.list
        - giveaway.winner.get
        - reserve.list
        - reserve.get
        - reserve.address.apply
        - reserve.remind
        - reserve.release
        - prize.list
        - prize.get
        - payout.prepare
        - payout.submit
        - payout.get_by_tx
        - user.prizes.list
        - action.get
    AllActionName:
      type: string
      enum:
        - announcement.create
        - announcement.get
        - announcement.list
        - announcement.update
        - announcement.delete
        - channel.create
        - channel.get
        - channel.list
        - channel.update
        - channel.delete
        - reaction_role.create
        - reaction_role.get
        - reaction_role.list
        - reaction_role.delete
        - role.assign
        - role.remove
        - role.get
        - wallet.binding.apply
        - wallet.payout.apply
        - wallet.prize.attach
        - wallet_role.configure
        - wallet_role.sync
        - wallet_role.status
        - wallet.setup.create
        - wallet.backup.verify
        - wallet.status
        - wallet.holdings
        - giveaway.create
        - giveaway.publish
        - giveaway.open
        - giveaway.end
        - giveaway.cancel
        - giveaway.draw
        - giveaway.close
        - giveaway.list
        - giveaway.winners.list
        - giveaway.winner.get
        - reserve.list
        - reserve.get
        - reserve.address.apply
        - reserve.remind
        - reserve.release
        - prize.list
        - prize.get
        - payout.prepare
        - payout.submit
        - payout.get_by_tx
        - user.prizes.list
        - action.get
    CreateActionRequest:
      oneOf:
        - $ref: "#/components/schemas/AnnouncementCreateRequest"
        - $ref: "#/components/schemas/AnnouncementGetRequest"
        - $ref: "#/components/schemas/AnnouncementListRequest"
        - $ref: "#/components/schemas/AnnouncementUpdateRequest"
        - $ref: "#/components/schemas/AnnouncementDeleteRequest"
        - $ref: "#/components/schemas/ChannelCreateRequest"
        - $ref: "#/components/schemas/ChannelGetRequest"
        - $ref: "#/components/schemas/ChannelListRequest"
        - $ref: "#/components/schemas/ChannelUpdateRequest"
        - $ref: "#/components/schemas/ChannelDeleteRequest"
        - $ref: "#/components/schemas/ReactionRoleCreateRequest"
        - $ref: "#/components/schemas/ReactionRoleGetRequest"
        - $ref: "#/components/schemas/ReactionRoleListRequest"
        - $ref: "#/components/schemas/ReactionRoleDeleteRequest"
        - $ref: "#/components/schemas/RoleAssignRequest"
        - $ref: "#/components/schemas/RoleRemoveRequest"
        - $ref: "#/components/schemas/RoleGetRequest"
        - $ref: "#/components/schemas/WalletBindingApplyRequest"
        - $ref: "#/components/schemas/WalletPayoutApplyRequest"
        - $ref: "#/components/schemas/WalletPrizeAttachRequest"
        - $ref: "#/components/schemas/WalletSetupCreateRequest"
        - $ref: "#/components/schemas/WalletBackupVerifyRequest"
        - $ref: "#/components/schemas/WalletStatusRequest"
        - $ref: "#/components/schemas/WalletHoldingsRequest"
        - $ref: "#/components/schemas/WalletRoleConfigureRequest"
        - $ref: "#/components/schemas/WalletRoleSyncRequest"
        - $ref: "#/components/schemas/WalletRoleStatusRequest"
        - $ref: "#/components/schemas/GiveawayCreateRequest"
        - $ref: "#/components/schemas/GiveawayPublishRequest"
        - $ref: "#/components/schemas/GiveawayOpenRequest"
        - $ref: "#/components/schemas/GiveawayEndRequest"
        - $ref: "#/components/schemas/GiveawayCancelRequest"
        - $ref: "#/components/schemas/GiveawayDrawRequest"
        - $ref: "#/components/schemas/GiveawayCloseRequest"
        - $ref: "#/components/schemas/GiveawayListRequest"
        - $ref: "#/components/schemas/GiveawayWinnersListRequest"
        - $ref: "#/components/schemas/GiveawayWinnerGetRequest"
        - $ref: "#/components/schemas/ReserveListRequest"
        - $ref: "#/components/schemas/ReserveGetRequest"
        - $ref: "#/components/schemas/ReserveAddressApplyRequest"
        - $ref: "#/components/schemas/ReserveRemindRequest"
        - $ref: "#/components/schemas/ReserveReleaseRequest"
        - $ref: "#/components/schemas/PrizeListRequest"
        - $ref: "#/components/schemas/PrizeGetRequest"
        - $ref: "#/components/schemas/PayoutPrepareRequest"
        - $ref: "#/components/schemas/PayoutSubmitRequest"
        - $ref: "#/components/schemas/PayoutGetByTxRequest"
        - $ref: "#/components/schemas/UserPrizesListRequest"
        - $ref: "#/components/schemas/ActionGetRequest"
      discriminator:
        propertyName: action
        mapping:
          announcement.create: "#/components/schemas/AnnouncementCreateRequest"
          announcement.get: "#/components/schemas/AnnouncementGetRequest"
          announcement.list: "#/components/schemas/AnnouncementListRequest"
          announcement.update: "#/components/schemas/AnnouncementUpdateRequest"
          announcement.delete: "#/components/schemas/AnnouncementDeleteRequest"
          channel.create: "#/components/schemas/ChannelCreateRequest"
          channel.get: "#/components/schemas/ChannelGetRequest"
          channel.list: "#/components/schemas/ChannelListRequest"
          channel.update: "#/components/schemas/ChannelUpdateRequest"
          channel.delete: "#/components/schemas/ChannelDeleteRequest"
          reaction_role.create: "#/components/schemas/ReactionRoleCreateRequest"
          reaction_role.get: "#/components/schemas/ReactionRoleGetRequest"
          reaction_role.list: "#/components/schemas/ReactionRoleListRequest"
          reaction_role.delete: "#/components/schemas/ReactionRoleDeleteRequest"
          role.assign: "#/components/schemas/RoleAssignRequest"
          role.remove: "#/components/schemas/RoleRemoveRequest"
          role.get: "#/components/schemas/RoleGetRequest"
          wallet.binding.apply: "#/components/schemas/WalletBindingApplyRequest"
          wallet.payout.apply: "#/components/schemas/WalletPayoutApplyRequest"
          wallet.prize.attach: "#/components/schemas/WalletPrizeAttachRequest"
          wallet.setup.create: "#/components/schemas/WalletSetupCreateRequest"
          wallet.backup.verify: "#/components/schemas/WalletBackupVerifyRequest"
          wallet.status: "#/components/schemas/WalletStatusRequest"
          wallet.holdings: "#/components/schemas/WalletHoldingsRequest"
          wallet_role.configure: "#/components/schemas/WalletRoleConfigureRequest"
          wallet_role.sync: "#/components/schemas/WalletRoleSyncRequest"
          wallet_role.status: "#/components/schemas/WalletRoleStatusRequest"
          giveaway.create: "#/components/schemas/GiveawayCreateRequest"
          giveaway.publish: "#/components/schemas/GiveawayPublishRequest"
          giveaway.open: "#/components/schemas/GiveawayOpenRequest"
          giveaway.end: "#/components/schemas/GiveawayEndRequest"
          giveaway.cancel: "#/components/schemas/GiveawayCancelRequest"
          giveaway.draw: "#/components/schemas/GiveawayDrawRequest"
          giveaway.close: "#/components/schemas/GiveawayCloseRequest"
          giveaway.list: "#/components/schemas/GiveawayListRequest"
          giveaway.winners.list: "#/components/schemas/GiveawayWinnersListRequest"
          giveaway.winner.get: "#/components/schemas/GiveawayWinnerGetRequest"
          reserve.list: "#/components/schemas/ReserveListRequest"
          reserve.get: "#/components/schemas/ReserveGetRequest"
          reserve.address.apply: "#/components/schemas/ReserveAddressApplyRequest"
          reserve.remind: "#/components/schemas/ReserveRemindRequest"
          reserve.release: "#/components/schemas/ReserveReleaseRequest"
          prize.list: "#/components/schemas/PrizeListRequest"
          prize.get: "#/components/schemas/PrizeGetRequest"
          payout.prepare: "#/components/schemas/PayoutPrepareRequest"
          payout.submit: "#/components/schemas/PayoutSubmitRequest"
          payout.get_by_tx: "#/components/schemas/PayoutGetByTxRequest"
          user.prizes.list: "#/components/schemas/UserPrizesListRequest"
          action.get: "#/components/schemas/ActionGetRequest"
    AnnouncementCreateRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: announcement.create
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/AnnouncementCreateCommand"
    AnnouncementGetRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: announcement.get
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/AnnouncementGetCommand"
    AnnouncementListRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: announcement.list
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/AnnouncementListCommand"
    AnnouncementUpdateRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: announcement.update
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/AnnouncementUpdateCommand"
    AnnouncementDeleteRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: announcement.delete
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/AnnouncementDeleteCommand"
    ChannelCreateRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: channel.create
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/ChannelCreateCommand"
    ChannelGetRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: channel.get
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/ChannelGetCommand"
    ChannelListRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: channel.list
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/ChannelListCommand"
    ChannelUpdateRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: channel.update
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/ChannelUpdateCommand"
    ChannelDeleteRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: channel.delete
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/ChannelDeleteCommand"
    ReactionRoleCreateRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: reaction_role.create
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/ReactionRoleCreateCommand"
    ReactionRoleGetRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: reaction_role.get
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/ReactionRoleGetCommand"
    ReactionRoleListRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: reaction_role.list
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/ReactionRoleListCommand"
    ReactionRoleDeleteRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: reaction_role.delete
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/ReactionRoleDeleteCommand"
    RoleAssignRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: role.assign
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/RoleAssignCommand"
    RoleRemoveRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: role.remove
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/RoleRemoveCommand"
    RoleGetRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: role.get
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/RoleGetCommand"
    WalletBindingApplyRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: wallet.binding.apply
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/WalletBindingApplyCommand"
    WalletPayoutApplyRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: wallet.payout.apply
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/WalletPayoutApplyCommand"
    WalletPrizeAttachRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: wallet.prize.attach
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/WalletPrizeAttachCommand"
    WalletSetupCreateRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: wallet.setup.create
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/WalletSetupCreateCommand"
    WalletBackupVerifyRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: wallet.backup.verify
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/WalletBackupVerifyCommand"
    WalletStatusRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: wallet.status
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/WalletStatusCommand"
    WalletHoldingsRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: wallet.holdings
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/WalletHoldingsCommand"
    WalletRoleConfigureRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: wallet_role.configure
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/WalletRoleConfigureCommand"
    WalletRoleSyncRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: wallet_role.sync
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/WalletRoleSyncCommand"
    WalletRoleStatusRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: wallet_role.status
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/WalletRoleSyncCommand"
    GiveawayCreateRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: giveaway.create
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/GiveawayCreateCommand"
    GiveawayPublishRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: giveaway.publish
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/GiveawayLifecycleCommand"
    GiveawayOpenRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: giveaway.open
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/GiveawayLifecycleCommand"
    GiveawayEndRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: giveaway.end
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/GiveawayLifecycleCommand"
    GiveawayCancelRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: giveaway.cancel
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/GiveawayLifecycleCommand"
    GiveawayDrawRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: giveaway.draw
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/GiveawayLifecycleCommand"
    GiveawayCloseRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: giveaway.close
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/GiveawayLifecycleCommand"
    GiveawayListRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: giveaway.list
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/GiveawayListCommand"
    GiveawayWinnersListRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: giveaway.winners.list
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/GiveawayWinnersListCommand"
    GiveawayWinnerGetRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: giveaway.winner.get
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/GiveawayWinnerGetCommand"
    ReserveListRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: reserve.list
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/ReserveListCommand"
    ReserveGetRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: reserve.get
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/ReserveGetCommand"
    ReserveAddressApplyRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: reserve.address.apply
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/ReserveAddressApplyCommand"
    ReserveRemindRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: reserve.remind
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/ReserveRemindCommand"
    ReserveReleaseRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: reserve.release
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/ReserveReleaseCommand"
    PrizeListRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: prize.list
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/PrizeListCommand"
    PrizeGetRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: prize.get
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/PrizeGetCommand"
    PayoutPrepareRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: payout.prepare
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/PayoutPrepareCommand"
    PayoutSubmitRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: payout.submit
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/PayoutSubmitCommand"
    PayoutGetByTxRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: payout.get_by_tx
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/PayoutGetByTxCommand"
    UserPrizesListRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: user.prizes.list
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/UserPrizesListCommand"
    ActionGetRequest:
      type: object
      additionalProperties: false
      required:
        - action
        - idempotencyKey
        - command
      properties:
        action:
          const: action.get
        idempotencyKey:
          $ref: "#/components/schemas/ActionIdempotencyKey"
        command:
          $ref: "#/components/schemas/ActionGetCommand"
    EmptyCommand:
      type: object
      additionalProperties: false
      maxProperties: 0
    WalletRoleSyncCommand:
      type: object
      additionalProperties: false
      required:
        - discordUserId
      properties:
        discordUserId:
          $ref: "#/components/schemas/DiscordSnowflake"
      description: Exact customer-local role synchronization target. Tenant, guild, connector, and account authority are never caller supplied.
    Sha256:
      type: string
      pattern: ^[a-f0-9]{64}$
    WalletId:
      type: string
      pattern: ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$
    WalletPayoutApplyCommand:
      type: object
      additionalProperties: false
      required:
        - guildId
        - registryChannelId
        - discordUserId
        - expectedMessageId
        - expectedVersion
        - expectedRecordHash
        - addressCommitment
        - verificationRef
        - verifiedAt
      properties:
        guildId:
          $ref: "#/components/schemas/DiscordSnowflake"
        registryChannelId:
          $ref: "#/components/schemas/DiscordSnowflake"
        discordUserId:
          $ref: "#/components/schemas/DiscordSnowflake"
        expectedMessageId:
          oneOf:
            - $ref: "#/components/schemas/DiscordSnowflake"
            - type: "null"
        expectedVersion:
          oneOf:
            - $ref: "#/components/schemas/PositiveVersion"
            - type: "null"
        expectedRecordHash:
          oneOf:
            - $ref: "#/components/schemas/Sha256"
            - type: "null"
        addressCommitment:
          $ref: "#/components/schemas/Sha256"
        verificationRef:
          $ref: "#/components/schemas/Id"
        verifiedAt:
          type: string
          format: date-time
      oneOf:
        - properties:
            expectedMessageId:
              type: "null"
            expectedVersion:
              type: "null"
            expectedRecordHash:
              type: "null"
        - properties:
            expectedMessageId:
              $ref: "#/components/schemas/DiscordSnowflake"
            expectedVersion:
              $ref: "#/components/schemas/PositiveVersion"
            expectedRecordHash:
              $ref: "#/components/schemas/Sha256"
      description: Address-free create-or-replace intent. All expected fields are null for first registration or all are present for replacement.
    WalletPrizeAttachCommand:
      type: object
      additionalProperties: false
      required:
        - walletId
      properties:
        walletId:
          $ref: "#/components/schemas/WalletId"
      description: Customer-local wallet selection for prize attachment.
    AnnouncementResourceCursor:
      type: string
      minLength: 1
      maxLength: 1024
      pattern: ^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
      x-f5ai-cursor-type: f5ai.resource-list.announcement.v1
      description: Opaque customer-local announcement resource cursor. Not accepted by GET /v1/actions.
    ChannelResourceCursor:
      type: string
      minLength: 1
      maxLength: 1024
      pattern: ^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
      x-f5ai-cursor-type: f5ai.resource-list.channel.v1
      description: Opaque customer-local channel resource cursor. Not accepted by GET /v1/actions.
    ReactionRoleResourceCursor:
      type: string
      minLength: 1
      maxLength: 1024
      pattern: ^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
      x-f5ai-cursor-type: f5ai.resource-list.reaction-role.v1
      description: Opaque customer-local reaction-role resource cursor. Not accepted by GET /v1/actions.
    AnnouncementCreateCommand:
      type: object
      additionalProperties: false
      required:
        - channelId
        - content
      properties:
        channelId:
          $ref: "#/components/schemas/Id"
        content:
          type: string
          minLength: 1
          maxLength: 2000
    AnnouncementGetCommand:
      type: object
      additionalProperties: false
      required:
        - announcementId
      properties:
        announcementId:
          $ref: "#/components/schemas/Id"
    AnnouncementListCommand:
      type: object
      additionalProperties: false
      properties:
        status:
          type: string
          enum:
            - active
            - deleted
            - all
        limit:
          type: integer
          minimum: 1
          maximum: 100
        cursor:
          $ref: "#/components/schemas/AnnouncementResourceCursor"
    AnnouncementUpdateCommand:
      type: object
      additionalProperties: false
      required:
        - announcementId
        - expectedVersion
        - content
      properties:
        announcementId:
          $ref: "#/components/schemas/Id"
        expectedVersion:
          $ref: "#/components/schemas/PositiveVersion"
        content:
          type: string
          minLength: 1
          maxLength: 2000
    AnnouncementDeleteCommand:
      type: object
      additionalProperties: false
      required:
        - announcementId
        - expectedVersion
        - confirm
      properties:
        announcementId:
          $ref: "#/components/schemas/Id"
        expectedVersion:
          $ref: "#/components/schemas/PositiveVersion"
        confirm:
          const: true
    ChannelName:
      type: string
      minLength: 1
      maxLength: 100
      pattern: ^(?!\s)(?!.*\s$)(?!.*<[@#&])(?!.*https?:\/\/)[^\u0000-\u001f\u007f\u200b-\u200f\u202a-\u202e]+$
      x-f5ai-normalization: NFC
      description: Trimmed NFC channel name without control or directionality characters, mentions, or URLs.
    ChannelTopic:
      type: string
      minLength: 1
      maxLength: 1024
      pattern: ^[^\u0000-\u0008\u000b-\u001f\u007f\u200b-\u200f\u202a-\u202e]+$
      x-f5ai-normalization: NFC
      description: NFC channel topic without forbidden control or directionality characters.
    ChannelCreateCommand:
      type: object
      additionalProperties: false
      required:
        - key
        - name
        - type
        - temporary
        - idempotencyKey
      properties:
        key:
          $ref: "#/components/schemas/Id"
        name:
          $ref: "#/components/schemas/ChannelName"
        type:
          type: string
          enum:
            - text
            - announcement
        temporary:
          type: boolean
        permissionMode:
          type: string
          enum:
            - private_admin_bot
          description: For permanent tracking channels, deny visibility to everyone and allow only the executing customer bot plus the supplied client-admin roles.
        authorizedAdminRoleIds:
          type: array
          minItems: 1
          maxItems: 20
          uniqueItems: true
          items:
            $ref: "#/components/schemas/Snowflake"
        parentId:
          $ref: "#/components/schemas/Snowflake"
        topic:
          $ref: "#/components/schemas/ChannelTopic"
        slowmodeSeconds:
          type: integer
          minimum: 0
          maximum: 21600
        nsfw:
          type: boolean
        idempotencyKey:
          $ref: "#/components/schemas/IdempotencyKey"
      allOf:
        - if:
            properties:
              temporary:
                const: false
            required:
              - temporary
          then:
            required:
              - permissionMode
              - authorizedAdminRoleIds
            properties:
              key:
                enum:
                  - wallet_address_registry
                  - giveaway_payout_ledger
              type:
                const: text
              permissionMode:
                const: private_admin_bot
              nsfw:
                const: false
          else:
            not:
              anyOf:
                - required:
                    - permissionMode
                - required:
                    - authorizedAdminRoleIds
    ChannelGetCommand:
      type: object
      additionalProperties: false
      required:
        - channelResourceId
      properties:
        channelResourceId:
          $ref: "#/components/schemas/Id"
    ChannelListCommand:
      type: object
      additionalProperties: false
      properties:
        type:
          type: string
          enum:
            - text
            - announcement
        status:
          type: string
          enum:
            - active
            - deleted
            - all
        limit:
          type: integer
          minimum: 1
          maximum: 100
        cursor:
          $ref: "#/components/schemas/ChannelResourceCursor"
    ChannelUpdateCommand:
      type: object
      additionalProperties: false
      required:
        - channelResourceId
        - expectedVersion
        - idempotencyKey
      properties:
        channelResourceId:
          $ref: "#/components/schemas/Id"
        expectedVersion:
          $ref: "#/components/schemas/PositiveVersion"
        name:
          $ref: "#/components/schemas/ChannelName"
        parentId:
          $ref: "#/components/schemas/Snowflake"
        clearParent:
          const: true
        topic:
          $ref: "#/components/schemas/ChannelTopic"
        clearTopic:
          const: true
        slowmodeSeconds:
          type: integer
          minimum: 0
          maximum: 21600
        nsfw:
          type: boolean
        idempotencyKey:
          $ref: "#/components/schemas/IdempotencyKey"
      anyOf:
        - required:
            - name
        - required:
            - parentId
        - required:
            - clearParent
        - required:
            - topic
        - required:
            - clearTopic
        - required:
            - slowmodeSeconds
        - required:
            - nsfw
      allOf:
        - not:
            required:
              - parentId
              - clearParent
        - not:
            required:
              - topic
              - clearTopic
    ChannelDeleteCommand:
      type: object
      additionalProperties: false
      required:
        - channelResourceId
        - expectedVersion
        - confirm
        - expectedDiscordChannelId
        - expectedName
        - idempotencyKey
      properties:
        channelResourceId:
          $ref: "#/components/schemas/Id"
        expectedVersion:
          $ref: "#/components/schemas/PositiveVersion"
        confirm:
          const: true
        expectedDiscordChannelId:
          $ref: "#/components/schemas/Snowflake"
        expectedName:
          $ref: "#/components/schemas/ChannelName"
        idempotencyKey:
          $ref: "#/components/schemas/IdempotencyKey"
    UnicodeEmoji:
      type: object
      additionalProperties: false
      required:
        - type
        - name
      properties:
        type:
          const: unicode
        name:
          type: string
          minLength: 1
          maxLength: 64
          pattern: ^[^\u0000-\u0020\u007f]+$
          x-f5ai-normalization: NFC
          x-f5ai-grapheme-count: 1
          description: Exactly one normalized Unicode emoji grapheme.
    CustomEmoji:
      type: object
      additionalProperties: false
      required:
        - type
        - id
      properties:
        type:
          const: custom
        id:
          $ref: "#/components/schemas/Snowflake"
    ReactionMapping:
      type: object
      additionalProperties: false
      required:
        - emoji
        - roleId
      properties:
        emoji:
          oneOf:
            - $ref: "#/components/schemas/UnicodeEmoji"
            - $ref: "#/components/schemas/CustomEmoji"
          discriminator:
            propertyName: type
            mapping:
              unicode: "#/components/schemas/UnicodeEmoji"
              custom: "#/components/schemas/CustomEmoji"
        roleId:
          $ref: "#/components/schemas/Snowflake"
    ReactionRoleCreateCommand:
      type: object
      additionalProperties: false
      required:
        - key
        - channelId
        - content
        - mappings
        - removeOnUnreact
        - idempotencyKey
      properties:
        key:
          $ref: "#/components/schemas/Id"
        channelId:
          $ref: "#/components/schemas/Snowflake"
        content:
          type: string
          minLength: 1
          maxLength: 2000
        mappings:
          type: array
          minItems: 1
          maxItems: 20
          uniqueItems: true
          items:
            $ref: "#/components/schemas/ReactionMapping"
          x-f5ai-unique-by:
            - emoji
            - roleId
          description: Emoji keys and role IDs must each be unique across the array.
        singleChoiceGroup:
          oneOf:
            - $ref: "#/components/schemas/Id"
            - type: "null"
        removeOnUnreact:
          type: boolean
        idempotencyKey:
          $ref: "#/components/schemas/IdempotencyKey"
    ReactionRoleGetCommand:
      type: object
      additionalProperties: false
      required:
        - reactionRoleId
      properties:
        reactionRoleId:
          $ref: "#/components/schemas/Id"
    ReactionRoleListCommand:
      type: object
      additionalProperties: false
      properties:
        status:
          type: string
          enum:
            - active
            - deleted
            - all
        limit:
          type: integer
          minimum: 1
          maximum: 100
        cursor:
          $ref: "#/components/schemas/ReactionRoleResourceCursor"
    ReactionRoleDeleteCommand:
      type: object
      additionalProperties: false
      required:
        - reactionRoleId
        - expectedVersion
        - confirm
        - expectedMessageId
        - idempotencyKey
      properties:
        reactionRoleId:
          $ref: "#/components/schemas/Id"
        expectedVersion:
          $ref: "#/components/schemas/PositiveVersion"
        confirm:
          const: true
        expectedMessageId:
          $ref: "#/components/schemas/Snowflake"
        idempotencyKey:
          $ref: "#/components/schemas/IdempotencyKey"
    RoleAssignCommand:
      type: object
      additionalProperties: false
      required:
        - guildId
        - discordUserId
        - roleId
        - idempotencyKey
      properties:
        guildId:
          $ref: "#/components/schemas/Snowflake"
        discordUserId:
          $ref: "#/components/schemas/Snowflake"
        roleId:
          $ref: "#/components/schemas/Snowflake"
        idempotencyKey:
          $ref: "#/components/schemas/IdempotencyKey"
    RoleRemoveCommand:
      type: object
      additionalProperties: false
      required:
        - guildId
        - discordUserId
        - roleId
        - confirm
        - idempotencyKey
      properties:
        guildId:
          $ref: "#/components/schemas/Snowflake"
        discordUserId:
          $ref: "#/components/schemas/Snowflake"
        roleId:
          $ref: "#/components/schemas/Snowflake"
        confirm:
          const: true
        idempotencyKey:
          $ref: "#/components/schemas/IdempotencyKey"
    RoleGetCommand:
      type: object
      additionalProperties: false
      required:
        - guildId
        - discordUserId
      properties:
        guildId:
          $ref: "#/components/schemas/Snowflake"
        discordUserId:
          $ref: "#/components/schemas/Snowflake"
    WalletBindingApplyCommand:
      type: object
      additionalProperties: false
      required:
        - guildId
        - registryChannelId
        - discordUserId
        - expectedMessageId
        - expectedVersion
        - expectedRecordHash
        - identityCommitment
        - verificationRef
        - verifiedAt
      properties:
        guildId:
          $ref: "#/components/schemas/DiscordSnowflake"
        registryChannelId:
          $ref: "#/components/schemas/DiscordSnowflake"
        discordUserId:
          $ref: "#/components/schemas/DiscordSnowflake"
        expectedMessageId:
          oneOf:
            - $ref: "#/components/schemas/DiscordSnowflake"
            - type: "null"
        expectedVersion:
          oneOf:
            - $ref: "#/components/schemas/PositiveVersion"
            - type: "null"
        expectedRecordHash:
          oneOf:
            - $ref: "#/components/schemas/Sha256"
            - type: "null"
        identityCommitment:
          $ref: "#/components/schemas/Sha256"
        verificationRef:
          $ref: "#/components/schemas/Id"
        verifiedAt:
          type: string
          format: date-time
      oneOf:
        - properties:
            expectedMessageId:
              type: "null"
            expectedVersion:
              type: "null"
            expectedRecordHash:
              type: "null"
        - properties:
            expectedMessageId:
              $ref: "#/components/schemas/DiscordSnowflake"
            expectedVersion:
              $ref: "#/components/schemas/PositiveVersion"
            expectedRecordHash:
              $ref: "#/components/schemas/Sha256"
      description: Address-free holder-binding registry create-or-replace command. The customer verifies the wallet and retains all wallet addresses and proof material.
    WalletSetupCreateCommand:
      type: object
      additionalProperties: false
      required:
        - walletId
        - network
        - confirm
      properties:
        walletId:
          type: string
          pattern: ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$
        network:
          const: mainnet
        confirm:
          const: true
    WalletBackupVerifyCommand:
      type: object
      additionalProperties: false
      required:
        - walletId
      properties:
        walletId:
          type: string
          pattern: ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$
    WalletStatusCommand:
      type: object
      additionalProperties: false
      required:
        - walletId
      properties:
        walletId:
          type: string
          pattern: ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$
    WalletHoldingsCommand:
      type: object
      additionalProperties: false
      required:
        - walletId
        - policyIds
      properties:
        walletId:
          type: string
          pattern: ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$
        policyIds:
          type: array
          minItems: 1
          maxItems: 25
          uniqueItems: true
          items:
            type: string
            pattern: ^[a-f0-9]{56}$
        limit:
          type: integer
          minimum: 1
          maximum: 500
    WalletRoleTier:
      type: object
      additionalProperties: false
      required:
        - tierKey
        - roleId
        - name
        - min
        - max
      properties:
        tierKey:
          $ref: "#/components/schemas/Id"
        roleId:
          $ref: "#/components/schemas/Snowflake"
        name:
          type: string
          minLength: 1
          maxLength: 100
        min:
          type: integer
          minimum: 1
        max:
          type: integer
          minimum: 1
    WalletPartnerPolicyRole:
      type: object
      additionalProperties: false
      required:
        - policyId
        - minQuantity
        - roleId
        - roleName
      properties:
        policyId:
          type: string
          pattern: ^[a-f0-9]{56}$
        minQuantity:
          type: integer
          minimum: 1
        roleId:
          $ref: "#/components/schemas/Snowflake"
        roleName:
          type: string
          minLength: 1
          maxLength: 100
    WalletRoleConfiguration:
      type: object
      additionalProperties: false
      required:
        - version
        - policyId
        - roles
        - partnerPolicies
      properties:
        version:
          $ref: "#/components/schemas/PositiveVersion"
        policyId:
          type: string
          pattern: ^[a-f0-9]{56}$
        roles:
          type: array
          minItems: 1
          maxItems: 25
          items:
            $ref: "#/components/schemas/WalletRoleTier"
          description: Tier keys and role IDs must be unique, and quantity ranges must be contiguous from one.
        partnerPolicies:
          type: array
          maxItems: 25
          items:
            $ref: "#/components/schemas/WalletPartnerPolicyRole"
          description: Policy IDs and role IDs must each be unique.
    WalletRoleConfigureCommand:
      type: object
      additionalProperties: false
      required:
        - expectedVersion
        - configuration
      properties:
        expectedVersion:
          $ref: "#/components/schemas/PositiveVersion"
        configuration:
          $ref: "#/components/schemas/WalletRoleConfiguration"
    CanonicalTimestamp:
      type: string
      format: date-time
      pattern: ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$
      description: Canonical UTC timestamp with millisecond precision.
    PolicyId:
      type: string
      pattern: ^[a-f0-9]{56}$
    TransactionId:
      type: string
      pattern: ^[a-f0-9]{64}$
    BoundedListLimit:
      type: integer
      minimum: 1
      maximum: 100
    GiveawayEligibilityPolicy:
      oneOf:
        - type: object
          additionalProperties: false
          required:
            - kind
          properties:
            kind:
              enum:
                - open
                - verified_identity
        - type: object
          additionalProperties: false
          required:
            - kind
            - roleId
          properties:
            kind:
              const: discord_role
            roleId:
              $ref: "#/components/schemas/Snowflake"
    PrizeAsset:
      type: object
      additionalProperties: false
      required:
        - unit
        - quantity
      properties:
        unit:
          type: string
          pattern: ^(lovelace|[a-f0-9]{56,120})$
        quantity:
          type: string
          pattern: ^[1-9][0-9]*$
    PrizeAssets:
      type: array
      minItems: 1
      maxItems: 100
      uniqueItems: true
      items:
        $ref: "#/components/schemas/PrizeAsset"
      x-f5ai-unique-by:
        - unit
      description: Asset units must be unique across the array.
    ExactAssetsPrizeSelection:
      type: object
      additionalProperties: false
      required:
        - kind
        - assets
      properties:
        kind:
          const: exact_assets
        assets:
          $ref: "#/components/schemas/PrizeAssets"
    RandomPolicyPrizeSelection:
      type: object
      additionalProperties: false
      required:
        - kind
        - policyIds
        - assetsPerWinner
      properties:
        kind:
          const: random_from_policies
        policyIds:
          type: array
          minItems: 1
          maxItems: 25
          uniqueItems: true
          items:
            $ref: "#/components/schemas/PolicyId"
        assetsPerWinner:
          type: integer
          minimum: 1
          maximum: 20
    GiveawayPrizeSelection:
      oneOf:
        - $ref: "#/components/schemas/ExactAssetsPrizeSelection"
        - $ref: "#/components/schemas/RandomPolicyPrizeSelection"
    GiveawayCreateCommand:
      type: object
      additionalProperties: false
      required:
        - title
        - startsAt
        - endsAt
        - winnerCount
        - eligibilityPolicy
        - prizeDescription
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 100
        startsAt:
          $ref: "#/components/schemas/CanonicalTimestamp"
        endsAt:
          $ref: "#/components/schemas/CanonicalTimestamp"
        winnerCount:
          type: integer
          minimum: 1
          maximum: 100
        eligibilityPolicy:
          $ref: "#/components/schemas/GiveawayEligibilityPolicy"
        prizeDescription:
          type: string
          minLength: 1
          maxLength: 500
        prizeAssets:
          $ref: "#/components/schemas/PrizeAssets"
        prizeSelection:
          $ref: "#/components/schemas/GiveawayPrizeSelection"
      oneOf:
        - required:
            - prizeAssets
          not:
            required:
              - prizeSelection
        - required:
            - prizeSelection
          not:
            required:
              - prizeAssets
      x-f5ai-ordering: endsAt must be later than startsAt
    GiveawayLifecycleCommand:
      type: object
      additionalProperties: false
      required:
        - giveawayId
        - expectedVersion
        - confirm
      properties:
        giveawayId:
          $ref: "#/components/schemas/Id"
        expectedVersion:
          $ref: "#/components/schemas/PositiveVersion"
        confirm:
          const: true
    GiveawayListCommand:
      type: object
      additionalProperties: false
      properties:
        status:
          enum:
            - draft
            - scheduled
            - published
            - active
            - ended
            - draw_prepared
            - drawn
            - closed
            - cancelled
            - failed
            - all
        limit:
          $ref: "#/components/schemas/BoundedListLimit"
    GiveawayWinnersListCommand:
      type: object
      additionalProperties: false
      required:
        - giveawayId
      properties:
        giveawayId:
          $ref: "#/components/schemas/Id"
        limit:
          $ref: "#/components/schemas/BoundedListLimit"
    GiveawayWinnerGetCommand:
      type: object
      additionalProperties: false
      required:
        - giveawayId
        - winnerDiscordUserId
      properties:
        giveawayId:
          $ref: "#/components/schemas/Id"
        winnerDiscordUserId:
          $ref: "#/components/schemas/Snowflake"
    ReserveListCommand:
      type: object
      additionalProperties: false
      properties:
        giveawayId:
          $ref: "#/components/schemas/Id"
        status:
          enum:
            - waiting_for_address
            - reserved
            - ready_to_submit
            - built
            - signed
            - submitted
            - confirmed
            - submission_unknown
            - expired_missing_address
            - released_unpaid
            - error
        limit:
          $ref: "#/components/schemas/BoundedListLimit"
    ReserveGetCommand:
      type: object
      additionalProperties: false
      required:
        - reserveId
      properties:
        reserveId:
          $ref: "#/components/schemas/Id"
    ReserveAddressApplyCommand:
      type: object
      additionalProperties: false
      required:
        - reserveId
        - expectedVersion
        - winnerDiscordUserId
      properties:
        reserveId:
          $ref: "#/components/schemas/Id"
        expectedVersion:
          $ref: "#/components/schemas/PositiveVersion"
        winnerDiscordUserId:
          $ref: "#/components/schemas/Snowflake"
    ReserveRemindCommand:
      type: object
      additionalProperties: false
      required:
        - reserveId
        - expectedVersion
        - confirm
      properties:
        reserveId:
          $ref: "#/components/schemas/Id"
        expectedVersion:
          $ref: "#/components/schemas/PositiveVersion"
        confirm:
          const: true
    ReserveReleaseCommand:
      type: object
      additionalProperties: false
      required:
        - reserveId
        - expectedVersion
        - reasonCode
        - confirm
      properties:
        reserveId:
          $ref: "#/components/schemas/Id"
        expectedVersion:
          $ref: "#/components/schemas/PositiveVersion"
        reasonCode:
          enum:
            - left_server
            - declined
            - address_not_provided
            - ineligible_after_review
            - owner_exception
        confirm:
          const: true
    PrizeListCommand:
      type: object
      additionalProperties: false
      properties:
        giveawayId:
          $ref: "#/components/schemas/Id"
        winnerDiscordUserId:
          $ref: "#/components/schemas/Snowflake"
        deliveryStatus:
          $ref: "#/components/schemas/Id"
        limit:
          $ref: "#/components/schemas/BoundedListLimit"
    PrizeGetCommand:
      type: object
      additionalProperties: false
      required:
        - prizeId
      properties:
        prizeId:
          $ref: "#/components/schemas/Id"
    PayoutPrepareCommand:
      type: object
      additionalProperties: false
      required:
        - walletId
        - feeCeilingLovelace
        - confirm
      properties:
        giveawayId:
          $ref: "#/components/schemas/Id"
        walletId:
          $ref: "#/components/schemas/Id"
        feeCeilingLovelace:
          type: string
          pattern: ^[1-9][0-9]*$
        confirm:
          const: true
    PayoutSubmitCommand:
      type: object
      additionalProperties: false
      required:
        - prizeId
        - expectedVersion
        - confirm
      properties:
        prizeId:
          $ref: "#/components/schemas/Id"
        expectedVersion:
          $ref: "#/components/schemas/PositiveVersion"
        confirm:
          const: true
    PayoutGetByTxCommand:
      type: object
      additionalProperties: false
      required:
        - transactionId
      properties:
        transactionId:
          $ref: "#/components/schemas/TransactionId"
    UserPrizesListCommand:
      type: object
      additionalProperties: false
      required:
        - winnerDiscordUserId
      properties:
        winnerDiscordUserId:
          $ref: "#/components/schemas/Snowflake"
        deliveryStatus:
          $ref: "#/components/schemas/Id"
        limit:
          $ref: "#/components/schemas/BoundedListLimit"
    ActionGetCommand:
      type: object
      additionalProperties: false
      required:
        - actionId
      properties:
        actionId:
          $ref: "#/components/schemas/Id"
