openapi: 3.1.0
info:
  title: TAS-CHA Public API
  version: '2026-06-09'
  summary: External REST API for TAS-CHA developer integrations.
  description: |
    本仕様は外部開発者向け Public API (`/v1/...`) の安定境界を定義する。
    TAS-CHA 内部用の `/api/...` は本仕様の対象外。

    認証:
      - **API key** (`Authorization: Bearer tcha_live_...` または `X-Api-Key`)
      - **OAuth 2.1 access token** (`Authorization: Bearer tcha_at_...`)
        - PKCE 必須、 refresh token rotation あり

    リクエスト ID:
      - クライアントが `X-Request-Id` を送ると採用、 無ければ server が発行
      - レスポンスとエラー envelope の `requestId` に必ず含まれる

    Rate limit:
      - 短期 burst は IETF `RateLimit-*` で公開
      - 月次 API units は `Tascha-Api-Units-*` で公開
  contact:
    name: TAS-CHA Developers
    url: https://hp.tas-cha.com/contact/
servers:
  - url: https://api.tas-cha.com/v1
    description: Production
  - url: https://stg-api.tas-cha.com/v1
    description: Staging
security:
  - ApiKeyAuth: []
  - BearerAuth: []
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-Api-Key
      description: |
        `tcha_live_...` / `tcha_test_...` prefix の API key。 一度しか表示されず、
        DB には hash のみ保存される。
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: tcha_at
      description: |
        OAuth 2.1 + PKCE で発行された access token。 audience は MCP endpoint と
        Public API で分離する (resource indicator 必須)。
  parameters:
    LimitParam:
      name: limit
      in: query
      schema:
        type: integer
        minimum: 1
        maximum: 200
      description: 1 page の最大件数。 endpoint ごとに上限が異なる。
    CursorParam:
      name: cursor
      in: query
      schema:
        type: string
      description: opaque base64url cursor。 前 page の `nextCursor` をそのまま渡す。
    IdempotencyKeyHeader:
      name: Idempotency-Key
      in: header
      required: false
      description: |
        write 系の冪等キー (1-255 文字, 24h 保持)。 同一 key の再送には同じ結果を返す。
        同一 key + 異なる payload は 409 `idempotency_conflict`。
      schema:
        type: string
        minLength: 1
        maxLength: 255
    RoomIdParam:
      name: roomId
      in: path
      required: true
      schema:
        type: string
      description: Room の cuid。
  headers:
    RateLimitLimit:
      description: 現在 window の request 上限。
      schema: &ref_1
        type: integer
    RateLimitRemaining:
      description: 現在 window で残っている request 数。
      schema: &ref_2
        type: integer
    RateLimitReset:
      description: 現在 window が reset されるまでの秒数。
      schema: &ref_3
        type: integer
    RateLimitPolicy:
      description: 例 `300;w=60`。
      schema: &ref_4
        type: string
    RetryAfter:
      description: 再試行可能になるまでの秒数 (429 / 503 時)。
      schema:
        type: integer
    TaschaApiUnitsLimit:
      description: 月次 API units の上限。
      schema: &ref_5
        type: integer
    TaschaApiUnitsRemaining:
      description: 月次 API units 残量。
      schema: &ref_6
        type: integer
    TaschaApiUnitsUsed:
      description: 月次 API units 使用量 (confirmed + reserved)。
      schema: &ref_7
        type: integer
    TaschaApiUnitsCost:
      description: 当該 request の units 消費量。
      schema: &ref_8
        type: integer
    TaschaApiUnitsReset:
      description: 月次 quota reset 時刻 (ISO 8601)。
      schema: &ref_9
        type: string
        format: date-time
    TaschaRateLimitScope:
      description: 最も制約的だった scope (`user`/`organization`/`api_key`/`actor`/`endpoint`)。
      schema: &ref_10
        type: string
    TaschaRateLimitSubject:
      description: |
        scope 対象 id。 token が当該 subject への read 権限を持つ場合のみ返す。
      schema: &ref_11
        type: string
    XRequestId:
      description: 当該リクエスト ID。 クライアント指定または server 発行。
      schema: &ref_0
        type: string
  schemas:
    PublicApiError:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
            - requestId
          properties:
            code:
              type: string
              enum:
                - unauthenticated
                - invalid_api_key
                - expired_api_key
                - revoked_api_key
                - insufficient_scope
                - forbidden
                - not_found
                - room_not_found
                - rate_limit_exceeded
                - api_units_exceeded
                - idempotency_conflict
                - version_conflict
                - validation_error
                - internal_error
                - rate_limit_backend_unavailable
            message:
              type: string
            requestId:
              type: string
            details:
              type: object
              additionalProperties: true
    CursorPage:
      type: object
      required:
        - data
        - nextCursor
      properties:
        data:
          type: array
          items: {}
        nextCursor:
          type: string
          nullable: true
    PublicMeResponse:
      type: object
      required:
        - id
        - email
        - name
        - locale
        - billingContext
        - createdAt
      properties:
        id:
          type: string
        email:
          type: string
          format: email
        name:
          type: string
        locale:
          type: string
        organizationId:
          type: string
          nullable: true
        billingContext:
          type: string
          enum:
            - individual
            - organization
        createdAt:
          type: string
          format: date-time
    PublicOrganizationSummary:
      type: object
      required:
        - id
        - name
        - isActive
        - createdAt
      properties:
        id:
          type: string
        name:
          type: string
        description:
          type: string
          nullable: true
        isActive:
          type: boolean
        createdAt:
          type: string
          format: date-time
    PublicRoomSummary:
      type: object
      required:
        - id
        - name
        - roomType
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
        name:
          type: string
        roomType:
          type: integer
          description: 0=通常 / 1=DM (Public API では 0 のみ返却)
        organizationId:
          type: string
          nullable: true
        createdById:
          type: string
          nullable: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    PublicRoomMember:
      type: object
      required:
        - userId
      properties:
        userId:
          type: string
        role:
          type: integer
          nullable: true
        joinedAt:
          type: string
          format: date-time
          nullable: true
          description: |
            現在 RoomMember には joinedAt 専用カラムが無く、 値は常に null になる。
            将来追加された時点で実際の値が返るようになる。
        displayName:
          type: string
          nullable: true
    PublicMessageSummary:
      type: object
      required:
        - id
        - roomId
        - body
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
        roomId:
          type: string
        authorId:
          type: string
          nullable: true
        body:
          type: string
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    PublicTaskSummary:
      type: object
      required:
        - id
        - roomId
        - title
        - status
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
        roomId:
          type: string
        title:
          type: string
        description:
          type: string
          nullable: true
        status:
          type: string
        dueDate:
          type: string
          format: date-time
          nullable: true
        createdById:
          type: string
          nullable: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    PublicRecordSummary:
      type: object
      required:
        - id
        - roomId
        - title
        - status
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
        roomId:
          type: string
        title:
          type: string
        status:
          type: string
        createdById:
          type: string
          nullable: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    PublicWebhookDelivery:
      type: object
      required:
        - id
        - webhookId
        - event
        - success
        - attempt
        - deliveredAt
      description: |
        Public API で露出する配信履歴 1 行。
        受信側 server が返した response body の preview は **Public API では露出しない**
        (二次漏洩リスクのため)。
      properties:
        id:
          type: string
          description: 配信履歴行 ID。 redeliver のパスパラメータ `deliveryId` にはこの値を使う。
        webhookId:
          type: string
        event:
          type: string
        eventId:
          type: string
          nullable: true
          description: v2 event id (`evt_...`)。 v1 互換配信では null。
        deliveryId:
          type: string
          nullable: true
          description: |
            `Tascha-Webhook-Delivery` / `X-Webhook-Delivery-Id` ヘッダーで受信側に渡る
            配信冪等キー。 自動リトライの各試行 (= 複数行) で同一値を共有し、
            redeliver では新しい値が振られる。 カラム追加前の legacy 行では null。
        success:
          type: boolean
        statusCode:
          type: integer
          nullable: true
        error:
          type: string
          nullable: true
        attempt:
          type: integer
        durationMs:
          type: integer
          nullable: true
        deliveredAt:
          type: string
          format: date-time
        nextRetryAt:
          type: string
          format: date-time
          nullable: true
        finalizedAt:
          type: string
          format: date-time
          nullable: true
    PublicWebhookEventTypeSummary:
      type: object
      required:
        - type
        - description
        - stable
      properties:
        type:
          type: string
          description: event type (例 `record.created`)
        description:
          type: string
        stable:
          type: boolean
          description: false の場合は preview。 安定化までは破壊的変更あり
    PublicCreateMessageRequest:
      type: object
      required:
        - body
      properties:
        body:
          type: string
          minLength: 1
          maxLength: 10000
        replyToMessageId:
          type: string
          description: 同一 room 内のメッセージ id
    PublicCreateTaskRequest:
      type: object
      required:
        - title
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 200
        description:
          type: string
          maxLength: 10000
        boardId:
          type: string
          description: 省略時は room の最初の board。 board が無い room は 400。
        dueDate:
          type: string
          format: date-time
    PublicUpdateTaskRequest:
      type: object
      description: |
        title / description / status / dueDate のいずれか 1 つ以上が必須。
        description のクリア (null) は未対応。
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 200
        description:
          type: string
          maxLength: 10000
        status:
          type: string
          enum:
            - NOT_STARTED
            - IN_PROGRESS
            - COMPLETED
        dueDate:
          type:
            - string
            - 'null'
          format: date-time
        version:
          type: integer
          description: 楽観ロック。 現在 version と不一致なら 409 `version_conflict`。
    PublicTaskWriteResponse:
      allOf:
        - $ref: '#/components/schemas/PublicTaskSummary'
        - type: object
          required:
            - version
          properties:
            version:
              type: integer
    PublicCreateRecordRequest:
      type: object
      required:
        - title
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 200
        content:
          type: string
          maxLength: 200000
    PublicUpdateRecordRequest:
      type: object
      description: title / content のいずれか 1 つ以上が必須。 creator のみ更新可。
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 200
        content:
          type: string
          maxLength: 200000
    PublicFileSummary:
      type: object
      required:
        - id
        - roomId
        - fileName
        - size
        - mimeType
        - source
        - uploadedById
        - createdAt
      properties:
        id:
          type: string
        roomId:
          type: string
        fileName:
          type: string
        size:
          type: integer
        mimeType:
          type: string
        source:
          type: string
          description: chat / task / memo / board
        messageId:
          type:
            - string
            - 'null'
        taskId:
          type:
            - string
            - 'null'
        uploadedById:
          type: string
        createdAt:
          type: string
          format: date-time
  responses:
    ValidationError:
      description: リクエスト形式エラー
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/PublicApiError'
    Conflict:
      description: idempotency_conflict / version_conflict
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/PublicApiError'
    Unauthenticated:
      description: 認証情報なし / 不正 token
      headers:
        X-Request-Id:
          $ref: '#/components/headers/XRequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/PublicApiError'
    InsufficientScope:
      description: token が必要な scope を持っていない
      headers:
        X-Request-Id:
          $ref: '#/components/headers/XRequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/PublicApiError'
    NotFound:
      description: Room / リソース未存在、 アクセス不可
      headers:
        X-Request-Id:
          $ref: '#/components/headers/XRequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/PublicApiError'
    RateLimited:
      description: 短期 burst もしくは月次 API units 超過
      headers:
        X-Request-Id:
          $ref: '#/components/headers/XRequestId'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
        RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
        RateLimit-Reset:
          $ref: '#/components/headers/RateLimitReset'
        Tascha-Api-Units-Limit:
          $ref: '#/components/headers/TaschaApiUnitsLimit'
        Tascha-Api-Units-Remaining:
          $ref: '#/components/headers/TaschaApiUnitsRemaining'
        Tascha-Api-Units-Used:
          $ref: '#/components/headers/TaschaApiUnitsUsed'
        Tascha-Api-Units-Reset:
          $ref: '#/components/headers/TaschaApiUnitsReset'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/PublicApiError'
