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

# Map a track's beats and downbeats

> Map a track's beat grid. Returns every beat, and separately every **downbeat**, the
first beat of each bar, which is what decides whether a cut lands musically right.

Always asynchronous: mapping is real compute on real audio, so this returns `202` with
a `job_id`. Poll `GET /v1/beats/{job_id}`. Billed per minute of audio, once, on
success, and a failed job is not billed.

The submit checks only that `audio_url` is present and is an `https://` or `s3://` URL.
Everything about the audio itself is decided by the worker after it fetches the file,
and reaches you as a `422` on the poll: that it decodes, that it is under the
30-minute limit.




## OpenAPI

````yaml /api-reference/openapi.yaml post /v1/beats
openapi: 3.1.0
info:
  title: Cue API
  version: 1.0.0
  description: >
    Match licensed music to a video, or to a script. Five things it does:
    **match** music to

    an analyzed video, **underscore** a text script with no video at all,
    **sync** a track to

    the moments in a clip, **loop** a short track across a long one, and map a
    track's

    **beats** and downbeats. Videos are ingested once and reused by match, sync
    and loop;

    underscore and beats take no video at all.


    Responses carry timings and catalog identifiers, not recordings. You resolve
    a track through

    your own catalog access and render it yourself.


    Work that is already cached answers `200`. Work that needs a first-time
    build answers `202`,

    in one of two shapes: a JOB (`job_id` plus a job URL) or a VIDEO STILL
    INGESTING (`video_id`

    and a `/v1/videos/{id}` URL, with no `job_id`). Branch on whether `job_id`
    is present.


    All requests authenticate with an API key, sent either as `Authorization:
    Bearer vbsk_...`

    or in an `x-api-key` header. Both work. Keys are created in the

    [developer portal](https://cue.vibeset.ai/developer).
servers:
  - url: https://cue.vibeset.ai
    description: Production
security:
  - bearerAuth: []
  - apiKeyAuth: []
tags:
  - name: Match
    description: Match music to a video.
  - name: Underscore
    description: Match music to a script, no video needed.
  - name: Sync
    description: Get sync points, where a track lands with a video.
  - name: Loop
    description: Loop a short track seamlessly across a long video.
  - name: Beats
    description: Map the beat and downbeat grid of any audio you can point us at.
  - name: Videos
    description: Ingest and check the status of videos.
paths:
  /v1/beats:
    post:
      tags:
        - Beats
      summary: Map a track's beats and downbeats
      description: >
        Map a track's beat grid. Returns every beat, and separately every
        **downbeat**, the

        first beat of each bar, which is what decides whether a cut lands
        musically right.


        Always asynchronous: mapping is real compute on real audio, so this
        returns `202` with

        a `job_id`. Poll `GET /v1/beats/{job_id}`. Billed per minute of audio,
        once, on

        success, and a failed job is not billed.


        The submit checks only that `audio_url` is present and is an `https://`
        or `s3://` URL.

        Everything about the audio itself is decided by the worker after it
        fetches the file,

        and reaches you as a `422` on the poll: that it decodes, that it is
        under the

        30-minute limit.
      operationId: mapBeats
      parameters:
        - in: header
          name: Idempotency-Key
          required: false
          schema:
            type: string
            maxLength: 255
          description: >
            A true idempotency key, unlike the match-family endpoints. A repeat
            with the same

            key returns the ORIGINAL `job_id` instead of starting a second job,
            so a timeout you

            never saw the response to cannot bill you twice. Honoured for 24
            hours. Reusing a

            key for a different request is a `422`.


            Re-signing the URL between attempts is fine. The request is
            identified by the

            object it points at, not by the signature on it.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - audio_url
              properties:
                audio_url:
                  type: string
                  maxLength: 2048
                  description: >
                    Signed `https://` URL to the audio, from whatever storage
                    you already use:

                    S3 presigned, GCS V4 signed, Azure SAS, R2/B2 presigned, or
                    your own

                    CDN. Nothing has to be public. Must be reachable from the
                    public internet

                    and respond within 120 seconds.


                    Sign for 12-24 hours when mapping a catalog: jobs queue, and
                    a link signed

                    for one hour can expire while its job is still waiting.


                    `s3://` is accepted only for buckets outside our own
                    account, and only once

                    you have granted our worker read access with a bucket
                    policy.
                  example: https://your-storage/track.mp3
                track_id:
                  type: string
                  maxLength: 128
                  description: >
                    Your own identifier, echoed back on the job so you can map a
                    catalog

                    without keeping a job_id-to-track table of your own.
                known_bpm:
                  type: number
                  minimum: 20
                  maximum: 400
                  description: >
                    Your catalog's tempo for this track, if you have one. Worth
                    +6.73 downbeat

                    CMLt on GTZAN when correct, and it tolerates being a few
                    percent off

                    (+6.65 at 5% error, +4.76 at 10%).


                    Only the OCTAVE matters: half or double the real tempo
                    measured around -60,

                    so we verify your value against our own unconstrained
                    reading and discard it

                    when the two disagree by an octave. Sending a bad tempo
                    cannot make the

                    result worse than omitting the field.
                  example: 128
                audio_sha256:
                  type: string
                  pattern: ^[0-9a-fA-F]{64}$
                  description: >
                    SHA-256 of the audio bytes, if you already know it. When
                    this account has

                    mapped those exact bytes before, we answer from cache
                    without fetching the

                    file at all, which is what you want when re-running a
                    catalog. Omitting it only costs the

                    fetch, because we hash whatever we download regardless.
      responses:
        '202':
          description: >
            Accepted. Poll the job. `idempotent_replay: true` means this key had
            already

            started a job and nothing new was created or billed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BeatsJob'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '422':
          description: This `Idempotency-Key` was already used for a different request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          description: >-
            Beat mapping is not configured on this deployment, or the key claim
            could not be verified.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    BeatsJob:
      type: object
      description: An accepted beat-mapping job.
      properties:
        job_id:
          type: string
        track_id:
          type:
            - string
            - 'null'
        status:
          type: string
          example: processing
        poll:
          type: string
          example: /v1/beats/9f2c
        next:
          type: string
        idempotent_replay:
          type: boolean
          description: >-
            Present and true only when this `Idempotency-Key` had already
            started a job. No new job was created and nothing was billed twice.
    Error:
      type: object
      description: Every `/v1` error body has this shape.
      properties:
        error:
          type: object
          properties:
            type:
              type: string
              enum:
                - invalid_request
                - authentication_error
                - payment_required
                - forbidden
                - not_found
                - conflict
                - payload_too_large
                - unsupported_media_type
                - rate_limited
                - internal_error
                - upstream_error
                - service_unavailable
                - error
              description: >-
                A stable machine-readable type. Branch on this, not on the
                message.
            message:
              type: string
              description: A human-readable explanation of what went wrong.
            request_id:
              type:
                - string
                - 'null'
              description: >-
                The same value as the `X-Request-Id` response header. Quote it
                to support.
            details:
              type: array
              description: >-
                Present only on a request-validation `422`, listing which fields
                were invalid.
              items:
                type: object
      example:
        error:
          type: invalid_request
          message: Provide either 'video_id' or 'video_url'.
          request_id: a1b2c3d4e5f6
  responses:
    BadRequest:
      description: The request was malformed.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unauthorized:
      description: The key is missing, invalid, expired or revoked.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    PaymentRequired:
      description: >
        No active payment method, the credit grant is exhausted or expired, or
        the monthly

        spend cap was reached. `error.message` names which.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    RateLimited:
      description: Too many requests. See the `Retry-After` header.
      headers:
        Retry-After:
          description: Seconds to wait before retrying.
          schema:
            type: integer
        X-RateLimit-Limit:
          description: Requests allowed per minute.
          schema:
            type: integer
        X-RateLimit-Remaining:
          description: Requests remaining in the current window.
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        Your API key as a Bearer token, e.g. `Authorization: Bearer
        vbsk_live_...`
    apiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: 'Your API key in the x-api-key header, e.g. `x-api-key: vbsk_live_...`'

````