audivo-dev Connected Apps API

Dashboard-JWT consent for an assistant connecting to the MCP server through OAuth, and the account's connected apps, each revocable.

Operations 4

GET /v1/oauth/requests/{request_id} Read a pending connection request #
POST /v1/oauth/requests/{request_id}/decision Approve or decline a connection request #
GET /v1/connected-apps List the account's connected apps #
DELETE /v1/connected-apps/{grant_id} Revoke a connected app #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/audivo-dev:audivo-dev-connected-apps-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

audivo-dev-connected-apps-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Audivo Connected Apps API
  version: 0.16.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, by the episode id an earlier answer gave you, or by an upload of audio you hold; get back structured, timed text — at once for an episode already transcribed, or as a job you poll for fresh audio. One call does it: `POST /v1/transcripts`. Quotes are for pricing a selection of many episodes before confirming it.


    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.


    The hosted MCP server at `/mcp` and the OAuth 2.1 endpoints that connect an assistant to it — `/.well-known/oauth-protected-resource`, `/.well-known/oauth-authorization-server`, and `/oauth/authorize`, `/oauth/token`, `/oauth/register`, `/oauth/revoke` — are protocol transports on the same host, not contract operations: an MCP client discovers and describes them from those two metadata documents (RFC 9728, RFC 8414), and they are documented with the MCP server. Their access tokens are checked by the same authorizer as an API key and metered by the same plan; the consent they need is two dashboard operations below, under **Connected Apps**.


    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, 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://api.audivo.dev
  description: Production API. Append the operation path, including /v1.
tags:
- name: Connected Apps
  description: Dashboard-JWT consent for an assistant connecting to the MCP server through OAuth, and the account's connected apps, each revocable.
paths:
  /v1/oauth/requests/{request_id}:
    get:
      operationId: getOAuthRequest
      tags:
      - Connected Apps
      summary: Read a pending connection request
      security:
      - dashboardJwt: []
      description: 'What the consent page shows for an app asking to connect: the app, where its authorization code would be sent, and what the scope allows. Read back from the request the authorization endpoint recorded — nothing here comes from the page''s own URL. `client.verified` is `true` only when something the app cannot forge (its metadata document''s address, or its redirect''s host) says who it is; otherwise `client.name` is the app''s own claim. A request lives ten minutes and is consumed by its decision.'
      parameters:
      - $ref: '#/components/parameters/OAuthRequestIdPathParam'
      responses:
        '200':
          description: The pending request.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthRequestView'
        '401':
          $ref: '#/components/responses/UnauthorizedDashboard'
        '404':
          $ref: '#/components/responses/NotFoundDashboard'
        '429':
          $ref: '#/components/responses/TooManyRequestsDashboard'
        '500':
          $ref: '#/components/responses/InternalErrorDashboard'
  /v1/oauth/requests/{request_id}/decision:
    post:
      operationId: decideOAuthRequest
      tags:
      - Connected Apps
      summary: Approve or decline a connection request
      security:
      - dashboardJwt: []
      description: 'The signed-in account holder''s answer. Approving connects the app to this account: a one-use authorization code and the connection (`grant_id`) are created and the request consumed in one write, and `redirect_to` is the app''s redirect carrying `code`, `state`, and `iss`. Declining consumes the request and `redirect_to` carries `error=access_denied`. The caller sends the browser to `redirect_to`; this API never redirects. A request approved twice connects once.'
      parameters:
      - $ref: '#/components/parameters/OAuthRequestIdPathParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OAuthDecisionRequest'
      responses:
        '200':
          description: Where to send the browser.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthDecision'
        '400':
          $ref: '#/components/responses/BadRequestDashboard'
        '401':
          $ref: '#/components/responses/UnauthorizedDashboard'
        '404':
          $ref: '#/components/responses/NotFoundDashboard'
        '409':
          $ref: '#/components/responses/ConflictAccountStateDashboard'
        '429':
          $ref: '#/components/responses/TooManyRequestsDashboard'
        '500':
          $ref: '#/components/responses/InternalErrorDashboard'
  /v1/connected-apps:
    get:
      operationId: listConnectedApps
      tags:
      - Connected Apps
      summary: List the account's connected apps
      security:
      - dashboardJwt: []
      description: Every app connected through OAuth, newest first, revoked ones included. `last_used_at` is when the connection last obtained a token.
      responses:
        '200':
          description: The account's connected apps.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectedAppsListResponse'
        '401':
          $ref: '#/components/responses/UnauthorizedDashboard'
        '429':
          $ref: '#/components/responses/TooManyRequestsDashboard'
        '500':
          $ref: '#/components/responses/InternalErrorDashboard'
  /v1/connected-apps/{grant_id}:
    delete:
      operationId: revokeConnectedApp
      tags:
      - Connected Apps
      summary: Revoke a connected app
      security:
      - dashboardJwt: []
      description: 'Ends the connection: every access token it issued stops working within five minutes, the same bound as a revoked API key, and it can refresh nothing. Idempotent; available on an account on hold.'
      parameters:
      - $ref: '#/components/parameters/GrantIdPathParam'
      responses:
        '204':
          description: The connection is revoked (or was already revoked).
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
        '401':
          $ref: '#/components/responses/UnauthorizedDashboard'
        '404':
          $ref: '#/components/responses/NotFoundDashboard'
        '429':
          $ref: '#/components/responses/TooManyRequestsDashboard'
        '500':
          $ref: '#/components/responses/InternalErrorDashboard'