x-rate-limit-headers: &ref_12
  X-Request-Id:
    description: 当該リクエスト ID。 クライアント指定または server 発行。
    schema: *ref_0
  RateLimit-Limit:
    description: 現在 window の request 上限。
    schema: *ref_1
  RateLimit-Remaining:
    description: 現在 window で残っている request 数。
    schema: *ref_2
  RateLimit-Reset:
    description: 現在 window が reset されるまでの秒数。
    schema: *ref_3
  RateLimit-Policy:
    description: 例 `300;w=60`。
    schema: *ref_4
  Tascha-Api-Units-Limit:
    description: 月次 API units の上限。
    schema: *ref_5
  Tascha-Api-Units-Remaining:
    description: 月次 API units 残量。
    schema: *ref_6
  Tascha-Api-Units-Used:
    description: 月次 API units 使用量 (confirmed + reserved)。
    schema: *ref_7
  Tascha-Api-Units-Cost:
    description: 当該 request の units 消費量。
    schema: *ref_8
  Tascha-Api-Units-Reset:
    description: 月次 quota reset 時刻 (ISO 8601)。
    schema: *ref_9
  Tascha-RateLimit-Scope:
    description: 最も制約的だった scope (`user`/`organization`/`api_key`/`actor`/`endpoint`)。
    schema: *ref_10
  Tascha-RateLimit-Subject:
    description: |
      scope 対象 id。 token が当該 subject への read 権限を持つ場合のみ返す。
    schema: *ref_11
