{
  "openapi": "3.1.0",
  "info": {
    "title": "Recoup API - Releases",
    "description": "API documentation for the Recoup platform - an AI agent platform for the music industry",
    "license": {
      "name": "MIT"
    },
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "https://api.recoupable.dev"
    }
  ],
  "paths": {
    "/api/catalogs/{catalogId}/streams": {
      "get": {
        "operationId": "getCatalogsByCatalogIdStreams",
        "summary": "Read daily catalog streams",
        "description": "Saved Luminate worldwide aggregate calendar-day streams across reporting DSPs, not Spotify-only or royalty statements. Includes current catalog recordings even when unmeasured, latest collection status, per-recording daily rows/retrieval time, missing dates, equal-period totals and growth. Newer source corrections win; historical versions remain stored. Missing days and changed provider recording identity suppress comparisons. Zero baseline yields null percentage. Page through pagination.has_more; summary_scope is page. Reads make no provider requests. Standard MCP: get_catalog_streams. Requires API and database feature release; unavailable to delegated OAuth. Unchanged daily values reuse their saved versions; new dates and corrections are appended, with fresh coverage/provenance receipts for every run.\n\nUse paging for large catalogs. Accepted date ranges do not establish measurement completeness; inspect per-record coverage and missing data.",
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "catalogId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Catalog owned by the authenticated account or an organization it currently belongs to."
          },
          {
            "name": "since",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Current period start, inclusive. Previous period has the same length and immediately precedes it."
          },
          {
            "name": "days",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 366
            },
            "description": "Comparison period length, 1–366 days; end dates are exclusive. Missing measurements suppress growth. This range does not imply complete backfill. The separate catalog playcount endpoint retains its 31-day limit.",
            "example": 90
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000000,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 25,
              "default": 25
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Saved history and comparisons",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string"
                    },
                    "catalog_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "provider": {
                      "type": "string",
                      "enum": [
                        "luminate"
                      ]
                    },
                    "platform": {
                      "type": "string",
                      "enum": [
                        "all_dsps"
                      ]
                    },
                    "metric": {
                      "type": "string",
                      "enum": [
                        "daily_streams"
                      ]
                    },
                    "latest_run": {
                      "$ref": "#/components/schemas/CatalogStreamRun",
                      "description": "Run status, per-ISRC coverage, errors, window and completion timestamp."
                    },
                    "territory": {
                      "type": "string",
                      "enum": [
                        "worldwide"
                      ]
                    },
                    "semantics": {
                      "type": "string"
                    },
                    "source_updated_at": {
                      "type": "null"
                    },
                    "periods": {
                      "type": "object",
                      "properties": {
                        "previous": {
                          "type": "object",
                          "properties": {
                            "start": {
                              "type": "string",
                              "format": "date"
                            },
                            "end_exclusive": {
                              "type": "string",
                              "format": "date"
                            }
                          }
                        },
                        "current": {
                          "type": "object",
                          "properties": {
                            "start": {
                              "type": "string",
                              "format": "date"
                            },
                            "end_exclusive": {
                              "type": "string",
                              "format": "date"
                            }
                          }
                        },
                        "days": {
                          "type": "integer"
                        },
                        "timezone": {
                          "type": "string",
                          "enum": [
                            "UTC"
                          ]
                        }
                      }
                    },
                    "pagination": {
                      "type": "object",
                      "properties": {
                        "page": {
                          "type": "integer"
                        },
                        "limit": {
                          "type": "integer"
                        },
                        "total_count": {
                          "type": "integer"
                        },
                        "has_more": {
                          "type": "boolean"
                        }
                      }
                    },
                    "summary_scope": {
                      "type": "string",
                      "enum": [
                        "page"
                      ]
                    },
                    "recordings": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "isrc": {
                            "type": "string"
                          },
                          "name": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "provider_recording_id": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "retrieved_at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date-time"
                          },
                          "days": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "date": {
                                  "type": "string",
                                  "format": "date"
                                },
                                "streams": {
                                  "type": [
                                    "integer",
                                    "null"
                                  ]
                                }
                              },
                              "required": [
                                "date",
                                "streams"
                              ]
                            }
                          },
                          "state": {
                            "type": "string",
                            "enum": [
                              "comparable",
                              "incomplete",
                              "unfinished_period",
                              "invalid_observation"
                            ]
                          },
                          "missing_days": {
                            "type": "array",
                            "items": {
                              "type": "string",
                              "format": "date"
                            }
                          },
                          "previous_streams": {
                            "type": [
                              "integer",
                              "null"
                            ]
                          },
                          "current_streams": {
                            "type": [
                              "integer",
                              "null"
                            ]
                          },
                          "absolute_growth": {
                            "type": [
                              "integer",
                              "null"
                            ]
                          },
                          "percentage_growth": {
                            "type": [
                              "number",
                              "null"
                            ]
                          },
                          "zero_baseline": {
                            "type": "boolean"
                          },
                          "latest_attempt": {
                            "$ref": "#/components/schemas/CatalogStreamReceipt",
                            "description": "Fresh recording-level receipt for the latest run, including state, observed_days, retrieved_at, source_hash and provider_recording_id. Observation retrieved_at is when its retained version was introduced."
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or incomplete calendar period"
          },
          "401": {
            "description": "Authentication required"
          },
          "404": {
            "description": "Catalog not accessible"
          },
          "503": {
            "description": "Storage, configuration or dispatch unavailable"
          }
        }
      }
    },
    "/api/catalogs/{catalogId}/stream-tracking": {
      "get": {
        "operationId": "getCatalogsByCatalogIdStreamTracking",
        "summary": "Inspect daily catalog tracking",
        "description": "Read enabled state and latest run coverage without starting provider work. Standard MCP manage_catalog_stream_tracking with action=status. Unchanged daily values reuse their saved versions; new dates and corrections are appended, with fresh coverage/provenance receipts for every run.",
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "catalogId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Catalog owned by the authenticated account or an organization it currently belongs to."
          }
        ],
        "responses": {
          "200": {
            "description": "Collection status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string"
                    },
                    "catalog_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "provider": {
                      "type": "string",
                      "enum": [
                        "luminate"
                      ]
                    },
                    "platform": {
                      "type": "string",
                      "enum": [
                        "all_dsps"
                      ]
                    },
                    "metric": {
                      "type": "string",
                      "enum": [
                        "daily_streams"
                      ]
                    },
                    "latest_run": {
                      "$ref": "#/components/schemas/CatalogStreamRun",
                      "description": "Run status, per-ISRC coverage, errors, window and completion timestamp."
                    },
                    "territory": {
                      "type": "string",
                      "enum": [
                        "worldwide"
                      ]
                    },
                    "tracking": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "catalog_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "owner_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "enabled": {
                          "type": "boolean"
                        },
                        "revision": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    },
                    "collection": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "state": {
                          "type": "string",
                          "enum": [
                            "started",
                            "already_claimed_or_disabled"
                          ]
                        },
                        "run_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "workflow_run_id": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "description": "Authentication required"
          },
          "404": {
            "description": "Catalog not accessible"
          },
          "503": {
            "description": "Storage, configuration or dispatch unavailable"
          }
        }
      },
      "post": {
        "operationId": "postCatalogsByCatalogIdStreamTracking",
        "summary": "Control daily catalog tracking",
        "description": "Enable starts initial 62-day backfill and daily refresh at 09:00 UTC. Source window ends two UTC dates before the run date to allow reporting lag; source freshness is not guaranteed. New recordings join the next run. Maximum 250 recordings per catalog; larger catalogs fail before provider calls. Disable fences future writes and retains saved history. Refresh claims at most one run per UTC day and subscription revision; repeated enable is idempotent. If today was already claimed, collection.state=already_claimed_or_disabled and no duplicate provider work starts; failed claims can run again the next day. Failures are isolated by recording and exposed in coverage. Standard MCP manage_catalog_stream_tracking. Enabled tracking makes asynchronous provider calls. API-key/Privy bearer auth supported; identity overrides rejected. Requires API and database feature release. Unchanged daily values reuse their saved versions; new dates and corrections are appended, with fresh coverage/provenance receipts for every run.",
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "catalogId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Catalog owned by the authenticated account or an organization it currently belongs to."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "action"
                ],
                "additionalProperties": false,
                "properties": {
                  "action": {
                    "type": "string",
                    "enum": [
                      "enable",
                      "disable",
                      "refresh"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tracking state and asynchronous collection receipt",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string"
                    },
                    "catalog_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "provider": {
                      "type": "string",
                      "enum": [
                        "luminate"
                      ]
                    },
                    "platform": {
                      "type": "string",
                      "enum": [
                        "all_dsps"
                      ]
                    },
                    "metric": {
                      "type": "string",
                      "enum": [
                        "daily_streams"
                      ]
                    },
                    "latest_run": {
                      "$ref": "#/components/schemas/CatalogStreamRun",
                      "description": "Run status, per-ISRC coverage, errors, window and completion timestamp."
                    },
                    "territory": {
                      "type": "string",
                      "enum": [
                        "worldwide"
                      ]
                    },
                    "tracking": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "catalog_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "owner_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "enabled": {
                          "type": "boolean"
                        },
                        "revision": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    },
                    "collection": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "state": {
                          "type": "string",
                          "enum": [
                            "started",
                            "already_claimed_or_disabled"
                          ]
                        },
                        "run_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "workflow_run_id": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid input"
          },
          "401": {
            "description": "Authentication required"
          },
          "404": {
            "description": "Catalog not accessible"
          },
          "409": {
            "description": "Tracking must be enabled before refresh"
          },
          "429": {
            "description": "Too many tracking control requests; limited to 10 per account per minute across REST and MCP."
          },
          "503": {
            "description": "Storage, configuration or dispatch unavailable"
          }
        }
      }
    },
    "/api/catalogs/{catalogId}/playcount-history": {
      "get": {
        "operationId": "getCatalogsByCatalogIdPlaycountHistory",
        "summary": "Read catalog playcount history and period comparisons",
        "description": "Read-only, store-backed public Spotify observations for current catalog membership, including unmeasured recordings. No collection, provider calls, charges or cron activation. API keys and Privy bearer tokens supported; identity overrides, unknown and duplicate query parameters rejected. Compare two adjacent equal observation periods with complete daily coverage and at most one hour capture drift. Missing days, invalid counts, negative corrections and excessive drift suppress growth. Real zero values remain zero; zero prior movement produces null percentage growth. Summaries apply only to this page; increment page while pagination.has_more. Catalog edits can change paging. Legacy rows lack historical provider counter identity and upstream update time: comparisons are provisional, not verified same-counter history, exact daily streams, royalties or causal marketing uplift. This endpoint does not import private artist analytics. Standard MCP tool: get_catalog_playcount_history; unavailable for delegated OAuth pending organization-grant audit. Proposed endpoint requires the linked API feature release; documentation is not proof of production availability.",
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "catalogId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Catalog owned by the authenticated account or an organization it currently belongs to."
          },
          {
            "name": "since",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Current UTC observation-period start. Previous period immediately precedes it. Boundaries use the latest observation on that date, not midnight stream totals."
          },
          {
            "name": "days",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 31
            },
            "description": "Equal length of both periods. Every UTC observation day through the final boundary must be finished and captured."
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000000,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 25,
              "default": 25
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Saved observations and page-scoped comparison coverage",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "description": "private, no-store"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "success"
                      ]
                    },
                    "catalog_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "platform": {
                      "type": "string",
                      "enum": [
                        "spotify"
                      ]
                    },
                    "metric": {
                      "type": "string",
                      "enum": [
                        "platform_displayed_play_count"
                      ]
                    },
                    "data_source": {
                      "type": "string",
                      "enum": [
                        "apify_spotify_playcount"
                      ]
                    },
                    "semantics": {
                      "type": "string"
                    },
                    "source_timestamp_available": {
                      "type": "boolean",
                      "enum": [
                        false
                      ]
                    },
                    "provider_identity_available": {
                      "type": "boolean",
                      "enum": [
                        false
                      ]
                    },
                    "comparison_quality": {
                      "type": "string",
                      "enum": [
                        "legacy_recording_series_identity_unverified"
                      ]
                    },
                    "missing_observation_reason_available": {
                      "type": "boolean",
                      "enum": [
                        false
                      ]
                    },
                    "collection_enabled": {
                      "type": "boolean",
                      "enum": [
                        false
                      ]
                    },
                    "periods": {
                      "type": "object",
                      "properties": {
                        "previous": {
                          "type": "object",
                          "properties": {
                            "start": {
                              "type": "string",
                              "format": "date"
                            },
                            "end": {
                              "type": "string",
                              "format": "date"
                            }
                          }
                        },
                        "current": {
                          "type": "object",
                          "properties": {
                            "start": {
                              "type": "string",
                              "format": "date"
                            },
                            "end": {
                              "type": "string",
                              "format": "date"
                            }
                          }
                        },
                        "days": {
                          "type": "integer"
                        },
                        "timezone": {
                          "type": "string",
                          "enum": [
                            "UTC"
                          ]
                        },
                        "boundary_semantics": {
                          "type": "string"
                        },
                        "alignment_tolerance_seconds": {
                          "type": "integer",
                          "enum": [
                            3600
                          ]
                        }
                      }
                    },
                    "pagination": {
                      "type": "object",
                      "properties": {
                        "page": {
                          "type": "integer"
                        },
                        "limit": {
                          "type": "integer"
                        },
                        "total_count": {
                          "type": "integer"
                        },
                        "total_pages": {
                          "type": "integer"
                        },
                        "has_more": {
                          "type": "boolean"
                        }
                      }
                    },
                    "summary_scope": {
                      "type": "string",
                      "enum": [
                        "page"
                      ]
                    },
                    "comparable_recordings": {
                      "type": "integer"
                    },
                    "recordings": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "isrc": {
                            "type": "string"
                          },
                          "name": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "state": {
                            "type": "string",
                            "enum": [
                              "comparable",
                              "incomplete",
                              "counter_correction",
                              "invalid_observation",
                              "unaligned_observations"
                            ]
                          },
                          "observations": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "date": {
                                  "type": "string",
                                  "format": "date"
                                },
                                "captured_at": {
                                  "type": "string",
                                  "format": "date-time"
                                },
                                "value": {
                                  "type": "number"
                                }
                              }
                            }
                          },
                          "missing_days": {
                            "type": "array",
                            "items": {
                              "type": "string",
                              "format": "date"
                            }
                          },
                          "invalid_days": {
                            "type": "array",
                            "items": {
                              "type": "string",
                              "format": "date"
                            }
                          },
                          "correction_days": {
                            "type": "array",
                            "items": {
                              "type": "string",
                              "format": "date"
                            }
                          },
                          "previous_change": {
                            "type": [
                              "number",
                              "null"
                            ]
                          },
                          "current_change": {
                            "type": [
                              "number",
                              "null"
                            ]
                          },
                          "absolute_growth": {
                            "type": [
                              "number",
                              "null"
                            ]
                          },
                          "percentage_growth": {
                            "type": [
                              "number",
                              "null"
                            ],
                            "description": "Percentage change in period counter movement; null for a zero baseline or non-comparable series."
                          },
                          "zero_baseline": {
                            "type": "boolean"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid or unfinished observation-period query"
          },
          "401": {
            "description": "Missing or invalid credentials"
          },
          "404": {
            "description": "Catalog missing or inaccessible"
          },
          "503": {
            "description": "Store unavailable or history exceeds bounded read; use a shorter period"
          }
        }
      }
    },
    "/api/tasks": {
      "get": {
        "operationId": "getTasks",
        "description": "Retrieve scheduled tasks. Each task includes `recent_runs` (last 5 runs), `upcoming` (next scheduled run times) sourced directly from the Trigger.dev API, and `owner_email` when an account email exists for the task owner. Supports filtering by id, account_id, or artist_account_id.\n\nFetching one task by id performs the owner-plan preflight used by the runner and can return 402 plan_limit. Listing tasks remains distinct from permission to execute them.",
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "description": "Filter by task ID (UUID). Returns a single task matching the provided ID. Admin callers may retrieve any task by ID regardless of owner; non-admin callers only receive the task if it belongs to their authenticated account.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "account_id",
            "in": "query",
            "description": "Filter tasks to only include those for the specified account.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "artist_account_id",
            "in": "query",
            "description": "Filter tasks to only include those for the specified artist account.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tasks retrieved successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TasksResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — send exactly one of `x-api-key` or `Authorization: Bearer …`. Missing or conflicting headers return a text `error`; malformed or rejected credentials return a text `message`. Both envelopes include `status: error`; read whichever text field is present.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskAuthenticationErrorResponse"
                },
                "examples": {
                  "missingOrConflictingCredentials": {
                    "summary": "Missing credentials or both credential headers supplied",
                    "value": {
                      "status": "error",
                      "error": "Exactly one of x-api-key or Authorization must be provided"
                    }
                  },
                  "invalidCredential": {
                    "summary": "Rejected API key, whether sent in x-api-key or as a Bearer key",
                    "value": {
                      "status": "error",
                      "message": "Unauthorized"
                    }
                  }
                }
              }
            }
          },
          "402": {
            "description": "A single task fetched by id is blocked by its owner plan. Returns plan_limit with task_count or min_cadence and billingUrl. Ordinary listing does not apply this pre-run gate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the requested `account_id` is outside the authenticated account's authorized access. Read the text `error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskFailureErrorResponse"
                },
                "examples": {
                  "accountOverrideDenied": {
                    "value": {
                      "status": "error",
                      "error": "Access denied to specified account_id"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error while retrieving tasks. Read the text `error`; the message can vary. The examples illustrate the response shape.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskFailureErrorResponse"
                },
                "examples": {
                  "serviceError": {
                    "value": {
                      "status": "error",
                      "error": "Task service unavailable"
                    }
                  },
                  "unexpectedFailure": {
                    "value": {
                      "status": "error",
                      "error": "Internal server error"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "postTasks",
        "description": "Create a new scheduled task that runs a prompt against an artist on a recurring cron schedule. The response matches the [GET endpoint](/api-reference/tasks/get) (a `TasksResponse` with the created task in the `tasks` array).",
        "requestBody": {
          "description": "Task to create",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateTaskRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Task created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TasksResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request — invalid JSON or request-body schema validation failure, checked before authentication. Read the text `error`; `missing_fields` identifies the first schema issue and is omitted for malformed JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpdateTaskValidationErrorResponse"
                },
                "examples": {
                  "invalidJson": {
                    "summary": "Malformed JSON body",
                    "value": {
                      "status": "error",
                      "error": "Invalid JSON body"
                    }
                  },
                  "invalidScheduleType": {
                    "summary": "Schedule supplied as a number instead of a string",
                    "value": {
                      "status": "error",
                      "missing_fields": [
                        "schedule"
                      ],
                      "error": "Invalid input: expected string, received number"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — send exactly one of `x-api-key` or `Authorization: Bearer …`. Missing or conflicting headers return a text `error`; malformed or rejected credentials return a text `message`. Both envelopes include `status: error`; read whichever text field is present.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskAuthenticationErrorResponse"
                },
                "examples": {
                  "missingOrConflictingCredentials": {
                    "summary": "Missing credentials or both credential headers supplied",
                    "value": {
                      "status": "error",
                      "error": "Exactly one of x-api-key or Authorization must be provided"
                    }
                  },
                  "invalidCredential": {
                    "summary": "Rejected API key, whether sent in x-api-key or as a Bearer key",
                    "value": {
                      "status": "error",
                      "message": "Unauthorized"
                    }
                  }
                }
              }
            }
          },
          "402": {
            "description": "Payment required - the account's plan does not allow this task. `limit` is `task_count` when the account already has `task_limit` enabled tasks, or `min_cadence` when the cron schedule fires more often than `min_cadence_minutes` allows. Nothing is written; upgrade at `billingUrl`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                },
                "examples": {
                  "taskCount": {
                    "summary": "Free account creating a second task",
                    "value": {
                      "status": "error",
                      "error": "plan_limit",
                      "limit": "task_count",
                      "message": "Free includes 1 task. Starter includes 3, Pro is unlimited.",
                      "plan": "free",
                      "task_limit": 1,
                      "min_cadence_minutes": 10080,
                      "current_task_count": 1,
                      "billingUrl": "https://app.recoupable.dev/plan"
                    }
                  },
                  "minCadence": {
                    "summary": "Free account scheduling a daily task",
                    "value": {
                      "status": "error",
                      "error": "plan_limit",
                      "limit": "min_cadence",
                      "message": "Free runs tasks weekly at most. Starter runs daily, Pro runs hourly.",
                      "plan": "free",
                      "task_limit": 1,
                      "min_cadence_minutes": 10080,
                      "current_task_count": 0,
                      "billingUrl": "https://app.recoupable.dev/plan"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the requested `account_id` is outside the authenticated account's authorized access. Read the text `error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskFailureErrorResponse"
                },
                "examples": {
                  "accountOverrideDenied": {
                    "value": {
                      "status": "error",
                      "error": "Access denied to specified account_id"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error while creating the task. Read the text `error`; the message can vary. The examples illustrate the response shape.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskFailureErrorResponse"
                },
                "examples": {
                  "serviceError": {
                    "value": {
                      "status": "error",
                      "error": "Task service unavailable"
                    }
                  },
                  "unexpectedFailure": {
                    "value": {
                      "status": "error",
                      "error": "Internal server error"
                    }
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "patch": {
        "operationId": "patchTasks",
        "summary": "Update scheduled task",
        "description": "Update an existing scheduled task. Only the id field is required; any additional fields you include will be updated on the task. The response shape matches the GET endpoint (an array containing the updated task).",
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "description": "JSON object with `id` and optional fields to merge onto the task.",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateTaskRequest"
              },
              "examples": {
                "updateTitleAndSchedule": {
                  "summary": "Change title and cron",
                  "value": {
                    "id": "aade2bce-55c7-468e-a606-c4e76fb2ea2a",
                    "title": "Weekly Genre Pulse Check (rev)",
                    "schedule": "0 10 * * 4"
                  }
                },
                "toggleEnabled": {
                  "summary": "Pause or resume a task",
                  "value": {
                    "id": "aade2bce-55c7-468e-a606-c4e76fb2ea2a",
                    "enabled": false
                  }
                },
                "changeModel": {
                  "summary": "Switch model only",
                  "value": {
                    "id": "aade2bce-55c7-468e-a606-c4e76fb2ea2a",
                    "model": "claude-sonnet-4-20250514"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Task updated successfully. Body is a `TasksResponse` with the updated task in `tasks` (typically one task).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TasksResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request — invalid JSON or request-body schema validation failure, checked before authentication. Read the text `error`; `missing_fields` identifies the first schema issue and is omitted for malformed JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpdateTaskValidationErrorResponse"
                },
                "examples": {
                  "invalidJson": {
                    "summary": "Malformed JSON body",
                    "value": {
                      "status": "error",
                      "error": "Invalid JSON body"
                    }
                  },
                  "invalidScheduleType": {
                    "summary": "Schedule supplied as a number instead of a string",
                    "value": {
                      "status": "error",
                      "missing_fields": [
                        "schedule"
                      ],
                      "error": "Invalid input: expected string, received number"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — send exactly one of `x-api-key` or `Authorization: Bearer …`. Missing or conflicting headers return a text `error`; malformed or rejected credentials return a text `message`. Both envelopes include `status: error`; read whichever text field is present.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskAuthenticationErrorResponse"
                },
                "examples": {
                  "missingOrConflictingCredentials": {
                    "summary": "Missing credentials or both credential headers supplied",
                    "value": {
                      "status": "error",
                      "error": "Exactly one of x-api-key or Authorization must be provided"
                    }
                  },
                  "invalidCredential": {
                    "summary": "Rejected API key, whether sent in x-api-key or as a Bearer key",
                    "value": {
                      "status": "error",
                      "message": "Unauthorized"
                    }
                  }
                }
              }
            }
          },
          "402": {
            "description": "Payment required - the account's plan does not allow this task. `limit` is `task_count` when the account already has `task_limit` enabled tasks, or `min_cadence` when the cron schedule fires more often than `min_cadence_minutes` allows. Nothing is written; upgrade at `billingUrl`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                },
                "examples": {
                  "taskCount": {
                    "summary": "Free account re-enabling a disabled task while another is enabled",
                    "value": {
                      "status": "error",
                      "error": "plan_limit",
                      "limit": "task_count",
                      "message": "Free includes 1 task. Starter includes 3, Pro is unlimited.",
                      "plan": "free",
                      "task_limit": 1,
                      "min_cadence_minutes": 10080,
                      "current_task_count": 1,
                      "billingUrl": "https://app.recoupable.dev/plan"
                    }
                  },
                  "minCadence": {
                    "summary": "Free account changing a task to a daily schedule",
                    "value": {
                      "status": "error",
                      "error": "plan_limit",
                      "limit": "min_cadence",
                      "message": "Free runs tasks weekly at most. Starter runs daily, Pro runs hourly.",
                      "plan": "free",
                      "task_limit": 1,
                      "min_cadence_minutes": 10080,
                      "current_task_count": 0,
                      "billingUrl": "https://app.recoupable.dev/plan"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the requested account selection is not allowed, or that account is not allowed to change this task. Read the text `error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskFailureErrorResponse"
                },
                "examples": {
                  "accountOverrideDenied": {
                    "value": {
                      "status": "error",
                      "error": "Access denied to specified account_id"
                    }
                  },
                  "taskAccessDenied": {
                    "value": {
                      "status": "error",
                      "error": "Access denied to this task"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No task exists with the given `id`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpdateTaskFailureResponse"
                },
                "examples": {
                  "notFound": {
                    "summary": "Unknown task id",
                    "value": {
                      "status": "error",
                      "error": "Task not found"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error while updating the task or syncing the Trigger.dev schedule.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpdateTaskFailureResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteTasks",
        "summary": "Delete scheduled task",
        "description": "Delete an existing scheduled task by `id`. Returns only the delete operation status.",
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "description": "JSON object containing the task `id` to delete.",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeleteTaskRequest"
              },
              "examples": {
                "deleteTask": {
                  "summary": "Delete one task",
                  "value": {
                    "id": "aade2bce-55c7-468e-a606-c4e76fb2ea2a"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Task deleted successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteTaskResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request — missing `id` or invalid request body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteTaskValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — send exactly one of `x-api-key` or `Authorization: Bearer …`. Missing or conflicting headers return a text `error`; malformed or rejected credentials return a text `message`. Both envelopes include `status: error`; read whichever text field is present.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskAuthenticationErrorResponse"
                },
                "examples": {
                  "missingOrConflictingCredentials": {
                    "summary": "Missing credentials or both credential headers supplied",
                    "value": {
                      "status": "error",
                      "error": "Exactly one of x-api-key or Authorization must be provided"
                    }
                  },
                  "invalidCredential": {
                    "summary": "Rejected API key, whether sent in x-api-key or as a Bearer key",
                    "value": {
                      "status": "error",
                      "message": "Unauthorized"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the authenticated account is not allowed to delete this task. Read the text `error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskFailureErrorResponse"
                },
                "examples": {
                  "taskAccessDenied": {
                    "value": {
                      "status": "error",
                      "error": "Access denied to this task"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No task exists with the given `id`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteTaskFailureResponse"
                },
                "examples": {
                  "notFound": {
                    "summary": "Unknown task id",
                    "value": {
                      "status": "error",
                      "error": "Task not found"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error while deleting the task or deleting its Trigger.dev schedule.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteTaskFailureResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/tasks/runs": {
      "get": {
        "operationId": "getTasksRuns",
        "summary": "Get task runs",
        "description": "Returns task runs for the authenticated account. When `runId` is provided, the response contains that single run (`runs` length 1) or 404 if not found. When `runId` is omitted, returns recent runs filtered by account context (default authenticated account, or `account_id` override when authorized).",
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "runId",
            "in": "query",
            "description": "The unique identifier of a specific task run to retrieve. If omitted, returns a list of recent runs for the authenticated account.",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum number of runs to return when listing (ignored when runId is provided). Default 20, max 100.",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 20,
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "account_id",
            "in": "query",
            "description": "Filter runs by account ID. When provided, returns runs tagged with account:<account_id>. Only applicable when the authenticated account has access to multiple accounts via organization membership.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Task runs retrieved successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskRunListResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request - invalid query parameters (for example non-numeric `limit`, out-of-range `limit`, or invalid empty values).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskRunsErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskRunsErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden - account_id is not accessible with the provided credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskRunsErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Task run not found (only when runId is provided)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskRunsErrorResponse"
                },
                "examples": {
                  "notFound": {
                    "summary": "Unknown run id",
                    "value": {
                      "status": "error",
                      "error": "Task run not found"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error while fetching runs from Trigger.dev.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskRunsErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/artists": {
      "get": {
        "operationId": "getArtists",
        "description": "Retrieve artists accessible to the authenticated account. The account is derived from the API key or Bearer token. When org_id is omitted, returns only the account's own artists. Pass org_id to view artists in a specific organization. Pass account_id to filter to a specific account the API key has access to.",
        "parameters": [
          {
            "name": "account_id",
            "in": "query",
            "description": "Filter to a specific account's artists. Only applicable when the authenticated account has access to multiple accounts via organization membership.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "org_id",
            "in": "query",
            "description": "Filter to artists in a specific organization. When omitted, returns only personal (non-organization) artists.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Artists retrieved successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArtistsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request - invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArtistsErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - missing or invalid authentication"
          },
          "403": {
            "description": "Forbidden - account_id is not accessible with the provided credentials"
          }
        }
      },
      "post": {
        "operationId": "postArtists",
        "description": "Create a new artist account. When spotify_artist_id is provided and a canonical artist already exists for it, the existing artist is linked to the account instead of creating a duplicate. The artist can optionally be linked to an organization.",
        "requestBody": {
          "description": "Artist creation parameters",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateArtistRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Existing canonical artist for the given spotify_artist_id linked to the account (no new artist created)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateArtistResponse"
                }
              }
            }
          },
          "201": {
            "description": "Artist created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateArtistResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request - validation error or invalid JSON",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateArtistError"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/artists/{id}": {
      "patch": {
        "operationId": "patchArtistsById",
        "description": "Update an artist. All body fields are optional, but at least one must be provided. Use this endpoint to set basic profile (name, image, label), AI instructions, knowledge base entries, social profile URLs (mapped by platform), and pinned state for the calling account.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Artist account ID to update.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "description": "Fields to update. At least one field must be provided.",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateArtistRequest"
              },
              "examples": {
                "pin": {
                  "summary": "Pin for the authenticated account",
                  "value": {
                    "pinned": true
                  }
                },
                "unpin": {
                  "summary": "Unpin for the authenticated account",
                  "value": {
                    "pinned": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Artist updated successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpdateArtistResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request - validation error (e.g. invalid image URL, no fields provided)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpdateArtistErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - missing or invalid authentication"
          },
          "403": {
            "description": "Forbidden - the authenticated account cannot update this artist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpdateArtistErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Artist not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpdateArtistErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpdateArtistErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteArtistsById",
        "description": "Delete an artist accessible to the authenticated account. This removes the caller's direct artist link and, if no owner links remain, deletes the artist account itself.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Artist account ID to delete.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "description": "Optional account context override.",
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "account_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "UUID of the account whose artist should be deleted. Only applicable when the authenticated account has access to multiple accounts via organization membership or Recoup admin access. If not provided, the deletion runs in the API key's own account context."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Artist deleted successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteArtistResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request - account_id is not a valid UUID",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteArtistErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - missing or invalid authentication"
          },
          "403": {
            "description": "Forbidden - the authenticated account cannot delete this artist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteArtistErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Artist not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteArtistErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/artists/{id}/profile": {
      "get": {
        "operationId": "getArtistsByIdProfile",
        "description": "Public artist profile: the artist's name, image, connected social profiles, and linked catalogs in one call.\n\n**No authentication.** This endpoint is deliberately public and unbilled — it backs the shareable artist page at `chat.recoupable.dev/artists/{id}` and can be called without an API key. Only public fields are returned; account settings, instructions, and valuation data are never included.\n\nResponses are cacheable (`Cache-Control: public, s-maxage=300, stale-while-revalidate=600`), so a value may be up to a few minutes stale.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "The unique identifier (UUID) of the artist account.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Artist profile retrieved successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArtistPublicProfileResponse"
                }
              }
            }
          },
          "404": {
            "description": "No artist with this id. Returned for unknown ids and for accounts that are not artists — the two cases are deliberately indistinguishable, so the endpoint cannot be used to probe which account ids exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArtistPublicProfileErrorResponse"
                },
                "example": {
                  "status": "error",
                  "error": "Artist not found"
                }
              }
            }
          }
        }
      }
    },
    "/api/artists/{id}/socials": {
      "get": {
        "operationId": "getArtistsByIdSocials",
        "description": "Retrieve all social media profiles associated with an artist, with the latest follower counts from the most recent scrape. Call this before the Social Posts or Social Scrape endpoints to obtain the social IDs. Pass `history` to also get each profile's follower snapshots, so week-over-week movement is a read instead of something the caller has to remember.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "The unique identifier of the artist account to fetch social profiles for",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "The page number to retrieve (default: 1)",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "The number of records per page (default: 20, max: 100)",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 20,
              "maximum": 100
            }
          },
          {
            "name": "history",
            "in": "query",
            "description": "Number of days of follower snapshots to include per profile as `history`, newest first. Snapshots are written by every scrape that reports a follower count, one point per social per day (the latest scrape that day wins), so a profile has one point after its first scrape and a second only after a scrape on a later day. Omitted: no `history` field on the profiles.",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 90
            },
            "example": 14
          }
        ],
        "responses": {
          "200": {
            "description": "Social profiles retrieved successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArtistSocialsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request - invalid `id`, `page`, `limit` or `history`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArtistSocialsErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArtistSocialsErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden - caller does not have access to this artist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArtistSocialsErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Artist not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArtistSocialsErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/artists/{id}/socials/{socialId}": {
      "delete": {
        "operationId": "deleteArtistsByIdSocialsBySocialId",
        "description": "Remove a social profile link from an artist. Use this to correct a wrongly auto-matched social. This unlinks the social from the artist account; it does not delete the underlying social record, which may still be linked to other accounts.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "The unique identifier of the artist account the social is linked to",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "socialId",
            "in": "path",
            "description": "The unique identifier (UUID) of the social profile to unlink from the artist",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Social profile unlinked successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteArtistSocialResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request - id or socialId is not a valid UUID",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteArtistErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - missing or invalid authentication"
          },
          "403": {
            "description": "Forbidden - the authenticated account cannot access this artist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteArtistErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Artist not found or the social is not linked to this artist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteArtistErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/artist/socials/scrape": {
      "post": {
        "operationId": "postArtistSocialsScrape",
        "description": "Trigger scrape jobs for all social profiles linked to an artist. Returns a runId per social profile that you can poll for status and results via the [Scraper Results API](/api-reference/apify/scraper).\n\n**Credits:** `50,000 + 10,000 × posts` micro-dollar credits per profile: $0.05 base + $0.01 per requested post. Omitting posts uses the 50,000 base. The upfront balance check covers every linked profile; deduction covers only profiles with a started scraper run ID. Per-profile start errors remain in the response and are not charged when no run starts. For two linked profiles and posts=20, the balance gate requires 500,000 credits ($0.50); one started run charges 250,000 credits ($0.25).",
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "description": "Artist to scrape socials for",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ArtistSocialsScrapeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Scrape jobs triggered successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArtistSocialsScrapeResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing required parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArtistSocialsErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArtistSocialsErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient credits — the body includes a static `billingUrl`. No Stripe object is created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArtistSocialsInsufficientCreditsResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden - caller does not have access to this artist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArtistSocialsErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/artists/{id}/fans": {
      "get": {
        "operationId": "getArtistsByIdFans",
        "description": "Retrieve the social profiles of an artist's fans: the accounts that commented on the artist's scraped posts (Instagram today). Every Instagram scrape is followed by one batched profile run over the new commenters, which fills `avatar`, `bio`, `follower_count` and `following_count` on each fan; `region` is null unless the platform reports one. Supports pagination for large fan lists.\n\n`last_comment_at` is the timestamp of the latest stored comment linked to that fan relationship, when the comment's author matches the returned fan. Missing or mismatched comment evidence returns `null`; it does not mean the fan has never commented. Profile refresh time and other engagement timestamps are not substituted. This is stored evidence, not a live scrape or exhaustive interaction history, and older relationship links are not guaranteed backfilled. Existing ordering, pagination and duplicate fan rows across relationships are unchanged.\n\nThe caller must be authenticated and have access to the artist. This field supports a report's Last heard column; it does not itself return comment bodies. The separate GET /api/comments operation reads saved comment bodies within its authorized linked-post scope, pagination and coverage limits.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "The unique identifier of the artist account to fetch fans for",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "The page number to retrieve (default: 1)",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "The number of records per page (default: 20, max: 100)",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 20,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Artist fans retrieved successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArtistFansResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing required parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArtistFansErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArtistFansErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArtistFansErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/songs": {
      "get": {
        "operationId": "getSongs",
        "description": "Retrieve songs from the database with optional filtering by ISRC (International Standard Recording Code) or artist account. This endpoint joins the songs table with song_artists and accounts tables to provide comprehensive song information.",
        "parameters": [
          {
            "name": "isrc",
            "in": "query",
            "description": "International Standard Recording Code to filter by specific song",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "artist_account_id",
            "in": "query",
            "description": "Artist account ID to filter songs by artist",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Songs retrieved successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SongsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request - invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SongsErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SongsErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SongsErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/accounts/{id}/catalogs": {
      "get": {
        "operationId": "getAccountsByIdCatalogs",
        "description": "Retrieve the catalogs visible to a specific account. Returns the catalogs the account owns directly, plus the catalogs owned by any organization the account belongs to - so every member of an organization sees that organization's catalogs without passing the organization's id. A catalog owned both directly and through an organization is returned once. An account that belongs to no organization receives only its own catalogs. Create an organization-owned catalog with [Create catalog](/api-reference/songs/catalogs-create) or [Run valuation](/api-reference/songs/valuation-run).\n\nEach catalog carries its estimated value band and its owner, so a list of catalogs can be ranked by worth and a member can tell an organization's catalog from their own. `valuation` is null for a catalog with no measured songs; `owner.image` is null when the owner account has no avatar.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "The unique identifier of the account to query. The catalogs of every organization this account belongs to are included in the response.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Catalogs retrieved successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing or invalid account id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogsErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - missing or invalid authentication",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogsErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden - caller lacks access to the target account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogsErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Account not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogsErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogsErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/catalogs": {
      "post": {
        "operationId": "postCatalogs",
        "description": "Create a catalog, owned by the calling account or - with organization_id - by one of the caller's organizations, in which case every member of that organization sees it via [Get Catalogs](/api-reference/songs/catalogs). When materializing from a valuation snapshot, the endpoint also attaches the snapshot's canonical artist to the caller's roster: the measured songs' existing song-artist links are resolved to the dominant artist account, which is added to the caller's roster if not already present. The attach also runs on idempotent re-claims of an already-materialized snapshot. The roster attach always targets the calling account, even when the catalog is owned by an organization.",
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "description": "Catalog to create. Provide name, snapshot, or both - at least one is required.",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCatalogRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Catalog created, or the existing catalog returned when re-materializing the same snapshot (idempotent)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateCatalogResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request - neither name nor snapshot provided, or invalid snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogSongsErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogSongsErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden - the snapshot belongs to a different account, or the caller is not a member of the organization named by organization_id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogSongsErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found - no snapshot exists for the supplied snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogSongsErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/runs": {
      "get": {
        "operationId": "getRuns",
        "summary": "Get runs",
        "description": "Get background runs, newest first. A run is the generic status resource for long-running work. `kind` selects the run type (`valuation` and `music` today); future kinds are new enum values here, never new endpoints.",
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "kind",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "valuation",
                "music"
              ]
            },
            "description": "Required. The run type to list. Unknown kinds are rejected with 400."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 20,
              "default": 1
            },
            "description": "Optional. Maximum runs to return, newest first. Defaults to 1 (the latest run)."
          }
        ],
        "responses": {
          "200": {
            "description": "The calling account's runs of the requested kind, newest first. Empty when the account has never run one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetRunsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Missing or unknown kind, or invalid limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogSongsErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogSongsErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/valuation": {
      "post": {
        "operationId": "postValuation",
        "summary": "Run valuation",
        "description": "Generate a catalog from a Spotify artist in one call. Resolves the artist's releases, captures current Spotify play counts (spending the account's credits), materializes a catalog from the resulting snapshot (idempotent - see [Create catalog](/api-reference/songs/catalogs-create)), and returns the catalog with its estimated value band. The catalog is owned by the calling account unless organization_id names one of the caller's organizations to own it instead, in which case every member of that organization sees it. The calling account is always taken from the credentials, never the body, and it is the account whose credits are spent. The searched artist is also linked to the caller's roster (so a funnel signup lands with a populated `GET /api/artists` it can confirm); when the catalog's songs already resolve a canonical artist that one is used, otherwise the searched Spotify artist is linked directly. Synchronous: the request waits for the capture to land (typically under two minutes). Each run also persists a row in the catalog's valuation history, readable via [Get Catalog Valuations](/api-reference/songs/catalog-valuations).",
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "description": "The Spotify artist to value.",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RunValuationRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Catalog created (or idempotently re-fetched) with its estimated value band.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RunValuationResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing or invalid spotify_artist_id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogSongsErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogSongsErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient credits to measure the catalog",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogSongsErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden - the caller is not a member of the organization named by organization_id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogSongsErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found - no releases found for the Spotify artist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogSongsErrorResponse"
                }
              }
            }
          },
          "504": {
            "description": "The measurement did not complete within the wait window - retry shortly",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogSongsErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/catalogs/songs": {
      "get": {
        "operationId": "getCatalogsSongs",
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "description": "Retrieve songs in an accessible catalog with pagination. When a nonempty artistName is supplied, only songs with a matching artist name contribute to the results and total count. Without that filter, songs without a linked artist remain eligible. A successful query with no matching songs returns HTTP 200 with songs: [] and pagination.total_count: 0.",
        "parameters": [
          {
            "name": "catalog_id",
            "in": "query",
            "description": "The unique identifier of the catalog to query songs for",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "artistName",
            "in": "query",
            "description": "Optional. Filters songs to only include those with matching artist name",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "Page number for pagination (default: 1)",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Number of songs per page (default: 20, max: 100)",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 20,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Catalog songs retrieved successfully, including an empty songs array and zero total count when no songs match.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogSongsResponse"
                },
                "examples": {
                  "empty": {
                    "summary": "Accessible catalog has no matching songs",
                    "value": {
                      "status": "success",
                      "songs": [],
                      "pagination": {
                        "total_count": 0,
                        "page": 1,
                        "limit": 20,
                        "total_pages": 0
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing or invalid catalog_id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogSongsErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogSongsErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden - the catalog is missing or does not belong to the authenticated account or one of its organizations",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogSongsErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server or database failure. Returns status: error and error: Internal server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogSongsErrorResponse"
                },
                "example": {
                  "status": "error",
                  "error": "Internal server error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "postCatalogsSongs",
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "description": "Batch add songs to a catalog by ISRC. For each song, the API attempts to look up metadata via internal search. If no data is found, optional fallback fields (name, album, notes, artists) are used.",
        "requestBody": {
          "description": "Array of songs to add to catalog",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddCatalogSongsRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Songs added to catalog successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogSongsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing required fields",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogSongsErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogSongsErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden - the catalog does not belong to the authenticated account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogSongsErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteCatalogsSongs",
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "description": "Batch remove songs from a catalog by ISRC. Deletes the relationship in catalog_songs for each catalog_id and ISRC pair.",
        "requestBody": {
          "description": "Array of songs to remove from catalog",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeleteCatalogSongsRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Songs removed from catalog successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogSongsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing catalog_id or isrc",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogSongsErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogSongsErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden - the catalog does not belong to the authenticated account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogSongsErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/catalogs/{catalogId}": {
      "patch": {
        "operationId": "patchCatalogsByCatalogId",
        "description": "Rename a catalog. The only mutable field is the display name — catalog membership is managed through [Add catalog songs](/api-reference/songs/catalog-songs-add) and [Remove catalog songs](/api-reference/songs/catalog-songs-delete).\n\nCatalogs created by [Run valuation](/api-reference/songs/valuation-run) are named after the measured artist. Catalogs created before that shipped are all named `Valuation Catalog` — this endpoint is how you give them real names.",
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "catalogId",
            "in": "path",
            "description": "ID of the catalog to rename.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "description": "The new catalog name.",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateCatalogRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Catalog renamed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpdateCatalogResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request - catalogId is not a valid UUID, or name is missing or empty",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogsErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogsErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Catalog not found - no catalog with this ID is visible to the caller. A catalog that exists but belongs to neither the authenticated account nor one of its organizations returns 404, not 403: the same visibility rule the catalog read paths use, so a catalog you cannot see is indistinguishable from one that does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogsErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogsErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteCatalogsByCatalogId",
        "description": "Delete a catalog. Removes the catalog, its song membership (catalog_songs), its ownership links (account_catalogs) and its stored valuation history (catalog_valuations).\n\nThe measurement that produced the catalog is **not** deleted. A playcount snapshot is a metered capture that spent credits and cannot be reproduced for a past date, while a catalog is a label over songs that can be rebuilt from it in one call — so deleting a catalog only clears the snapshot's reference to it. The snapshot then reads as unclaimed, and [Create catalog](/api-reference/songs/catalogs-create) can re-materialize it under a correct name. The released snapshot IDs are returned so that recovery does not require hunting for them.\n\nUse this for genuine duplicates. To fix a catalog's name, use [Rename catalog](/api-reference/songs/catalog-rename) instead — it keeps the catalog's measurements and valuation history intact.",
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "catalogId",
            "in": "path",
            "description": "ID of the catalog to delete.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Catalog deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteCatalogResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request - catalogId is not a valid UUID",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogsErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogsErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Catalog not found - no catalog with this ID is visible to the caller. A catalog that exists but belongs to neither the authenticated account nor one of its organizations returns 404, not 403: the same visibility rule the catalog read paths use, so a catalog you cannot see is indistinguishable from one that does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogsErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogsErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/catalogs/{catalogId}/measurements": {
      "get": {
        "operationId": "getCatalogsByCatalogIdMeasurements",
        "description": "Get the latest play counts and a derived valuation band for a catalog. Measurements are captured by [Create measurement job](/api-reference/research/measurement-jobs) runs; the band is computed at read time from the latest capture per song.",
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "catalogId",
            "in": "path",
            "description": "The unique identifier of the catalog to read measurements for. The catalog must belong to the authenticated account. Malformed (non-uuid) values are rejected with 400.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "account_id",
            "in": "query",
            "description": "Act on behalf of this account (UUID). Only applicable when the authenticated account has access to the target account, for example an organization key acting for a member account. The catalog ownership check then runs against this account instead of the caller. Omitted: the caller's own account.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "artist_account_id",
            "in": "query",
            "description": "Optional artist account to scope the read to. When present, measurements and the valuation band cover only the catalog's songs linked to this artist account (via song_artists); when absent, the whole catalog. A song linked to multiple artists counts for each of them. An artist with no linked measured songs in the catalog yields measured_song_count 0 and an empty measurements array. Malformed (non-uuid) values are rejected with 400.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "Page number of the measurements array (default 1). Pagination only windows the measurements rows - the aggregates always cover the entire scope. Invalid values are rejected with 400.",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Number of measurement rows per page (default 50, max 100). Invalid values are rejected with 400.",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Latest per-song measurements and the derived valuation band",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogMeasurementsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request - malformed catalogId or artist_account_id, or invalid page/limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogSongsErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogSongsErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found - the catalog does not exist or belongs to a different account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogSongsErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/songs/analyze/presets": {
      "get": {
        "operationId": "getSongsAnalyzePresets",
        "description": "Lists all available music analysis presets. Each preset is a curated prompt with optimized generation parameters for a specific use case (e.g. catalog metadata enrichment, sync licensing analysis, audience profiling). Requires authentication via API key or Bearer token.",
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Presets listed successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "success"
                      ]
                    },
                    "presets": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string",
                            "description": "Preset identifier to pass in the analyze request"
                          },
                          "label": {
                            "type": "string",
                            "description": "Human-readable preset name"
                          },
                          "description": {
                            "type": "string",
                            "description": "What this preset does"
                          },
                          "requiresAudio": {
                            "type": "boolean",
                            "description": "Always `true`: every analyze request requires `audio_url`, whichever preset or prompt it uses. Kept on the response for compatibility.",
                            "example": true
                          },
                          "responseFormat": {
                            "type": "string",
                            "enum": [
                              "json",
                              "text"
                            ],
                            "description": "Expected response format"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — invalid or missing API key / Bearer token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SongAnalyzeErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/songs/analyze": {
      "post": {
        "operationId": "postSongsAnalyze",
        "description": "Analyze music with an audio language model that listens directly to the audio waveform.\n\nSend `audio_url` with exactly one of `preset` (a curated analysis, listed by [`GET /api/songs/analyze/presets`](/api-reference/songs/analyze-presets)) or `prompt` (a free-form question). `preset: \"full_report\"` runs all 13 sections and returns them together in `report`.\n\nA `full_report` response has `status: \"success\"` when every section returned and `status: \"partial\"` when some did not. A `partial` response is still HTTP `200`: `failed` lists the report keys of the sections that failed, and each of those keys holds `{ \"error\": \"Section failed: ...\" }` in place of its result. If every section failed, the response is `502` instead.\n\nPricing: `$0.05` per model call plus `$0.001166` per second of inference time. `full_report` makes 13 calls, so its base prices add up to `$0.65`. A call that fails is not charged: failed sections of a `partial` report and requests answered with `502` cost nothing for the failed work. To ask many short questions about one file, use [`POST /api/songs/analyze/batch`](/api-reference/songs/analyze-batch), which pays the base price once.",
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "description": "Music analysis request",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SongAnalyzeRequest"
              },
              "examples": {
                "preset": {
                  "summary": "One structured analysis",
                  "value": {
                    "preset": "mood_tags",
                    "audio_url": "https://example.com/song.m4a"
                  }
                },
                "prompt": {
                  "summary": "A free-form question",
                  "value": {
                    "prompt": "Describe the genre, tempo, and mood of this track.",
                    "audio_url": "https://example.com/song.mp3",
                    "max_new_tokens": 512
                  }
                },
                "fullReport": {
                  "summary": "All 13 sections",
                  "value": {
                    "preset": "full_report",
                    "audio_url": "https://example.com/song.mp3"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Music analysis completed. A single preset or custom prompt answers `success` with `response`. A `full_report` answers `success` with every section, or `partial` with `failed` naming the sections that did not return.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SongAnalyzeResponse"
                },
                "examples": {
                  "preset": {
                    "summary": "A structured preset",
                    "value": {
                      "status": "success",
                      "preset": "mood_tags",
                      "response": {
                        "tags": [
                          "dreamy",
                          "late-night",
                          "warm",
                          "nostalgic",
                          "mellow",
                          "hazy",
                          "intimate",
                          "slow-burn"
                        ],
                        "primary_mood": "dreamy"
                      },
                      "elapsed_seconds": 2.87
                    }
                  },
                  "prompt": {
                    "summary": "A custom prompt",
                    "value": {
                      "status": "success",
                      "response": "A mid-tempo indie pop track, around 96 BPM, with a warm and nostalgic mood.",
                      "elapsed_seconds": 4.12
                    }
                  },
                  "partial": {
                    "summary": "A partial full report, shown with three of its thirteen sections (every response carries all thirteen)",
                    "value": {
                      "status": "partial",
                      "preset": "full_report",
                      "report": {
                        "mood": {
                          "tags": [
                            "dreamy",
                            "late-night",
                            "warm",
                            "nostalgic",
                            "mellow",
                            "hazy",
                            "intimate",
                            "slow-burn"
                          ],
                          "primary_mood": "dreamy"
                        },
                        "lyrics": {
                          "error": "Section failed: Music analysis failed (status 422)"
                        },
                        "sync": {
                          "error": "Section failed: Music analysis failed (status 422)"
                        }
                      },
                      "elapsed_seconds": 41.6,
                      "failed": [
                        "lyrics",
                        "sync"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request — missing or invalid fields, including a missing `audio_url` (`missing_fields: [\"audio_url\"]`) or both `preset` and `prompt` in one request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SongAnalyzeErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — invalid or missing API key / Bearer token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SongAnalyzeErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient credits — the balance cannot cover the base price of this request (`$0.05` per model call, `$0.65` for `full_report`). The model is not called, nothing is charged, and no Stripe object is created. The body includes a static `billingUrl`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SongAnalyzeInsufficientCreditsResponse"
                }
              }
            }
          },
          "422": {
            "description": "Unverifiable audio. Before the model is called, `audio_url` is checked with a `HEAD` request (a ranged `GET` for hosts that reject `HEAD`). It must answer 2xx within 10 seconds with an `audio/*` or `application/octet-stream` content type. When it does not, the model is not called and nothing is charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SongAnalyzeAudioUrlErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error. The request failed for a reason other than a rejection from the model service, for example the model service could not be reached or answered with an unexpected body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SongAnalyzeErrorResponse"
                }
              }
            }
          },
          "502": {
            "description": "The model service could not produce the analysis. Either it rejected a single preset or custom-prompt call (`upstream_status` and `detail` say why, for example `422` with `audio_unreachable` or `audio_undecodable`), or every section of a `full_report` failed (`failed` lists them). Nothing is charged for the failed work.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SongAnalyzeUpstreamErrorResponse"
                },
                "examples": {
                  "rejectedCall": {
                    "summary": "The model service could not read the audio",
                    "value": {
                      "status": "error",
                      "error": "Music analysis failed (status 422)",
                      "upstream_status": 422,
                      "detail": "{\"detail\":\"audio_undecodable: could not decode the audio file\"}"
                    }
                  },
                  "everySectionFailed": {
                    "summary": "Every section of a full report failed",
                    "value": {
                      "status": "error",
                      "error": "Music analysis failed for every section",
                      "failed": [
                        "overview",
                        "mood",
                        "description",
                        "lyrics",
                        "content_advisory",
                        "music_theory",
                        "mix_feedback",
                        "similar_artists",
                        "sample_detection",
                        "audience",
                        "sync",
                        "playlist_pitch",
                        "development"
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/songs/analyze/batch": {
      "post": {
        "operationId": "postSongsAnalyzeBatch",
        "summary": "Ask many questions about one song",
        "description": "Ask up to 40 short questions about one audio file in a single request. The audio is downloaded once, then every prompt is answered against it, one after another, in the order sent. Use it instead of one [`POST /api/songs/analyze`](/api-reference/songs/analyze) call per question: a batch pays the per-call base price once, not once per prompt.\n\nSend `audio_url` and `prompts`, an array of `{ id, prompt, max_new_tokens }`. `id` is your key for the answer (1 to 64 characters, unique within the batch), `prompt` is 1 to 4,000 characters, and the optional `max_new_tokens` (1 to 2048, default 400) bounds that one answer. `temperature` (default 0.2), `top_p` (default 1) and `do_sample` (default false) apply to every prompt in the batch.\n\nPricing: `$0.05` once per batch plus `$0.001166` per second of the batch's total inference time (`elapsed_seconds`, audio download included), charged once after the batch succeeds, as a single usage row. A batch that runs 90 seconds costs `$0.15494`. The balance must hold `$0.05` before the model runs, otherwise the response is `402`. A batch the model service rejects is not charged.\n\nA batch is all or nothing: if the model service rejects it, the response is `502` and no answers are returned. Prompts run one after another, so a batch of many long answers can take several minutes; the request stays open until it finishes, so use a generous client timeout.",
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "description": "One audio file and its prompts",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SongAnalyzeBatchRequest"
              },
              "examples": {
                "twoQuestions": {
                  "summary": "Two short questions about one file",
                  "value": {
                    "audio_url": "https://example.com/song.mp3",
                    "prompts": [
                      {
                        "id": "genre",
                        "prompt": "What genre is this?",
                        "max_new_tokens": 64
                      },
                      {
                        "id": "tempo",
                        "prompt": "What is the tempo in BPM?"
                      }
                    ],
                    "temperature": 0.2,
                    "top_p": 1,
                    "do_sample": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Every prompt was answered. `answers` holds one entry per prompt, in the order sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SongAnalyzeBatchResponse"
                },
                "examples": {
                  "twoQuestions": {
                    "summary": "Answers to the two questions above",
                    "value": {
                      "status": "success",
                      "answers": [
                        {
                          "id": "genre",
                          "response": "Indie pop.",
                          "elapsed_seconds": 1.4
                        },
                        {
                          "id": "tempo",
                          "response": "Approximately 96 BPM.",
                          "elapsed_seconds": 0.6
                        }
                      ],
                      "elapsed_seconds": 3.1,
                      "prompts": 2
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request. The body is not valid JSON, or a field is invalid: `audio_url` missing, `prompts` empty or longer than 40, a prompt with an empty or over-long `id` or `prompt`, `max_new_tokens` outside 1 to 2048, or an `id` repeated within the batch. `missing_fields` is the path to the first invalid field.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SongAnalyzeErrorResponse"
                },
                "examples": {
                  "repeatedId": {
                    "summary": "Two prompts share an id",
                    "value": {
                      "status": "error",
                      "error": "prompt ids must be unique",
                      "missing_fields": [
                        "prompts"
                      ]
                    }
                  },
                  "tooManyPrompts": {
                    "summary": "More than 40 prompts",
                    "value": {
                      "status": "error",
                      "error": "prompts cannot contain more than 40 prompts",
                      "missing_fields": [
                        "prompts"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — invalid or missing API key / Bearer token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SongAnalyzeErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient credits — the balance cannot cover the base price of a batch (`$0.05`). The model is not called, nothing is charged, and no Stripe object is created. The body includes a static `billingUrl`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SongAnalyzeInsufficientCreditsResponse"
                },
                "examples": {
                  "short": {
                    "summary": "A balance that cannot cover a batch",
                    "value": {
                      "error": "insufficient_credits",
                      "remaining_credits": 12000,
                      "required_credits": 50000,
                      "billingUrl": "https://app.recoupable.dev"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Unverifiable audio. Before the model is called, `audio_url` is checked with a `HEAD` request (a ranged `GET` for hosts that reject `HEAD`). It must answer 2xx within 10 seconds with an `audio/*` or `application/octet-stream` content type. When it does not, the model is not called and nothing is charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SongAnalyzeAudioUrlErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error. The request failed for a reason other than a rejection from the model service, for example the model service could not be reached or answered with an unexpected body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SongAnalyzeErrorResponse"
                }
              }
            }
          },
          "502": {
            "description": "The model service rejected the batch. `upstream_status` is the status it answered with and `detail` is its error text; a `422` with `audio_unreachable` or `audio_undecodable` means it could not fetch the file or could not read it as audio. No answers are returned and nothing is charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SongAnalyzeUpstreamErrorResponse"
                },
                "examples": {
                  "rejectedBatch": {
                    "summary": "The model service could not fetch the audio",
                    "value": {
                      "status": "error",
                      "error": "Music analysis failed (status 422)",
                      "upstream_status": 422,
                      "detail": "{\"detail\":\"audio_unreachable: could not download the audio file\"}"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/songs/moments": {
      "post": {
        "operationId": "postSongsMoments",
        "summary": "Find a song's sections and moments",
        "description": "Finds a song's sections and its best short moments for licensing and editing, with the measurements an edit or a sync pitch needs. Times and numbers come from measuring the audio itself (the beat grid, loudness, which passages repeat), not from a language model. An audio model then listens to a clip of each moment and adds a short description.\n\nSend `audio_url` (MP3, WAV or FLAC). Optionally send `section_order`, the section headers from a lyric sheet in order, to name the sections from the sheet, and `tempo_hint`, the tempo in BPM from your metadata, to correct a beat tracker that locked onto half or double time. `describe` chooses which moments get a description: `true` (the default) for every moment, `false` for none, or a list of moment types.\n\nThe response has the song's `sections`, its `moments` with `6`, `15` and `30` second `cuts` on the beat grid, and a per-second `energy_curve`. It also carries an extended measurement: loudness and peaks (`lufs`, `true_peak_dbtp`, `broadcast_ready`), the stereo image, a seven-band spectrum (`band_profile`), a six-axis sound `fingerprint`, `key`, `time_signature`, `valence` and `arousal`, `mute_drops`, a `chord_loop` estimate and typed `edit_points`. Store `measured_with` with each result: it changes whenever the measurement's results would change. The fingerprint formulas and the band reference split were calibrated on a small sample of songs, so treat them as comparable numbers within a version, not as norms.\n\nPricing: `$0.01` per song measured, plus the [analyze](/api-reference/songs/analyze) rate (`$0.05` per model call plus `$0.001166` per second of inference time) for each moment described. The balance must hold `$0.01` plus `$0.05` for every moment that may be described before anything runs, which is `$0.36` with the default `describe: true`, otherwise the response is `402`. A moment whose description fails comes back without a description and is not charged. If the measurement itself fails, nothing is charged.\n\nA request can take a few minutes when the measurement and model services are starting cold, up to a limit of 300 seconds.",
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "description": "The audio to measure, with optional section names, a tempo hint and the moments to describe",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SongMomentsRequest"
              },
              "examples": {
                "lyricSheetAndTempo": {
                  "summary": "With a lyric sheet's section order and a tempo hint",
                  "value": {
                    "audio_url": "https://example.com/song.mp3",
                    "section_order": [
                      "Intro",
                      "Verse 1",
                      "Chorus",
                      "Verse 2",
                      "Chorus",
                      "Outro"
                    ],
                    "tempo_hint": 120
                  }
                },
                "measureOnly": {
                  "summary": "Measurements only, no descriptions",
                  "value": {
                    "audio_url": "https://example.com/song.mp3",
                    "describe": false
                  }
                },
                "someMoments": {
                  "summary": "Describe two moment types",
                  "value": {
                    "audio_url": "https://example.com/song.mp3",
                    "describe": [
                      "vocal_hook",
                      "climax"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The song was measured. Every moment named by `describe` carries `description`, `emotion`, `best_for` and `vocals`; a moment whose description failed is returned without them.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SongMomentsResponse"
                },
                "examples": {
                  "lyricSheetAndTempo": {
                    "summary": "A 96-second song measured with the first request example above",
                    "value": {
                      "status": "success",
                      "measured_with": "song-moments-2026-11-04+extended-2026-11-04",
                      "duration_seconds": 96,
                      "tempo_bpm": 120,
                      "loudness_lufs": -9.4,
                      "ending": "fade",
                      "names_from": "lyrics",
                      "sections": [
                        {
                          "label": "intro",
                          "start": 0,
                          "end": 8,
                          "group": "A",
                          "energy": 36
                        },
                        {
                          "label": "verse",
                          "start": 8,
                          "end": 24,
                          "group": "B",
                          "energy": 53
                        },
                        {
                          "label": "chorus",
                          "start": 24,
                          "end": 44,
                          "group": "C",
                          "energy": 83
                        },
                        {
                          "label": "verse",
                          "start": 44,
                          "end": 60,
                          "group": "B",
                          "energy": 47
                        },
                        {
                          "label": "chorus",
                          "start": 60,
                          "end": 84,
                          "group": "C",
                          "energy": 91
                        },
                        {
                          "label": "outro",
                          "start": 84,
                          "end": 96,
                          "group": "D",
                          "energy": 17
                        }
                      ],
                      "moments": [
                        {
                          "type": "intro",
                          "start": 0,
                          "end": 8,
                          "section": "intro",
                          "energy": 36,
                          "cuts": {
                            "6": [
                              0,
                              6
                            ],
                            "15": [
                              0,
                              15
                            ],
                            "30": [
                              0,
                              30
                            ]
                          },
                          "description": "Sparse opening with a filtered pad and a soft kick pattern that builds toward the first verse.",
                          "emotion": [
                            "calm",
                            "anticipation"
                          ],
                          "best_for": [
                            "documentary",
                            "end credits"
                          ],
                          "vocals": false
                        },
                        {
                          "type": "vocal_hook",
                          "start": 24,
                          "end": 39,
                          "section": "chorus",
                          "energy": 87,
                          "cuts": {
                            "6": [
                              24,
                              30
                            ],
                            "15": [
                              24,
                              39
                            ],
                            "30": [
                              24,
                              54
                            ]
                          },
                          "description": "The first chorus lands with a full drum kit and a layered vocal hook over a rising bass line.",
                          "emotion": [
                            "uplifting",
                            "energetic",
                            "anthemic"
                          ],
                          "best_for": [
                            "ad",
                            "social video",
                            "TV promo"
                          ],
                          "vocals": true
                        },
                        {
                          "type": "build_up",
                          "start": 46,
                          "end": 64,
                          "section": "verse",
                          "energy": 56,
                          "cuts": {
                            "6": [
                              58,
                              64
                            ],
                            "15": [
                              49,
                              64
                            ],
                            "30": [
                              34,
                              64
                            ]
                          },
                          "description": "Drums drop out and a riser climbs as the vocal repeats a short phrase into the second chorus.",
                          "emotion": [
                            "tense",
                            "driving"
                          ],
                          "best_for": [
                            "film trailer",
                            "sports"
                          ],
                          "vocals": true
                        },
                        {
                          "type": "climax",
                          "start": 62,
                          "end": 77,
                          "section": "chorus",
                          "energy": 91,
                          "cuts": {
                            "6": [
                              62,
                              68
                            ],
                            "15": [
                              62,
                              77
                            ],
                            "30": [
                              62,
                              92
                            ]
                          },
                          "also": [
                            "chorus_lift"
                          ],
                          "description": "The second chorus returns at full volume with doubled vocals and a stacked harmony on the last line.",
                          "emotion": [
                            "triumphant",
                            "powerful",
                            "euphoric"
                          ],
                          "best_for": [
                            "film trailer",
                            "sports",
                            "TV promo"
                          ],
                          "vocals": true
                        },
                        {
                          "type": "outro",
                          "start": 84,
                          "end": 96,
                          "section": "outro",
                          "energy": 17,
                          "cuts": {
                            "6": [
                              90,
                              96
                            ],
                            "15": [
                              81,
                              96
                            ],
                            "30": [
                              66,
                              96
                            ]
                          },
                          "description": "The band thins to pad and a single guitar line that fades to silence.",
                          "emotion": [
                            "wistful",
                            "gentle"
                          ],
                          "best_for": [
                            "end credits",
                            "documentary"
                          ],
                          "vocals": false
                        }
                      ],
                      "energy_curve": [
                        24,
                        29,
                        29,
                        36,
                        37,
                        38,
                        45,
                        46,
                        52,
                        54,
                        51,
                        55,
                        53,
                        50,
                        54,
                        52,
                        52,
                        54,
                        51,
                        55,
                        53,
                        50,
                        54,
                        52,
                        86,
                        88,
                        85,
                        89,
                        87,
                        84,
                        88,
                        86,
                        86,
                        88,
                        85,
                        89,
                        87,
                        84,
                        88,
                        86,
                        86,
                        88,
                        85,
                        18,
                        56,
                        53,
                        57,
                        55,
                        55,
                        57,
                        54,
                        58,
                        22,
                        19,
                        23,
                        21,
                        55,
                        57,
                        54,
                        58,
                        91,
                        88,
                        92,
                        90,
                        90,
                        92,
                        89,
                        93,
                        91,
                        88,
                        92,
                        90,
                        90,
                        92,
                        89,
                        93,
                        91,
                        88,
                        92,
                        90,
                        90,
                        92,
                        89,
                        93,
                        31,
                        25,
                        27,
                        22,
                        20,
                        20,
                        14,
                        16,
                        11,
                        6,
                        8,
                        3
                      ],
                      "tempo_note": "beat tracker 60 reconciled to hint 120",
                      "lufs": -9.4,
                      "true_peak_dbtp": -1.3,
                      "sample_peak_dbfs": -1.62,
                      "crest_factor_db": 9.8,
                      "loudness_range_lu": 5.1,
                      "broadcast_ready": true,
                      "stereo": {
                        "lr_correlation": 0.842,
                        "side_to_mid": 0.31
                      },
                      "spectral_centroid_hz": 2410,
                      "rolloff95_hz": 6931,
                      "band_profile": [
                        {
                          "band": "sub",
                          "lo_hz": 20,
                          "hi_hz": 60,
                          "share_pct": 6.5,
                          "side_pct": 2.1,
                          "level_pct": 32,
                          "status": "light"
                        },
                        {
                          "band": "low",
                          "lo_hz": 60,
                          "hi_hz": 250,
                          "share_pct": 31,
                          "side_pct": 3.4,
                          "level_pct": 49,
                          "status": "clear"
                        },
                        {
                          "band": "low_mid",
                          "lo_hz": 250,
                          "hi_hz": 500,
                          "share_pct": 13,
                          "side_pct": 9.8,
                          "level_pct": 51,
                          "status": "clear"
                        },
                        {
                          "band": "mid",
                          "lo_hz": 500,
                          "hi_hz": 2000,
                          "share_pct": 21.5,
                          "side_pct": 18.6,
                          "level_pct": 54,
                          "status": "clear"
                        },
                        {
                          "band": "high_mid",
                          "lo_hz": 2000,
                          "hi_hz": 6000,
                          "share_pct": 13.5,
                          "side_pct": 31.2,
                          "level_pct": 55,
                          "status": "clear"
                        },
                        {
                          "band": "high",
                          "lo_hz": 6000,
                          "hi_hz": 12000,
                          "share_pct": 10.2,
                          "side_pct": 38.7,
                          "level_pct": 57,
                          "status": "clear"
                        },
                        {
                          "band": "air",
                          "lo_hz": 12000,
                          "hi_hz": 22050,
                          "share_pct": 4.3,
                          "side_pct": 44,
                          "level_pct": 51,
                          "status": "clear"
                        }
                      ],
                      "fingerprint": {
                        "organic_electronic": 0.36,
                        "sparse_dense": 0.51,
                        "dark_bright": 0.38,
                        "intimate_anthemic": 0.54,
                        "smooth_textured": 0.33,
                        "slow_fast": 0.64
                      },
                      "fingerprint_inputs": {
                        "flatness": 0.0213,
                        "contrast_db": 20.4,
                        "zcr_std": 0.0412,
                        "perc_ratio": 0.318,
                        "onsets_per_s": 4.11
                      },
                      "key": {
                        "tonic": "A",
                        "mode": "minor",
                        "confidence": 0.81
                      },
                      "time_signature": "4/4",
                      "valence": 0.37,
                      "arousal": 0.72,
                      "mute_drops": [
                        {
                          "type": "mute",
                          "start": 43.5,
                          "end": 44,
                          "beats": 1,
                          "depth_db": 14.2,
                          "mmss": "0:43"
                        },
                        {
                          "type": "breakdown",
                          "start": 52,
                          "end": 56,
                          "beats": 8,
                          "depth_db": 11.3,
                          "mmss": "0:52"
                        }
                      ],
                      "chord_loop": {
                        "dominant_loop": "Am F C G",
                        "loop_share": 0.52,
                        "distinct_chords": 5,
                        "bars": 47,
                        "confidence": "low"
                      },
                      "edit_points": [
                        {
                          "type": "in",
                          "time": 0,
                          "name": "Cold open (intro)",
                          "note": "Starts clean from the top.",
                          "mmss": "0:00"
                        },
                        {
                          "type": "in",
                          "time": 24,
                          "name": "Chorus drop",
                          "note": "Full entry into the chorus after the verse.",
                          "mmss": "0:24"
                        },
                        {
                          "type": "out",
                          "time": 43.5,
                          "name": "Mute drop",
                          "note": "14.2 dB dip for 1.0 beats; a zero-bleed edit.",
                          "mmss": "0:43"
                        },
                        {
                          "type": "out",
                          "time": 44,
                          "name": "Chorus cut point",
                          "note": "End of the chorus before the verse; cut or dip here.",
                          "mmss": "0:44"
                        },
                        {
                          "type": "out",
                          "time": 52,
                          "name": "Breakdown",
                          "note": "Band pulls back 11.3 dB for 4.0 s.",
                          "mmss": "0:52"
                        },
                        {
                          "type": "in",
                          "time": 56,
                          "name": "Re-entry after the breakdown",
                          "note": "Full band returns; lands as a hit.",
                          "mmss": "0:56"
                        },
                        {
                          "type": "in",
                          "time": 60,
                          "name": "Chorus drop",
                          "note": "Full entry into the chorus after the verse.",
                          "mmss": "1:00"
                        },
                        {
                          "type": "out",
                          "time": 84,
                          "name": "Chorus cut point",
                          "note": "End of the chorus before the outro; cut or dip here.",
                          "mmss": "1:24"
                        },
                        {
                          "type": "out",
                          "time": 84,
                          "name": "Fade begins",
                          "note": "Natural fade; needs no tail edit.",
                          "mmss": "1:24"
                        }
                      ],
                      "elapsed_seconds": 41.62
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request. The body is not valid JSON, or a field is invalid: `audio_url` missing, `section_order` with more than 60 labels or a label empty or over 40 characters, `tempo_hint` outside 30 to 300, or `describe` neither a boolean nor a list of moment types. `missing_fields` is the path to the first invalid field.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SongAnalyzeErrorResponse"
                },
                "examples": {
                  "badTempo": {
                    "summary": "tempo_hint out of range",
                    "value": {
                      "status": "error",
                      "error": "tempo_hint must be at most 300 BPM",
                      "missing_fields": [
                        "tempo_hint"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — invalid or missing API key / Bearer token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SongAnalyzeErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient credits — the balance cannot cover the measurement (`$0.01`) plus a model base price (`$0.05`) for every moment that may be described. Nothing runs, nothing is charged, and no Stripe object is created. The body includes a static `billingUrl`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SongAnalyzeInsufficientCreditsResponse"
                },
                "examples": {
                  "short": {
                    "summary": "A balance that cannot cover the default request",
                    "value": {
                      "error": "insufficient_credits",
                      "remaining_credits": 120000,
                      "required_credits": 360000,
                      "billingUrl": "https://app.recoupable.dev"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Unverifiable audio. Before anything runs, `audio_url` is checked with a `HEAD` request (a ranged `GET` for hosts that reject `HEAD`). It must answer 2xx within 10 seconds with an `audio/*` or `application/octet-stream` content type. When it does not, nothing runs and nothing is charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SongAnalyzeAudioUrlErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "The measurement failed, for example because the file is larger than 100 MB or cannot be decoded as audio, or the measurement service could not be reached. `error` carries the reason, such as `Song measurement failed (status 422)`. Nothing is charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SongAnalyzeErrorResponse"
                },
                "examples": {
                  "measurementFailed": {
                    "summary": "The measurement service rejected the file",
                    "value": {
                      "status": "error",
                      "error": "Song measurement failed (status 422)"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer"
      },
      "apiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "Your Recoup API key. [Learn more](/quickstart#api-keys)."
      }
    },
    "schemas": {
      "CatalogStreamReceipt": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "state": {
            "type": "string",
            "enum": [
              "complete",
              "partial",
              "unavailable",
              "failed"
            ]
          },
          "observed_days": {
            "type": "integer",
            "minimum": 0,
            "maximum": 62
          },
          "retrieved_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "source_hash": {
            "type": [
              "string",
              "null"
            ]
          },
          "provider_recording_id": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "CatalogStreamRun": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "catalog_id": {
            "type": "string",
            "format": "uuid"
          },
          "revision": {
            "type": "string",
            "format": "uuid"
          },
          "scheduled_day": {
            "type": "string",
            "format": "date"
          },
          "since": {
            "type": "string",
            "format": "date"
          },
          "until": {
            "type": "string",
            "format": "date"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "running",
              "complete",
              "partial",
              "failed",
              "cancelled"
            ]
          },
          "coverage": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/CatalogStreamReceipt"
            }
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "finished_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "AddCatalogSongInput": {
        "type": "object",
        "required": [
          "catalog_id",
          "isrc"
        ],
        "properties": {
          "catalog_id": {
            "type": "string",
            "format": "uuid",
            "description": "Catalog ID to which the song will be added"
          },
          "isrc": {
            "type": "string",
            "description": "Song ISRC to associate to the catalog"
          },
          "name": {
            "type": "string",
            "description": "Optional. Applied only if internal search cannot find valid info for ISRC"
          },
          "album": {
            "type": "string",
            "description": "Optional. Applied only if internal search cannot find valid info for ISRC"
          },
          "notes": {
            "type": "string",
            "description": "Optional. Applied only if internal search cannot find valid info for ISRC"
          },
          "artists": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Optional array of artist names. Applied only if internal search lacks info"
          }
        }
      },
      "AddCatalogSongsRequest": {
        "type": "object",
        "required": [
          "songs"
        ],
        "properties": {
          "songs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AddCatalogSongInput"
            },
            "description": "Array of songs for batch updates"
          }
        }
      },
      "ApifyRunResult": {
        "type": "object",
        "properties": {
          "runId": {
            "type": "string",
            "description": "Unique identifier for the Apify run"
          },
          "datasetId": {
            "type": "string",
            "description": "Unique identifier for the dataset containing scraped data"
          },
          "error": {
            "type": "string",
            "nullable": true,
            "description": "Error message if the run failed (null if successful)"
          }
        }
      },
      "Artist": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "UUID of the artist account. Equal to account_id; use this (or account_id) for /api/artists/{id}/* sub-resources."
          },
          "account_id": {
            "type": "string",
            "format": "uuid",
            "description": "UUID of the artist account"
          },
          "name": {
            "type": "string",
            "description": "Artist display name"
          },
          "image": {
            "type": "string",
            "nullable": true,
            "description": "Artist profile image URL"
          },
          "instruction": {
            "type": "string",
            "nullable": true,
            "description": "Artist-specific agent instruction / system prompt"
          },
          "knowledges": {
            "type": "array",
            "description": "Artist knowledge-base entries",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "type": {
                  "type": "string"
                }
              }
            }
          },
          "label": {
            "type": "string",
            "nullable": true,
            "description": "Artist label / category"
          },
          "pinned": {
            "type": "boolean",
            "description": "Whether the account has pinned this artist"
          },
          "account_socials": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ArtistSocial"
            },
            "description": "Existing social media profiles linked to the artist. Missing joined profile records are omitted. A profile with an empty URL is retained with link \"\" and type \"NONE\"."
          }
        }
      },
      "ArtistFan": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique identifier for the fan's social profile"
          },
          "username": {
            "type": "string",
            "description": "Username or handle on the platform"
          },
          "avatar": {
            "type": "string",
            "description": "URL to the fan's avatar/profile image"
          },
          "profile_url": {
            "type": "string",
            "description": "Full URL to the fan's profile on the platform"
          },
          "region": {
            "type": "string",
            "description": "Geographic region or location of the fan"
          },
          "bio": {
            "type": "string",
            "description": "Fan's biography or profile description"
          },
          "follower_count": {
            "type": "integer",
            "description": "Number of followers the fan has"
          },
          "following_count": {
            "type": "integer",
            "description": "Number of accounts the fan is following"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO timestamp of when the fan data was last updated"
          },
          "last_comment_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Timestamp of the latest stored comment linked to this fan relationship, only when the comment author matches this fan. Null means matching stored comment evidence is unavailable, not that the fan never commented. This is not the profile refresh time, a generic engagement time or a live cross-platform history.",
            "example": "2026-10-01T12:00:00Z"
          }
        }
      },
      "ArtistFansErrorResponse": {
        "type": "object",
        "required": [
          "status",
          "error"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "error"
            ],
            "description": "Status of the request"
          },
          "error": {
            "type": "string",
            "description": "Error message describing what went wrong"
          }
        }
      },
      "ArtistFansPagination": {
        "type": "object",
        "properties": {
          "total_count": {
            "type": "integer",
            "description": "Total number of records available"
          },
          "page": {
            "type": "integer",
            "description": "Current page number"
          },
          "limit": {
            "type": "integer",
            "description": "Number of records per page"
          },
          "total_pages": {
            "type": "integer",
            "description": "Total number of pages available"
          }
        }
      },
      "ArtistFansResponse": {
        "type": "object",
        "required": [
          "status",
          "fans",
          "pagination"
        ],
        "example": {
          "status": "success",
          "fans": [
            {
              "id": "fan_fixture_known",
              "username": "example_fan",
              "last_comment_at": "2026-10-01T12:00:00Z"
            },
            {
              "id": "fan_fixture_unknown",
              "username": "example_fan_unknown",
              "last_comment_at": null
            }
          ],
          "pagination": {
            "total_count": 2,
            "page": 1,
            "limit": 20,
            "total_pages": 1
          }
        },
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success"
            ],
            "description": "Status of the request"
          },
          "fans": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ArtistFan"
            },
            "description": "List of social profiles from fans across all platforms"
          },
          "pagination": {
            "$ref": "#/components/schemas/ArtistFansPagination",
            "description": "Pagination metadata for the response"
          }
        }
      },
      "ArtistProfile": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique identifier for the artist"
          },
          "profiles": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ArtistProfileSocialProfile"
            },
            "description": "List of social media profiles"
          },
          "total_followers": {
            "type": "integer",
            "description": "Sum of followers across all platforms"
          },
          "total_following": {
            "type": "integer",
            "description": "Sum of following across all platforms"
          },
          "total_posts": {
            "type": "integer",
            "description": "Sum of posts across all platforms"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO timestamp of when the data was last updated"
          }
        }
      },
      "ArtistProfileSocialProfile": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique identifier for the social profile"
          },
          "username": {
            "type": "string",
            "description": "Username on the platform"
          },
          "profile_url": {
            "type": "string",
            "description": "Direct URL to the profile"
          },
          "avatar": {
            "type": "string",
            "nullable": true,
            "description": "URL to the profile avatar image"
          },
          "bio": {
            "type": "string",
            "nullable": true,
            "description": "Profile biography or description"
          },
          "follower_count": {
            "type": "integer",
            "nullable": true,
            "description": "Number of followers on this platform"
          },
          "following_count": {
            "type": "integer",
            "nullable": true,
            "description": "Number of accounts followed on this platform"
          },
          "post_count": {
            "type": "integer",
            "nullable": true,
            "description": "Number of posts on this platform"
          },
          "region": {
            "type": "string",
            "nullable": true,
            "description": "Geographic region of the profile"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO timestamp of when the profile was last updated"
          }
        }
      },
      "ArtistSocial": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "UUID of the social profile"
          },
          "username": {
            "type": "string",
            "description": "Username on the platform"
          },
          "profile_url": {
            "type": "string",
            "description": "Full URL to the social media profile"
          },
          "link": {
            "type": "string",
            "description": "Profile URL mirrored from profile_url; an empty URL remains an empty string."
          },
          "type": {
            "type": "string",
            "description": "Uppercase platform identifier inferred from the profile URL, for example INSTAGRAM or LINKEDIN. Unrecognized or empty URLs produce NONE."
          }
        },
        "example": {
          "id": "11111111-1111-4111-8111-111111111111",
          "username": "river",
          "profile_url": "https://instagram.com/river",
          "link": "https://instagram.com/river",
          "type": "INSTAGRAM"
        }
      },
      "ArtistPublicProfileErrorResponse": {
        "type": "object",
        "properties": {
          "status": {
            "description": "Always `error`.",
            "type": "string",
            "enum": [
              "error"
            ]
          },
          "error": {
            "description": "Human-readable error message or documented error code.",
            "type": "string"
          }
        },
        "required": [
          "status",
          "error"
        ],
        "additionalProperties": {}
      },
      "ArtistPublicProfileResponse": {
        "type": "object",
        "required": [
          "id",
          "name",
          "image",
          "socials",
          "catalogs",
          "valuation"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The artist's account id."
          },
          "name": {
            "type": "string",
            "description": "The artist's display name."
          },
          "image": {
            "type": "string",
            "nullable": true,
            "description": "URL of the artist's profile image, or null when none is set."
          },
          "socials": {
            "type": "array",
            "description": "Connected social profiles, empty when none are linked.",
            "items": {
              "$ref": "#/components/schemas/ArtistPublicProfileSocial"
            }
          },
          "catalogs": {
            "type": "array",
            "description": "Catalogs linked to the artist, newest first; empty when none are linked.",
            "items": {
              "$ref": "#/components/schemas/ArtistPublicProfileCatalog"
            }
          },
          "valuation": {
            "$ref": "#/components/schemas/ArtistPublicProfileValuation",
            "description": "Estimated value band for the artist's catalog(s), from the published Recoup valuation model over all credited songs' latest play counts. Null when the artist has no measured songs.",
            "nullable": true
          }
        }
      },
      "ArtistPublicProfileSocial": {
        "type": "object",
        "required": [
          "type",
          "username",
          "profile_url"
        ],
        "properties": {
          "type": {
            "type": "string",
            "description": "Platform name derived from the profile URL, e.g. SPOTIFY, INSTAGRAM, TIKTOK, YOUTUBE.",
            "example": "SPOTIFY"
          },
          "username": {
            "type": "string",
            "nullable": true,
            "description": "The artist's handle on the platform, when known."
          },
          "profile_url": {
            "type": "string",
            "description": "Public URL of the profile on the platform."
          }
        }
      },
      "ArtistPublicProfileCatalog": {
        "type": "object",
        "required": [
          "id",
          "name",
          "song_count",
          "updated_at",
          "songs"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The catalog id."
          },
          "name": {
            "type": "string",
            "description": "The catalog's display name."
          },
          "song_count": {
            "type": "integer",
            "description": "Number of songs currently in the catalog."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the catalog last changed."
          },
          "songs": {
            "type": "array",
            "description": "The catalog's songs crediting this artist, sorted by plays descending and capped at the top 50 — `song_count` reflects the full catalog and may exceed `songs.length`.",
            "items": {
              "$ref": "#/components/schemas/ArtistPublicProfileSong"
            }
          }
        }
      },
      "ArtistPublicProfileSong": {
        "type": "object",
        "required": [
          "isrc",
          "name",
          "album",
          "artwork_url",
          "plays",
          "est_value_usd"
        ],
        "properties": {
          "isrc": {
            "type": "string",
            "description": "The song's ISRC.",
            "example": "QZTPX2553097"
          },
          "name": {
            "type": "string",
            "description": "The song's title."
          },
          "album": {
            "type": "string",
            "nullable": true,
            "description": "The album or release the song belongs to, when known."
          },
          "artwork_url": {
            "type": "string",
            "nullable": true,
            "description": "Apple Music artwork URL for the song's release, or null when artwork has not been resolved yet."
          },
          "plays": {
            "type": "integer",
            "description": "The song's latest captured Spotify play count. Captures are periodic, so this can lag the live number."
          },
          "est_value_usd": {
            "type": "number",
            "description": "Estimated value of the song in USD — the mid point of the published Recoup valuation model applied to this song's plays."
          }
        }
      },
      "ArtistPublicProfileValuation": {
        "type": "object",
        "required": [
          "low",
          "mid",
          "high"
        ],
        "properties": {
          "low": {
            "type": "number",
            "description": "Low end of the estimated catalog value band, USD."
          },
          "mid": {
            "type": "number",
            "description": "Mid estimate of the catalog value, USD."
          },
          "high": {
            "type": "number",
            "description": "High end of the estimated catalog value band, USD."
          }
        }
      },
      "ArtistSocialsErrorResponse": {
        "type": "object",
        "required": [
          "status",
          "message"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "error"
            ],
            "description": "Status of the request"
          },
          "message": {
            "type": "string",
            "description": "Error message describing what went wrong"
          }
        }
      },
      "ArtistSocialsInsufficientCreditsResponse": {
        "type": "object",
        "description": "Returned (402) when the account lacks credits for the scrapes. Nothing is charged and no Stripe object is created. Credits must be bought explicitly via [`POST /api/credits/sessions`](/api-reference/credits/sessions-create). Example: two linked profiles with posts=20 require 500,000 micro-dollar credits ($0.50) at the upfront balance gate.",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "insufficient_credits"
            ]
          },
          "remaining_credits": {
            "type": "integer",
            "example": 0
          },
          "required_credits": {
            "type": "integer",
            "example": 500000
          },
          "billingUrl": {
            "type": "string",
            "description": "Static link to the Recoup app, where a human can save a card and buy credits. It is a constant, not a freshly minted Stripe Checkout Session, so a credit-gated endpoint that keeps returning 402 creates nothing. To buy credits programmatically, call [`POST /api/credits/sessions`](/api-reference/credits/sessions-create).",
            "example": "https://app.recoupable.dev"
          }
        },
        "required": [
          "error",
          "remaining_credits",
          "required_credits",
          "billingUrl"
        ]
      },
      "ArtistSocialsPagination": {
        "type": "object",
        "properties": {
          "total_count": {
            "type": "integer",
            "description": "Total number of social profiles available"
          },
          "page": {
            "type": "integer",
            "description": "Current page number"
          },
          "limit": {
            "type": "integer",
            "description": "Number of social profiles per page"
          },
          "total_pages": {
            "type": "integer",
            "description": "Total number of pages available"
          }
        }
      },
      "ArtistSocialsResponse": {
        "type": "object",
        "required": [
          "status",
          "socials",
          "pagination"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success"
            ],
            "description": "Status of the request"
          },
          "socials": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SocialProfile"
            },
            "description": "List of social media profiles associated with the artist"
          },
          "pagination": {
            "$ref": "#/components/schemas/ArtistSocialsPagination",
            "description": "Pagination metadata for the response"
          }
        }
      },
      "ArtistSocialsScrapeRequest": {
        "type": "object",
        "required": [
          "artist_account_id"
        ],
        "properties": {
          "artist_account_id": {
            "type": "string",
            "format": "uuid",
            "description": "UUID of the artist account to scrape socials for",
            "example": "1873859c-dd37-4e9a-9bac-80d35a1b2c3d"
          },
          "posts": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100,
            "example": 10,
            "description": "Recent-post depth applied to every profile in the batch. Honored on TikTok, YouTube (per content type), X and LinkedIn; ignored on Instagram (always the profile's latest posts, up to 12), Threads and Facebook. Without it TikTok, YouTube and X return a single latest item. Every returned post is stored with its engagement counts; see the per-platform table on [Social Scrape](/api-reference/social/scrape). Credits: `5 + posts` per profile."
          }
        }
      },
      "ArtistSocialsScrapeResponse": {
        "type": "array",
        "items": {
          "$ref": "#/components/schemas/ApifyRunResult"
        },
        "description": "Array of Apify run results, one for each social profile scraped"
      },
      "ArtistsErrorResponse": {
        "type": "object",
        "required": [
          "status",
          "message"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "error"
            ],
            "description": "Status of the request"
          },
          "message": {
            "type": "string",
            "description": "Error message describing what went wrong"
          }
        }
      },
      "ArtistsResponse": {
        "type": "object",
        "required": [
          "status",
          "artists"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success",
              "error"
            ],
            "description": "Status of the request"
          },
          "artists": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Artist"
            },
            "description": "List of artist objects"
          },
          "message": {
            "type": "string",
            "description": "Error message (only present if status is error)"
          }
        }
      },
      "RunValuationRequest": {
        "type": "object",
        "required": [
          "spotify_artist_id"
        ],
        "properties": {
          "spotify_artist_id": {
            "type": "string",
            "description": "The Spotify artist id to value (e.g. from a Spotify artist search). The endpoint resolves this artist's releases, measures them, and builds the catalog."
          },
          "organization_id": {
            "type": "string",
            "format": "uuid",
            "description": "Optional. ID of an organization to own the resulting catalog instead of the calling account. Every member of that organization then sees it in their own [Get Catalogs](/api-reference/songs/catalogs) response. The caller must be a member of the organization; otherwise the request is rejected with 403. Credits are still spent by the calling account, and the searched artist is still linked to the caller's roster. Omit to own the catalog personally, which is the default."
          },
          "account_id": {
            "type": "string",
            "format": "uuid",
            "description": "Optional. Act on behalf of this account (UUID). Only applicable when the authenticated account has access to the target account, for example an organization key acting for a member account. Every side effect of the run - the playcount snapshot, the catalog claim, the roster attach, and the report email - is scoped to this account instead of the caller. Omit to run for the caller's own account, which is the default."
          }
        }
      },
      "MusicRun": {
        "type": "object",
        "description": "One music generation, surfaced as a run. `id` is the generation's id: read the full record, including its logs, with [Get Music Generation](/api-reference/music/get).",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The generation's id."
          },
          "kind": {
            "type": "string",
            "enum": [
              "music"
            ],
            "description": "The run type."
          },
          "state": {
            "type": "string",
            "enum": [
              "queued",
              "generating",
              "complete",
              "failed"
            ],
            "description": "Domain phase of the run: `queued` before the workflow picks it up, `generating` while the model renders, then `complete` or `failed`."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the generation was created."
          },
          "result": {
            "type": "object",
            "nullable": true,
            "description": "Set once the generation completes; null before that.",
            "properties": {
              "generation_id": {
                "type": "string",
                "format": "uuid",
                "description": "The completed generation."
              },
              "audio_url": {
                "type": "string",
                "description": "Playable audio URL."
              }
            }
          }
        }
      },
      "ValuationRun": {
        "type": "object",
        "description": "One background run. `id` is opaque; `state` is a domain phase, not a storage value: `queued` (capture accepted, not yet scraping), `measuring` (capture in flight, or finished moments ago and being claimed), `claimed` (catalog materialized - `result.catalog_id` is set), `failed` (the capture finished but no catalog was claimed, or the capture itself failed).",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Opaque run id. Do not infer anything from its format; it is stable for polling a single run across requests."
          },
          "kind": {
            "type": "string",
            "enum": [
              "valuation"
            ],
            "description": "The run type."
          },
          "state": {
            "type": "string",
            "enum": [
              "queued",
              "measuring",
              "claimed",
              "failed"
            ],
            "description": "Domain phase of the run."
          },
          "album_count": {
            "type": "integer",
            "description": "Number of releases in the run's capture scope."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the run was created."
          },
          "result": {
            "type": "object",
            "nullable": true,
            "description": "Set once the run is claimed; null before that.",
            "properties": {
              "catalog_id": {
                "type": "string",
                "format": "uuid",
                "description": "The materialized catalog. Read its value band via [Get Catalog Measurements](/api-reference/songs/catalog-measurements)."
              }
            }
          }
        }
      },
      "GetRunsResponse": {
        "type": "object",
        "description": "The calling account's runs of the requested kind, newest first.",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success",
              "error"
            ],
            "description": "Status of the request"
          },
          "runs": {
            "type": "array",
            "description": "Runs, newest first. Empty when the account has never run one of this kind. The item shape follows the requested `kind`.",
            "items": {
              "oneOf": [
                {
                  "title": "Valuation run",
                  "$ref": "#/components/schemas/ValuationRun"
                },
                {
                  "title": "Music run",
                  "$ref": "#/components/schemas/MusicRun"
                }
              ]
            }
          },
          "error": {
            "type": "string",
            "description": "Error message (only present if status is 'error')"
          }
        }
      },
      "RunValuationResponse": {
        "type": "object",
        "description": "Result of a valuation run: the materialized catalog and its estimated value band.",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success",
              "error"
            ],
            "description": "Status of the request"
          },
          "catalog": {
            "$ref": "#/components/schemas/Catalog"
          },
          "band": {
            "$ref": "#/components/schemas/ValuationBand"
          },
          "songs_measured": {
            "type": "integer",
            "description": "Number of tracks with a captured play count that were materialized into the catalog."
          },
          "error": {
            "type": "string",
            "description": "Error message (only present if status is 'error')"
          }
        }
      },
      "CreateCatalogRequest": {
        "type": "object",
        "description": "Request body for creating a catalog. At least one of name or snapshot must be supplied. The catalog is owned by the authenticated account unless organization_id names an organization to own it instead.",
        "properties": {
          "name": {
            "type": "string",
            "description": "Optional. Display name for the catalog. If omitted when snapshot is supplied, a name is derived from the source run."
          },
          "snapshot": {
            "type": "string",
            "format": "uuid",
            "description": "Optional. ID of a completed playcount snapshot (valuation run) owned by the authenticated account. Its measured ISRCs are added to the new catalog as catalog songs. Create one with [Create measurement job](/api-reference/research/measurement-jobs)."
          },
          "organization_id": {
            "type": "string",
            "format": "uuid",
            "description": "Optional. ID of an organization to own the catalog instead of the calling account. Every member of that organization then sees the catalog in their own [Get Catalogs](/api-reference/songs/catalogs) response, without passing the organization's id. The caller must be a member of the organization; otherwise the request is rejected with 403. Omit to own the catalog personally, which is the default."
          }
        }
      },
      "CreateCatalogResponse": {
        "type": "object",
        "description": "Response returned after creating, or idempotently re-fetching, a catalog",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success",
              "error"
            ],
            "description": "Status of the request"
          },
          "catalog": {
            "$ref": "#/components/schemas/Catalog"
          },
          "songs_added": {
            "type": "integer",
            "description": "Number of catalog songs materialized from the source. 0 when no snapshot was supplied, or when the run was already materialized (idempotent re-claim)."
          },
          "error": {
            "type": "string",
            "description": "Error message (only present if status is 'error')"
          }
        }
      },
      "UpdateCatalogRequest": {
        "type": "object",
        "description": "Request body for renaming a catalog.",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "description": "New display name for the catalog. Must not be empty."
          }
        }
      },
      "UpdateCatalogResponse": {
        "type": "object",
        "description": "Response returned after renaming a catalog",
        "required": [
          "status",
          "catalog"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success"
            ],
            "description": "Status of the request"
          },
          "catalog": {
            "$ref": "#/components/schemas/Catalog"
          }
        }
      },
      "DeleteCatalogResponse": {
        "type": "object",
        "description": "Response returned after deleting a catalog",
        "required": [
          "status",
          "catalog_id",
          "released_snapshot_ids"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success"
            ],
            "description": "Status of the request"
          },
          "catalog_id": {
            "type": "string",
            "format": "uuid",
            "description": "ID of the deleted catalog"
          },
          "released_snapshot_ids": {
            "type": "array",
            "description": "IDs of the playcount snapshots that pointed at this catalog. Those snapshots still exist - only their catalog reference was cleared - so each can be re-materialized into a new, correctly named catalog with Create catalog. Empty when the catalog was not created from a measurement run.",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          }
        }
      },
      "Catalog": {
        "type": "object",
        "description": "A catalog with its metadata",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique identifier for the catalog"
          },
          "name": {
            "type": "string",
            "description": "Name of the catalog"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO timestamp of when the catalog was created"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO timestamp of when the catalog was last updated"
          }
        }
      },
      "CatalogOwner": {
        "type": "object",
        "description": "The account a catalog belongs to. A catalog owned both directly and through an organization reports the organization - that is the ownership worth surfacing to a member.",
        "required": [
          "id",
          "name",
          "image",
          "is_organization"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Owner account id"
          },
          "name": {
            "type": "string",
            "nullable": true,
            "description": "Owner display name, null when the account has none"
          },
          "image": {
            "type": "string",
            "nullable": true,
            "description": "Owner avatar URL from account_info, null when the account has no image. Clients should fall back to initials rather than render a broken image."
          },
          "is_organization": {
            "type": "boolean",
            "description": "True when the owner is an organization the caller belongs to, false when it is a personal account"
          }
        }
      },
      "CatalogListItem": {
        "type": "object",
        "description": "A catalog as returned by [Get catalogs](/api-reference/songs/catalogs): the catalog plus its estimated value and its owner. Only this list carries those two fields - the create and rename responses return the bare catalog.",
        "required": [
          "id",
          "name",
          "created_at",
          "updated_at",
          "measured_song_count",
          "valuation",
          "owner"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique identifier for the catalog"
          },
          "name": {
            "type": "string",
            "description": "Name of the catalog"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO timestamp of when the catalog was created"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO timestamp of when the catalog was last updated"
          },
          "measured_song_count": {
            "type": "integer",
            "description": "Songs in the catalog with at least one play-count measurement. 0 for a catalog that was never measured, in which case valuation is null."
          },
          "valuation": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ValuationBand"
              },
              {
                "type": "null"
              }
            ],
            "description": "Estimated value band - the same schema and model as [Get catalog measurements](/api-reference/songs/catalog-measurements) returns, so a list and the report it opens cannot disagree. Null when nothing in the catalog has been measured; clients should say so rather than render $0."
          },
          "owner": {
            "$ref": "#/components/schemas/CatalogOwner"
          }
        }
      },
      "CatalogSong": {
        "type": "object",
        "description": "A song within a catalog with its metadata and associated artists",
        "properties": {
          "catalog_id": {
            "type": "string",
            "format": "uuid",
            "description": "Catalog ID this song entry is associated with"
          },
          "isrc": {
            "type": "string",
            "description": "International Standard Recording Code (primary key)"
          },
          "name": {
            "type": "string",
            "description": "Name of the song"
          },
          "album": {
            "type": "string",
            "description": "Name of the album the song belongs to"
          },
          "lyrics": {
            "type": "string",
            "description": "Full lyrics of the song"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO timestamp of when the song data was last updated"
          },
          "artists": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SongArtist"
            },
            "description": "Array of artist objects associated with this song"
          }
        }
      },
      "CatalogSongsErrorResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "error"
            ],
            "description": "Status of the request"
          },
          "error": {
            "type": "string",
            "description": "Error message describing what went wrong"
          }
        }
      },
      "CatalogSongsPagination": {
        "type": "object",
        "description": "Pagination metadata for catalog songs response",
        "properties": {
          "total_count": {
            "type": "integer",
            "description": "Total number of songs in the catalog"
          },
          "page": {
            "type": "integer",
            "description": "Current page number"
          },
          "limit": {
            "type": "integer",
            "description": "Number of songs per page"
          },
          "total_pages": {
            "type": "integer",
            "description": "Total number of pages available"
          }
        }
      },
      "CatalogSongsResponse": {
        "type": "object",
        "description": "Response containing catalog songs data with pagination",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success",
              "error"
            ],
            "description": "Status of the request"
          },
          "songs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CatalogSong"
            },
            "description": "Array of song objects with artist information"
          },
          "pagination": {
            "$ref": "#/components/schemas/CatalogSongsPagination",
            "description": "Pagination metadata for the response"
          },
          "error": {
            "type": "string",
            "description": "Error message (only present if status is 'error')"
          }
        }
      },
      "CatalogsErrorResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "error"
            ],
            "description": "Status of the request"
          },
          "error": {
            "type": "string",
            "description": "Error message describing what went wrong"
          }
        }
      },
      "CatalogsResponse": {
        "type": "object",
        "description": "Response containing catalogs data",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success",
              "error"
            ],
            "description": "Status of the request"
          },
          "catalogs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CatalogListItem"
            },
            "description": "Array of catalog objects, each with its estimated value and its owner"
          },
          "error": {
            "type": "string",
            "description": "Error message (only present if status is 'error')"
          }
        }
      },
      "CatalogMeasurementsResponse": {
        "type": "object",
        "description": "Latest per-song play counts for a catalog plus the valuation band derived from them",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success",
              "error"
            ],
            "description": "Status of the request"
          },
          "measurements": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CatalogTrackMeasurement"
            },
            "description": "One page of latest-per-ISRC measurements (the requested page/limit window), sorted by play count descending. One entry per measured song: the newest capture per ISRC; songs never measured are omitted. Use measured_song_count / pagination for the size of the full set - the aggregates are NOT limited to this page."
          },
          "pagination": {
            "$ref": "#/components/schemas/CatalogMeasurementsPagination"
          },
          "measured_song_count": {
            "type": "integer",
            "description": "Total number of measured songs in scope (the whole catalog, or the artist subset when filtered), computed over the entire scope regardless of pagination - no row cap"
          },
          "valuation": {
            "$ref": "#/components/schemas/ValuationBand"
          },
          "total_streams": {
            "type": "integer",
            "description": "Sum of the latest play counts across ALL measured songs in scope, computed in a single SQL aggregate regardless of pagination - no row cap"
          },
          "artist_account_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Echoes the applied artist_account_id filter: the uuid when the response was scoped to that artist, null when the response covers the whole catalog. Clients that request an artist scope should verify this echo before treating the numbers as artist-scoped."
          },
          "catalog_age_years": {
            "type": "integer",
            "description": "Catalog age in years used for the annual run-rate: from the earliest Spotify release date of the source run's albums, minimum 1; defaults to 5 when no release date is resolvable."
          },
          "error": {
            "type": "string",
            "description": "Error message (only present if status is 'error')"
          }
        }
      },
      "CatalogMeasurementsPagination": {
        "type": "object",
        "description": "Pagination metadata for the measurements array - the same envelope as catalog songs",
        "properties": {
          "total_count": {
            "type": "integer",
            "description": "Total number of measured songs in scope (equals measured_song_count)"
          },
          "page": {
            "type": "integer",
            "description": "Current page number"
          },
          "limit": {
            "type": "integer",
            "description": "Number of measurement rows per page"
          },
          "total_pages": {
            "type": "integer",
            "description": "Total number of pages available"
          }
        }
      },
      "CatalogTrackMeasurement": {
        "type": "object",
        "description": "The most recent Spotify play-count measurement for one song (ISRC) in the catalog",
        "properties": {
          "isrc": {
            "type": "string",
            "description": "ISRC of the song"
          },
          "title": {
            "type": "string",
            "nullable": true,
            "description": "Song title, when known"
          },
          "playcount": {
            "type": "integer",
            "description": "Latest Spotify platform-displayed play count"
          },
          "measured_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO timestamp of when the play count was captured"
          }
        }
      },
      "ValuationBand": {
        "type": "object",
        "description": "Estimated catalog value in USD, derived at read time from the latest measurements - the same model as the recoupable.dev valuation card. Annual run-rate = lifetime streams / catalog age (lifetime-average proxy), converted to net label share (all-DSP gross-up 1.25/1.4/1.6, 15% distribution fee, 25% royalty share, $0.0035 per Spotify stream) and multiplied by a 10x/13x/16x master-catalog market multiple.",
        "properties": {
          "low": {
            "type": "number",
            "description": "Low estimate in USD (1.25x gross-up, 10x multiple)"
          },
          "mid": {
            "type": "number",
            "description": "Central estimate in USD (1.4x gross-up, 13x multiple)"
          },
          "high": {
            "type": "number",
            "description": "High estimate in USD (1.6x gross-up, 16x multiple)"
          }
        }
      },
      "CreateArtistError": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "error"
            ],
            "description": "Status of the request"
          },
          "missing_fields": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "List of missing or invalid field names"
          },
          "error": {
            "type": "string",
            "description": "Error message describing the validation failure"
          },
          "message": {
            "type": "string",
            "description": "Error message (for invalid JSON or other errors)"
          }
        }
      },
      "CreateArtistRequest": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "description": "The name of the artist to create"
          },
          "spotify_artist_id": {
            "type": "string",
            "description": "Optional Spotify artist id. When provided, the endpoint resolves-or-creates the canonical artist for that id: if an artist already carries it, that artist is linked to the account and returned (200) instead of creating a duplicate; otherwise the artist is created with its Spotify profile attached (201)."
          },
          "account_id": {
            "type": "string",
            "format": "uuid",
            "description": "UUID of the account to create the artist for. Only applicable when the authenticated account has access to multiple accounts via organization membership. If not provided, the artist is created for the API key's own account."
          },
          "organization_id": {
            "type": "string",
            "format": "uuid",
            "description": "Optional organization ID to link the new artist to"
          }
        }
      },
      "CreateArtistResponse": {
        "type": "object",
        "required": [
          "artist"
        ],
        "properties": {
          "artist": {
            "$ref": "#/components/schemas/CreatedArtist"
          }
        }
      },
      "DeleteArtistErrorResponse": {
        "type": "object",
        "required": [
          "status",
          "error"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "error"
            ],
            "description": "Status of the request"
          },
          "error": {
            "type": "string",
            "description": "Error message describing what went wrong"
          }
        }
      },
      "DeleteArtistResponse": {
        "type": "object",
        "required": [
          "success",
          "artistId"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "description": "Whether the artist delete completed successfully"
          },
          "artistId": {
            "type": "string",
            "format": "uuid",
            "description": "UUID of the artist account that was deleted or unlinked"
          }
        }
      },
      "DeleteArtistSocialResponse": {
        "type": "object",
        "required": [
          "success",
          "socialId"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "description": "Whether the social profile was unlinked successfully"
          },
          "socialId": {
            "type": "string",
            "format": "uuid",
            "description": "UUID of the social profile that was unlinked from the artist"
          }
        }
      },
      "PinArtistErrorResponse": {
        "type": "object",
        "required": [
          "status",
          "error"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "error"
            ],
            "description": "Status of the request"
          },
          "missing_fields": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "List of missing or invalid field names"
          },
          "error": {
            "type": "string",
            "description": "Error message describing what went wrong"
          }
        }
      },
      "PinArtistResponse": {
        "type": "object",
        "required": [
          "success",
          "artistId",
          "pinned"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "description": "Whether the request succeeded"
          },
          "artistId": {
            "type": "string",
            "format": "uuid",
            "description": "The artist ID"
          },
          "pinned": {
            "type": "boolean",
            "description": "The current pinned state"
          }
        }
      },
      "CreateCatalogInput": {
        "type": "object",
        "required": [
          "account_id"
        ],
        "properties": {
          "account_id": {
            "type": "string",
            "format": "uuid",
            "description": "The account to associate the catalog with"
          },
          "name": {
            "type": "string",
            "description": "Catalog name to create if catalog_id is omitted"
          },
          "catalog_id": {
            "type": "string",
            "format": "uuid",
            "description": "Existing catalog ID to link to the account"
          }
        }
      },
      "CreateCatalogsRequest": {
        "type": "object",
        "required": [
          "catalogs"
        ],
        "properties": {
          "catalogs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CreateCatalogInput"
            },
            "description": "Array of catalog inputs for bulk create/link operations"
          }
        }
      },
      "CreateTaskRequest": {
        "type": "object",
        "required": [
          "title",
          "prompt",
          "schedule",
          "artist_account_id"
        ],
        "properties": {
          "title": {
            "type": "string",
            "description": "Descriptive title of the task",
            "example": "Weekly Genre Pulse Check"
          },
          "prompt": {
            "type": "string",
            "description": "Instruction/prompt executed by the task",
            "example": "Execute this weekly genre analysis workflow and email a summary to the team."
          },
          "schedule": {
            "type": "string",
            "description": "Cron expression defining when the task runs (e.g., '0 9 * * 4' for Thursdays at 9 AM)",
            "example": "0 9 * * 4"
          },
          "timezone": {
            "type": "string",
            "default": "UTC",
            "description": "Optional IANA time zone (e.g. America/New_York) the cron schedule is interpreted in, DST-aware. Defaults to UTC. Applied to the Trigger.dev schedule (the source of truth); not stored separately.",
            "example": "America/New_York"
          },
          "account_id": {
            "type": "string",
            "format": "uuid",
            "description": "UUID of the account to create the task for. Only applicable when the authenticated account has access to multiple accounts via organization membership. If not provided, the task is created for the API key's own account.",
            "example": "848cd58d-700f-4b38-ab4c-d9f52a1b2c3d"
          },
          "artist_account_id": {
            "type": "string",
            "format": "uuid",
            "description": "UUID of the associated artist account",
            "example": "1873859c-dd37-4e9a-9bac-80d35a1b2c3d"
          },
          "model": {
            "type": "string",
            "description": "Optional AI model ID. Omit it to use the configured default. Empty strings are accepted by input validation; select a supported model when explicitly overriding."
          }
        }
      },
      "CreatedArtist": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "UUID of the created artist account"
          },
          "account_id": {
            "type": "string",
            "format": "uuid",
            "description": "UUID of the artist account (same as id)"
          },
          "name": {
            "type": "string",
            "description": "Name of the artist"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "ISO timestamp of when the artist was created"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "ISO timestamp of when the artist was last updated"
          },
          "image": {
            "type": "string",
            "nullable": true,
            "description": "Artist profile image URL"
          },
          "instruction": {
            "type": "string",
            "nullable": true,
            "description": "Custom AI instruction for this artist"
          },
          "knowledges": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true,
            "description": "Knowledge base references for this artist"
          },
          "label": {
            "type": "string",
            "nullable": true,
            "description": "Record label name"
          },
          "organization": {
            "type": "string",
            "nullable": true,
            "description": "Organization name"
          },
          "company_name": {
            "type": "string",
            "nullable": true,
            "description": "Company name"
          },
          "job_title": {
            "type": "string",
            "nullable": true,
            "description": "Job title"
          },
          "role_type": {
            "type": "string",
            "nullable": true,
            "description": "Role type"
          },
          "onboarding_status": {
            "type": "string",
            "nullable": true,
            "description": "Onboarding status"
          },
          "onboarding_data": {
            "nullable": true,
            "description": "Onboarding data"
          },
          "account_info": {
            "type": "array",
            "description": "Account info records"
          },
          "account_socials": {
            "type": "array",
            "description": "Linked social media accounts"
          }
        }
      },
      "DeleteCatalogInput": {
        "type": "object",
        "required": [
          "catalog_id",
          "account_id"
        ],
        "properties": {
          "catalog_id": {
            "type": "string",
            "format": "uuid",
            "description": "Catalog ID to remove"
          },
          "account_id": {
            "type": "string",
            "format": "uuid",
            "description": "Account ID whose relationship will be removed"
          }
        }
      },
      "DeleteCatalogSongInput": {
        "type": "object",
        "required": [
          "catalog_id",
          "isrc"
        ],
        "properties": {
          "catalog_id": {
            "type": "string",
            "format": "uuid",
            "description": "Catalog ID from which the song will be removed"
          },
          "isrc": {
            "type": "string",
            "description": "Song ISRC to remove from the catalog"
          }
        }
      },
      "DeleteCatalogSongsRequest": {
        "type": "object",
        "required": [
          "songs"
        ],
        "properties": {
          "songs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DeleteCatalogSongInput"
            },
            "description": "Array of songs for batch deletes"
          }
        }
      },
      "DeleteCatalogsRequest": {
        "type": "object",
        "required": [
          "catalogs"
        ],
        "properties": {
          "catalogs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DeleteCatalogInput"
            },
            "description": "Array of catalog-account pairs to remove"
          }
        }
      },
      "DeleteTaskRequest": {
        "type": "object",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "UUID of the task to delete",
            "example": "aade2bce-55c7-468e-a606-c4e76fb2ea2a"
          }
        }
      },
      "DeleteTaskResponse": {
        "type": "object",
        "required": [
          "status"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success"
            ],
            "description": "Status of the delete operation"
          }
        }
      },
      "DeleteTaskFailureResponse": {
        "type": "object",
        "required": [
          "status",
          "error"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "error"
            ],
            "description": "Always `error` for this response shape"
          },
          "error": {
            "type": "string",
            "description": "Human-readable error message (for example `Task not found` on 404)"
          }
        }
      },
      "DeleteTaskValidationErrorResponse": {
        "type": "object",
        "required": [
          "status",
          "missing_fields",
          "error"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "error"
            ],
            "description": "Always `error` when validation fails"
          },
          "missing_fields": {
            "type": "array",
            "description": "JSON path segments to the first field that failed validation (from Zod), e.g. `[\"id\"]`",
            "items": {
              "oneOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            }
          },
          "error": {
            "type": "string",
            "description": "Validation message for the first failing field"
          }
        }
      },
      "Error": {
        "required": [
          "error",
          "message"
        ],
        "type": "object",
        "properties": {
          "error": {
            "type": "integer",
            "format": "int32"
          },
          "message": {
            "type": "string"
          }
        }
      },
      "SocialProfile": {
        "type": "object",
        "properties": {
          "social_id": {
            "type": "string",
            "format": "uuid",
            "description": "UUID of the artist's socials account"
          },
          "username": {
            "type": "string",
            "description": "Username on the platform"
          },
          "profile_url": {
            "type": "string",
            "description": "Direct URL to the profile"
          },
          "avatar": {
            "type": "string",
            "nullable": true,
            "description": "URL to the profile avatar image"
          },
          "bio": {
            "type": "string",
            "nullable": true,
            "description": "Profile biography or description"
          },
          "follower_count": {
            "type": "integer",
            "nullable": true,
            "description": "Number of followers on this platform"
          },
          "following_count": {
            "type": "integer",
            "nullable": true,
            "description": "Number of accounts followed on this platform"
          },
          "region": {
            "type": "string",
            "nullable": true,
            "description": "Geographic region of the profile"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO timestamp of when the profile was last updated"
          },
          "history": {
            "type": "array",
            "description": "Follower snapshots for the last `history` days, newest first. Present only when the `history` query parameter is passed.",
            "items": {
              "$ref": "#/components/schemas/SocialSnapshot"
            }
          }
        }
      },
      "SocialSnapshot": {
        "type": "object",
        "required": [
          "captured_at",
          "follower_count"
        ],
        "properties": {
          "captured_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the scrape that produced this point completed"
          },
          "follower_count": {
            "type": "integer",
            "description": "Followers (subscribers on YouTube) at capture time"
          },
          "following_count": {
            "type": "integer",
            "nullable": true,
            "description": "Accounts followed at capture time; null where the platform does not report it"
          },
          "post_count": {
            "type": "integer",
            "nullable": true,
            "description": "Lifetime post count at capture time (Instagram, TikTok, YouTube, X); null where the platform does not report it"
          }
        }
      },
      "Song": {
        "type": "object",
        "description": "A song with its metadata and associated artists",
        "properties": {
          "isrc": {
            "type": "string",
            "description": "International Standard Recording Code (primary key)"
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Name of the song, or null when unavailable."
          },
          "album": {
            "type": [
              "string",
              "null"
            ],
            "description": "Name of the song's album, or null when unavailable."
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "description": "Saved notes for the song, or null when unavailable."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO timestamp of when the song data was last updated"
          },
          "artists": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SongArtist"
            },
            "description": "Array of artist objects associated with this song"
          }
        }
      },
      "SongAnalyzeAudioUrlErrorResponse": {
        "type": "object",
        "description": "Returned (422) when `audio_url` could not be verified. The model is not called and nothing is charged.",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "error"
            ]
          },
          "error": {
            "type": "string",
            "enum": [
              "audio_url_unreachable",
              "audio_url_not_audio"
            ],
            "description": "`audio_url_unreachable`: the URL did not answer 2xx within 10 seconds. `audio_url_not_audio`: it answered, but with a content type that is not `audio/*` or `application/octet-stream`.",
            "example": "audio_url_not_audio"
          },
          "message": {
            "type": "string",
            "description": "What the check observed, for the caller's logs.",
            "example": "audio_url answered 200 with content type text/html"
          }
        },
        "required": [
          "status",
          "error",
          "message"
        ]
      },
      "SongAnalyzeErrorResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "error"
            ],
            "description": "Error status"
          },
          "missing_fields": {
            "type": "array",
            "description": "Path to the first invalid or missing field when validation fails. A position inside an array appears as an integer, for example `[\"prompts\", 1, \"id\"]`.",
            "items": {
              "type": [
                "string",
                "integer"
              ]
            }
          },
          "error": {
            "type": "string",
            "description": "Error message describing what went wrong"
          }
        }
      },
      "SongAnalyzeInsufficientCreditsResponse": {
        "type": "object",
        "description": "Returned (402) when the account lacks credits for the analysis. Nothing is charged and no Stripe object is created. Credits must be bought explicitly via [`POST /api/credits/sessions`](/api-reference/credits/sessions-create).",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "insufficient_credits"
            ]
          },
          "remaining_credits": {
            "type": "integer",
            "description": "The account's balance, in credits (micro-dollars: 1,000,000 = `$1.00`).",
            "example": 0
          },
          "required_credits": {
            "type": "integer",
            "description": "The base price of this request, in credits. Analyze: 50000 (`$0.05`) for a single preset or custom prompt, 650000 (`$0.65`) for `full_report`. Analyze batch: 50000, once for the whole batch. Song moments: 10000 (`$0.01`) plus 50000 for every moment that may be described, which is 360000 (`$0.36`) with the default `describe: true`. The per-second inference charge is added after the model returns.",
            "example": 50000
          },
          "billingUrl": {
            "type": "string",
            "description": "Static link to the Recoup app, where a human can save a card and buy credits. It is a constant, not a freshly minted Stripe Checkout Session, so a credit-gated endpoint that keeps returning 402 creates nothing. To buy credits programmatically, call [`POST /api/credits/sessions`](/api-reference/credits/sessions-create).",
            "example": "https://app.recoupable.dev"
          }
        },
        "required": [
          "error",
          "remaining_credits",
          "required_credits",
          "billingUrl"
        ]
      },
      "SongAnalyzeRequest": {
        "type": "object",
        "description": "Provide exactly one of `preset` or `prompt`, plus `audio_url`. Use `preset` for structured analysis workflows, or `prompt` for free-form questions about the audio.",
        "required": [
          "audio_url"
        ],
        "properties": {
          "preset": {
            "type": "string",
            "enum": [
              "catalog_metadata",
              "mood_tags",
              "lyric_transcription",
              "mix_feedback",
              "song_description",
              "music_theory",
              "similar_artists",
              "sample_detection",
              "sync_brief_match",
              "audience_profile",
              "content_advisory",
              "playlist_pitch",
              "artist_development_notes",
              "full_report"
            ],
            "description": "Name of a curated analysis preset. Use instead of prompt for structured, optimized output. The 'full_report' preset runs all 13 presets in parallel and returns a comprehensive report. See [List Analyze Presets](/api-reference/songs/analyze-presets) for the full list of available presets.",
            "example": "catalog_metadata"
          },
          "prompt": {
            "type": "string",
            "minLength": 1,
            "maxLength": 24000,
            "description": "Text prompt or question about the music",
            "example": "Describe the genre, tempo, and mood of this track."
          },
          "audio_url": {
            "type": "string",
            "format": "uri",
            "description": "Public URL to an audio file (MP3, WAV, FLAC, M4A, AAC, OGG or WebM, up to 20 minutes). Required in every mode, including a custom `prompt`. The format is read from the file extension in the URL path, so give the URL a real extension (`.m4a`, `.ogg`, ...); a URL without one is treated as MP3. Verified before the model is called: the URL must answer a `HEAD` request (or a ranged `GET`) with 2xx within 10 seconds and an `audio/*` or `application/octet-stream` content type, else the request returns 422 and nothing is charged.",
            "example": "https://example.com/song.mp3"
          },
          "max_new_tokens": {
            "type": "integer",
            "minimum": 1,
            "maximum": 2048,
            "default": 512,
            "description": "Maximum number of tokens to generate",
            "example": 512
          },
          "temperature": {
            "type": "number",
            "minimum": 0,
            "maximum": 2,
            "default": 1,
            "description": "Controls output creativity — higher values produce more varied responses",
            "example": 0.7
          },
          "top_p": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "default": 1,
            "description": "Nucleus sampling probability cutoff",
            "example": 0.9
          },
          "do_sample": {
            "type": "boolean",
            "default": false,
            "description": "Enable sampling (set true when using temperature or top_p)",
            "example": false
          }
        }
      },
      "SongAnalyzeResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success",
              "partial"
            ],
            "description": "`success` when the analysis completed. `partial` only for a `full_report` in which at least one section failed; the HTTP status is still `200`, and `failed` names the sections that did not return."
          },
          "preset": {
            "type": "string",
            "description": "Preset used for analysis, when applicable",
            "example": "catalog_metadata"
          },
          "response": {
            "description": "Model output for single-preset or custom-prompt analysis. May be plain text or structured JSON depending on the preset."
          },
          "report": {
            "type": "object",
            "description": "Full report payload, returned only for the `full_report` preset. It always has all 13 section keys: `overview`, `mood`, `description`, `lyrics`, `content_advisory`, `music_theory`, `mix_feedback`, `similar_artists`, `sample_detection`, `audience`, `sync`, `playlist_pitch` and `development`. A section that failed holds `{ \"error\": \"Section failed: ...\" }` instead of its result."
          },
          "elapsed_seconds": {
            "type": "number",
            "format": "float",
            "description": "Inference time in seconds. For `full_report` it is the wall time of the whole report, with its sections running in parallel."
          },
          "failed": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Returned with `full_report` only. The report keys of the sections that failed, in report order; empty when `status` is `success`. A failed section is not charged. Retry it with the single preset that fills its key (see the report key table on the [Analyze songs](/api-reference/songs/analyze) page).",
            "example": [
              "lyrics",
              "sync"
            ]
          }
        }
      },
      "SongArtist": {
        "type": "object",
        "description": "Artist associated with a song",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique identifier for the artist account"
          },
          "name": {
            "type": "string",
            "nullable": true,
            "description": "Name of the artist (can be null)"
          },
          "timestamp": {
            "type": "integer",
            "nullable": true,
            "description": "Timestamp associated with the artist account (can be null)"
          }
        }
      },
      "SongsErrorResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "error"
            ],
            "description": "Status of the request"
          },
          "error": {
            "type": "string",
            "description": "Error message describing what went wrong"
          }
        }
      },
      "SongsResponse": {
        "type": "object",
        "description": "Response containing songs data",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success",
              "error"
            ],
            "description": "Status of the request"
          },
          "songs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Song"
            },
            "description": "Array of song objects with artist information"
          },
          "error": {
            "type": "string",
            "description": "Error message (only present if status is 'error')"
          }
        }
      },
      "Task": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique identifier for the task"
          },
          "title": {
            "type": "string",
            "description": "Descriptive title or name of the task"
          },
          "prompt": {
            "type": "string",
            "description": "Detailed instruction or prompt for task execution"
          },
          "schedule": {
            "type": "string",
            "description": "Cron expression defining when the task should execute (e.g., '0 10 * * *')"
          },
          "timezone": {
            "type": "string",
            "nullable": true,
            "description": "IANA time zone the task's cron is interpreted in, read from the Trigger.dev schedule (the source of truth). Null when the task has no schedule or the timezone can't be read.",
            "example": "America/New_York"
          },
          "account_id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique identifier for the associated account"
          },
          "artist_account_id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique identifier for the associated artist account"
          },
          "enabled": {
            "type": "boolean",
            "nullable": true,
            "description": "Whether the task is enabled. Defaults to true."
          },
          "trigger_schedule_id": {
            "type": "string",
            "nullable": true,
            "description": "Identifier for the trigger schedule associated with this task"
          },
          "recent_runs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TaskRunResponse"
            },
            "description": "Last 5 runs for this task, sourced from the Trigger.dev API."
          },
          "upcoming": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Next scheduled run times."
          },
          "owner_email": {
            "type": "string",
            "nullable": true,
            "description": "Primary email address for the task owner account when one exists."
          },
          "artist_name": {
            "type": "string",
            "nullable": true,
            "description": "Display name of the artist account the task runs for (`artist_account_id`), so a task list can label rows without a roster lookup. Null when the artist account no longer exists.",
            "example": "Braden Bales"
          },
          "model": {
            "type": "string",
            "nullable": true,
            "minLength": 1,
            "description": "AI model identifier used when the task runs. When present as a string, must be non-empty; otherwise `null` if unset."
          }
        }
      },
      "TaskRunListResponse": {
        "type": "object",
        "required": [
          "status",
          "runs"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success"
            ],
            "description": "Indicates the request was successful"
          },
          "runs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TaskRunResponse"
            },
            "description": "List of recent task runs for the authenticated account. Each item is a raw Trigger.dev SDK run object (same shape as TaskRunResponse, but without `output` and `error` fields)."
          }
        }
      },
      "CreateContentRunOutput": {
        "type": "object",
        "description": "Output payload for a `create-content` task run. Returned in `output` when `status` is `COMPLETED`. Agents should poll `/api/tasks/runs?runId=…` and read these fields once the run is done.",
        "required": [
          "videoSourceUrl",
          "imageUrl",
          "captionText",
          "template"
        ],
        "properties": {
          "videoSourceUrl": {
            "type": "string",
            "format": "uri",
            "description": "URL to the rendered final 9:16 video (image + motion + audio + caption already composed)."
          },
          "imageUrl": {
            "type": "string",
            "format": "uri",
            "description": "URL to the base image used for video generation."
          },
          "captionText": {
            "type": "string",
            "description": "The caption text burned into the video."
          },
          "template": {
            "type": "string",
            "description": "Template id used for this run (e.g. `album-record-store`, `artist-caption-bedroom`).",
            "example": "artist-caption-bedroom"
          },
          "lipsync": {
            "type": "boolean",
            "description": "Whether lipsync was applied (audio-driven mouth animation)."
          },
          "audio": {
            "type": "object",
            "description": "Metadata about the song clip used in the final composition.",
            "properties": {
              "songTitle": {
                "type": "string"
              },
              "songFilename": {
                "type": "string"
              },
              "startSeconds": {
                "type": "number",
                "description": "Offset into the source song where the clip starts."
              },
              "durationSeconds": {
                "type": "number",
                "description": "Length of the audio clip in seconds."
              },
              "clipLyrics": {
                "type": "string",
                "nullable": true,
                "description": "Lyrics for the chosen clip, when available."
              },
              "clipMood": {
                "type": "string",
                "nullable": true,
                "description": "Inferred mood for the chosen clip, when available."
              }
            }
          }
        }
      },
      "TaskRunsErrorResponse": {
        "type": "object",
        "required": [
          "status",
          "error"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "error"
            ],
            "description": "Always `error` for this response shape"
          },
          "error": {
            "type": "string",
            "description": "Human-readable error message"
          }
        }
      },
      "TaskRunResponse": {
        "type": "object",
        "description": "Raw Trigger.dev SDK run object. The API passes through the SDK response without field mapping. See https://trigger.dev/docs/management/runs/retrieve for the full reference. When listing runs, `output`, `error`, `payload`, and `attempts` are not included.",
        "required": [
          "id",
          "status",
          "taskIdentifier",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The unique run identifier, prefixed with `run_`"
          },
          "status": {
            "type": "string",
            "enum": [
              "PENDING_VERSION",
              "DELAYED",
              "QUEUED",
              "EXECUTING",
              "REATTEMPTING",
              "FROZEN",
              "COMPLETED",
              "CANCELED",
              "FAILED",
              "CRASHED",
              "INTERRUPTED",
              "SYSTEM_FAILURE"
            ],
            "description": "Current run status"
          },
          "taskIdentifier": {
            "type": "string",
            "description": "The task type identifier (e.g. 'setup-sandbox', 'run-sandbox-command')"
          },
          "idempotencyKey": {
            "type": "string",
            "nullable": true,
            "description": "Idempotency key used to deduplicate trigger requests"
          },
          "version": {
            "type": "string",
            "description": "The worker version that executed the run"
          },
          "isTest": {
            "type": "boolean",
            "description": "Whether this is a test run"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the run was created (ISO 8601)"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the run was last updated (ISO 8601)"
          },
          "startedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When execution started (null if not yet started)"
          },
          "finishedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the run finished (null if still running)"
          },
          "delayedUntil": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "If delayed, when the run becomes eligible to execute"
          },
          "ttl": {
            "description": "Time-to-live. If the run is not started within this duration, it expires.",
            "nullable": true
          },
          "expiredAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the run expired (null if not expired)"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Tags associated with this run (max 10)"
          },
          "metadata": {
            "type": "object",
            "nullable": true,
            "description": "JSON metadata attached to the run"
          },
          "costInCents": {
            "type": "number",
            "description": "Compute cost of the run in cents"
          },
          "baseCostInCents": {
            "type": "number",
            "description": "Base invocation cost in cents"
          },
          "durationMs": {
            "type": "number",
            "description": "Compute duration in milliseconds"
          },
          "env": {
            "type": "object",
            "description": "Environment the run executed in",
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "user": {
                "type": "string",
                "nullable": true
              }
            }
          },
          "depth": {
            "type": "integer",
            "description": "Nesting depth for child runs"
          },
          "batchId": {
            "type": "string",
            "nullable": true,
            "description": "Batch ID if triggered as part of a batch"
          },
          "triggerFunction": {
            "type": "string",
            "enum": [
              "trigger",
              "triggerAndWait",
              "batchTrigger",
              "batchTriggerAndWait"
            ],
            "description": "The function used to trigger this run"
          },
          "payload": {
            "description": "Input payload for the task. Only present when retrieving by runId.",
            "nullable": true
          },
          "output": {
            "description": "Task output data. Only present when retrieving by runId, and only populated when `status` is `COMPLETED`. Shape varies by `taskIdentifier`. The `create-content` task returns the schema below; other tasks return their own.",
            "nullable": true,
            "oneOf": [
              {
                "$ref": "#/components/schemas/CreateContentRunOutput"
              },
              {
                "type": "object",
                "description": "Generic task output (for tasks not yet schema'd)"
              }
            ]
          },
          "error": {
            "type": "object",
            "nullable": true,
            "description": "Error details if the run failed. Only present when retrieving by runId.",
            "properties": {
              "message": {
                "type": "string",
                "description": "Human-readable error message"
              },
              "name": {
                "type": "string",
                "description": "Error name or type"
              },
              "stackTrace": {
                "type": "string",
                "description": "Stack trace"
              }
            }
          },
          "attempts": {
            "type": "array",
            "description": "Attempt history. Only present when retrieving by runId.",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "description": "Attempt ID, prefixed with `attempt_`"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "PENDING",
                    "EXECUTING",
                    "PAUSED",
                    "COMPLETED",
                    "FAILED",
                    "CANCELED"
                  ]
                },
                "createdAt": {
                  "type": "string",
                  "format": "date-time"
                },
                "updatedAt": {
                  "type": "string",
                  "format": "date-time"
                },
                "startedAt": {
                  "type": "string",
                  "format": "date-time",
                  "nullable": true
                },
                "completedAt": {
                  "type": "string",
                  "format": "date-time",
                  "nullable": true
                },
                "error": {
                  "type": "object",
                  "nullable": true,
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "stackTrace": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "schedule": {
            "type": "object",
            "nullable": true,
            "description": "Schedule information if triggered by a schedule. Only present when retrieving by runId."
          },
          "relatedRuns": {
            "type": "object",
            "nullable": true,
            "description": "Related run references (root, parent, children). Only present when retrieving by runId."
          }
        }
      },
      "TasksResponse": {
        "type": "object",
        "required": [
          "status",
          "tasks"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success",
              "error"
            ],
            "description": "Status of the request"
          },
          "tasks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Task"
            },
            "description": "Array of task objects"
          },
          "error": {
            "type": "string",
            "description": "Error message (only present if status is error)"
          }
        }
      },
      "PlanLimitError": {
        "type": "object",
        "required": [
          "status",
          "error",
          "limit",
          "message",
          "plan",
          "task_limit",
          "min_cadence_minutes",
          "current_task_count",
          "billingUrl"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "error"
            ],
            "description": "Always `error` for this response shape"
          },
          "error": {
            "type": "string",
            "enum": [
              "plan_limit"
            ],
            "description": "Machine-readable code. Literally `plan_limit`."
          },
          "limit": {
            "type": "string",
            "enum": [
              "task_count",
              "min_cadence"
            ],
            "description": "Which entitlement blocked the request: `task_count` (the plan's enabled-task cap is reached) or `min_cadence` (the cron fires more often than the plan allows)."
          },
          "message": {
            "type": "string",
            "description": "Human-readable explanation naming the plan and the limit, suitable to show to a user.",
            "example": "Free includes 1 task. Starter includes 3, Pro is unlimited."
          },
          "plan": {
            "type": "string",
            "enum": [
              "free",
              "starter",
              "pro"
            ],
            "description": "The account's current plan, resolved from its subscription."
          },
          "task_limit": {
            "type": "integer",
            "nullable": true,
            "minimum": 1,
            "description": "Maximum enabled tasks on the current plan. Null means uncapped.",
            "example": 1
          },
          "min_cadence_minutes": {
            "type": "integer",
            "minimum": 1,
            "description": "Shortest allowed gap between two consecutive runs of a task on the current plan, in minutes (Free 10080, Starter 1440, Pro 60).",
            "example": 10080
          },
          "current_task_count": {
            "type": "integer",
            "minimum": 0,
            "description": "Enabled tasks the account has right now, excluding the one being created or updated.",
            "example": 1
          },
          "billingUrl": {
            "type": "string",
            "format": "uri",
            "description": "Static link to `/plan` in the Recoup app, where a human can upgrade. Constant; retrying creates nothing.",
            "example": "https://app.recoupable.dev/plan"
          }
        }
      },
      "UpdateTaskFailureResponse": {
        "type": "object",
        "required": [
          "status",
          "error"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "error"
            ],
            "description": "Always `error` for this response shape"
          },
          "error": {
            "type": "string",
            "description": "Human-readable error message (for example `Task not found` on 404)"
          }
        }
      },
      "UpdateTaskValidationErrorResponse": {
        "type": "object",
        "required": [
          "status",
          "error"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "error"
            ],
            "description": "Always `error` when validation fails"
          },
          "missing_fields": {
            "type": "array",
            "description": "JSON path segments for the first schema validation issue, e.g. `[\"id\"]` or `[\"schedule\"]`. The path can be empty for a body-level issue. Omitted when the body is not valid JSON.",
            "items": {
              "oneOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            }
          },
          "error": {
            "type": "string",
            "description": "`Invalid JSON body` when JSON parsing fails; otherwise the message for the first schema validation issue."
          }
        },
        "description": "Request-body validation error for creating or updating a task. Invalid JSON omits `missing_fields`; a schema validation failure includes the path of the first issue."
      },
      "UpdateTaskRequest": {
        "type": "object",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "UUID of the task to update",
            "example": "aade2bce-55c7-468e-a606-c4e76fb2ea2a"
          },
          "title": {
            "type": "string",
            "minLength": 1,
            "description": "New descriptive title. If sent, must be a non-empty string.",
            "example": "Weekly Genre Pulse Check (Updated)"
          },
          "prompt": {
            "type": "string",
            "minLength": 1,
            "description": "New instruction/prompt executed by the task. If sent, must be a non-empty string.",
            "example": "Execute this weekly genre analysis workflow and email a summary to the team."
          },
          "schedule": {
            "type": "string",
            "minLength": 1,
            "description": "New cron expression. If sent, must be non-empty and valid for your environment.",
            "example": "0 10 * * 4"
          },
          "timezone": {
            "type": "string",
            "description": "Optional IANA time zone the cron is interpreted in (DST-aware). If sent, updates the Trigger.dev schedule; a timezone-only change re-syncs the schedule. A cron-only update preserves the existing time zone.",
            "example": "America/New_York"
          },
          "account_id": {
            "type": "string",
            "format": "uuid",
            "description": "UUID of the account to update the task for. Only applicable when the authenticated account has access to multiple accounts via organization membership. If not provided, the task is updated for the API key's own account.",
            "example": "848cd58d-700f-4b38-ab4c-d9f52a1b2c3d"
          },
          "artist_account_id": {
            "type": "string",
            "format": "uuid",
            "description": "UUID of the artist account to associate with the task after update",
            "example": "1873859c-dd37-4e9a-9bac-80d35a1b2c3d"
          },
          "enabled": {
            "type": "boolean",
            "nullable": true,
            "description": "Whether the task is enabled (`true` / `false`), or `null` to clear an explicit override depending on server rules.",
            "example": true
          },
          "model": {
            "type": "string",
            "minLength": 1,
            "description": "AI model identifier to use when the task runs. If sent, must be a non-empty string.",
            "example": "claude-sonnet-4-20250514"
          }
        }
      },
      "ArtistKnowledge": {
        "type": "object",
        "required": [
          "name",
          "url",
          "type"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Display name for the knowledge entry",
            "example": "Artist Knowledge Base Report"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Public URL where the knowledge content is stored. New entries use Supabase Storage; existing legacy URLs remain readable. Anyone with the URL can read the content without authentication.",
            "example": "https://exampleproject.supabase.co/storage/v1/object/public/public-uploads/example/knowledge.txt"
          },
          "type": {
            "type": "string",
            "description": "MIME type of the knowledge content",
            "example": "text/plain"
          }
        }
      },
      "UpdateArtistRequest": {
        "type": "object",
        "description": "All fields are optional. The request must include at least one field.",
        "minProperties": 1,
        "properties": {
          "name": {
            "type": "string",
            "description": "Artist display name",
            "example": "The Weeknd"
          },
          "image": {
            "type": "string",
            "description": "Artist profile image URL. Pass an empty string to clear the existing image; otherwise must be a valid URL.",
            "example": "https://i.scdn.co/image/abc123"
          },
          "instruction": {
            "type": "string",
            "description": "Custom AI instruction shown to assistants when this artist is the active context",
            "example": "Always reference the artist's R&B catalog when discussing collaborations."
          },
          "label": {
            "type": "string",
            "description": "Record label name",
            "example": "XO / Republic Records"
          },
          "knowledges": {
            "type": "array",
            "description": "Knowledge base entries for this artist. Replaces the existing array when provided.",
            "items": {
              "$ref": "#/components/schemas/ArtistKnowledge"
            }
          },
          "profileUrls": {
            "type": "object",
            "description": "Map of uppercase platform identifier to social profile URL. Each entry replaces the existing social for that platform; platforms not included are preserved. Recognized keys: SPOTIFY, INSTAGRAM, TIKTOK, TWITTER, YOUTUBE, APPLE, FACEBOOK, THREADS, BANDSINTOWN. Keys are matched case-sensitively, so lowercase keys will create duplicate socials instead of replacing the existing entry.",
            "additionalProperties": {
              "type": "string"
            },
            "example": {
              "SPOTIFY": "https://open.spotify.com/artist/1Xyo4u8uXC1ZmMpatF05PJ",
              "INSTAGRAM": "https://instagram.com/theweeknd",
              "TIKTOK": "https://tiktok.com/@theweeknd",
              "TWITTER": "https://x.com/theweeknd",
              "YOUTUBE": "https://youtube.com/@theweeknd",
              "APPLE": "https://music.apple.com/us/artist/the-weeknd/479756766",
              "FACEBOOK": "https://facebook.com/theweeknd",
              "THREADS": "https://threads.net/@theweeknd",
              "BANDSINTOWN": "https://www.bandsintown.com/a/1371750-the-weeknd"
            }
          },
          "pinned": {
            "type": "boolean",
            "description": "Pin or unpin the artist for the authenticated account. Affects only the caller's pin state, not other accounts."
          }
        }
      },
      "UpdateArtistResponse": {
        "type": "object",
        "required": [
          "artist"
        ],
        "properties": {
          "artist": {
            "$ref": "#/components/schemas/UpdatedArtist"
          }
        }
      },
      "UpdateArtistErrorResponse": {
        "type": "object",
        "required": [
          "status",
          "error"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "error"
            ],
            "description": "Status of the request"
          },
          "missing_fields": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Path to the field that failed validation, when applicable"
          },
          "error": {
            "type": "string",
            "description": "Error message describing what went wrong"
          }
        }
      },
      "UpdatedArtist": {
        "type": "object",
        "required": [
          "account_id",
          "name",
          "account_socials",
          "pinned"
        ],
        "properties": {
          "account_id": {
            "type": "string",
            "format": "uuid",
            "description": "UUID of the artist account"
          },
          "name": {
            "type": "string",
            "nullable": true,
            "description": "Artist display name"
          },
          "image": {
            "type": "string",
            "nullable": true,
            "description": "Artist profile image URL"
          },
          "instruction": {
            "type": "string",
            "nullable": true,
            "description": "Custom AI instruction for this artist"
          },
          "label": {
            "type": "string",
            "nullable": true,
            "description": "Record label name"
          },
          "knowledges": {
            "type": "array",
            "nullable": true,
            "description": "Knowledge base entries for this artist",
            "items": {
              "$ref": "#/components/schemas/ArtistKnowledge"
            }
          },
          "account_socials": {
            "type": "array",
            "description": "Social profiles linked to the artist after the update",
            "items": {
              "$ref": "#/components/schemas/UpdatedArtistSocial"
            }
          },
          "pinned": {
            "type": "boolean",
            "description": "Whether the authenticated account has pinned this artist"
          }
        }
      },
      "UpdatedArtistSocial": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "UUID of the underlying social record"
          },
          "username": {
            "type": "string",
            "nullable": true,
            "description": "Username on the platform"
          },
          "profile_url": {
            "type": "string",
            "nullable": true,
            "description": "Full profile URL on the platform"
          },
          "link": {
            "type": "string",
            "description": "Profile URL (mirror of profile_url for legacy clients)"
          },
          "type": {
            "type": "string",
            "description": "Uppercase platform identifier inferred from the profile URL, for example INSTAGRAM or LINKEDIN. Unrecognized or empty URLs produce NONE."
          }
        }
      },
      "TaskAuthenticationErrorResponse": {
        "anyOf": [
          {
            "type": "object",
            "properties": {
              "status": {
                "description": "Always `error`.",
                "type": "string",
                "enum": [
                  "error"
                ]
              },
              "error": {
                "description": "Human-readable error message or documented error code.",
                "type": "string"
              }
            },
            "required": [
              "status",
              "error"
            ],
            "additionalProperties": {}
          },
          {
            "type": "object",
            "properties": {
              "status": {
                "description": "Always `error`.",
                "type": "string",
                "enum": [
                  "error"
                ]
              },
              "message": {
                "description": "Human-readable authentication error message.",
                "type": "string"
              }
            },
            "required": [
              "status",
              "message"
            ],
            "additionalProperties": {}
          }
        ]
      },
      "TaskFailureErrorResponse": {
        "type": "object",
        "properties": {
          "status": {
            "description": "Always `error`.",
            "type": "string",
            "enum": [
              "error"
            ]
          },
          "error": {
            "description": "Human-readable error message or documented error code.",
            "type": "string"
          }
        },
        "required": [
          "status",
          "error"
        ],
        "additionalProperties": {}
      },
      "SongAnalyzeUpstreamErrorResponse": {
        "type": "object",
        "description": "Returned (502) when the model service could not produce the analysis. Nothing is charged for the failed work. There are two forms. A single preset or custom prompt the model service rejected carries `upstream_status` and `detail`. A `full_report` in which every section failed carries `failed` instead.",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "error"
            ]
          },
          "error": {
            "type": "string",
            "description": "`Music analysis failed (status N)`, where N is `upstream_status`, for a rejected single call. `Music analysis failed for every section` for a `full_report` in which no section returned.",
            "example": "Music analysis failed (status 422)"
          },
          "upstream_status": {
            "type": "integer",
            "description": "The HTTP status the model service answered with. `422` means it could not fetch the audio (`audio_unreachable`) or could not read the file as audio (`audio_undecodable`). Present on a rejected single call only.",
            "example": 422
          },
          "detail": {
            "type": "string",
            "description": "The model service's error response, as text. For an audio problem it names the cause, beginning `audio_unreachable` or `audio_undecodable`, followed by a reason that varies. Present on a rejected single call only."
          },
          "failed": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "All 13 report keys, in report order. Present on a `full_report` in which every section failed only."
          }
        },
        "required": [
          "status",
          "error"
        ]
      },
      "SongAnalyzeBatchPrompt": {
        "type": "object",
        "required": [
          "id",
          "prompt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64,
            "description": "Your key for this prompt's answer. Unique within the batch.",
            "example": "genre"
          },
          "prompt": {
            "type": "string",
            "minLength": 1,
            "maxLength": 4000,
            "description": "The question to ask about the audio.",
            "example": "What genre is this?"
          },
          "max_new_tokens": {
            "type": "integer",
            "minimum": 1,
            "maximum": 2048,
            "default": 400,
            "description": "Maximum number of tokens in this prompt's answer.",
            "example": 64
          }
        }
      },
      "SongAnalyzeBatchRequest": {
        "type": "object",
        "description": "One audio file and up to 40 short prompts about it. Presets are not available in a batch; use [`POST /api/songs/analyze`](/api-reference/songs/analyze) for those.",
        "required": [
          "audio_url",
          "prompts"
        ],
        "properties": {
          "audio_url": {
            "type": "string",
            "format": "uri",
            "description": "Public URL to an audio file, with the same formats, length limit and verification as [`POST /api/songs/analyze`](/api-reference/songs/analyze): the URL must answer a `HEAD` request (or a ranged `GET`) with 2xx within 10 seconds and an `audio/*` or `application/octet-stream` content type, else the request returns 422 and nothing is charged. The file is downloaded once for the whole batch.",
            "example": "https://example.com/song.mp3"
          },
          "prompts": {
            "type": "array",
            "minItems": 1,
            "maxItems": 40,
            "items": {
              "$ref": "#/components/schemas/SongAnalyzeBatchPrompt"
            },
            "description": "The prompts to answer, 1 to 40. Each `id` must be unique within the batch. Answers come back in this order."
          },
          "temperature": {
            "type": "number",
            "minimum": 0,
            "maximum": 2,
            "default": 0.2,
            "description": "Controls output creativity for every prompt in the batch. Lower than the single endpoint's default of 1, because batched questions are usually factual.",
            "example": 0.2
          },
          "top_p": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "default": 1,
            "description": "Nucleus sampling probability cutoff, for every prompt in the batch.",
            "example": 1
          },
          "do_sample": {
            "type": "boolean",
            "default": false,
            "description": "Enable sampling for every prompt in the batch (set true when using `temperature` or `top_p`).",
            "example": false
          }
        }
      },
      "SongAnalyzeBatchAnswer": {
        "type": "object",
        "required": [
          "id",
          "response",
          "elapsed_seconds"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The `id` you gave the prompt."
          },
          "response": {
            "type": "string",
            "description": "The model's text answer."
          },
          "elapsed_seconds": {
            "type": "number",
            "format": "float",
            "description": "Inference time for this prompt alone, in seconds."
          }
        }
      },
      "SongAnalyzeBatchResponse": {
        "type": "object",
        "required": [
          "status",
          "answers",
          "elapsed_seconds",
          "prompts"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success"
            ],
            "description": "Request status."
          },
          "answers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SongAnalyzeBatchAnswer"
            },
            "description": "One answer per prompt, in the order the prompts were sent."
          },
          "elapsed_seconds": {
            "type": "number",
            "format": "float",
            "description": "Wall time for the whole batch in seconds, audio download included. The charge is priced on this figure."
          },
          "prompts": {
            "type": "integer",
            "description": "How many prompts the batch answered."
          }
        }
      },
      "SongMomentsRequest": {
        "type": "object",
        "required": [
          "audio_url"
        ],
        "properties": {
          "audio_url": {
            "type": "string",
            "format": "uri",
            "description": "Public URL to an audio file (MP3, WAV or FLAC). The first 15 minutes are measured, and the file can be up to 100 MB. Verified before anything runs: the URL must answer a `HEAD` request (or a ranged `GET`) with 2xx within 10 seconds and an `audio/*` or `application/octet-stream` content type, else the request returns 422 and nothing is charged.",
            "example": "https://example.com/song.mp3"
          },
          "section_order": {
            "type": "array",
            "maxItems": 60,
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 40
            },
            "description": "The song's section headers in order, as a lyric sheet lists them (`Intro`, `Verse 1`, `Pre-Chorus`, `Chorus`, ...). With it, sections are named from the sheet. If the sheet does not fit the recording, sections are named from the audio instead and `names_from` says `audio`. Headers the API does not recognize are ignored.",
            "example": [
              "Intro",
              "Verse 1",
              "Chorus",
              "Verse 2",
              "Chorus",
              "Outro"
            ]
          },
          "tempo_hint": {
            "type": "number",
            "minimum": 30,
            "maximum": 300,
            "description": "The song's tempo in BPM, from your metadata. The beat tracker often locks onto a multiple of the real tempo, so its reading, or half, double, three-quarters, four-thirds, two-thirds or one and a half times of it, is used when that lands within 8% of the hint. When none does, the hint is used. `tempo_note` in the response says which happened.",
            "example": 120
          },
          "describe": {
            "oneOf": [
              {
                "type": "boolean"
              },
              {
                "type": "array",
                "maxItems": 7,
                "items": {
                  "type": "string",
                  "enum": [
                    "intro",
                    "vocal_hook",
                    "chorus_lift",
                    "build_up",
                    "climax",
                    "breakdown",
                    "outro"
                  ]
                }
              }
            ],
            "default": true,
            "description": "Which moments to describe. `true` (the default) describes every moment found, `false` describes none, and a list of moment types describes only those. A described moment adds `description`, `emotion`, `best_for` and `vocals`, and each description is charged at the analyze rate.",
            "example": [
              "vocal_hook",
              "climax"
            ]
          }
        }
      },
      "SongSection": {
        "type": "object",
        "required": [
          "label",
          "start",
          "end",
          "group",
          "energy"
        ],
        "properties": {
          "label": {
            "type": "string",
            "description": "The section's name: `intro`, `verse`, `pre-chorus`, `chorus`, `post-chorus`, `bridge`, `instrumental`, `drop`, `breakdown`, `outro`, or `section` when nothing fits better.",
            "example": "chorus"
          },
          "start": {
            "type": "number",
            "description": "Start, in seconds from the beginning of the song.",
            "example": 24
          },
          "end": {
            "type": "number",
            "description": "End, in seconds from the beginning of the song.",
            "example": 44
          },
          "group": {
            "type": "string",
            "description": "Sections that share a letter sound alike (`A`, `B`, `A`, `C`, ...), so repeated choruses share a group.",
            "example": "C"
          },
          "energy": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100,
            "description": "Average loudness of the section, 0 to 100 relative to the song's own quietest and loudest seconds.",
            "example": 83
          }
        }
      },
      "SongMoment": {
        "type": "object",
        "required": [
          "type",
          "start",
          "end",
          "section",
          "energy",
          "cuts"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "intro",
              "vocal_hook",
              "chorus_lift",
              "build_up",
              "climax",
              "breakdown",
              "outro"
            ],
            "description": "What the moment is. A song has at most one moment of each type, and not every song has every type."
          },
          "start": {
            "type": "number",
            "description": "Start, in seconds from the beginning of the song.",
            "example": 24
          },
          "end": {
            "type": "number",
            "description": "End, in seconds from the beginning of the song.",
            "example": 39
          },
          "section": {
            "type": "string",
            "description": "The label of the section the moment starts in.",
            "example": "chorus"
          },
          "energy": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100,
            "description": "Average loudness of the moment, 0 to 100 relative to the song's own quietest and loudest seconds.",
            "example": 87
          },
          "cuts": {
            "type": "object",
            "description": "Edit points for each standard length that fits inside the song, keyed `6`, `15` and `30` (seconds). Each value is `[start, end]` in seconds on the beat grid. A cut starts at the moment's start, except for `build_up` and `outro`, whose cuts end at the moment's end. A cut can run past the end of a short moment.",
            "additionalProperties": {
              "type": "array",
              "minItems": 2,
              "maxItems": 2,
              "items": {
                "type": "number"
              }
            },
            "example": {
              "6": [
                24,
                30
              ],
              "15": [
                24,
                39
              ],
              "30": [
                24,
                54
              ]
            }
          },
          "also": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "intro",
                "vocal_hook",
                "chorus_lift",
                "build_up",
                "climax",
                "breakdown",
                "outro"
              ]
            },
            "description": "Other moment types this one also covers. Moments that overlap by more than 60% are merged, and the one that is kept lists the others here. Present only when moments were merged.",
            "example": [
              "chorus_lift"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "description": "One sentence on what happens musically in the moment, written after listening to a clip of just that moment. Present only on described moments (see `describe`); `null` when the reply could not be read."
          },
          "emotion": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Up to three lowercase mood words. Present only on described moments; empty when the reply could not be read."
          },
          "best_for": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Up to three uses the moment suits, such as `film trailer`, `TV promo`, `ad`, `social video`, `video game`, `sports`, `documentary` or `end credits`. Present only on described moments; empty when the reply could not be read."
          },
          "vocals": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Whether the moment has vocals. Present only on described moments; `null` when the reply could not be read."
          }
        }
      },
      "SongBand": {
        "type": "object",
        "required": [
          "band",
          "lo_hz",
          "hi_hz",
          "share_pct",
          "side_pct",
          "level_pct",
          "status"
        ],
        "properties": {
          "band": {
            "type": "string",
            "enum": [
              "sub",
              "low",
              "low_mid",
              "mid",
              "high_mid",
              "high",
              "air"
            ],
            "description": "Band name."
          },
          "lo_hz": {
            "type": "integer",
            "description": "Lower edge of the band in hertz.",
            "example": 60
          },
          "hi_hz": {
            "type": "integer",
            "description": "Upper edge of the band in hertz.",
            "example": 250
          },
          "share_pct": {
            "type": "number",
            "description": "The band's share of the mix's energy, in percent. The seven shares add up to about 100.",
            "example": 31
          },
          "side_pct": {
            "type": "number",
            "description": "Side (left minus right) energy in the band, as a percent of the band's mid energy.",
            "example": 3.4
          },
          "level_pct": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100,
            "description": "The band's level against a typical pop energy split, 0 to 100. 50 is on the reference.",
            "example": 51
          },
          "status": {
            "type": "string",
            "enum": [
              "clear",
              "heavy",
              "light"
            ],
            "description": "`clear` when the band is within 4 dB of the reference split, `heavy` when it is more than 4 dB above, `light` when it is more than 4 dB below."
          }
        }
      },
      "SongFingerprint": {
        "type": "object",
        "description": "Six sound axes. Each runs from 0 at its left label to 1 at its right label.",
        "properties": {
          "organic_electronic": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "0 organic, 1 electronic."
          },
          "sparse_dense": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "0 sparse, 1 dense."
          },
          "dark_bright": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "0 dark, 1 bright."
          },
          "intimate_anthemic": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "0 intimate, 1 anthemic."
          },
          "smooth_textured": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "0 smooth, 1 textured."
          },
          "slow_fast": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "0 slow, 1 fast."
          }
        }
      },
      "SongDrop": {
        "type": "object",
        "required": [
          "type",
          "start",
          "end",
          "beats",
          "depth_db",
          "mmss"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "mute",
              "breakdown"
            ],
            "description": "`mute` is a sudden drop in level shorter than two beats, a clean place for a zero-bleed cut. `breakdown` is a drop of two beats or more, the band pulling back."
          },
          "start": {
            "type": "number",
            "description": "Start, in seconds.",
            "example": 52
          },
          "end": {
            "type": "number",
            "description": "End, in seconds.",
            "example": 56
          },
          "beats": {
            "type": "number",
            "description": "Length in beats.",
            "example": 8
          },
          "depth_db": {
            "type": "number",
            "description": "How far the level falls below the surrounding music, in decibels.",
            "example": 11.3
          },
          "mmss": {
            "type": "string",
            "description": "The start as `m:ss`.",
            "example": "0:52"
          }
        }
      },
      "SongEditPoint": {
        "type": "object",
        "required": [
          "type",
          "time",
          "name",
          "note",
          "mmss"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "in",
              "out"
            ],
            "description": "`in` is a place to cut into the song, `out` a place to cut out of it."
          },
          "time": {
            "type": "number",
            "description": "The point, in seconds.",
            "example": 24
          },
          "name": {
            "type": "string",
            "description": "What the point is, for example `Chorus drop`, `Breakdown` or `Fade begins`.",
            "example": "Chorus drop"
          },
          "note": {
            "type": "string",
            "description": "A short note on how to use the point.",
            "example": "Full entry into the chorus after the verse."
          },
          "mmss": {
            "type": "string",
            "description": "The point as `m:ss`.",
            "example": "0:24"
          }
        }
      },
      "SongMomentsResponse": {
        "type": "object",
        "required": [
          "status",
          "measured_with",
          "duration_seconds",
          "tempo_bpm",
          "loudness_lufs",
          "ending",
          "names_from",
          "sections",
          "moments",
          "energy_curve",
          "elapsed_seconds"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success"
            ],
            "description": "Request status."
          },
          "measured_with": {
            "type": "string",
            "description": "The version of the measurement that produced these numbers. Store it with the result: it changes whenever the measurement's results would change, so you know which stored results to rerun. A version ending `+extended-<date>` includes the extended fields below.",
            "example": "song-moments-2026-11-04+extended-2026-11-04"
          },
          "duration_seconds": {
            "type": "number",
            "description": "Length of the audio that was measured, in seconds (at most the first 15 minutes).",
            "example": 96
          },
          "tempo_bpm": {
            "type": "number",
            "description": "Tempo in beats per minute, reconciled to `tempo_hint` when one was sent.",
            "example": 120
          },
          "loudness_lufs": {
            "type": [
              "number",
              "null"
            ],
            "description": "Integrated loudness in LUFS. The same figure as `lufs`.",
            "example": -9.4
          },
          "ending": {
            "type": "string",
            "enum": [
              "fade",
              "cold"
            ],
            "description": "`fade` when the last seconds fade out by more than 10 dB, `cold` when the song stops."
          },
          "names_from": {
            "type": "string",
            "enum": [
              "lyrics",
              "audio"
            ],
            "description": "`lyrics` when `section_order` fit the audio and named the sections, `audio` when sections were named from repetition and loudness."
          },
          "sections": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SongSection"
            },
            "description": "The song's sections in order, covering the whole song."
          },
          "moments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SongMoment"
            },
            "description": "The song's best short moments for licensing and editing, in order of start time."
          },
          "energy_curve": {
            "type": "array",
            "items": {
              "type": "integer",
              "minimum": 0,
              "maximum": 100
            },
            "description": "Loudness for each second of the song, 0 to 100 relative to the song's own quietest and loudest seconds."
          },
          "tempo_note": {
            "type": "string",
            "description": "How `tempo_bpm` was decided: `beat tracker` alone, or reconciled to, or overruled by, `tempo_hint`.",
            "example": "beat tracker 60 reconciled to hint 120"
          },
          "lufs": {
            "type": [
              "number",
              "null"
            ],
            "description": "Integrated loudness of the stereo file, in LUFS. When the loudness meter cannot read the file, an estimate from the mono mix is used instead.",
            "example": -9.4
          },
          "true_peak_dbtp": {
            "type": [
              "number",
              "null"
            ],
            "description": "True peak in dBTP. `null` when the loudness meter could not read the file.",
            "example": -1.3
          },
          "sample_peak_dbfs": {
            "type": [
              "number",
              "null"
            ],
            "description": "Highest sample level, in dBFS.",
            "example": -1.62
          },
          "crest_factor_db": {
            "type": [
              "number",
              "null"
            ],
            "description": "Highest sample peak minus the average (RMS) level, in decibels. A low value means a heavily compressed master.",
            "example": 9.8
          },
          "loudness_range_lu": {
            "type": [
              "number",
              "null"
            ],
            "description": "Loudness range in LU, the spread between the quieter and louder parts. `null` when the loudness meter could not read the file.",
            "example": 5.1
          },
          "broadcast_ready": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "`true` when the true peak is at or under -1 dBTP and the integrated loudness is between -16 and -7 LUFS. `null` when the true peak is unknown.",
            "example": true
          },
          "stereo": {
            "type": "object",
            "description": "The stereo image.",
            "properties": {
              "lr_correlation": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Correlation between the left and right channels, -1 to 1. 1 means identical channels.",
                "example": 0.842
              },
              "side_to_mid": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Side (left minus right) level over mid (left plus right) level. 0 is mono; higher is wider.",
                "example": 0.31
              }
            }
          },
          "spectral_centroid_hz": {
            "type": [
              "number",
              "null"
            ],
            "description": "Mean spectral centroid in hertz, a measure of brightness.",
            "example": 2410
          },
          "rolloff95_hz": {
            "type": [
              "number",
              "null"
            ],
            "description": "The frequency in hertz under which 95% of the spectral energy lies.",
            "example": 6931
          },
          "band_profile": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SongBand"
            },
            "description": "Seven frequency bands from `sub` to `air`, with each band's share of the energy, its side energy and its level against a typical pop split."
          },
          "fingerprint": {
            "$ref": "#/components/schemas/SongFingerprint"
          },
          "fingerprint_inputs": {
            "type": "object",
            "description": "The raw measurements the fingerprint is built from.",
            "properties": {
              "flatness": {
                "type": "number",
                "description": "Mean spectral flatness: higher is noisier or more electronic.",
                "example": 0.0213
              },
              "contrast_db": {
                "type": "number",
                "description": "Mean spectral contrast in decibels.",
                "example": 20.4
              },
              "zcr_std": {
                "type": "number",
                "description": "Spread of the zero-crossing rate, a measure of textural change.",
                "example": 0.0412
              },
              "perc_ratio": {
                "type": "number",
                "description": "Share of the energy that is percussive, 0 to 1.",
                "example": 0.318
              },
              "onsets_per_s": {
                "type": "number",
                "description": "Note and hit onsets per second.",
                "example": 4.11
              }
            }
          },
          "key": {
            "type": "object",
            "description": "The song's key.",
            "properties": {
              "tonic": {
                "type": "string",
                "enum": [
                  "C",
                  "C#",
                  "D",
                  "D#",
                  "E",
                  "F",
                  "F#",
                  "G",
                  "G#",
                  "A",
                  "A#",
                  "B"
                ],
                "description": "The key's root note.",
                "example": "A"
              },
              "mode": {
                "type": "string",
                "enum": [
                  "major",
                  "minor"
                ],
                "example": "minor"
              },
              "confidence": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "How closely the song's pitch content matches the chosen key, as a correlation up to 1. Higher is more certain.",
                "example": 0.81
              }
            }
          },
          "time_signature": {
            "type": "string",
            "enum": [
              "3/4",
              "4/4"
            ],
            "description": "The meter, from how beat strength repeats."
          },
          "valence": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "The measured half of mood, 0 (dark) to 1 (bright), from mode and brightness. An estimate, not a listening judgment.",
            "example": 0.37
          },
          "arousal": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "How energetic the song is, 0 (calm) to 1 (intense), from loudness and tempo. An estimate, not a listening judgment.",
            "example": 0.72
          },
          "mute_drops": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SongDrop"
            },
            "description": "Sudden drops in level, in order of start time. Local drops are found against the preceding eight beats and whole-song breakdowns against the song's median level. None start in the first eight beats, inside the fade, or in the last three seconds."
          },
          "chord_loop": {
            "type": "object",
            "description": "The most common four-bar chord cycle, from one chord per bar chosen among the key's own chords. Treat it as one estimate beside another source, not a transcription: `confidence` is always `low`.",
            "properties": {
              "dominant_loop": {
                "type": "string",
                "description": "The cycle as four chord names, for example `Am F C G`.",
                "example": "Am F C G"
              },
              "loop_share": {
                "type": "number",
                "description": "The share of four-bar windows that match the cycle, 0 to 1.",
                "example": 0.52
              },
              "distinct_chords": {
                "type": "integer",
                "description": "How many different chords appear.",
                "example": 5
              },
              "bars": {
                "type": "integer",
                "description": "How many bars were analyzed.",
                "example": 47
              },
              "confidence": {
                "type": "string",
                "enum": [
                  "low"
                ],
                "description": "Always `low`."
              }
            }
          },
          "edit_points": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SongEditPoint"
            },
            "description": "Typed places to cut into (`in`) or out of (`out`) the song, in order of time: the cold open, chorus entries and exits, the deepest mutes, breakdowns and their re-entry, and where a fade begins."
          },
          "elapsed_seconds": {
            "type": "number",
            "format": "float",
            "description": "Seconds the whole request took, descriptions included.",
            "example": 41.62
          }
        }
      }
    }
  }
}