tunova-ai Utility API

Lyrics, clips, limits

Operations 6

POST /api/lyrics Generate song lyrics #
GET /api/clips Read clips by id (owner-only) #
GET /api/limits Account credit/limit snapshot (owner-only) #
GET /api/me Legacy alias for your token balance #
GET /api/balance Get your current Tunova token balance #
GET /api/models Selectable models + costs #

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/tunova-ai:tunova-ai-utility-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

tunova-ai-utility-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Tunova Utility API
  version: 1.0.0
  description: 'Suno-AI music generation API. Generation is **always async**: submit returns `202 {job_id}`; poll `GET /api/jobs/{id}` or pass `callback_url` for an HMAC-signed webhook. Billed in **tokens, on success only** (a failed render is refunded). Every response carries `X-Request-Id`. An **MCP server** for AI agents (Claude, Cursor, …) is also available at `POST /mcp` (stateless Streamable HTTP; tools `generate_song` / `wait_for_song` / `check_song`) — same `X-API-Key`, same token billing.'
servers:
- url: https://api.tunova.ai
  description: production
security:
- ApiKeyAuth: []
tags:
- name: Utility
  description: Lyrics, clips, limits
paths:
  /api/lyrics:
    post:
      tags:
      - Utility
      operationId: generateLyrics
      summary: Generate song lyrics
      description: 'Normally completes synchronously with `200`. The paid operation is durable: its token hold, provider id, result/error and settlement survive crashes and response loss. A concurrent/recovery retry may return `202`; poll the supplied `status_url` or retry the same POST with the same Idempotency-Key. Costs 2 tokens on success and refunds atomically on failure.'
      parameters:
      - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - prompt
              properties:
                prompt:
                  type: string
                  description: Theme / brief for the lyrics
      responses:
        '200':
          description: Lyrics
          content:
            application/json:
              schema:
                type: object
                properties:
                  lyrics:
                    type: object
                    additionalProperties: true
                  job_id:
                    type: string
                  idempotent_replay:
                    type: boolean
        '202':
          $ref: '#/components/responses/JobAccepted'
        '400':
          $ref: '#/components/responses/Error'
        '401':
          $ref: '#/components/responses/Error'
        '402':
          $ref: '#/components/responses/InsufficientTokens'
        '403':
          $ref: '#/components/responses/Error'
        '409':
          $ref: '#/components/responses/IdempotencyConflict'
        '429':
          $ref: '#/components/responses/RateLimited'
  /api/clips:
    get:
      tags:
      - Utility
      operationId: listClips
      summary: Read clips by id (owner-only)
      description: Operator utility — requires the grandfathered owner key. Customer `sk_live_` keys get **403**; customers read clips via `GET /api/jobs/{id}` instead.
      parameters:
      - name: ids
        in: query
        schema:
          type: string
        description: Comma-separated clip ids
      responses:
        '200':
          description: Clips
          content:
            application/json:
              schema:
                type: object
                properties:
                  account_id:
                    type: string
                  clips:
                    type: array
                    items:
                      $ref: '#/components/schemas/Clip'
        '401':
          $ref: '#/components/responses/Error'
        '403':
          $ref: '#/components/responses/Error'
  /api/limits:
    get:
      tags:
      - Utility
      operationId: getLimits
      summary: Account credit/limit snapshot (owner-only)
      description: Operator utility — requires the grandfathered owner key. Customer `sk_live_` keys get **403**; partners should use `GET /api/balance`.
      responses:
        '200':
          description: Limits
          content:
            application/json:
              schema:
                type: object
                properties:
                  account_id:
                    type: string
                  limits:
                    type: object
                    additionalProperties: true
        '401':
          $ref: '#/components/responses/Error'
        '403':
          $ref: '#/components/responses/Error'
  /api/me:
    get:
      tags:
      - Utility
      operationId: getMe
      deprecated: true
      summary: Legacy alias for your token balance
      description: Backward-compatible alias of `GET /api/balance`. New integrations should use `/api/balance`.
      responses:
        '200':
          description: Account
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Balance'
        '401':
          $ref: '#/components/responses/Error'
        '403':
          $ref: '#/components/responses/Error'
  /api/balance:
    get:
      tags:
      - Utility
      operationId: getBalance
      summary: Get your current Tunova token balance
      description: Machine-readable balance + live engine depth so an SDK/agent can pre-flight and self-pace instead of probing 402/429 bodies. A hold is already debited from `token_balance`, so `available` equals the non-negative current balance; `reserved` discloses in-flight holds and `total_liability = max(token_balance + reserved, 0)` reconstructs the remaining entitlement. `engine_inflight` = current fleet depth (queued+processing) across all clients. `concurrency_limit` is always `null` — there is no hard admission cap; the queue absorbs bursts.
      responses:
        '200':
          description: Current partner balance
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Balance'
        '401':
          $ref: '#/components/responses/Error'
        '403':
          $ref: '#/components/responses/Error'
  /api/models:
    get:
      tags:
      - Utility
      operationId: listModels
      summary: Selectable models + costs
      description: Models you can pass as `model` on generate, the unchanged account default, and per-call token costs. v5.5 is a compatibility alias for v6; effective_model reports the actual generation model.
      responses:
        '200':
          description: Models
          content:
            application/json:
              schema:
                type: object
                properties:
                  models:
                    type: array
                    items:
                      type: string
                      enum:
                      - v6
                      - v5.5
                  default:
                    type:
                    - string
                    - 'null'
                  effective_model:
                    type: string
                    enum:
                    - v6
                  model_aliases:
                    type: object
                    properties:
                      v5.5:
                        type: string
                        enum:
                        - v6
                  song_cost:
                    type: integer
                  lyrics_cost:
                    type: integer
        '401':
          $ref: '#/components/responses/Error'
        '403':
          $ref: '#/components/responses/Error'
