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

# Cancel a group's members that have not started

> Cancels every member still `validating` or `queued` and releases each one's reservation exactly once; a member a worker already owns is left to run and to settle or release on its own, exactly as `DELETE /v1/transcripts/{job_id}` leaves a claimed job. Idempotent: a repeat finds nothing left to cancel, releases nothing, and answers with the same rollup. The group's own status does not change — a cancelled group is a `complete` group whose members read `cancelled`.



## OpenAPI

````yaml /openapi.yaml post /v1/groups/{group_id}/cancel
openapi: 3.1.0
info:
  title: Audivo API
  version: 0.11.0
  description: >-
    The Audivo API turns a public podcast episode into a clean, timed
    transcript. Name an episode by an Apple Podcasts link, by its RSS feed URL
    and GUID, or by the episode id an earlier answer gave you; get back
    structured, timed text — at once for an episode already transcribed, or as a
    job you poll for fresh audio.


    Two authentication schemes, and which an operation takes is part of what it
    is: `apiKeyBearer` — an `hk_live_` key sent as `Authorization: Bearer` —
    authenticates the public API, and `dashboardJwt` authenticates the
    dashboard's own operations. No operation that takes one accepts the other in
    its place. An API-key caller reads its balance and reservations from any
    quote response.


    Deferred on purpose, and absent from this document rather than hidden:
    webhooks, subscriptions, speaker labels beyond the frozen `diarized: false`
    shape, custom vocabulary, caller-selected engines, raw-audio URLs and
    uploads, SDKs, translation, and cross-corpus search. What changed in each
    version is on the documentation site's changelog.
  license:
    name: Proprietary
    url: https://docs.audivo.dev/legal/terms
servers:
  - url: https://k8mfogcvz4.execute-api.us-east-1.amazonaws.com/prod
    description: Production API. Append the operation path, including /v1.
security: []
tags:
  - name: Transcripts
    description: Create, poll, retrieve, and cancel transcription jobs.
  - name: Catalog
    description: >-
      Public podcast show/episode search, charts, detail, and resolution. Every
      metadata/search/list call in this tag costs 1 credit; no catalog response
      echoes `credits_charged`, so the charge is stated here rather than
      per-response. Not yet applied: the discovery operations shipped in 0.2.0
      (`GET /v1/search/shows`, `GET /v1/charts`) are metered by request rate
      only and write no credit-bearing row; the per-call charge lands with the
      catalog reads that bill.
  - name: Quotes
    description: >-
      Price a selection before spending. A quote resolves every episode in it,
      records what it resolved under a short TTL, and reserves nothing; the
      confirm that follows is the operation that spends, and it answers with a
      job group.
  - name: Groups
    description: >-
      The job group: what one confirm created. A group holds N ≥ 1 members, each
      a transcription job or a settled cache read; a single-episode `POST
      /v1/transcripts` is a group of one. A caller reads a group's rollup, lists
      their own groups newest first, and cancels a group, which cancels every
      member not yet started and releases each reservation exactly once.
  - name: Session
    description: >-
      Dashboard-JWT session establishment. One operation, called by the
      dashboard's backend-for-frontend whenever it turns a Cognito sign-in into
      a Audivo session, and the one place a caller who does not yet have an
      account is served.
  - name: Account
    description: Usage, limits, and supported languages for the calling account.
  - name: API Keys
    description: Dashboard-JWT key lifecycle management.
  - name: Billing
    description: Dashboard-JWT Stripe Checkout, Customer Portal, invoices, and refunds.
  - name: Data
    description: Dashboard-JWT portable export and account deletion.