paths:
  /me:
    get:
      summary: 認証済み user の最小プロフィール
      description: |
        必要な scope: `me:read`
        Units cost: 1 (REST read)
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      responses:
        '200':
          description: OK
          headers: *ref_12
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicMeResponse'
        '401':
          $ref: '#/components/responses/Unauthenticated'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
  /organizations:
    get:
      summary: token がアクセス可能な organization の一覧
      description: |
        必要な scope: `organizations:read`
        Units cost: 1 (REST read)
      parameters:
        - $ref: '#/components/parameters/LimitParam'
        - $ref: '#/components/parameters/CursorParam'
      responses:
        '200':
          description: OK
          headers: *ref_12
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/CursorPage'
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: '#/components/schemas/PublicOrganizationSummary'
        '401':
          $ref: '#/components/responses/Unauthenticated'
        '403':
          $ref: '#/components/responses/InsufficientScope'
        '429':
          $ref: '#/components/responses/RateLimited'
  /rooms:
    get:
      summary: token holder がアクセス可能な room の一覧 (DM 除く)
      description: |
        必要な scope: `rooms:read`
        Units cost: 1 (REST read)
      parameters:
        - $ref: '#/components/parameters/LimitParam'
        - $ref: '#/components/parameters/CursorParam'
      responses:
        '200':
          description: OK
          headers: *ref_12
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/CursorPage'
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: '#/components/schemas/PublicRoomSummary'
        '401':
          $ref: '#/components/responses/Unauthenticated'
        '403':
          $ref: '#/components/responses/InsufficientScope'
        '429':
          $ref: '#/components/responses/RateLimited'
  /rooms/{roomId}/members:
    get:
      summary: Room メンバー一覧
      description: |
        必要な scope: `rooms:read`
        Units cost: 1 (REST read)
      parameters:
        - $ref: '#/components/parameters/RoomIdParam'
        - $ref: '#/components/parameters/LimitParam'
        - $ref: '#/components/parameters/CursorParam'
      responses:
        '200':
          description: OK
          headers: *ref_12
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/CursorPage'
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: '#/components/schemas/PublicRoomMember'
        '401':
          $ref: '#/components/responses/Unauthenticated'
        '403':
          $ref: '#/components/responses/InsufficientScope'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
  /rooms/{roomId}/messages:
    post:
      summary: Room へメッセージを投稿
      description: |
        必要な scope: `messages:write`
        Units cost: 3 (REST write)。 user-actor token のみ (組織 API key 単独は 403)。
      parameters:
        - $ref: '#/components/parameters/RoomIdParam'
        - $ref: '#/components/parameters/IdempotencyKeyHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PublicCreateMessageRequest'
      responses:
        '201':
          description: Created
          headers: *ref_12
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicMessageSummary'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthenticated'
        '403':
          $ref: '#/components/responses/InsufficientScope'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/RateLimited'
    get:
      summary: Room メッセージ一覧 (新しい順)
      description: |
        必要な scope: `messages:read`
        Units cost: 1 (REST read)
      parameters:
        - $ref: '#/components/parameters/RoomIdParam'
        - $ref: '#/components/parameters/LimitParam'
        - $ref: '#/components/parameters/CursorParam'
      responses:
        '200':
          description: OK
          headers: *ref_12
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/CursorPage'
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: '#/components/schemas/PublicMessageSummary'
        '401':
          $ref: '#/components/responses/Unauthenticated'
        '403':
          $ref: '#/components/responses/InsufficientScope'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
  /rooms/{roomId}/tasks:
    post:
      summary: Room にタスクを作成
      description: |
        必要な scope: `tasks:write`
        Units cost: 3 (REST write)。 user-actor token のみ。
      parameters:
        - $ref: '#/components/parameters/RoomIdParam'
        - $ref: '#/components/parameters/IdempotencyKeyHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PublicCreateTaskRequest'
      responses:
        '201':
          description: Created
          headers: *ref_12
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicTaskWriteResponse'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthenticated'
        '403':
          $ref: '#/components/responses/InsufficientScope'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/RateLimited'
    get:
      summary: Room 内タスク一覧
      description: |
        必要な scope: `tasks:read`
        Units cost: 1 (REST read)
      parameters:
        - $ref: '#/components/parameters/RoomIdParam'
        - $ref: '#/components/parameters/LimitParam'
        - $ref: '#/components/parameters/CursorParam'
      responses:
        '200':
          description: OK
          headers: *ref_12
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/CursorPage'
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: '#/components/schemas/PublicTaskSummary'
        '401':
          $ref: '#/components/responses/Unauthenticated'
        '403':
          $ref: '#/components/responses/InsufficientScope'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
  /rooms/{roomId}/tasks/{taskId}:
    patch:
      summary: タスクを更新
      description: |
        必要な scope: `tasks:write`
        Units cost: 3 (REST write)。 `version` 指定で楽観ロック。
      parameters:
        - $ref: '#/components/parameters/RoomIdParam'
        - name: taskId
          in: path
          required: true
          schema:
            type: string
        - $ref: '#/components/parameters/IdempotencyKeyHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PublicUpdateTaskRequest'
      responses:
        '200':
          description: OK
          headers: *ref_12
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicTaskWriteResponse'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthenticated'
        '403':
          $ref: '#/components/responses/InsufficientScope'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/RateLimited'
  /rooms/{roomId}/records:
    post:
      summary: Room に議事録を作成
      description: |
        必要な scope: `records:write`
        Units cost: 3 (REST write)。 user-actor token のみ。
      parameters:
        - $ref: '#/components/parameters/RoomIdParam'
        - $ref: '#/components/parameters/IdempotencyKeyHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PublicCreateRecordRequest'
      responses:
        '201':
          description: Created
          headers: *ref_12
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicRecordSummary'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthenticated'
        '403':
          $ref: '#/components/responses/InsufficientScope'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/RateLimited'
    get:
      summary: Room 内議事録一覧
      description: |
        必要な scope: `records:read`
        Units cost: 1 (REST read)
      parameters:
        - $ref: '#/components/parameters/RoomIdParam'
        - $ref: '#/components/parameters/LimitParam'
        - $ref: '#/components/parameters/CursorParam'
      responses:
        '200':
          description: OK
          headers: *ref_12
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/CursorPage'
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: '#/components/schemas/PublicRecordSummary'
        '401':
          $ref: '#/components/responses/Unauthenticated'
        '403':
          $ref: '#/components/responses/InsufficientScope'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
  /rooms/{roomId}/files:
    get:
      summary: Room 内ファイル一覧 (メタデータのみ, 新しい順)
      description: |
        必要な scope: `files:read`
        Units cost: 1 (file list)
      parameters:
        - $ref: '#/components/parameters/RoomIdParam'
        - $ref: '#/components/parameters/LimitParam'
        - $ref: '#/components/parameters/CursorParam'
      responses:
        '200':
          description: OK
          headers: *ref_12
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/CursorPage'
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: '#/components/schemas/PublicFileSummary'
        '401':
          $ref: '#/components/responses/Unauthenticated'
        '403':
          $ref: '#/components/responses/InsufficientScope'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
  /rooms/{roomId}/records/{recordId}:
    patch:
      summary: 議事録を更新 (creator のみ)
      description: |
        必要な scope: `records:write`
        Units cost: 3 (REST write)。 creator のみ。 locked / archived は 403。
      parameters:
        - $ref: '#/components/parameters/RoomIdParam'
        - name: recordId
          in: path
          required: true
          schema:
            type: string
        - $ref: '#/components/parameters/IdempotencyKeyHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PublicUpdateRecordRequest'
      responses:
        '200':
          description: OK
          headers: *ref_12
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicRecordSummary'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthenticated'
        '403':
          $ref: '#/components/responses/InsufficientScope'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/RateLimited'
  /rooms/{roomId}/webhooks/{webhookId}/deliveries:
    get:
      summary: Webhook 配信履歴一覧
      description: |
        必要な scope: `webhooks:read`
        Units cost: 1 (REST read)

        `eventId` で同じ event の試行を相関できる (redelivery 含む)。
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/RoomIdParam'
        - in: path
          name: webhookId
          required: true
          schema:
            type: string
        - $ref: '#/components/parameters/LimitParam'
        - $ref: '#/components/parameters/CursorParam'
      responses:
        '200':
          description: OK
          headers: *ref_12
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/CursorPage'
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: '#/components/schemas/PublicWebhookDelivery'
        '401':
          $ref: '#/components/responses/Unauthenticated'
        '403':
          $ref: '#/components/responses/InsufficientScope'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
  /rooms/{roomId}/webhooks/{webhookId}/deliveries/{deliveryId}/redeliver:
    post:
      summary: 失敗 / 任意の配信を再送する
      description: |
        必要な scope: `webhooks:write`
        Units cost: 1 (webhook delivery)

        元の `eventId` を維持したまま、 **新しい `deliveryId`** で 1 回再配信する。
        受信側は `Tascha-Webhook-Id` (= eventId) を冪等キーに使うことで重複処理を防げる。
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/RoomIdParam'
        - in: path
          name: webhookId
          required: true
          schema:
            type: string
        - in: path
          name: deliveryId
          required: true
          schema:
            type: string
      responses:
        '200':
          description: 再配信を試行した (HTTP 成否は body の delivery.success を参照)
          headers: *ref_12
          content:
            application/json:
              schema:
                type: object
                required:
                  - delivery
                properties:
                  delivery:
                    $ref: '#/components/schemas/PublicWebhookDelivery'
        '401':
          $ref: '#/components/responses/Unauthenticated'
        '403':
          $ref: '#/components/responses/InsufficientScope'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: webhook が inactive のため再送不可 (code `webhook_inactive`)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicApiError'
        '422':
          description: 元配信の stored payload が解釈不能で再送不可 (code `validation_error`)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicApiError'
        '429':
          $ref: '#/components/responses/RateLimited'
  /event-types:
    get:
      summary: Webhook v2 イベントカタログ
      description: |
        必要な scope: `events:read`
        Units cost: 1 (REST read)

        Webhook で subscribe 可能な event 一覧。
        `stable=false` は preview event であり破壊的変更が入りうる。
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      responses:
        '200':
          description: OK
          headers: *ref_12
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/PublicWebhookEventTypeSummary'
        '401':
          $ref: '#/components/responses/Unauthenticated'
        '403':
          $ref: '#/components/responses/InsufficientScope'
        '429':
          $ref: '#/components/responses/RateLimited'