webhooks:
  jobComplete:
    post:
      summary: Delivered to your callback_url on terminal state
      description: 'Signed: `X-Webhook-Signature: sha256=` over `.` (key = your webhook secret, `whsec_…` — view/rotate it on the dashboard''s API keys page). Retried 3× with backoff.'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                job_id:
                  type: string
                status:
                  type: string
                  enum:
                  - complete
                  - failed
                clips:
                  type: array
                  items:
                    $ref: '#/components/schemas/Clip'
                error:
                  type:
                  - string
                  - 'null'
      responses:
        '200':
          description: Acknowledged (any 2xx)
      tags:
      - Utility
components:
  responses:
    InsufficientTokens:
      description: 402 — token balance too low (billed on success only)
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Error:
      description: Error envelope
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    IdempotencyConflict:
      description: 409 — Idempotency-Key is already bound to a different canonical generation request
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    RateLimited:
      description: 429 — per-key rate limit (60 requests/min, sliding 60s window). When limiter state is available, `RateLimit-Limit`/`RateLimit-Remaining`/`RateLimit-Reset` are returned on rate-checked generation/lyrics accepts and 429 responses. They are omitted during a fail-open limiter outage and are not promised on auth/validation errors, status reads, or idempotent replay. `RateLimit-Reset` is a delay in seconds (0–60), not a Unix timestamp. On 429 also honor `Retry-After`.
      headers:
        Retry-After:
          description: Seconds to wait before retrying
          schema:
            type: integer
            minimum: 1
            maximum: 60
        RateLimit-Limit:
          description: Requests allowed in the sliding window
          schema:
            type: integer
        RateLimit-Remaining:
          description: Requests remaining in the current sliding window
          schema:
            type: integer
        RateLimit-Reset:
          description: Seconds until quota resets (delay, not Unix time)
          schema:
            type: integer
            minimum: 0
            maximum: 60
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    JobAccepted:
      description: Accepted — generation is async. Keep polling the returned job; a queued job may proceed normally or, if transport delivery was truly lost, fail and refund automatically (normally within about 30–45 minutes).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/JobAccepted'
  schemas:
    Error:
      type: object
      required:
      - code
      - detail
      properties:
        code:
          type: string
          example: VALIDATION_ERROR
        detail:
          type: string
        request_id:
          type: string
          description: Echoed in the X-Request-Id header; quote it in support requests
        balance:
          type: integer
          description: 'On 402 (INSUFFICIENT_TOKENS / DAILY_CAP_REACHED): your current token balance'
        cost:
          type: integer
          description: 'On 402: tokens this call needs'
        job_id:
          type: string
          description: 'On 409 IDEMPOTENCY_CONFLICT: the retained job already bound to this key'
    JobAccepted:
      type: object
      description: 'The job was recorded; for billed customer keys, its token cost was reserved. `queued` is also returned when generation-transport delivery is uncertain: poll `status_url` rather than submitting a duplicate. The job may still proceed normally; if it was truly not delivered, it is marked `failed` and refunded, normally within about 30–45 minutes.'
      example:
        job_id: 8f3a1c2e-7b9d-4c2a-9e5f-1a2b3c4d5e6f
        status: queued
        status_url: /api/jobs/8f3a1c2e-7b9d-4c2a-9e5f-1a2b3c4d5e6f
      properties:
        job_id:
          type: string
        status:
          type: string
          example: queued
        status_url:
          type: string
          example: /api/jobs/…
        idempotent_replay:
          type: boolean
          description: Present when an Idempotency-Key matched an existing job
    Balance:
      type: object
      required:
      - client_id
      - balance
      - token_balance
      - reserved
      - available
      - total_liability
      - status
      - song_cost
      - lyrics_cost
      properties:
        client_id:
          type: string
        balance:
          type: integer
          description: Current number of tokens the partner can spend
        token_balance:
          type: integer
          description: Current spendable balance; in-flight holds have already been debited
        reserved:
          type: integer
          description: Tokens held by non-terminal jobs (already debited from token_balance)
        available:
          type: integer
          description: 'Spendable now: max(token_balance, 0)'
        total_liability:
          type: integer
          description: 'Remaining entitlement: max(token_balance + reserved, 0)'
        daily_token_cap:
          type:
          - integer
          - 'null'
        status:
          type: string
        song_cost:
          type: integer
        lyrics_cost:
          type: integer
        rate_limit_rpm:
          type: integer
          description: Per-key requests/minute limit
        concurrency_limit:
          type:
          - integer
          - 'null'
          description: Always null — no hard admission cap
        engine_inflight:
          type:
          - integer
          - 'null'
          description: Jobs currently queued+processing across all clients
    Clip:
      type: object
      example:
        id: f81b3ad8-bc81-4135-80ac-dbddd7bf8b00
        status: complete
        audio_url: https://cdn1.suno.ai/f81b3ad8-bc81-4135-80ac-dbddd7bf8b00.mp3
        image_url: https://cdn2.suno.ai/image_f81b3ad8-bc81-4135-80ac-dbddd7bf8b00.jpeg
        title: Rain On Glass
        model: v6
        tags: lofi hip-hop, rainy, mellow
        duration: 128.16
      properties:
        id:
          type: string
        status:
          type: string
        audio_url:
          type: string
          format: uri
        image_url:
          type: string
          format: uri
          description: Cover art
        title:
          type: string
        model:
          type: string
          description: Actual model reported by Suno (v6 for new v6/v5.5 generations; historical results retain their original model)
        tags:
          type: string
          description: Style / genre tags
        duration:
          type: number
          description: Seconds
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      schema:
        type: string
        maxLength: 128
      description: Reuse for retries of one logical submission to get the same job and avoid duplicate renders/charges. Scoped to your account and bound to the accepted canonical request. Reusing a key with a different prompt, mode, model, generation options, or callback URL returns `409 IDEMPOTENCY_CONFLICT`; use a new key for new work. Maximum 128 UTF-8 bytes. The equivalent v6 and v5.5 model aliases replay the same job when all other fields match. While the job is retained (normally about 7 days), the key replays it even after terminal state; to start a new generation after `failed`, send a NEW Idempotency-Key. After retention expires, the old key may create a new job.
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: Per-customer key, e.g. sk_live_…