paths:
  /v1/groups/{group_id}/cancel:
    post:
      tags:
        - Groups
      summary: Cancel a group's members that have not started
      description: >-
        Cancels every member still `validating` or `queued` and releases each
        one's reservation exactly once; a member a worker already owns is left
        to run and to settle or release on its own, exactly as `DELETE
        /v1/transcripts/{job_id}` leaves a claimed job. Idempotent: a repeat
        finds nothing left to cancel, releases nothing, and answers with the
        same rollup. The group's own status does not change — a cancelled group
        is a `complete` group whose members read `cancelled`.
      operationId: cancelGroup
      parameters:
        - $ref: '#/components/parameters/GroupIdPathParam'
      responses:
        '200':
          description: The group after the cancel, with every member's state.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JobGroupResponse'
              examples:
                cancelled:
                  $ref: '#/components/examples/CancelledGroup'
        '400':
          $ref: '#/components/responses/BadRequestApiKey'
        '401':
          $ref: '#/components/responses/UnauthorizedApiKey'
        '404':
          $ref: '#/components/responses/GroupNotFoundApiKey'
        '429':
          $ref: '#/components/responses/TooManyRequestsApiKey'
        '500':
          $ref: '#/components/responses/InternalErrorApiKey'
      security:
        - apiKeyBearer: []