components:
  responses:
    UnauthorizedDashboard:
      description: 'Code `unauthenticated` — the Cognito JWT is missing, malformed, expired, or was rejected. An API key presented to one of the operations that declare this response lands here too: they take a session and nothing else, and that is an authentication failure, not an authorization one. That includes `GET /v1/usage` and `GET /v1/limits`, which are served by the control-plane API alone.'
      headers:
        X-Request-Id:
          $ref: '#/components/headers/XRequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    InternalErrorDashboard:
      description: 'Code `internal_error` — a call on our side failed and nothing was changed. On `POST /v1/session` this is what an unreachable identity service or an unreadable configuration answers with, deliberately rather than `401`: the session is not the problem, and telling a signed-in customer their token is bad during an outage of ours sends them into a sign-in loop. Retryable; `request_id` is what identifies it in our logs.'
      headers:
        X-Request-Id:
          $ref: '#/components/headers/XRequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    BadRequestDashboard:
      description: 'Code: `invalid_request`.'
      headers:
        X-Request-Id:
          $ref: '#/components/headers/XRequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    TooManyRequestsDashboard:
      description: Code `rate_limited`, e.g. plan-change rate limiting.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/XRequestId'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    NotFoundDashboard:
      description: 'The referenced resource does not exist or does not belong to this account. Codes: `api_key_not_found` (`DELETE /v1/api-keys/{key_id}`), `credit_lot_not_found` (`POST /v1/billing/refund`), `oauth_request_not_found` (the two `/v1/oauth/requests/{request_id}` operations: decided already, or past its ten minutes), or `connected_app_not_found` (`DELETE /v1/connected-apps/{grant_id}`) — each operation emits only the one code for the resource it looks up.'
      headers:
        X-Request-Id:
          $ref: '#/components/headers/XRequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    ConflictAccountStateDashboard:
      description: 'The account''s own state refuses this write. Codes: `account_suspended`, `account_closed`, or `account_not_found`. The same three `ConflictApiKeyCreate` declares, without the key-specific one — a separate component so no operation tells its callers about a refusal it cannot produce.'
      headers:
        X-Request-Id:
          $ref: '#/components/headers/XRequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  schemas:
    OAuthRequestId:
      type: string
      pattern: ^oar_[a-f0-9]{24}$
      maxLength: 40
      description: A pending OAuth authorization request, as the consent page's URL carries it.
    Error:
      type: object
      additionalProperties: false
      required:
      - error
      properties:
        error:
          $ref: '#/components/schemas/ErrorDetail'
    GrantId:
      type: string
      pattern: ^oag_[a-f0-9]{24}$
      maxLength: 40
      description: A connected app — one OAuth grant.
    OAuthDecision:
      type: object
      additionalProperties: false
      required:
      - redirect_to
      properties:
        redirect_to:
          type: string
          format: uri
          maxLength: 4096
          description: The app's redirect, carrying `code`, `state` and `iss` on an approval, or `error=access_denied` on a refusal.
        grant_id:
          $ref: '#/components/schemas/GrantId'
    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
      - upload_quota_exceeded
      - upload_not_found
      - upload_not_received
      - upload_mismatch
      - max_credits_exceeded
      - oauth_request_not_found
      - connected_app_not_found
      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`, `upload_not_found`, `oauth_request_not_found`, `connected_app_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`, `upload_not_received` → 409; `upload_not_received` is retryable once the file's PUT has landed. `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`, `upload_mismatch`, `max_credits_exceeded` → 422. `content_blocked` → 451. `rate_limited`, `concurrency_limited`, `upload_quota_exceeded` → 429; `upload_quota_exceeded` is retryable, at the earliest capacity return time its message names. `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.
    OAuthRequestView:
      type: object
      additionalProperties: false
      required:
      - request_id
      - client
      - redirect
      - scope
      - scope_description
      - expires_at
      properties:
        request_id:
          $ref: '#/components/schemas/OAuthRequestId'
        client:
          type: object
          additionalProperties: false
          required:
          - name
          - verified
          - client_id
          - kind
          properties:
            name:
              type: string
              maxLength: 100
            verified:
              type: boolean
              description: Whether the name is vouched for by where the code goes or by the app's metadata document, rather than claimed.
            client_id:
              type: string
              maxLength: 2048
            kind:
              type: string
              enum:
              - metadata_document
              - registered
        redirect:
          type: object
          additionalProperties: false
          required:
          - target
          - loopback
          properties:
            target:
              type: string
              maxLength: 300
              description: The host (or, for a native app, the scheme) the code is sent to.
            loopback:
              type: boolean
              description: 'A redirect to this computer: any program on it could be the listener, which the consent page warns about.'
        scope:
          type: string
          maxLength: 100
        scope_description:
          type: string
          maxLength: 300
        expires_at:
          type: string
          format: date-time
    ConnectedApp:
      type: object
      additionalProperties: false
      required:
      - grant_id
      - client_name
      - verified
      - redirect_target
      - scope
      - created_at
      - last_used_at
      - revoked
      - revoked_at
      properties:
        grant_id:
          $ref: '#/components/schemas/GrantId'
        client_name:
          type: string
          maxLength: 100
        verified:
          type: boolean
        redirect_target:
          type: string
          maxLength: 300
        scope:
          type: string
          maxLength: 100
        created_at:
          type: string
          format: date-time
        last_used_at:
          $ref: '#/components/schemas/NullableDateTime'
        revoked:
          type: boolean
        revoked_at:
          $ref: '#/components/schemas/NullableDateTime'
    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.'
    ConnectedAppsListResponse:
      type: object
      additionalProperties: false
      required:
      - data
      properties:
        data:
          type: array
          maxItems: 100
          items:
            $ref: '#/components/schemas/ConnectedApp'
    OAuthDecisionRequest:
      type: object
      additionalProperties: false
      required:
      - approve
      properties:
        approve:
          type: boolean
    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
            - upload_mismatch
            - max_credits_exceeded
          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
            - upload_not_received
          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
            - upload_not_found
            - oauth_request_not_found
            - connected_app_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
            - upload_quota_exceeded
          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
    RequestId:
      type: string
      pattern: ^req_[A-Za-z0-9-]+$
      maxLength: 100
    NullableDateTime:
      description: An ISO 8601 timestamp, or `null`.
      anyOf:
      - type: string
        format: date-time
      - enum:
        - null
  parameters:
    GrantIdPathParam:
      name: grant_id
      in: path
      required: true
      schema:
        $ref: '#/components/schemas/GrantId'
    OAuthRequestIdPathParam:
      name: request_id
      in: path
      required: true
      schema:
        $ref: '#/components/schemas/OAuthRequestId'
  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
    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
  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`.'
    dashboardJwt:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: 'A Cognito access token, issued through the dashboard''s own sign-up and sign-in pages (a backend-for-frontend; there is no Cognito Hosted UI) and sent as `Authorization: Bearer <jwt>`. Dashboard operations require this scheme and cannot be called with an API key, with no exception. `GET /v1/usage` and `GET /v1/limits` offered both schemes from 0.8.0; 0.8.1 withdrew that, because the control-plane API serves them alone and its authorizer has no API-key branch. An `account_id` supplied by the client is ignored; all reads/writes are scoped server-side to the authenticated account.'