components:
  parameters:
    GroupIdPathParam:
      name: group_id
      in: path
      required: true
      schema:
        $ref: '#/components/schemas/GroupId'
  headers:
    XRequestId:
      description: >-
        Echoed on every response, success or error. A refusal generated at the
        edge (401, 429, and the unmetered-key 500) carries API Gateway's own
        hyphenated request id after the `req_` prefix; a response from a
        function carries the same id without hyphens.
      schema:
        type: string
        pattern: ^req_[A-Za-z0-9-]+$
        maxLength: 100
    XRateLimitLimit:
      description: >-
        The caller's requests-per-minute ceiling for the current tier. Present
        on every response for a resolved caller, success and error alike; absent
        from `401 unauthenticated`, which has no account to report a ceiling
        for, and from the edge's own `429`, which is emitted before any function
        runs.
      schema:
        type: integer
        minimum: 0
    RetryAfter:
      description: >-
        Seconds to wait before retrying. Sent with the edge's `rate_limited`
        throttle refusal. Not sent with `concurrency_limited`, which clears when
        one of the account's own jobs reaches a terminal state — a moment the
        API cannot put a number on.
      schema:
        type: integer
        minimum: 0
  schemas:
    JobGroupResponse:
      type: object
      additionalProperties: false
      required:
        - group_id
        - status
        - quote_id
        - member_count
        - members
        - member_counts
        - credits_reserved
        - credits_settled
        - credits_released
        - created_at
        - completion_deadline
        - completed_at
        - abandoned_at
      description: >-
        One group's rollup. `member_count` is how many members the confirm set
        out to create; `members` is how many exist, which is fewer only while
        the group is `pending` (or after it was `abandoned` part way).
        `credits_reserved` is what the group still holds against the balance —
        the reservations of members not yet terminal; `credits_settled` is what
        it has been charged, job members and cache reads together;
        `credits_released` is what came back from members that failed, were
        cancelled, or settled below their ceiling. None of the three is a bill:
        each job member carries its own figures, and the ledger is the record.
      properties:
        group_id:
          $ref: '#/components/schemas/GroupId'
        status:
          $ref: '#/components/schemas/JobGroupStatus'
        quote_id:
          description: >-
            The quote this group confirmed, or `null` for a single-episode
            submission.
          anyOf:
            - $ref: '#/components/schemas/QuoteId'
            - enum:
                - null
        member_count:
          type: integer
          minimum: 1
        members:
          type: array
          maxItems: 100
          items:
            $ref: '#/components/schemas/JobGroupMember'
        member_counts:
          $ref: '#/components/schemas/JobGroupMemberCounts'
        credits_reserved:
          $ref: '#/components/schemas/CreditAmount'
        credits_settled:
          $ref: '#/components/schemas/CreditAmount'
        credits_released:
          $ref: '#/components/schemas/CreditAmount'
        created_at:
          type: string
          format: date-time
        completion_deadline:
          type: string
          format: date-time
          description: Past this, a still-`pending` group is swept.
        completed_at:
          $ref: '#/components/schemas/NullableDateTime'
        abandoned_at:
          $ref: '#/components/schemas/NullableDateTime'
    GroupId:
      type: string
      pattern: ^grp_[A-Za-z0-9]{16,32}$
      maxLength: 40
      description: Opaque, server-generated job group identifier.
    JobGroupStatus:
      type: string
      enum:
        - pending
        - complete
        - abandoned
      description: >-
        `pending` while a confirm is still fanning out (or died doing so);
        `complete` once every member landed; `abandoned` once the sweeper
        returned a pending group's reservations past its deadline. A cancelled
        group is `complete` with `cancelled` members.
    QuoteId:
      type: string
      pattern: ^qte_[A-Za-z0-9]{16,32}$
      maxLength: 40
      description: Opaque, server-generated quote identifier.
    JobGroupMember:
      description: >-
        One member of a group — a job, or a settled cache read. `kind`
        discriminates.
      oneOf:
        - $ref: '#/components/schemas/JobGroupJobMember'
        - $ref: '#/components/schemas/JobGroupCachedReadMember'
    JobGroupMemberCounts:
      type: object
      additionalProperties: false
      required:
        - validating
        - queued
        - downloading
        - transcribing
        - merging
        - completed
        - failed
        - cancelled
        - cached_read
      description: >-
        How many members are in each job state, plus how many were settled cache
        reads (which have no state — they were delivered at confirm). The nine
        sum to `member_count` on a complete group.
      properties:
        validating:
          type: integer
          minimum: 0
        queued:
          type: integer
          minimum: 0
        downloading:
          type: integer
          minimum: 0
        transcribing:
          type: integer
          minimum: 0
        merging:
          type: integer
          minimum: 0
        completed:
          type: integer
          minimum: 0
        failed:
          type: integer
          minimum: 0
        cancelled:
          type: integer
          minimum: 0
        cached_read:
          type: integer
          minimum: 0
    CreditAmount:
      type: integer
      minimum: 0
      maximum: 9007199254740991
      description: >-
        A non-negative JavaScript-safe integer credit amount. The upper bound
        matches `Number.MAX_SAFE_INTEGER`, which is enforced by the domain
        ledger so arithmetic and JSON round-trips cannot silently lose cents
        worth of credit precision.
    NullableDateTime:
      description: An ISO 8601 timestamp, or `null`.
      anyOf:
        - type: string
          format: date-time
        - enum:
            - null
    Error:
      type: object
      additionalProperties: false
      required:
        - error
      properties:
        error:
          $ref: '#/components/schemas/ErrorDetail'
    JobGroupJobMember:
      type: object
      additionalProperties: false
      required:
        - kind
        - job_id
        - episode_id
        - status
        - estimated_credits
        - reserved_credits
        - created_at
      description: >-
        A member with a job behind it. `reserved_credits` is the ceiling the
        confirm reserved; `settled_credits` and `released_credits` appear as the
        job's terminal state sets them, as on `JobStatus`. Poll or cancel the
        job itself through `/v1/transcripts/{job_id}`.
      properties:
        kind:
          const: job
        job_id:
          $ref: '#/components/schemas/JobId'
        episode_id:
          $ref: '#/components/schemas/EpisodeId'
        status:
          $ref: '#/components/schemas/JobState'
        estimated_credits:
          $ref: '#/components/schemas/CreditAmount'
        reserved_credits:
          $ref: '#/components/schemas/CreditAmount'
        settled_credits:
          $ref: '#/components/schemas/CreditAmount'
        released_credits:
          $ref: '#/components/schemas/CreditAmount'
        created_at:
          type: string
          format: date-time
    JobGroupCachedReadMember:
      type: object
      additionalProperties: false
      required:
        - kind
        - read_id
        - episode_id
        - credits_charged
        - created_at
      description: >-
        A member the confirm found already transcribed: charged
        `credits_charged` (the cached-read price for this account) at confirm,
        with no job behind it. `read_id` is the job-shaped id the read was
        charged under — the id its ledger event and access record carry — so
        every credit a group spent is explainable from the group alone, and it
        is the handle its transcript is fetched by: `GET /v1/reads/{read_id}`
        delivers it, in any format, and charges nothing further. The job poll
        cannot, because this member has no job.
      properties:
        kind:
          const: cached_read
        read_id:
          $ref: '#/components/schemas/JobId'
        episode_id:
          $ref: '#/components/schemas/EpisodeId'
        credits_charged:
          $ref: '#/components/schemas/CreditAmount'
        created_at:
          type: string
          format: date-time
    ErrorDetail:
      type: object
      additionalProperties: false
      required:
        - type
        - code
        - message
        - doc_url
        - request_id
        - retryable
      properties:
        type:
          $ref: '#/components/schemas/ErrorType'
        code:
          $ref: '#/components/schemas/ErrorCode'
        message:
          type: string
          minLength: 1
          maxLength: 1000
        doc_url:
          type: string
          format: uri
          maxLength: 300
        request_id:
          $ref: '#/components/schemas/RequestId'
        retryable:
          type: boolean
      oneOf:
        - required:
            - code
            - type
          properties:
            code:
              enum:
                - invalid_request
                - invalid_url
                - source_not_supported
                - unsafe_source
            type:
              const: invalid_request
        - required:
            - code
            - type
          properties:
            code:
              enum:
                - duration_exceeded
                - size_exceeded
                - unsupported_codec
                - unsupported_language
                - credits_not_refundable
            type:
              const: unprocessable_input
        - required:
            - code
            - type
          properties:
            code:
              const: unauthenticated
            type:
              const: unauthenticated
        - required:
            - code
            - type
          properties:
            code:
              enum:
                - idempotency_conflict
                - job_not_completed
                - quote_expired
                - quote_mismatch
                - quote_unverified
                - expected_total_mismatch
                - request_in_progress
                - account_suspended
                - account_closed
                - account_not_found
                - api_key_limit_reached
                - tier_unchanged
            type:
              const: conflict
        - required:
            - code
            - type
          properties:
            code:
              enum:
                - feed_dead
                - episode_not_found
                - show_not_found
                - job_not_found
                - quote_not_found
                - group_not_found
                - api_key_not_found
                - credit_lot_not_found
            type:
              const: not_found
        - required:
            - code
            - type
          properties:
            code:
              const: content_blocked
            type:
              const: content_blocked
        - required:
            - code
            - type
          properties:
            code:
              const: payment_required
            type:
              const: payment_required
        - required:
            - code
            - type
          properties:
            code:
              enum:
                - rate_limited
                - concurrency_limited
            type:
              const: rate_limited
        - required:
            - code
            - type
          properties:
            code:
              enum:
                - engine_unavailable
                - processing_failed
                - discovery_unavailable
                - internal_error
            type:
              const: unavailable
        - required:
            - code
            - type
          properties:
            code:
              const: nothing_to_quote
            type:
              const: unprocessable_input
    JobId:
      type: string
      pattern: ^job_[A-Za-z0-9]{16,32}$
      maxLength: 40
      description: Opaque, server-generated job identifier.
    EpisodeId:
      type: string
      pattern: ^ep_[a-z2-7]{16}$
      description: Canonical episode identifier.
    JobState:
      type: string
      enum:
        - validating
        - queued
        - downloading
        - transcribing
        - merging
        - completed
        - failed
        - cancelled
      description: >-
        Legal transitions: validating → queued|failed|cancelled; queued →
        downloading|failed|cancelled; downloading → transcribing|failed;
        transcribing → merging|failed; merging → completed|failed. Terminal
        states (completed, failed, cancelled) have no further transitions.
        Cancellation is legal only from validating/queued; every non-terminal
        state may fail.
    ErrorType:
      type: string
      enum:
        - invalid_request
        - unprocessable_input
        - unauthenticated
        - conflict
        - not_found
        - content_blocked
        - payment_required
        - rate_limited
        - unavailable
      description: >-
        The family an error belongs to, for handling; `code` is the precise
        reason. `invalid_request`: something in the request is wrong — fix the
        integration. `unprocessable_input`: the request is well-formed but the
        episode or resource is outside published support. `unauthenticated`:
        missing, malformed, revoked or expired credential. `payment_required`:
        the account's credits cannot cover the ceiling. `conflict`: the
        resource's or the account's state refuses this request as sent — a
        lapsed quote, a reused idempotency key, a held account. `not_found`: the
        job, quote, group or key the request named does not exist for this
        account. `rate_limited`: over a plan limit; wait and retry.
        `unavailable`: a failure on our side or at a provider; retry, and quote
        `request_id` if it persists.
    ErrorCode:
      type: string
      enum:
        - invalid_request
        - invalid_url
        - unauthenticated
        - idempotency_conflict
        - job_not_completed
        - source_not_supported
        - feed_dead
        - episode_not_found
        - show_not_found
        - unsafe_source
        - unsupported_codec
        - unsupported_language
        - duration_exceeded
        - size_exceeded
        - content_blocked
        - payment_required
        - rate_limited
        - concurrency_limited
        - job_not_found
        - api_key_not_found
        - api_key_limit_reached
        - credit_lot_not_found
        - credits_not_refundable
        - engine_unavailable
        - processing_failed
        - discovery_unavailable
        - nothing_to_quote
        - internal_error
        - quote_not_found
        - group_not_found
        - quote_expired
        - quote_mismatch
        - quote_unverified
        - expected_total_mismatch
        - request_in_progress
        - account_suspended
        - account_closed
        - account_not_found
        - tier_unchanged
      description: >-
        Every code the API returns, and the HTTP status it comes with.
        `invalid_request`, `invalid_url` → 400. `unauthenticated` → 401.
        `payment_required` → 402. `job_not_found`, `quote_not_found`,
        `group_not_found`, `api_key_not_found`, `credit_lot_not_found` → 404.
        `idempotency_conflict`, `request_in_progress`, `job_not_completed`,
        `quote_expired`, `quote_mismatch`, `quote_unverified`,
        `expected_total_mismatch`, `account_suspended`, `account_closed`,
        `account_not_found`, `api_key_limit_reached`, `tier_unchanged` → 409.
        `source_not_supported`, `feed_dead`, `episode_not_found`,
        `show_not_found`, `unsafe_source`, `unsupported_codec`,
        `unsupported_language`, `duration_exceeded`, `size_exceeded`,
        `nothing_to_quote`, `credits_not_refundable` → 422. `content_blocked` →
        451. `rate_limited`, `concurrency_limited` → 429. `internal_error`,
        `processing_failed` → 500. `discovery_unavailable`, `engine_unavailable`
        → 503. `processing_failed` also appears inside a failed job's `error`
        field rather than as a live status. Each code has a heading on the
        documentation site's errors page, which is what `doc_url` links to.
    RequestId:
      type: string
      pattern: ^req_[A-Za-z0-9-]+$
      maxLength: 100
  examples:
    CancelledGroup:
      summary: Cancelled — the queued member released, the running one left to finish
      value:
        group_id: grp_9k2fA7bQ3xzM1LpN
        status: complete
        quote_id: qte_9k2fA7bQ3xzM1LpN
        member_count: 3
        members:
          - kind: cached_read
            read_id: job_2c1d4e9fA7bQ3xzM
            episode_id: ep_rwg4do2imnjyhaj7
            credits_charged: 1
            created_at: '2026-09-08T09:01:00Z'
          - kind: job
            job_id: job_4e9f2c1dB8cR4yaN
            episode_id: ep_3iicyxg6ymbv75gy
            status: transcribing
            estimated_credits: 60
            reserved_credits: 75
            created_at: '2026-09-08T09:01:00Z'
          - kind: job
            job_id: job_9f2c1d4eC9dS5zbP
            episode_id: ep_uy4pqvjsmeyrhavz
            status: cancelled
            estimated_credits: 45
            reserved_credits: 57
            released_credits: 57
            created_at: '2026-09-08T09:01:00Z'
        member_counts:
          validating: 0
          queued: 0
          downloading: 0
          transcribing: 1
          merging: 0
          completed: 0
          failed: 0
          cancelled: 1
          cached_read: 1
        credits_reserved: 75
        credits_settled: 1
        credits_released: 57
        created_at: '2026-09-08T09:01:00Z'
        completion_deadline: '2026-09-08T09:06:00Z'
        completed_at: '2026-09-08T09:01:00Z'
        abandoned_at: null
  responses:
    BadRequestApiKey:
      description: >-
        Invalid request shape or an unrecognized pointer. Codes:
        `invalid_request`, `invalid_url`.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/XRequestId'
        X-RateLimit-Limit:
          $ref: '#/components/headers/XRateLimitLimit'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    UnauthorizedApiKey:
      description: >-
        Code `unauthenticated` — the API key is missing, malformed, revoked, or
        does not exist. Every operation in this document is authenticated, so
        this is the most common error path a client will hit; it is declared on
        all of them rather than left implicit. The response never distinguishes
        "no such key" from "revoked key". Unlike every other API-key response it
        carries no `X-RateLimit-Limit`: that is a per-account figure, and an
        unauthenticated request has no resolved account to report one for.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/XRequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    GroupNotFoundApiKey:
      description: >-
        Code `group_not_found` — no such job group for this account, for the
        same reason and with the same indistinguishability as `quote_not_found`.
        No member identifier is disclosed.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/XRequestId'
        X-RateLimit-Limit:
          $ref: '#/components/headers/XRateLimitLimit'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    TooManyRequestsApiKey:
      description: >-
        Codes `rate_limited` or `concurrency_limited`, from two different limits
        and two different places. `rate_limited` is the per-account request
        rate, refused at the edge before the operation runs: it carries
        `Retry-After` and no `X-RateLimit-Limit`, because nothing that knows the
        caller's tier has run yet. `concurrency_limited` is the cap on jobs open
        at once, refused by the operation itself: it carries `X-RateLimit-Limit`
        like every other answered response, and no `Retry-After`, because it
        clears when one of the account's own jobs finishes rather than after a
        fixed wait.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/XRequestId'
        X-RateLimit-Limit:
          $ref: '#/components/headers/XRateLimitLimit'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    InternalErrorApiKey:
      description: >-
        Code `internal_error` — a failure on our side that no other code
        describes. The message is fixed text and says nothing about the cause;
        `request_id` is what identifies it in our logs. Retryable.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/XRequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  securitySchemes:
    apiKeyBearer:
      type: http
      scheme: bearer
      bearerFormat: hk_live_... or hk_test_...
      description: >-
        `Authorization: Bearer hk_live_...` for live keys or `Authorization:
        Bearer hk_test_...` for test-mode keys. `hk_test_` keys resolve real
        public catalog metadata but return deterministic committed fixtures,
        never call inference, and never mutate live credits. This is the *only*
        transport for the credential: the `x-api-key` alias once documented was
        removed in 0.2.0, because the edge authorizer reads `Authorization` as
        its single identity source and a request on any other header is refused
        before it is authenticated.


        Never accepted on a `dashboardJwt` operation, and there are no
        exceptions. `GET /v1/usage` and `GET /v1/limits` briefly declared both
        schemes (0.8.0); that was withdrawn in 0.8.1 because no deployed route
        could honor it — both operations are served by the control-plane API,
        whose authorizer verifies a Cognito token and refuses an `hk_live_`
        credential on shape, and the customer API does not route either path. An
        API-key holder reads its balance and reservation from `QuoteResponse`,
        which carries `balance_credits` and `reserved_credits` on every quote.
        Every operation in this document takes one scheme or the other and
        refuses the wrong one as `unauthenticated`.

````