{
  "openapi": "3.1.0",
  "info": {
    "title": "Recoup API - Research",
    "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/chats": {
      "get": {
        "operationId": "getChats",
        "description": "Returns chats for the authenticated account. Personal Bearer tokens see only the caller's chats; org keys see chats across the org's member accounts; admin keys see all chats. Each row carries the chat id and the session id needed to build the chat URL.",
        "parameters": [
          {
            "name": "artist_account_id",
            "in": "query",
            "description": "Optional. Filter chats to only include those whose owning session's `artistId` matches the supplied artist account id. Combine with `account_id` to scope by both owner and artist.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "account_id",
            "in": "query",
            "description": "Filter to a specific account. Only applicable when the authenticated account has access to multiple accounts via organization membership.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Chats retrieved successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetChatsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request - invalid query parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetChatsErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden - account_id is not a member of the organization or account tried to filter by an account_id they don't have access to",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "postChats",
        "description": "Create a new chat room. Optionally associate it with an artist and/or provide a client-generated chat ID. Pass accountId to create a chat on behalf of a specific account the API key has access to.",
        "requestBody": {
          "description": "Chat creation parameters",
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateChatRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Chat created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateChatResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request - failed to create chat",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateChatErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden - API key does not have access to the specified accountId",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateChatErrorResponse"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "patchChats",
        "description": "Update a chat room's topic (display name). The chatId is required; the topic field will be updated. Topic must be between 3 and 50 characters.",
        "requestBody": {
          "description": "Chat fields to update",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateChatRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Chat updated successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpdateChatResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request - invalid parameters or validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpdateChatErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden - API key does not have access to this chat",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found - chat room does not exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpdateChatErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteChats",
        "description": "Delete a chat room by ID. This operation also removes related room records (memory emails, memories) before deleting the room itself.",
        "requestBody": {
          "description": "Chat deletion parameters",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeleteChatRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Chat deleted successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteChatResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request - invalid parameters or validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteChatErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden - API key does not have access to this chat",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found - chat room does not exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteChatErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error - failed to delete chat",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteChatErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/chats/{id}/artist": {
      "get": {
        "operationId": "getChatsByIdArtist",
        "description": "Retrieve the artist associated with a specific chat room. Returns 404 if the chat does not exist or is not accessible by the authenticated caller.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "The unique identifier (UUID) of the chat room.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Chat artist resolved successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetChatArtistResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request - invalid chat id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetChatArtistErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found - chat does not exist or is not accessible",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetChatArtistErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/chats/{id}/messages": {
      "get": {
        "operationId": "getChatsByIdMessages",
        "description": "Retrieve all messages (memories) for a specific chat room in chronological order.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "The unique identifier (UUID) of the chat room.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Messages retrieved successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetChatMessagesResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request - invalid chat id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetChatMessagesErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden - caller does not have access to this chat",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found - chat does not exist or is not accessible",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetChatMessagesErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error - failed to retrieve messages",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetChatMessagesErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/chats/{id}/messages/copy": {
      "post": {
        "operationId": "postChatsByIdMessagesCopy",
        "description": "Copy all messages from the source chat (`id` path param) to a target chat (`targetChatId` in request body). By default, existing target messages are cleared before copy.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "The source chat room UUID.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "description": "Copy target options",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CopyChatMessagesRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Messages copied successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CopyChatMessagesResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request - invalid payload or chat identifiers",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CopyChatMessagesErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden - caller lacks access to source or target chat",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CopyChatMessagesErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found - source or target chat does not exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CopyChatMessagesErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error - failed to copy messages",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CopyChatMessagesErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/chats/{id}/messages/trailing": {
      "delete": {
        "operationId": "deleteChatsByIdMessagesTrailing",
        "description": "Delete a message in a Workflow chat and every message after it, ordered by `created_at` then `id`. Concurrent edit/resend is unsupported: stop other writers first, because an active workflow run returns `409`. `200` means the delete ran, possibly affecting zero rows; after a transport failure, refresh the chat history instead of resending.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "UUID of the owned Workflow chat.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "from_message_id",
            "in": "query",
            "description": "UUID of the persisted message in this Workflow chat to use as the inclusive created_at, id boundary. Any persisted message role is eligible.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "account_id",
            "in": "query",
            "required": false,
            "description": "Optional effective account override, accepted only when current authentication authorizes access to that account. It does not grant access by itself.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Delete statement succeeded, including a possible zero-row result. No deleted count, durable-absence or concurrent edit/resend guarantee.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteTrailingChatMessagesResponse"
                },
                "examples": {
                  "statementSuccess": {
                    "value": {
                      "status": "success",
                      "chat_id": "11111111-1111-4111-8111-111111111111",
                      "from_message_id": "22222222-2222-4222-8222-222222222222"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid chat UUID or missing/invalid from_message_id query UUID.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteTrailingChatMessagesErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing, conflicting or invalid credentials. Authentication errors retain the error or message envelope returned by the selected authentication path.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteTrailingChatAuthenticationErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Access denied to the requested account_id override or the effective account does not own the Workflow chat session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteTrailingChatMessagesErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Workflow chat/session is absent, or the anchor is absent from this chat (including a foreign-chat anchor).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteTrailingChatMessagesErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "An occupied workflow slot was observed. Pending reservations and other nonnull values are rejected without provider calls, cancellation or slot mutation. This check is not a concurrency lock.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteTrailingChatMessagesErrorResponse"
                },
                "examples": {
                  "occupiedSlot": {
                    "value": {
                      "status": "error",
                      "error": "Chat has an active workflow reservation; retry after it clears"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Authentication/storage failure, chat/session/anchor read failure, invalid stored boundary, or delete failure. A transport failure may leave the commit outcome unknown; refresh history before further action.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteTrailingChatMessagesErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/chats/compact": {
      "post": {
        "operationId": "postChatsCompact",
        "description": "Compact one or more chat conversations into summarized versions. This reduces the size of chat history while preserving key information. Optionally provide a prompt to control what information gets preserved in the compacted summary.",
        "requestBody": {
          "description": "Chat compaction parameters",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CompactChatsRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Chats compacted successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CompactChatsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request - invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized - invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found - one or more chat IDs do not exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/chat/runs": {
      "post": {
        "operationId": "postChatRuns",
        "description": "Start an asynchronous, headless chat-generation run on the durable agent workflow — the same engine as interactive [`POST /api/chat`](/api-reference/chat/workflow).\n\n**Related endpoints**\n- [`POST /api/chat`](/api-reference/chat/workflow) — use that for **interactive, streaming** turns on an existing chat; use this for **headless/programmatic** runs (no browser or pre-provisioned session needed).\n- [`GET /api/chat/runs/{runId}`](/api-reference/chat/runs-status) — poll to learn **whether** the run finished (and if it succeeded).\n- [`GET /api/chat/{chatId}/stream`](/api-reference/chat/workflow-stream) — **watch the output live** by passing the returned `chatId`.",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "requestBody": {
          "description": "Chat generation request",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChatGenerateRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Run accepted. A durable workflow run was started; `runId` identifies it. `chatId` / `sessionId` identify the persisted output — read the result via [`GET /api/chat/{chatId}/stream`](/api-reference/chat/workflow-stream) (resume the stream) or the chat's persisted messages. Poll [`GET /api/chat/runs/{runId}`](/api-reference/chat/runs-status) for status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatGenerateAcceptedResponse"
                }
              }
            },
            "headers": {
              "Location": {
                "description": "Relative URL of the run-status resource for the started run.",
                "schema": {
                  "type": "string",
                  "format": "uri-reference"
                },
                "example": "/api/chat/runs/wrun_01KVWZNM82NA7XKNEWWHG8VPHJ"
              }
            }
          },
          "400": {
            "description": "Bad request - missing required parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatGenerateErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/chat/{chatId}/stop": {
      "post": {
        "operationId": "postChatByChatIdStop",
        "description": "Stop the response currently being generated for a chat, freeing it to accept a new message. Idempotent — returns `stopped: false` when nothing is in progress. The chat must belong to the authenticated account.\n\nThe active slot is released only after cancellation is confirmed. Cancellation failure (502) or termination timeout (504) keeps it occupied; a later stop can retry. A successful no-active stop returns stopped: false.\n\nWhen a workflow is still starting and has no cancellable run ID, Stop returns retryable `503` and retains the active reservation. It does not report `stopped: true`; retry Stop shortly. A local reader abort is not confirmation that server work stopped.\n\nFor readers using `streamLifecycle=explicit-stop-v1`, this authenticated endpoint remains the explicit cancellation path.",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "chatId",
            "in": "path",
            "required": true,
            "description": "UUID of the chat to stop.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "No active workflow exists (`stopped: false`), or cancellation and terminal status were confirmed (`stopped: true`). A pending start returns 503 instead of successful cancellation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StopChatResponse"
                }
              }
            }
          },
          "400": {
            "description": "chatId is not a valid UUID",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StopChatErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid authentication.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StopChatErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "The chat belongs to a different account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StopChatErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Chat not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StopChatErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Session ownership lookup failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StopChatErrorResponse"
                }
              }
            }
          },
          "502": {
            "description": "Workflow cancellation failed; the active slot remains held.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StopChatErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Workflow start has reserved the chat but has not returned a cancellable run ID. The reservation remains held, and cancellation is not confirmed. Retry Stop shortly.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StopChatErrorResponse"
                },
                "examples": {
                  "pendingStart": {
                    "summary": "Stop requested before the workflow run ID is available",
                    "value": {
                      "status": "error",
                      "error": "Workflow is still starting; retry Stop shortly"
                    }
                  }
                }
              }
            }
          },
          "504": {
            "description": "Workflow did not reach a terminal state before the wait deadline; the active slot remains held.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StopChatErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/chat/{chatId}/stream": {
      "get": {
        "operationId": "getChatByChatIdStream",
        "summary": "Resume a chat response stream",
        "description": "Reconnects to an in-progress chat response without submitting another message. POST /api/chat starts a new turn: an existing live run returns 409, while uncertain state or a startup reservation returns retryable 503. Returns a live Server-Sent Events stream while a response is being generated, or a `200` finish-only SSE stream when no active response exists or the run is terminal. The chat must belong to the authenticated account.\n\nSend `?streamLifecycle=explicit-stop-v1` on both the initial POST and this reconnect GET to keep server work running when a reader disconnects. Use the authenticated Stop endpoint for cancellation; closing or aborting this reader alone does not confirm that the workflow stopped. Only exactly one streamLifecycle parameter with the exact value opts in; omitted, empty, unknown or repeated parameters retain legacy disconnect cancellation. Keep the existing `startIndex` cursor when reconnecting an already-read response.\n\n**Related endpoints**\n- [`POST /api/chat`](/api-reference/chat/workflow) — the interactive turn whose stream this resumes.\n- [`POST /api/chat/runs`](/api-reference/chat/runs) — start a **headless** run, then pass the returned `chatId` here to **watch its output live**.\n- [`GET /api/chat/runs/{runId}`](/api-reference/chat/runs-status) — check **whether** that run finished (status, not content).",
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "chatId",
            "in": "path",
            "required": true,
            "description": "UUID of the chat to resume.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "account_id",
            "in": "query",
            "required": false,
            "description": "Act on a chat belonging to another account. Only honoured for a key that is authorised for that account — an organization key may pass a member account's id; a personal key passing anyone else's is rejected with `403 Access denied to specified account_id`. Omit it to act as the authenticated account.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "startIndex",
            "in": "query",
            "required": false,
            "description": "Zero-based index of the first stream chunk to return, so a client that was already receiving this response resumes where it left off instead of replaying the whole turn.\n\nPass the number of chunks already received. Omit it to receive the response from the beginning — the right choice for a fresh reader such as a page load, or a client watching a headless [`POST /api/chat/runs`](/api-reference/chat/runs) run for the first time.\n\nA long turn's stream can end before the run does; reconnecting with `startIndex` is how a client keeps rendering without duplicating or skipping chunks.",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "streamLifecycle",
            "in": "query",
            "required": false,
            "description": "Send streamLifecycle=explicit-stop-v1 exactly once on the initial POST and every reconnect GET to keep server work running when that reader disconnects. Only exactly one parameter with that exact value opts in. Omitting it, an empty or unknown value, or repeating the parameter retains legacy disconnect cancellation. Use authenticated POST /api/chat/{chatId}/stop for cancellation; a local reader abort is not confirmation that server work stopped. This is a query parameter, not a request-body field.",
            "schema": {
              "type": "string"
            },
            "example": "explicit-stop-v1"
          }
        ],
        "responses": {
          "200": {
            "description": "Server-Sent Events stream of UI message parts for the in-progress response, compatible with the Vercel AI SDK. The `x-workflow-run-id` response header carries the run ID for this response, and `x-workflow-stream-tail-index` carries the index of the last chunk known when this read was opened — a base for computing absolute positions, not a record of where the response ended. When there is nothing to resume, the stream contains one terminal finish chunk; workflow headers are absent on that path.",
            "headers": {
              "x-workflow-run-id": {
                "description": "Durable workflow run ID of the resumed run.",
                "schema": {
                  "type": "string"
                }
              },
              "x-workflow-stream-tail-index": {
                "description": "Zero-based index of the last chunk known to the stream **at the moment this read was opened**. Because headers are sent before the body, a read that stays open past that point will deliver chunks beyond it — so this is a base for computing absolute positions, not a record of where the response ended. A client resuming precisely should count the chunks it receives on top of this value. Omitted if the runtime cannot report it.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "description": "Server-Sent Events stream containing UI message parts"
                }
              }
            }
          },
          "400": {
            "description": "Invalid chatId UUID or malformed startIndex. When supplied, startIndex must parse to a non-negative integer; blank, nonnumeric, fractional and negative values fail validation. Authentication/ownership is checked first. Validation errors include status: error, error and missing_fields when a validation path is available.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatStreamErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid authentication.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatStreamErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "The caller does not own the chat session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatStreamErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Chat or owning session not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatStreamErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "The chat or session ownership lookup failed. This is not a missing-chat result.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatStreamErrorResponse"
                }
              }
            }
          },
          "502": {
            "description": "Workflow status lookup failed; this is not an empty or completed response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatStreamErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/chat": {
      "post": {
        "operationId": "postChat",
        "summary": "Stream sandbox-driven chat (Vercel Workflow)",
        "description": "Streams an agent loop running as a durable [Vercel Workflow](https://vercel.com/docs/workflow) against the session's sandbox. The agent uses sandbox-only tools (`bash`, `read`, `write`, `grep`, `glob`, `todo`, `task`, `ask_user_question`, `skill`, `fetch`) — no MCP or Composio. Requires a sandbox provisioned via [`POST /api/sandbox`](/api-reference/sandbox/create).\n\n**Related endpoints**\n- [`POST /api/chat/runs`](/api-reference/chat/runs) — the **headless** counterpart: it provisions its own session + sandbox and runs without a streaming client. Use that for tasks/cron/programmatic runs; use this for **interactive, streaming** turns on an existing chat.\n- [`GET /api/chat/{chatId}/stream`](/api-reference/chat/workflow-stream) — reconnect to an in-progress response.\n- [`GET /api/chat/runs/{runId}`](/api-reference/chat/runs-status) — check **whether** a headless run finished (status, not content).\n\nA new turn is separate from reconnecting. If the early active-run check finds an existing run whose status is running or pending, POST returns `409` before saving the new message or starting a new workflow. Keep the rejected text and attachments locally, reconnect with GET, and let the caller decide when to submit them again. Do not queue or automatically resend them, or delete persisted chat history to recover an unaccepted submission. Later start conflicts can also return `409` after work has started; inspect chat state before retrying. An unresolved startup reservation, unknown run status, failed status lookup or failed stale-slot clear returns retryable `503` before saving the submitted turn or starting a new workflow. Preserve the text and attachments for a later manual retry.\n\nTo make a reader disconnect leave the workflow running, send `?streamLifecycle=explicit-stop-v1` on this POST and every reconnect GET. Use `POST /api/chat/{chatId}/stop` for explicit cancellation; aborting the local reader is not confirmation that server work stopped. Only exactly one streamLifecycle query parameter with that exact value opts in. Omitted, empty, unknown or repeated parameters retain legacy disconnect cancellation.",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "requestBody": {
          "description": "Chat workflow request",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChatWorkflowRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Server-Sent Events stream of UI message parts for the newly started workflow, compatible with the Vercel AI SDK `createUIMessageStreamResponse`. The `x-workflow-run-id` response header carries the durable workflow run ID for resume/stop operations. An existing active response is rejected with 409 instead of being returned as acceptance of the submitted new message.",
            "headers": {
              "x-workflow-run-id": {
                "description": "Durable workflow run ID. Persist alongside the chat so a later `GET /api/chat/{chatId}/stream` request can resume this run after a disconnect.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "description": "Server-Sent Events stream containing UI message parts"
                }
              }
            }
          },
          "400": {
            "description": "Bad request — missing required fields, invalid body, or sandbox not initialized for the session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatStreamErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatStreamErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "The API key's account does not own the referenced `sessionId` or `chatId`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatStreamErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Session or chat not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatStreamErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "A response is already active, a concurrent request claimed the chat, or another conflict prevented this start. The early active-run rejection does not save the submitted message or start a new workflow. Later conflicts may occur after work starts. Preserve rejected text and attachments, inspect chat state and use GET /api/chat/{chatId}/stream to reconnect before a manual retry; no automatic replay is performed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatStreamErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error, including a failed chat or session lookup. A failed lookup is not a missing-chat result.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatStreamErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Run state cannot be resolved safely, including an unresolved startup reservation, unknown status, failed status lookup or failed stale-slot clear. A pending startup reservation remains held. The early rejection does not save the submitted turn or start a new workflow. Keep the text and attachments for a later manual retry; no automatic replay.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatStreamErrorResponse"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "streamLifecycle",
            "in": "query",
            "required": false,
            "description": "Send streamLifecycle=explicit-stop-v1 exactly once on the initial POST and every reconnect GET to keep server work running when that reader disconnects. Only exactly one parameter with that exact value opts in. Omitting it, an empty or unknown value, or repeating the parameter retains legacy disconnect cancellation. Use authenticated POST /api/chat/{chatId}/stop for cancellation; a local reader abort is not confirmation that server work stopped. This is a query parameter, not a request-body field.",
            "schema": {
              "type": "string"
            },
            "example": "explicit-stop-v1"
          }
        ]
      }
    },
    "/api/research/deep": {
      "post": {
        "operationId": "postResearchDeep",
        "description": "Perform deep, comprehensive research on a topic. Browses multiple sources extensively and returns a cited report. Use for full artist deep dives, competitive analysis, and any research requiring synthesis across many sources.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ResearchDeepRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Deep research report with citations",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchDeepResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed — invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "501": {
            "$ref": "#/components/responses/ResearchDataSourceUnsupported"
          }
        }
      }
    },
    "/api/research/enrich": {
      "post": {
        "operationId": "postResearchEnrich",
        "description": "Enrich an entity with structured data from web research. Provide a description of who or what to research and a JSON schema defining the fields to extract. Returns typed data with citations. **Important:** The `schema` object must include `\"type\": \"object\"` at the top level — requests without an explicit type will be rejected.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ResearchEnrichRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Structured enrichment data",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchEnrichResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed — invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "501": {
            "$ref": "#/components/responses/ResearchDataSourceUnsupported"
          }
        }
      }
    },
    "/api/research/extract": {
      "post": {
        "operationId": "postResearchExtract",
        "description": "Extract clean markdown content from one or more public URLs. Handles JavaScript-heavy pages and PDFs. Returns focused excerpts aligned to an objective, or full page content.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ResearchExtractRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Extracted content from URLs",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchExtractResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed — invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "501": {
            "$ref": "#/components/responses/ResearchDataSourceUnsupported"
          }
        }
      }
    },
    "/api/research/people": {
      "post": {
        "operationId": "postResearchPeople",
        "description": "Search for people in the music industry — artists, managers, A&R reps, producers. Returns multi-source profiles including LinkedIn data.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ResearchPeopleRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "People search results",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchPeopleResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed — invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "501": {
            "$ref": "#/components/responses/ResearchDataSourceUnsupported"
          }
        }
      }
    },
    "/api/research/web": {
      "post": {
        "operationId": "postResearchWeb",
        "description": "Search the web for real-time information. Returns ranked results with titles, URLs, and content snippets. Use for narrative context, press coverage, and cultural research that structured data endpoints don't cover.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ResearchWebRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Web search results",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchWebResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed — invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "501": {
            "$ref": "#/components/responses/ResearchDataSourceUnsupported"
          }
        }
      }
    },
    "/api/research/events": {
      "post": {
        "operationId": "postResearchEvents",
        "description": "List an artist's live shows. Returns one row per event with venue, city, country, ticket link, and lineup.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ResearchEventsRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Events for the artist. An artist with no matching events returns an empty `events` array, not an error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchEventsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed — invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient research credits — the body includes a static `billingUrl`. No Stripe object is created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchInsufficientCreditsResponse"
                }
              }
            }
          },
          "404": {
            "description": "The artist has no live-events profile connected, so no events can be looked up. Distinct from a 200 with an empty `events` array, which means the artist is connected but has no matching events.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                },
                "example": {
                  "status": "error",
                  "error": "Error: no bandsintown ID connected to this artist. Please connect the bandsintown ID in this format: bandsintown.com/a/{id}-{slug} Docs here: https://docs.recoupable.dev/api-reference/artists/update#body-profile-urls"
                }
              }
            }
          }
        }
      }
    },
    "/api/research/track/stats": {
      "get": {
        "operationId": "getResearchTrackStats",
        "description": "Read current track statistics using exactly one of `isrc`, `spotify_track_id` or `apple_music_track_id`, plus the required platform `source`. Recoup uses stored Spotify measurements when available for an ISRC and otherwise requests current research data. Fields and coverage vary. Preserve returned `data_source` and capture timestamps; missing provenance remains unknown.",
        "parameters": [
          {
            "name": "isrc",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Supply exactly one track identifier.",
            "example": "USQY51771120"
          },
          {
            "name": "spotify_track_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Supply exactly one track identifier."
          },
          {
            "name": "apple_music_track_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Supply exactly one track identifier."
          },
          {
            "name": "source",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Required provider source or comma-separated sources; not limited to Spotify.",
            "example": "spotify"
          },
          {
            "name": "with_playlists",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Optional raw string forwarded unchanged to the configured research data connection. Use true or false per the provider contract; Recoup does not validate or coerce this flag.",
            "example": "true"
          },
          {
            "name": "with_charts",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Optional raw string forwarded unchanged to the configured research data connection. Use true or false per the provider contract; Recoup does not validate or coerce this flag.",
            "example": "true"
          },
          {
            "name": "with_stations",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Optional raw string forwarded unchanged to the configured research data connection. Use true or false per the provider contract; Recoup does not validate or coerce this flag.",
            "example": "true"
          },
          {
            "name": "with_videos",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Optional raw string forwarded unchanged to the configured research data connection. Use true or false per the provider contract; Recoup does not validate or coerce this flag.",
            "example": "true"
          },
          {
            "name": "with_links",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Optional raw string forwarded unchanged to the configured research data connection. Use true or false per the provider contract; Recoup does not validate or coerce this flag.",
            "example": "true"
          },
          {
            "name": "only_current",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Optional raw string forwarded unchanged to the configured research data connection. Use true or false per the provider contract; Recoup does not validate or coerce this flag.",
            "example": "true"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Optional raw pagination string forwarded unchanged to the configured research data connection. Recoup does not validate its numeric range; provider limits apply.",
            "example": "50"
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Optional raw pagination string forwarded unchanged to the configured research data connection. Recoup does not validate its numeric range; provider limits apply.",
            "example": "0"
          }
        ],
        "responses": {
          "200": {
            "description": "Current per-source track statistics from the measurement store or the configured research data connection, with data_source on recognized stats entries.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchTrackStatsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Missing source, or zero/multiple track identifiers. Supply exactly one of the four supported identifiers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed — invalid or missing API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient research credits — the body includes a static `billingUrl`. No Stripe object is created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchInsufficientCreditsResponse"
                }
              }
            }
          },
          "404": {
            "description": "Requested track or source data was not found by the provider.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Provider rate/quota limit; do not retry in an unbounded loop."
          },
          "500": {
            "description": "Missing provider configuration, transport failure or internal error."
          },
          "504": {
            "description": "the configured research data connection request timed out."
          }
        }
      }
    },
    "/api/research/tracks/{id}/measurements": {
      "get": {
        "operationId": "getResearchTracksByIdMeasurements",
        "summary": "Track measurements",
        "description": "Time-series of a track's measured counts.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Provider-neutral track id — ISRC or Spotify track id."
          },
          {
            "name": "platform",
            "in": "query",
            "schema": {
              "type": "string",
              "default": "spotify"
            },
            "description": "Platform to read. Currently `spotify`."
          },
          {
            "name": "metric",
            "in": "query",
            "schema": {
              "type": "string",
              "default": "platform_displayed_play_count"
            },
            "description": "Metric to read."
          },
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Inclusive start date (ISO) for the series."
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Inclusive end date (ISO) for the series."
          },
          {
            "name": "granularity",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "daily"
              ],
              "default": "daily"
            },
            "description": "Series granularity. `daily` returns the per-day cumulative series."
          },
          {
            "name": "aggregate",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "run_rate"
              ]
            },
            "description": "Return a derived aggregate instead of the raw series. `run_rate` returns the trailing-window annualized run-rate (a projection of the series)."
          },
          {
            "name": "window",
            "in": "query",
            "schema": {
              "type": "string",
              "default": "365d"
            },
            "description": "Trailing window for `aggregate=run_rate` (e.g. `365d`)."
          }
        ],
        "responses": {
          "200": {
            "description": "The measurement series (or the requested aggregate).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchMeasurementsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error — bad identifier or query param.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed — invalid or missing API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient credits.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchInsufficientCreditsResponse"
                }
              }
            }
          },
          "404": {
            "description": "No measurements for this track yet — create a `current` measurement job to capture it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/research/albums/{id}/measurements": {
      "get": {
        "operationId": "getResearchAlbumsByIdMeasurements",
        "summary": "Album measurements",
        "description": "Latest measured count per track on an album, from the measurement store. `{id}` is a Spotify album id.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Spotify album id."
          },
          {
            "name": "platform",
            "in": "query",
            "schema": {
              "type": "string",
              "default": "spotify"
            },
            "description": "Platform to read. Currently `spotify`."
          },
          {
            "name": "metric",
            "in": "query",
            "schema": {
              "type": "string",
              "default": "platform_displayed_play_count"
            },
            "description": "Metric to read."
          },
          {
            "name": "latest",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": true
            },
            "description": "Return the latest capture per track (the only supported mode today)."
          }
        ],
        "responses": {
          "200": {
            "description": "Latest per-track measured counts for the album.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchAlbumMeasurementsResponse"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed — invalid or missing API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient credits.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchInsufficientCreditsResponse"
                }
              }
            }
          },
          "404": {
            "description": "No capture exists for this album yet — create a `current` measurement-job.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/research/measurement-jobs": {
      "post": {
        "operationId": "postResearchMeasurementJobs",
        "summary": "Create a measurement job",
        "description": "Create a current capture or enqueue historical the configured research data connection backfill. Provide exactly one of catalog_id, album_ids or isrcs in scope; platforms supports spotify only. source=current uses the snapshot pipeline and returns an ID that can be passed to [Create catalog](/api-reference/songs/catalogs-create). source=historical requires a card on file, resolves scope recordings and returns id:null with enqueued/skipped counts; it attempts to start the existing budget-gated backfill workflow, with daily maintenance as a backstop. Enqueue success is not capture completion or evidence of complete historical coverage. Songs with any historical research measurement are skipped, including potentially partial history. Historical enqueue itself makes no credit deduction; current capture estimates and provider/account gates remain separate.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ResearchMeasurementJobRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Job accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchMeasurementJobResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid scope/source/platform, or no recordings resolvable from the selected historical scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed — invalid or missing API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Historical backfill requires an existing saved payment method. If unavailable, returns a neutral entitlement error without creating a card-setup or checkout session. Preserve the job request; do not retry automatically.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "status",
                    "error",
                    "message"
                  ],
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "error"
                      ]
                    },
                    "error": {
                      "type": "string",
                      "enum": [
                        "payment_method_required"
                      ]
                    },
                    "message": {
                      "type": "string",
                      "description": "Neutral explanation that the feature is unavailable with current account entitlements."
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Per-organization monthly cap reached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal, enqueue or provider configuration error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/chat/runs/{runId}": {
      "get": {
        "operationId": "getChatRunsByRunId",
        "description": "Status of an asynchronous run started via [`POST /api/chat/runs`](/api-reference/chat/runs). Returns a point-in-time snapshot (**is it done?**) — not the generated content.\n\n**Related endpoints**\n- [`POST /api/chat/runs`](/api-reference/chat/runs) — starts the run this reports on.\n- [`GET /api/chat/{chatId}/stream`](/api-reference/chat/workflow-stream) — read the **content**: poll *this* to know **whether** a run finished; use the stream to **watch the output** as it's produced.\n- [`POST /api/chat`](/api-reference/chat/workflow) — the interactive, streaming counterpart to a headless run.",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "runId",
            "in": "path",
            "required": true,
            "description": "The durable workflow run id returned by [POST /api/chat/runs](/api-reference/chat/runs).",
            "schema": {
              "type": "string"
            },
            "example": "wrun_01KVWZNM82NA7XKNEWWHG8VPHJ"
          }
        ],
        "responses": {
          "200": {
            "description": "Run status snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatRunStatusResponse"
                }
              }
            }
          },
          "404": {
            "description": "Run not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatRunNotFoundError"
                }
              }
            }
          }
        }
      }
    },
    "/api/research": {
      "get": {
        "operationId": "getResearch",
        "description": "Search Recoup Research for artists, tracks or labels by name. Artist and track matches include an `id` when an identifier is available. Select a match and reuse that ID for detail requests. `type` accepts artists, tracks or labels, plus their singular aliases, case-insensitively. Playlist search is unavailable. Recoup defaults `limit` to 10; pagination strings are passed to the data connection and its limits apply.\n\nUse your Recoup API key (`x-api-key`) or supported bearer credential. Successful artist and track research reads cost $0.05 (50,000 micro-dollar credits). Recoup manages the data connections. Coverage and rate limits vary; see [Research availability](/research-availability).",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "Required search text, such as an artist or track name. For a Spotify artist URL use the lookup endpoint.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "Entity type to search: artists, tracks, or labels.",
            "schema": {
              "type": "string",
              "enum": [
                "artists",
                "tracks",
                "labels",
                "artist",
                "track",
                "label"
              ],
              "default": "artists"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Raw pagination string forwarded to the configured research data connection. Recoup defaults limit to 10; offset is omitted when absent. Provider limits apply.",
            "schema": {
              "type": "string",
              "default": "10"
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Raw pagination string forwarded to the configured research data connection. Recoup defaults limit to 10; offset is omitted when absent. Provider limits apply.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Search results",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchSearchResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error (e.g., missing required query parameter `q`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed — invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient research credits. The response includes a static billingUrl; no checkout session is created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchInsufficientCreditsResponse"
                }
              }
            }
          },
          "403": {
            "description": "Credential, account or provider permission denied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Artist/track resolution or requested provider data was not found. A failed name search may be reported as 404; retry with an exact provider ID when known.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Provider rate or quota limit. The adapter returns an error status but does not forward provider Retry-After headers. Avoid an unbounded retry loop.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Missing provider configuration, transport failure or internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "501": {
            "$ref": "#/components/responses/ResearchDataSourceUnsupported"
          },
          "502": {
            "description": "Upstream provider failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "504": {
            "description": "the configured research data connection request timed out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/research/albums": {
      "get": {
        "operationId": "getResearchAlbums",
        "description": "Read the artist catalog through the `albums` view using the `artist_id` selected from [Search](/api-reference/research/search). Results can include track-shaped catalog entries rather than distinct album objects. `is_primary` accepts true/false; filtering depends on the data connection. `limit` must be a positive integer string and `offset` a non-negative integer string.\n\nUse your Recoup API key (`x-api-key`) or supported bearer credential. Successful artist and track research reads cost $0.05 (50,000 micro-dollar credits). Recoup manages the data connections. Coverage and rate limits vary; see [Research availability](/research-availability).",
        "parameters": [
          {
            "name": "artist_id",
            "in": "query",
            "required": true,
            "description": "Provider artist ID. Obtain it from search or lookup results.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
              "example": "artist_123"
            }
          },
          {
            "name": "is_primary",
            "in": "query",
            "required": false,
            "description": "When `true` (default), returns only albums where the artist is a main artist. Set to `false` to include feature appearances, DJ compilations, and soundtracks.",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ],
              "default": "true"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Number of albums to return. Defaults to the provider's default when omitted.",
            "schema": {
              "type": "string",
              "pattern": "^[1-9][0-9]*$"
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Pagination offset. Defaults to 0 when omitted.",
            "schema": {
              "type": "string",
              "pattern": "^(0|[1-9][0-9]*)$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Artist albums",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchAlbumsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error — `artist_id` missing or invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed — invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient research credits. The response includes a static billingUrl; no checkout session is created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchInsufficientCreditsResponse"
                }
              }
            }
          },
          "403": {
            "description": "Credential, account or provider permission denied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Artist/track resolution or requested provider data was not found. A failed name search may be reported as 404; retry with an exact provider ID when known.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Provider rate or quota limit. The adapter returns an error status but does not forward provider Retry-After headers. Avoid an unbounded retry loop.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Missing provider configuration, transport failure or internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "501": {
            "$ref": "#/components/responses/ResearchDataSourceUnsupported"
          },
          "502": {
            "description": "Upstream provider failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "504": {
            "description": "the configured research data connection request timed out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/research/audience": {
      "get": {
        "operationId": "getResearchAudience",
        "description": "Read available audience data for an artist and platform. The default is `instagram`; other supported data connections can include Spotify, TikTok and YouTube. Geographic and demographic coverage varies. Age and gender breakdowns are not guaranteed.\n\nUse your Recoup API key (`x-api-key`) or supported bearer credential. Successful artist and track research reads cost $0.05 (50,000 micro-dollar credits). Recoup manages the data connections. Coverage and rate limits vary; see [Research availability](/research-availability).",
        "parameters": [
          {
            "name": "artist",
            "in": "query",
            "required": false,
            "description": "Artist name; Recoup resolves the first provider search match. Required unless id is supplied. Recoup artist UUID resolution is not implemented here.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "platform",
            "in": "query",
            "required": false,
            "description": "Audience platform. Leading/trailing whitespace is trimmed; omitted, empty or whitespace-only values default to `instagram`. Other values must be one of the listed lowercase names. Invalid platform values return `400` before authentication, artist resolution, credit checks or research dispatch. Availability and returned coverage vary by platform.",
            "schema": {
              "type": "string",
              "enum": [
                "bandsintown",
                "deezer",
                "facebook",
                "instagram",
                "line",
                "melon",
                "soundcloud",
                "spotify",
                "tiktok",
                "twitch",
                "twitter",
                "wikipedia",
                "youtube_artist",
                "youtube_channel"
              ],
              "default": "instagram"
            }
          },
          {
            "name": "id",
            "in": "query",
            "required": false,
            "description": "Research artist ID returned by search or lookup; treat it as an opaque string. Required unless artist is supplied; takes precedence when both are provided. This is not a Recoup artist/account ID.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
              "example": "artist_123"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Audience demographics",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchAudienceResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed — invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient research credits. The response includes a static billingUrl; no checkout session is created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchInsufficientCreditsResponse"
                }
              }
            }
          },
          "403": {
            "description": "Credential, account or provider permission denied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Artist/track resolution or requested provider data was not found. A failed name search may be reported as 404; retry with an exact provider ID when known.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Provider rate or quota limit. The adapter returns an error status but does not forward provider Retry-After headers. Avoid an unbounded retry loop.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Missing provider configuration, transport failure or internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "501": {
            "$ref": "#/components/responses/ResearchDataSourceUnsupported"
          },
          "502": {
            "description": "Upstream provider failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "504": {
            "description": "the configured research data connection request timed out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/research/career": {
      "get": {
        "operationId": "getResearchCareer",
        "description": "Read available dated artist activities under `career`. Career, insights and milestones use the same activity feed. This endpoint does not calculate a career stage or score, and does not guarantee a complete career history.\n\nUse your Recoup API key (`x-api-key`) or supported bearer credential. Successful artist and track research reads cost $0.05 (50,000 micro-dollar credits). Recoup manages the data connections. Coverage and rate limits vary; see [Research availability](/research-availability).",
        "parameters": [
          {
            "name": "artist",
            "in": "query",
            "required": false,
            "description": "Artist name; Recoup resolves the first provider search match. Required unless id is supplied. Recoup artist UUID resolution is not implemented here.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "id",
            "in": "query",
            "required": false,
            "description": "Research artist ID returned by search or lookup; treat it as an opaque string. Required unless artist is supplied; takes precedence when both are provided. This is not a Recoup artist/account ID.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
              "example": "artist_123"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Career timeline",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchCareerResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed — invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient research credits. The response includes a static billingUrl; no checkout session is created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchInsufficientCreditsResponse"
                }
              }
            }
          },
          "403": {
            "description": "Credential, account or provider permission denied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Artist/track resolution or requested provider data was not found. A failed name search may be reported as 404; retry with an exact provider ID when known.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Provider rate or quota limit. The adapter returns an error status but does not forward provider Retry-After headers. Avoid an unbounded retry loop.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Missing provider configuration, transport failure or internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "501": {
            "$ref": "#/components/responses/ResearchDataSourceUnsupported"
          },
          "502": {
            "description": "Upstream provider failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "504": {
            "description": "the configured research data connection request timed out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/research/insights": {
      "get": {
        "operationId": "getResearchInsights",
        "description": "Read available artist activities under `insights`. This endpoint returns activity data; it does not generate an AI analysis or a separate insight report.\n\nUse your Recoup API key (`x-api-key`) or supported bearer credential. Successful artist and track research reads cost $0.05 (50,000 micro-dollar credits). Recoup manages the data connections. Coverage and rate limits vary; see [Research availability](/research-availability).",
        "parameters": [
          {
            "name": "artist",
            "in": "query",
            "required": false,
            "description": "Artist name; Recoup resolves the first provider search match. Required unless id is supplied. Recoup artist UUID resolution is not implemented here.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "id",
            "in": "query",
            "required": false,
            "description": "Research artist ID returned by search or lookup; treat it as an opaque string. Required unless artist is supplied; takes precedence when both are provided. This is not a Recoup artist/account ID.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
              "example": "artist_123"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "AI-generated insights",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchInsightsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed — invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient research credits. The response includes a static billingUrl; no checkout session is created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchInsufficientCreditsResponse"
                }
              }
            }
          },
          "403": {
            "description": "Credential, account or provider permission denied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Artist/track resolution or requested provider data was not found. A failed name search may be reported as 404; retry with an exact provider ID when known.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Provider rate or quota limit. The adapter returns an error status but does not forward provider Retry-After headers. Avoid an unbounded retry loop.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Missing provider configuration, transport failure or internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "501": {
            "$ref": "#/components/responses/ResearchDataSourceUnsupported"
          },
          "502": {
            "description": "Upstream provider failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "504": {
            "description": "the configured research data connection request timed out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/research/lookup": {
      "get": {
        "operationId": "getResearchLookup",
        "description": "Resolve a Spotify artist URL or ID through Recoup Research. Supply `spotifyId` (22 alphanumeric characters) or a Spotify artist `url`; a nonempty `spotifyId` takes precedence. Apple Music URLs and Recoup account UUIDs are not accepted. Available artist metadata is returned; an `id` is included when the artist identifier can be resolved.\n\nUse your Recoup API key (`x-api-key`) or supported bearer credential. Successful artist and track research reads cost $0.05 (50,000 micro-dollar credits). Recoup manages the data connections. Coverage and rate limits vary; see [Research availability](/research-availability).",
        "parameters": [
          {
            "name": "url",
            "in": "query",
            "required": false,
            "description": "Spotify artist URL. Required unless `spotifyId` is provided.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "spotifyId",
            "in": "query",
            "required": false,
            "description": "Spotify artist ID. Required unless `url` is provided.",
            "schema": {
              "type": "string",
              "example": "3TVXtAsR1Inumwj472S9r4",
              "pattern": "^[A-Za-z0-9]{22}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Artist profile matching the platform URL",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchLookupResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed — invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient research credits. The response includes a static billingUrl; no checkout session is created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchInsufficientCreditsResponse"
                }
              }
            }
          },
          "403": {
            "description": "Credential, account or provider permission denied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Artist/track resolution or requested provider data was not found. A failed name search may be reported as 404; retry with an exact provider ID when known.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Provider rate or quota limit. The adapter returns an error status but does not forward provider Retry-After headers. Avoid an unbounded retry loop.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Missing provider configuration, transport failure or internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "501": {
            "$ref": "#/components/responses/ResearchDataSourceUnsupported"
          },
          "502": {
            "description": "Upstream provider failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "504": {
            "description": "the configured research data connection request timed out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/research/metrics": {
      "get": {
        "operationId": "getResearchMetrics",
        "description": "Read current artist statistics for the required platform `source`. This is a synchronous snapshot, without a historical series or refresh-pending job. A valid source does not guarantee data is available. Save dated responses to compare results over time.\n\nUse your Recoup API key (`x-api-key`) or supported bearer credential. Successful artist and track research reads cost $0.05 (50,000 micro-dollar credits). Recoup manages the data connections. Coverage and rate limits vary; see [Research availability](/research-availability).",
        "parameters": [
          {
            "name": "artist",
            "in": "query",
            "required": false,
            "description": "Artist name; Recoup resolves the first provider search match. Required unless id is supplied. Recoup artist UUID resolution is not implemented here.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "source",
            "in": "query",
            "required": true,
            "description": "Platform to get metrics for.",
            "schema": {
              "type": "string",
              "enum": [
                "spotify",
                "instagram",
                "tiktok",
                "twitter",
                "facebook",
                "youtube_channel",
                "youtube_artist",
                "soundcloud",
                "deezer",
                "twitch",
                "line",
                "melon",
                "wikipedia",
                "bandsintown",
                "radio",
                "sxm"
              ]
            }
          },
          {
            "name": "id",
            "in": "query",
            "required": false,
            "description": "Research artist ID returned by search or lookup; treat it as an opaque string. Required unless artist is supplied; takes precedence when both are provided. This is not a Recoup artist/account ID.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
              "example": "artist_123"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Platform metrics over time",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchMetricsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed — invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient research credits. The response includes a static billingUrl; no checkout session is created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchInsufficientCreditsResponse"
                }
              }
            }
          },
          "403": {
            "description": "Credential, account or provider permission denied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Artist/track resolution or requested provider data was not found. A failed name search may be reported as 404; retry with an exact provider ID when known.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Provider rate or quota limit. The adapter returns an error status but does not forward provider Retry-After headers. Avoid an unbounded retry loop.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Missing provider configuration, transport failure or internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "501": {
            "$ref": "#/components/responses/ResearchDataSourceUnsupported"
          },
          "502": {
            "description": "Upstream provider failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "504": {
            "description": "the configured research data connection request timed out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/research/milestones": {
      "get": {
        "operationId": "getResearchMilestones",
        "description": "Read available artist activities under `milestones`. Coverage varies; an empty result does not prove the artist has no milestones. This endpoint does not guarantee a complete history or scored career timeline.\n\nUse your Recoup API key (`x-api-key`) or supported bearer credential. Successful artist and track research reads cost $0.05 (50,000 micro-dollar credits). Recoup manages the data connections. Coverage and rate limits vary; see [Research availability](/research-availability).",
        "parameters": [
          {
            "name": "artist",
            "in": "query",
            "required": false,
            "description": "Artist name; Recoup resolves the first provider search match. Required unless id is supplied. Recoup artist UUID resolution is not implemented here.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "id",
            "in": "query",
            "required": false,
            "description": "Research artist ID returned by search or lookup; treat it as an opaque string. Required unless artist is supplied; takes precedence when both are provided. This is not a Recoup artist/account ID.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
              "example": "artist_123"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Milestone list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchMilestonesResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed — invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient research credits. The response includes a static billingUrl; no checkout session is created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchInsufficientCreditsResponse"
                }
              }
            }
          },
          "403": {
            "description": "Credential, account or provider permission denied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Artist/track resolution or requested provider data was not found. A failed name search may be reported as 404; retry with an exact provider ID when known.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Provider rate or quota limit. The adapter returns an error status but does not forward provider Retry-After headers. Avoid an unbounded retry loop.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Missing provider configuration, transport failure or internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "501": {
            "$ref": "#/components/responses/ResearchDataSourceUnsupported"
          },
          "502": {
            "description": "Upstream provider failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "504": {
            "description": "the configured research data connection request timed out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/research/playlists": {
      "get": {
        "operationId": "getResearchPlaylists",
        "description": "Read available playlist placements for an artist. `status=current` selects current scope; `status=past` selects all-time scope, which can include current placements. `limit` is an optional raw string with no Recoup default; supported limits and coverage depend on the data connection.\n\nUse your Recoup API key (`x-api-key`) or supported bearer credential. Successful artist and track research reads cost $0.05 (50,000 micro-dollar credits). Recoup manages the data connections. Coverage and rate limits vary; see [Research availability](/research-availability).",
        "parameters": [
          {
            "name": "artist",
            "in": "query",
            "required": false,
            "description": "Artist name; Recoup resolves the first provider search match. Required unless id is supplied. Recoup artist UUID resolution is not implemented here.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "platform",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "spotify",
                "applemusic",
                "deezer",
                "amazon",
                "youtube"
              ],
              "default": "spotify"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "current",
                "past"
              ],
              "default": "current"
            },
            "description": "current uses current placements; past uses all-time scope total, which may include current placements."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Optional raw string; no Recoup default. For similar artists, only a positive integer limits the returned list; for playlists it passes through to the provider."
          },
          {
            "name": "id",
            "in": "query",
            "required": false,
            "description": "Research artist ID returned by search or lookup; treat it as an opaque string. Required unless artist is supplied; takes precedence when both are provided. This is not a Recoup artist/account ID.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
              "example": "artist_123"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Playlist placements",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchPlaylistsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed — invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient research credits. The response includes a static billingUrl; no checkout session is created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchInsufficientCreditsResponse"
                }
              }
            }
          },
          "403": {
            "description": "Credential, account or provider permission denied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Artist/track resolution or requested provider data was not found. A failed name search may be reported as 404; retry with an exact provider ID when known.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Provider rate or quota limit. The adapter returns an error status but does not forward provider Retry-After headers. Avoid an unbounded retry loop.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Missing provider configuration, transport failure or internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "501": {
            "$ref": "#/components/responses/ResearchDataSourceUnsupported"
          },
          "502": {
            "description": "Upstream provider failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "504": {
            "description": "the configured research data connection request timed out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/research/profile": {
      "get": {
        "operationId": "getResearchProfile",
        "description": "Read artist identity and available biography, links, genres and related-artist metadata. Fields depend on available research data. For current platform counts, use [Metrics](/api-reference/research/metrics).\n\nUse your Recoup API key (`x-api-key`) or supported bearer credential. Successful artist and track research reads cost $0.05 (50,000 micro-dollar credits). Recoup manages the data connections. Coverage and rate limits vary; see [Research availability](/research-availability).",
        "parameters": [
          {
            "name": "artist",
            "in": "query",
            "required": false,
            "description": "Artist name; Recoup resolves the first provider search match. Required unless id is supplied. Recoup artist UUID resolution is not implemented here.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "id",
            "in": "query",
            "required": false,
            "description": "Research artist ID returned by search or lookup; treat it as an opaque string. Required unless artist is supplied; takes precedence when both are provided. This is not a Recoup artist/account ID.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
              "example": "artist_123"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Artist profile",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchProfileResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed — invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient research credits. The response includes a static billingUrl; no checkout session is created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchInsufficientCreditsResponse"
                }
              }
            }
          },
          "403": {
            "description": "Credential, account or provider permission denied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Artist/track resolution or requested provider data was not found. A failed name search may be reported as 404; retry with an exact provider ID when known.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Provider rate or quota limit. The adapter returns an error status but does not forward provider Retry-After headers. Avoid an unbounded retry loop.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Missing provider configuration, transport failure or internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "501": {
            "$ref": "#/components/responses/ResearchDataSourceUnsupported"
          },
          "502": {
            "description": "Upstream provider failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "504": {
            "description": "the configured research data connection request timed out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/research/similar": {
      "get": {
        "operationId": "getResearchSimilar",
        "description": "Read unweighted related artists. The accepted `audience`, `genre`, `mood` and `musicality` values (high/medium/low) do not currently change the ranking. No similarity score is calculated. A positive integer `limit` slices the returned list; invalid or non-positive limits are ignored.\n\nUse your Recoup API key (`x-api-key`) or supported bearer credential. Successful artist and track research reads cost $0.05 (50,000 micro-dollar credits). Recoup manages the data connections. Coverage and rate limits vary; see [Research availability](/research-availability).",
        "parameters": [
          {
            "name": "artist",
            "in": "query",
            "required": false,
            "description": "Artist name; Recoup resolves the first provider search match. Required unless id is supplied. Recoup artist UUID resolution is not implemented here.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "audience",
            "in": "query",
            "required": false,
            "description": "Audience overlap weight.",
            "schema": {
              "type": "string",
              "enum": [
                "high",
                "medium",
                "low"
              ],
              "default": "medium"
            }
          },
          {
            "name": "genre",
            "in": "query",
            "required": false,
            "description": "Genre similarity weight.",
            "schema": {
              "type": "string",
              "enum": [
                "high",
                "medium",
                "low"
              ],
              "default": "medium"
            }
          },
          {
            "name": "mood",
            "in": "query",
            "required": false,
            "description": "Mood similarity weight.",
            "schema": {
              "type": "string",
              "enum": [
                "high",
                "medium",
                "low"
              ],
              "default": "medium"
            }
          },
          {
            "name": "musicality",
            "in": "query",
            "required": false,
            "description": "Musicality similarity weight.",
            "schema": {
              "type": "string",
              "enum": [
                "high",
                "medium",
                "low"
              ],
              "default": "medium"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Optional raw string; no Recoup default. For similar artists, only a positive integer limits the returned list; for playlists it passes through to the provider."
          },
          {
            "name": "id",
            "in": "query",
            "required": false,
            "description": "Research artist ID returned by search or lookup; treat it as an opaque string. Required unless artist is supplied; takes precedence when both are provided. This is not a Recoup artist/account ID.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
              "example": "artist_123"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Unweighted related artists with provider fields; no overlap scores are computed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchSimilarResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed — invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient research credits. The response includes a static billingUrl; no checkout session is created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchInsufficientCreditsResponse"
                }
              }
            }
          },
          "403": {
            "description": "Credential, account or provider permission denied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Artist/track resolution or requested provider data was not found. A failed name search may be reported as 404; retry with an exact provider ID when known.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Provider rate or quota limit. The adapter returns an error status but does not forward provider Retry-After headers. Avoid an unbounded retry loop.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Missing provider configuration, transport failure or internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "501": {
            "$ref": "#/components/responses/ResearchDataSourceUnsupported"
          },
          "502": {
            "description": "Upstream provider failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "504": {
            "description": "the configured research data connection request timed out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/research/track": {
      "get": {
        "operationId": "getResearchTrack",
        "description": "Read track details using the `id` selected from [Search](/api-reference/research/search) with `type=tracks`. Treat this ID as an opaque string, separate from a Recoup account UUID. Track metadata and available fields vary by recording.\n\nUse your Recoup API key (`x-api-key`) or supported bearer credential. Successful artist and track research reads cost $0.05 (50,000 micro-dollar credits). Recoup manages the data connections. Coverage and rate limits vary; see [Research availability](/research-availability).",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": true,
            "description": "Provider track ID. Obtain it from search results or a track lookup.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
              "example": "track_123"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Track metadata",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchTrackResponse"
                }
              }
            }
          },
          "400": {
            "description": "Missing id or invalid provider ID string pattern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed — invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient research credits. The response includes a static billingUrl; no checkout session is created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchInsufficientCreditsResponse"
                }
              }
            }
          },
          "403": {
            "description": "Credential, account or provider permission denied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Artist/track resolution or requested provider data was not found. A failed name search may be reported as 404; retry with an exact provider ID when known.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Provider rate or quota limit. The adapter returns an error status but does not forward provider Retry-After headers. Avoid an unbounded retry loop.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Missing provider configuration, transport failure or internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "501": {
            "$ref": "#/components/responses/ResearchDataSourceUnsupported"
          },
          "502": {
            "description": "Upstream provider failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "504": {
            "description": "the configured research data connection request timed out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/research/tracks": {
      "get": {
        "operationId": "getResearchTracks",
        "description": "Read available artist catalog entries under `tracks`. Entries can include remixes and features; completeness and fields vary. This endpoint accepts an artist identifier without catalog pagination or filtering controls.\n\nUse your Recoup API key (`x-api-key`) or supported bearer credential. Successful artist and track research reads cost $0.05 (50,000 micro-dollar credits). Recoup manages the data connections. Coverage and rate limits vary; see [Research availability](/research-availability).",
        "parameters": [
          {
            "name": "artist",
            "in": "query",
            "required": false,
            "description": "Artist name; Recoup resolves the first provider search match. Required unless id is supplied. Recoup artist UUID resolution is not implemented here.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "id",
            "in": "query",
            "required": false,
            "description": "Research artist ID returned by search or lookup; treat it as an opaque string. Required unless artist is supplied; takes precedence when both are provided. This is not a Recoup artist/account ID.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
              "example": "artist_123"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Artist tracks",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchTracksResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed — invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient research credits. The response includes a static billingUrl; no checkout session is created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchInsufficientCreditsResponse"
                }
              }
            }
          },
          "403": {
            "description": "Credential, account or provider permission denied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Artist/track resolution or requested provider data was not found. A failed name search may be reported as 404; retry with an exact provider ID when known.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Provider rate or quota limit. The adapter returns an error status but does not forward provider Retry-After headers. Avoid an unbounded retry loop.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Missing provider configuration, transport failure or internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "501": {
            "$ref": "#/components/responses/ResearchDataSourceUnsupported"
          },
          "502": {
            "description": "Upstream provider failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "504": {
            "description": "the configured research data connection request timed out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/research/urls": {
      "get": {
        "operationId": "getResearchUrls",
        "description": "Read available platform URLs as `{domain, url}` entries. A missing link means no matching URL was returned; it does not prove the artist lacks an account on that platform.\n\nUse your Recoup API key (`x-api-key`) or supported bearer credential. Successful artist and track research reads cost $0.05 (50,000 micro-dollar credits). Recoup manages the data connections. Coverage and rate limits vary; see [Research availability](/research-availability).",
        "parameters": [
          {
            "name": "artist",
            "in": "query",
            "required": false,
            "description": "Artist name; Recoup resolves the first provider search match. Required unless id is supplied. Recoup artist UUID resolution is not implemented here.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "id",
            "in": "query",
            "required": false,
            "description": "Research artist ID returned by search or lookup; treat it as an opaque string. Required unless artist is supplied; takes precedence when both are provided. This is not a Recoup artist/account ID.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
              "example": "artist_123"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Social and streaming URLs",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchUrlsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed — invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient research credits. The response includes a static billingUrl; no checkout session is created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchInsufficientCreditsResponse"
                }
              }
            }
          },
          "403": {
            "description": "Credential, account or provider permission denied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Artist/track resolution or requested provider data was not found. A failed name search may be reported as 404; retry with an exact provider ID when known.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Provider rate or quota limit. The adapter returns an error status but does not forward provider Retry-After headers. Avoid an unbounded retry loop.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Missing provider configuration, transport failure or internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "501": {
            "$ref": "#/components/responses/ResearchDataSourceUnsupported"
          },
          "502": {
            "description": "Upstream provider failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "504": {
            "description": "the configured research data connection request timed out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/research/track/playlists": {
      "get": {
        "operationId": "getResearchTrackPlaylists",
        "description": "Read available track playlist and activity entries. Supply a selected track `id`, or a track name `q` with optional `artist`. Name resolution selects the first Spotify match and resolves its research ID; `id` takes precedence. Filtering and pagination support depend on the data connection. With no filter flags, editorial, indie, majorCurator and popularIndie default to true. Track-name resolution can add separately charged lookup calls; reuse a selected ID to avoid them.\n\nUse your Recoup API key (`x-api-key`) or supported bearer credential. Successful artist and track research reads cost $0.05 (50,000 micro-dollar credits). Recoup manages the data connections. Coverage and rate limits vary; see [Research availability](/research-availability).",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "description": "Exact provider track ID; required unless q is supplied. Takes precedence over q. Recoup does not validate its format here.",
            "schema": {
              "type": "string",
              "example": "track_123"
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Track name to look up. Required if `id` is not provided. Combine with `artist` to narrow the match.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "artist",
            "in": "query",
            "description": "Optional artist name to narrow Spotify track-name matching. Ignored when id is supplied.",
            "schema": {
              "type": "string"
            },
            "required": false
          },
          {
            "name": "platform",
            "in": "query",
            "description": "Streaming platform to return playlists for. Defaults to `spotify`.",
            "schema": {
              "type": "string",
              "enum": [
                "spotify",
                "applemusic",
                "deezer",
                "amazon"
              ],
              "default": "spotify"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Return current or past playlist placements. Defaults to `current`.",
            "schema": {
              "type": "string",
              "enum": [
                "current",
                "past"
              ],
              "default": "current"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum number of placements to return. Forwarded unchanged as a raw string; Recoup does not validate its range, date format or boolean value. Provider support applies.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "offset",
            "in": "query",
            "description": "Pagination offset. Forwarded unchanged as a raw string; Recoup does not validate its range, date format or boolean value. Provider support applies.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "description": "ISO date lower bound for placements. Forwarded unchanged as a raw string; Recoup does not validate its range, date format or boolean value. Provider support applies.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "until",
            "in": "query",
            "description": "ISO date upper bound for placements. Forwarded unchanged as a raw string; Recoup does not validate its range, date format or boolean value. Provider support applies.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "description": "Sort column. Forwarded unchanged as a raw string; Recoup does not validate its range, date format or boolean value. Provider support applies.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "editorial",
            "in": "query",
            "description": "Include editorial playlists. When no filter flags are set, defaults to `true` along with `indie`, `majorCurator`, `popularIndie`. Forwarded unchanged as a raw string; Recoup does not validate its range, date format or boolean value. Provider support applies.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "indie",
            "in": "query",
            "description": "Include indie playlists. Forwarded unchanged as a raw string; Recoup does not validate its range, date format or boolean value. Provider support applies.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "majorCurator",
            "in": "query",
            "description": "Include major-curator playlists. Forwarded unchanged as a raw string; Recoup does not validate its range, date format or boolean value. Provider support applies.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "popularIndie",
            "in": "query",
            "description": "Include popular indie playlists. Forwarded unchanged as a raw string; Recoup does not validate its range, date format or boolean value. Provider support applies.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "personalized",
            "in": "query",
            "description": "Include personalized playlists. Forwarded unchanged as a raw string; Recoup does not validate its range, date format or boolean value. Provider support applies.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "chart",
            "in": "query",
            "description": "Include chart playlists. Forwarded unchanged as a raw string; Recoup does not validate its range, date format or boolean value. Provider support applies.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "newMusicFriday",
            "in": "query",
            "description": "Include New Music Friday playlists. Forwarded unchanged as a raw string; Recoup does not validate its range, date format or boolean value. Provider support applies.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "thisIs",
            "in": "query",
            "description": "Include \"This Is\" artist playlists. Forwarded unchanged as a raw string; Recoup does not validate its range, date format or boolean value. Provider support applies.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "radio",
            "in": "query",
            "description": "Include algorithmic radio playlists. Forwarded unchanged as a raw string; Recoup does not validate its range, date format or boolean value. Provider support applies.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "brand",
            "in": "query",
            "description": "Include brand playlists. Forwarded unchanged as a raw string; Recoup does not validate its range, date format or boolean value. Provider support applies.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Playlist placements for the track.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "status",
                    "placements"
                  ],
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "success"
                      ],
                      "example": "success"
                    },
                    "placements": {
                      "type": "array",
                      "description": "Playlist placement objects returned by the configured provider (shape varies per platform).",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error — missing `id`/`q`, invalid `platform`, or invalid `status`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed — invalid or missing API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient research credits. The response includes a static billingUrl; no checkout session is created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchInsufficientCreditsResponse"
                }
              }
            }
          },
          "403": {
            "description": "Credential, account or provider permission denied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Artist/track resolution or requested provider data was not found. A failed name search may be reported as 404; retry with an exact provider ID when known.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Provider rate or quota limit. The adapter returns an error status but does not forward provider Retry-After headers. Avoid an unbounded retry loop.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Missing provider configuration, transport failure or internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "501": {
            "$ref": "#/components/responses/ResearchDataSourceUnsupported"
          },
          "502": {
            "description": "Upstream provider failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          },
          "504": {
            "description": "the configured research data connection request timed out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchErrorResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "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": {
      "StopChatResponse": {
        "type": "object",
        "required": [
          "success",
          "stopped"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "description": "Always true when the request was processed.",
            "example": true
          },
          "stopped": {
            "type": "boolean",
            "description": "True when an in-progress response was cancelled; false when nothing was running.",
            "example": true
          }
        }
      },
      "StopChatErrorResponse": {
        "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"
          }
        }
      },
      "ChatGenerateErrorResponse": {
        "type": "object",
        "required": [
          "status",
          "error"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "error"
            ],
            "description": "Status of the request"
          },
          "error": {
            "type": "string",
            "description": "Validation error describing why the headless request was rejected."
          },
          "missing_fields": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Optional validation path when available; a general request error can omit it."
          }
        }
      },
      "ChatGenerateRequest": {
        "type": "object",
        "description": "Request body for chat generation. Exactly one of 'prompt' or 'messages' must be provided.",
        "properties": {
          "prompt": {
            "type": "string",
            "description": "Single text prompt for the assistant. Required if 'messages' is not provided.",
            "minLength": 1
          },
          "messages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UIMessage"
            },
            "description": "Array of UIMessage objects for context. Required if 'prompt' is not provided.",
            "minItems": 1
          },
          "artistId": {
            "type": "string",
            "format": "uuid",
            "description": "The unique identifier of the artist (optional)"
          },
          "model": {
            "type": "string",
            "description": "The AI model to use for text generation (optional)",
            "example": "openai/gpt-5-mini"
          },
          "trigger_run_id": {
            "type": "string",
            "description": "Optional. When the run is kicked off by a Trigger.dev scheduled task, the Trigger run id (`run_...`). The started workflow's `{ sessionId, chatId, workflowRunId }` are written to that Trigger run's metadata so the scheduled run can be traced to the chat that did the work.",
            "example": "run_06g3i0e3logru439uh9e1m8801"
          }
        },
        "example": {
          "messages": [
            {
              "id": "user-1",
              "role": "user",
              "parts": [
                {
                  "type": "text",
                  "text": "Summarize my next release."
                }
              ]
            }
          ]
        },
        "examples": [
          {
            "messages": [
              {
                "id": "user-inline-file",
                "role": "user",
                "parts": [
                  {
                    "type": "text",
                    "text": "Summarize this attached note."
                  },
                  {
                    "type": "file",
                    "mediaType": "text/plain",
                    "url": "data:text/plain;base64,U3ludGhldGljIHJlbGVhc2Ugbm90ZS4="
                  }
                ]
              }
            ]
          }
        ]
      },
      "ChatMessage": {
        "type": "object",
        "required": [
          "id",
          "room_id",
          "content",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "UUID of the memory message"
          },
          "room_id": {
            "type": "string",
            "format": "uuid",
            "description": "UUID of the parent chat room"
          },
          "content": {
            "type": "object",
            "description": "Structured message payload stored for the memory"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO timestamp of the memory update"
          }
        }
      },
      "ChatRoom": {
        "type": "object",
        "required": [
          "id",
          "title",
          "accountId",
          "sessionId",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Chat id."
          },
          "title": {
            "type": "string",
            "description": "Display title for the chat."
          },
          "accountId": {
            "type": "string",
            "format": "uuid",
            "description": "Owning account for this chat."
          },
          "sessionId": {
            "type": "string",
            "format": "uuid",
            "description": "Session that owns this chat. Combine with `id` to build the chat URL: `/sessions/{sessionId}/chats/{id}`."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "ISO timestamp of the last update."
          },
          "artistId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Artist account id this chat's session was created in the context of, or `null` when no artist was associated. Inherited from `sessions.artist_id` on the owning session."
          }
        }
      },
      "ChatStreamErrorResponse": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "error"
            ]
          },
          "error": {
            "type": "string"
          },
          "missing_fields": {
            "type": "array",
            "items": {
              "type": [
                "string",
                "integer"
              ]
            },
            "description": "Optional validation path for the first rejected field. String entries identify object/query fields; integer entries identify array positions. An empty path can represent validation of the path value itself. Lookup or workflow errors can omit this field."
          }
        }
      },
      "ChatWorkflowRequest": {
        "type": "object",
        "description": "Request body for the sandbox-driven, Workflow-backed chat endpoint. Requires an existing session, chat, and provisioned sandbox. The Bearer key from the `Authorization` header is forwarded into the sandbox for skill-based callbacks to the Recoup API — no separate access-token field needed.",
        "required": [
          "messages",
          "chatId",
          "sessionId"
        ],
        "properties": {
          "messages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UIMessage"
            },
            "description": "Conversation history as an array of `UIMessage` objects, matching the [Vercel AI SDK](https://sdk.vercel.ai/) `UIMessage` shape. The last entry is typically the new user turn — the agent loop reads the entire array and persists the latest user message before the workflow starts."
          },
          "chatId": {
            "type": "string",
            "format": "uuid",
            "description": "UUID of an existing chat under `sessionId`. The chat's `active_stream_id` column is used for compare-and-set race control across concurrent requests."
          },
          "sessionId": {
            "type": "string",
            "format": "uuid",
            "description": "UUID of the session that owns both the chat and the sandbox. The session must have a non-null `sandbox_state` — call [POST /api/sandbox](/api-reference/sandbox/create) first if not."
          },
          "context": {
            "type": "object",
            "description": "Optional per-prompt context overrides forwarded into the agent loop.",
            "properties": {
              "contextLimit": {
                "type": "integer",
                "description": "Upper bound on the number of tokens / messages of conversation history the agent should consider for this prompt. Overrides the model's default for this call only."
              }
            }
          }
        }
      },
      "CompactChatsRequest": {
        "type": "object",
        "required": [
          "chatId"
        ],
        "properties": {
          "chatId": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Array of chat IDs to compact"
          },
          "prompt": {
            "type": "string",
            "description": "Optional prompt to control what information gets preserved in the compacted summary"
          }
        }
      },
      "CompactChatsResponse": {
        "type": "object",
        "required": [
          "chats"
        ],
        "properties": {
          "chats": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CompactedChat"
            },
            "description": "Array of compacted chat results"
          }
        }
      },
      "CompactedChat": {
        "type": "object",
        "required": [
          "chatId",
          "compacted"
        ],
        "properties": {
          "chatId": {
            "type": "string",
            "format": "uuid",
            "description": "The ID of the chat that was compacted"
          },
          "compacted": {
            "type": "string",
            "description": "The compacted summary text of the chat"
          }
        }
      },
      "CopyChatMessagesErrorResponse": {
        "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."
          }
        }
      },
      "CopyChatMessagesRequest": {
        "type": "object",
        "required": [
          "targetChatId"
        ],
        "properties": {
          "targetChatId": {
            "type": "string",
            "format": "uuid",
            "description": "Target chat room UUID to receive the copied messages."
          },
          "clearExisting": {
            "type": "boolean",
            "default": true,
            "description": "When true, existing messages in the target chat are deleted before copy."
          }
        }
      },
      "CopyChatMessagesResponse": {
        "type": "object",
        "required": [
          "status",
          "source_chat_id",
          "target_chat_id",
          "copied_count",
          "cleared_existing"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success"
            ],
            "description": "Status of the request",
            "example": "success"
          },
          "source_chat_id": {
            "type": "string",
            "format": "uuid",
            "description": "Source chat room UUID."
          },
          "target_chat_id": {
            "type": "string",
            "format": "uuid",
            "description": "Target chat room UUID."
          },
          "copied_count": {
            "type": "integer",
            "description": "Number of messages copied from source to target."
          },
          "cleared_existing": {
            "type": "boolean",
            "description": "Whether existing target messages were deleted before copy."
          }
        }
      },
      "CreateChatErrorResponse": {
        "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"
          }
        }
      },
      "CreateChatRequest": {
        "type": "object",
        "properties": {
          "artistId": {
            "type": "string",
            "format": "uuid",
            "description": "UUID of the artist account the chat is associated with"
          },
          "chatId": {
            "type": "string",
            "format": "uuid",
            "description": "UUID for the new chat (client-generated). If not provided, one will be generated automatically."
          },
          "accountId": {
            "type": "string",
            "format": "uuid",
            "description": "UUID of the account to create the chat for. Only applicable when the authenticated account has access to multiple accounts via organization membership. If not provided, the chat is created for the API key's own account."
          },
          "topic": {
            "type": "string",
            "description": "Topic name for the new chat room (e.g., 'Pulse Feb 2'). To edit the topic of an existing room, use [PATCH /api/chats](/api-reference/chat/update)."
          }
        }
      },
      "CreateChatResponse": {
        "type": "object",
        "required": [
          "status",
          "chat"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success"
            ],
            "description": "Status of the request",
            "example": "success"
          },
          "chat": {
            "$ref": "#/components/schemas/ChatRoom",
            "description": "The created chat room object"
          }
        }
      },
      "DeleteChatErrorResponse": {
        "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."
          }
        }
      },
      "DeleteChatRequest": {
        "type": "object",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The unique identifier (UUID) of the chat room to delete."
          }
        }
      },
      "DeleteChatResponse": {
        "type": "object",
        "required": [
          "status",
          "id",
          "message"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success"
            ],
            "description": "Status of the request",
            "example": "success"
          },
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The UUID of the deleted chat room."
          },
          "message": {
            "type": "string",
            "description": "Success message describing the deletion result."
          }
        }
      },
      "DeleteTrailingChatMessagesErrorResponse": {
        "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."
          },
          "missing_fields": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "List of missing or invalid parameter fields (when validation fails)."
          }
        }
      },
      "DeleteTrailingChatMessagesResponse": {
        "type": "object",
        "required": [
          "status",
          "chat_id",
          "from_message_id"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success"
            ],
            "description": "Status of the request",
            "example": "success"
          },
          "chat_id": {
            "type": "string",
            "format": "uuid",
            "description": "The chat UUID where deletion was applied."
          },
          "from_message_id": {
            "type": "string",
            "format": "uuid",
            "description": "The message UUID used as the trailing deletion boundary."
          }
        }
      },
      "Error": {
        "required": [
          "error",
          "message"
        ],
        "type": "object",
        "properties": {
          "error": {
            "type": "integer",
            "format": "int32"
          },
          "message": {
            "type": "string"
          }
        }
      },
      "GetChatArtistErrorResponse": {
        "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"
          }
        }
      },
      "GetChatArtistResponse": {
        "type": "object",
        "required": [
          "status",
          "room_id",
          "artist_id",
          "artist_exists"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success"
            ],
            "description": "Status of the request",
            "example": "success"
          },
          "room_id": {
            "type": "string",
            "format": "uuid",
            "description": "UUID of the chat room"
          },
          "artist_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "UUID of the artist account associated with the chat, or null when no artist is linked"
          },
          "artist_exists": {
            "type": "boolean",
            "description": "Whether an artist is linked to the chat room"
          }
        }
      },
      "GetChatMessagesErrorResponse": {
        "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"
          }
        }
      },
      "GetChatMessagesResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ChatMessage"
            },
            "description": "Chronologically ordered list of messages for the chat"
          }
        }
      },
      "GetChatsErrorResponse": {
        "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"
          }
        }
      },
      "GetChatsResponse": {
        "type": "object",
        "required": [
          "status",
          "chats"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success"
            ],
            "description": "Status of the request",
            "example": "success"
          },
          "chats": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ChatRoom"
            },
            "description": "Array of chat objects"
          }
        }
      },
      "ResearchDeepRequest": {
        "type": "object",
        "required": [
          "query"
        ],
        "description": "Request body for deep research. Performs comprehensive multi-source analysis.",
        "properties": {
          "query": {
            "type": "string",
            "description": "The research question — be specific and detailed for best results."
          }
        }
      },
      "ResearchDeepResponse": {
        "type": "object",
        "description": "Comprehensive research report with citations.",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success",
              "error"
            ],
            "example": "success"
          },
          "content": {
            "type": "string",
            "description": "The full research report as markdown."
          },
          "citations": {
            "type": "array",
            "description": "Source URLs cited in the report.",
            "items": {
              "type": "string",
              "format": "uri"
            }
          }
        }
      },
      "ResearchEnrichRequest": {
        "type": "object",
        "required": [
          "input",
          "schema"
        ],
        "properties": {
          "input": {
            "type": "string",
            "description": "What to research (e.g., \"Drake rapper from Dallas Texas\")."
          },
          "schema": {
            "type": "object",
            "description": "JSON schema defining the fields to extract. Must include `\"type\": \"object\"` at the top level.",
            "additionalProperties": true,
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "object"
                ],
                "description": "Must be \"object\""
              },
              "properties": {
                "type": "object",
                "description": "Field definitions to extract",
                "additionalProperties": true
              }
            },
            "required": [
              "type",
              "properties"
            ]
          },
          "processor": {
            "type": "string",
            "enum": [
              "base",
              "core",
              "ultra"
            ],
            "default": "base",
            "description": "Research depth: base (fast), core (balanced), ultra (comprehensive)."
          }
        }
      },
      "ResearchEnrichResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "example": "success"
          },
          "output": {
            "type": "object",
            "description": "Structured data matching the provided schema.",
            "additionalProperties": true
          },
          "citations": {
            "type": "array",
            "description": "Source citations supporting the enriched output.",
            "items": {
              "type": "object",
              "properties": {
                "url": {
                  "type": "string",
                  "format": "uri"
                },
                "title": {
                  "type": "string"
                },
                "field": {
                  "type": "string",
                  "description": "Which output field this citation supports."
                }
              }
            }
          }
        }
      },
      "ResearchExtractRequest": {
        "type": "object",
        "required": [
          "urls"
        ],
        "properties": {
          "urls": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            },
            "maxItems": 10,
            "description": "URLs to extract content from (max 10).",
            "minItems": 1
          },
          "objective": {
            "type": "string",
            "description": "What information to focus on (optional, max 3000 chars)."
          },
          "full_content": {
            "type": "boolean",
            "default": false,
            "description": "Return full page content instead of focused excerpts."
          }
        }
      },
      "ResearchExtractResponse": {
        "type": "object",
        "required": [
          "status",
          "results"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success",
              "error"
            ],
            "example": "success"
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResearchExtractResult"
            }
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "URLs that failed to extract. Only present when one or more URLs could not be processed."
          }
        }
      },
      "ResearchExtractResult": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri"
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "publish_date": {
            "type": "string",
            "nullable": true,
            "description": "Publish date in YYYY-MM-DD format."
          },
          "excerpts": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true,
            "description": "Focused excerpts as markdown."
          },
          "full_content": {
            "type": "string",
            "nullable": true,
            "description": "Full page content as markdown."
          }
        }
      },
      "ResearchPeopleRequest": {
        "type": "object",
        "required": [
          "query"
        ],
        "properties": {
          "query": {
            "type": "string",
            "description": "Natural language search for people (e.g., \"A&R reps at Atlantic Records\")."
          },
          "num_results": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100,
            "default": 10,
            "description": "Number of results to return."
          }
        }
      },
      "ResearchPeopleResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success",
              "error"
            ],
            "example": "success"
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResearchPeopleResult"
            }
          }
        }
      },
      "ResearchPeopleResult": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "description": "Person name and role."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Profile URL (often LinkedIn)."
          },
          "id": {
            "type": "string",
            "description": "Unique result identifier from the search provider."
          },
          "publishedDate": {
            "type": "string",
            "nullable": true,
            "description": "Date the profile or page was published."
          },
          "author": {
            "type": "string",
            "nullable": true,
            "description": "Author of the page, if available."
          },
          "highlights": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true,
            "description": "Key excerpts from the profile."
          },
          "summary": {
            "type": "string",
            "nullable": true,
            "description": "Brief summary of the person."
          }
        }
      },
      "ResearchWebRequest": {
        "type": "object",
        "required": [
          "query"
        ],
        "description": "Request body for web research.",
        "properties": {
          "query": {
            "type": "string",
            "description": "The search query — what you want to find on the web."
          },
          "max_results": {
            "type": "integer",
            "minimum": 1,
            "maximum": 20,
            "default": 10,
            "description": "Maximum number of results to return."
          },
          "country": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2,
            "description": "ISO country code for regional results (e.g., 'US', 'GB')."
          }
        }
      },
      "ResearchWebResponse": {
        "type": "object",
        "description": "Web search results with titles, URLs, and content snippets.",
        "properties": {
          "status": {
            "type": "string",
            "example": "success"
          },
          "results": {
            "type": "array",
            "description": "Ranked web search results.",
            "items": {
              "type": "object",
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "snippet": {
                  "type": "string",
                  "description": "Content snippet from the search result."
                },
                "date": {
                  "type": "string",
                  "nullable": true,
                  "description": "Publication date if available."
                },
                "last_updated": {
                  "type": "string",
                  "nullable": true,
                  "description": "Last updated date if available."
                }
              }
            }
          },
          "formatted": {
            "type": "string",
            "description": "Results formatted as markdown for easy reading."
          }
        }
      },
      "UIMessage": {
        "type": "object",
        "required": [
          "id",
          "role",
          "parts"
        ],
        "description": "AI SDK UIMessage. Parts are structurally validated before provisioning for headless runs. Legacy content-only messages are rejected.",
        "properties": {
          "id": {
            "type": "string"
          },
          "role": {
            "type": "string",
            "enum": [
              "user",
              "assistant",
              "system"
            ]
          },
          "metadata": {},
          "parts": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "object",
              "required": [
                "type"
              ],
              "properties": {
                "type": {
                  "type": "string"
                }
              },
              "additionalProperties": true
            },
            "description": "SDK discriminated parts: text (text), file (mediaType and url), reasoning, supported tool and data-* parts. Each must satisfy the installed AI SDK schema; a type field alone is insufficient.",
            "example": [
              {
                "type": "text",
                "text": "Summarize my next release."
              }
            ]
          }
        }
      },
      "UpdateChatErrorResponse": {
        "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"
          }
        }
      },
      "UpdateChatRequest": {
        "type": "object",
        "required": [
          "chatId",
          "topic"
        ],
        "properties": {
          "chatId": {
            "type": "string",
            "format": "uuid",
            "description": "The unique identifier (UUID) of the chat room to update"
          },
          "topic": {
            "type": "string",
            "minLength": 3,
            "maxLength": 50,
            "description": "The new display name for the chat room. Must be between 3 and 50 characters."
          }
        }
      },
      "UpdateChatResponse": {
        "type": "object",
        "required": [
          "status",
          "chat"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success"
            ],
            "description": "Status of the request",
            "example": "success"
          },
          "chat": {
            "$ref": "#/components/schemas/ChatRoom",
            "description": "The updated chat room object"
          }
        }
      },
      "ResearchEventsRequest": {
        "type": "object",
        "required": [
          "artist_id"
        ],
        "description": "Request body for artist event lookup.",
        "properties": {
          "artist_id": {
            "type": "string",
            "format": "uuid",
            "description": "Recoup artist id. Events are resolved through the live-events profile connected to this artist, so the lookup is exact and cannot drift to a same-named performer. Returns 404 if no live-events profile is connected.",
            "example": "123694f2-1dab-40b4-8a75-84d39571c0bc"
          },
          "date": {
            "type": "string",
            "enum": [
              "upcoming",
              "past",
              "all"
            ],
            "default": "upcoming",
            "description": "Which events to return."
          }
        }
      },
      "ResearchEventsResponse": {
        "type": "object",
        "required": [
          "status",
          "events"
        ],
        "description": "Live events for one artist.",
        "properties": {
          "status": {
            "type": "string",
            "example": "success"
          },
          "events": {
            "type": "array",
            "description": "Matching events, ascending by date. Empty when the artist has none.",
            "items": {
              "type": "object",
              "properties": {
                "date": {
                  "type": "string",
                  "description": "Event date as ISO 8601 (YYYY-MM-DD).",
                  "example": "2026-09-26"
                },
                "venue": {
                  "type": "string",
                  "description": "Venue name.",
                  "example": "O2 Academy Brixton"
                },
                "city": {
                  "type": "string",
                  "example": "London"
                },
                "region": {
                  "type": "string",
                  "nullable": true,
                  "description": "State or region where the source provides one. US venues carry a state code such as \"TN\"; most non-US venues return an empty string, as in the example below.",
                  "example": ""
                },
                "country": {
                  "type": "string",
                  "description": "Country name. Pair with `city` before filtering by market, since city names such as London and Paris are not unique across countries.",
                  "example": "United Kingdom"
                },
                "ticket_url": {
                  "type": "string",
                  "nullable": true,
                  "description": "Ticket link where one is published."
                },
                "sold_out": {
                  "type": "boolean",
                  "description": "Whether the source marks the event sold out."
                },
                "lineup": {
                  "type": "array",
                  "description": "Billed artists, the queried artist first. More than one entry means a shared bill.",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      },
      "ResearchErrorResponse": {
        "type": "object",
        "required": [
          "status",
          "error"
        ],
        "description": "Error response returned by all research endpoints for validation failures (400) and authentication errors (401).",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "error"
            ],
            "example": "error"
          },
          "error": {
            "type": "string",
            "description": "Human-readable error message describing what went wrong.",
            "example": "Missing required parameter: artist"
          }
        }
      },
      "ResearchTrackStatsResponse": {
        "type": "object",
        "description": "Current per-source track statistics from the Spotify measurement store or the configured research data connection. Provider payloads and coverage vary; inspect per-entry provenance when present.",
        "properties": {
          "status": {
            "type": "string",
            "example": "success"
          },
          "result": {
            "type": "string",
            "example": "success"
          },
          "stats": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResearchTrackStat"
            }
          }
        }
      },
      "ResearchTrackStat": {
        "type": "object",
        "description": "Per-source current statistics for a single track.",
        "properties": {
          "source": {
            "type": "string",
            "description": "Requested provider platform (for example spotify or apple_music); coverage depends on provider entitlement."
          },
          "data": {
            "type": "object",
            "description": "Provider-specific track counters. A Spotify store streams_total is a platform-displayed play count, not a royalty-bearing stream count.",
            "additionalProperties": true,
            "example": {
              "streams_total": 84213771
            }
          },
          "data_source": {
            "type": "string",
            "description": "Opaque provenance label identifying the measurement origin. Preserve this value for traceability. Labels can vary with the data connection and are not a supported vendor selector. Missing provenance remains unknown."
          },
          "captured_at": {
            "type": "string",
            "format": "date-time",
            "description": "When this entry's values were captured into the measurement store."
          }
        },
        "additionalProperties": true
      },
      "ResearchInsufficientCreditsResponse": {
        "type": "object",
        "description": "Returned (402) when the account lacks research credits. 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",
            "example": 0
          },
          "required_credits": {
            "type": "integer",
            "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"
        ]
      },
      "ResearchMeasurementPoint": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "format": "date",
            "example": "2026-06-12"
          },
          "value": {
            "type": "number",
            "description": "Cumulative metric value as of `date`.",
            "example": 297289495
          },
          "data_source": {
            "type": "string"
          }
        }
      },
      "ResearchMeasurementsResponse": {
        "type": "object",
        "description": "A track's measured series, or — when `aggregate` is set — the derived projection.",
        "properties": {
          "status": {
            "type": "string",
            "example": "success"
          },
          "id": {
            "type": "string",
            "description": "The provider-neutral track id queried.",
            "example": "USQY51771120"
          },
          "platform": {
            "type": "string",
            "example": "spotify"
          },
          "metric": {
            "type": "string",
            "example": "platform_displayed_play_count"
          },
          "series": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResearchMeasurementPoint"
            },
            "description": "Present unless `aggregate` is set."
          },
          "aggregate": {
            "type": "object",
            "nullable": true,
            "description": "Present when `aggregate=run_rate`.",
            "properties": {
              "kind": {
                "type": "string",
                "example": "run_rate"
              },
              "window_days": {
                "type": "integer",
                "example": 365
              },
              "delta": {
                "type": "number",
                "example": 42000000
              },
              "run_rate_annualized": {
                "type": "number",
                "example": 42000000
              }
            }
          }
        }
      },
      "ResearchAlbumMeasurementItem": {
        "type": "object",
        "properties": {
          "isrc": {
            "type": "string",
            "example": "USQY51771120"
          },
          "spotify_track_id": {
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "value": {
            "type": "number",
            "description": "Latest measured count.",
            "example": 297289495
          },
          "captured_at": {
            "type": "string",
            "format": "date-time"
          },
          "data_source": {
            "type": "string"
          }
        }
      },
      "ResearchAlbumMeasurementsResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "example": "success"
          },
          "id": {
            "type": "string",
            "description": "Spotify album id.",
            "example": "70Zkfb99ladZ3q0JVg97co"
          },
          "platform": {
            "type": "string",
            "example": "spotify"
          },
          "metric": {
            "type": "string",
            "example": "platform_displayed_play_count"
          },
          "measurements": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResearchAlbumMeasurementItem"
            }
          }
        }
      },
      "ResearchMeasurementJobScope": {
        "type": "object",
        "description": "Provide exactly one of `catalog_id`, `album_ids`, or `isrcs`.",
        "properties": {
          "catalog_id": {
            "type": "string",
            "format": "uuid"
          },
          "album_ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "isrcs": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "ResearchMeasurementJobRequest": {
        "type": "object",
        "required": [
          "scope",
          "source"
        ],
        "properties": {
          "scope": {
            "$ref": "#/components/schemas/ResearchMeasurementJobScope"
          },
          "source": {
            "type": "string",
            "enum": [
              "current",
              "historical"
            ],
            "description": "current captures present counts through the snapshot pipeline; historical enqueues the configured research data connection backfill. Historical requires a saved payment method and does not promise complete history."
          },
          "platforms": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "spotify"
              ]
            },
            "default": [
              "spotify"
            ],
            "example": [
              "spotify"
            ]
          }
        },
        "example": {
          "scope": {
            "isrcs": [
              "USQY51771120"
            ]
          },
          "source": "historical",
          "platforms": [
            "spotify"
          ]
        }
      },
      "ResearchMeasurementJobResponse": {
        "oneOf": [
          {
            "type": "object",
            "required": [
              "status",
              "source",
              "id",
              "state",
              "album_count",
              "estimated_cost_usd"
            ],
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "success"
                ]
              },
              "source": {
                "type": "string",
                "enum": [
                  "current"
                ]
              },
              "id": {
                "type": "string",
                "format": "uuid",
                "description": "Snapshot ID for current captures."
              },
              "state": {
                "type": "string",
                "description": "State returned by the snapshot pipeline; reused captures need not be queued."
              },
              "album_count": {
                "type": "integer"
              },
              "estimated_cost_usd": {
                "type": "number"
              },
              "reused": {
                "type": "boolean",
                "description": "Present when an existing capture is handed back."
              }
            }
          },
          {
            "type": "object",
            "required": [
              "status",
              "source",
              "id",
              "enqueued",
              "skipped"
            ],
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "success"
                ]
              },
              "source": {
                "type": "string",
                "enum": [
                  "historical"
                ]
              },
              "id": {
                "type": "null",
                "description": "No pollable snapshot ID is created for historical backfill."
              },
              "enqueued": {
                "type": "integer",
                "description": "Recordings submitted to the backfill queue; this does not mean history is captured."
              },
              "skipped": {
                "type": "integer",
                "description": "Recordings with any existing historical research measurement; skipping does not prove complete history."
              }
            }
          }
        ],
        "description": "Current captures return a snapshot ID; historical backfill returns queue counts and id:null."
      },
      "ChatGenerateAcceptedResponse": {
        "type": "object",
        "required": [
          "runId",
          "chatId",
          "sessionId"
        ],
        "description": "Confirmation that an asynchronous chat-generation run has been started on the durable agent workflow.",
        "properties": {
          "runId": {
            "type": "string",
            "description": "Durable workflow run id for the started generation. Same identifier surfaced as the `x-workflow-run-id` header on interactive [POST /api/chat](/api-reference/chat/workflow).",
            "example": "wrun_01KVWZNM82NA7XKNEWWHG8VPHJ"
          },
          "chatId": {
            "type": "string",
            "format": "uuid",
            "description": "Chat the run writes its assistant messages to. Use with [`GET /api/chat/{chatId}/stream`](/api-reference/chat/workflow-stream) to resume the stream, or to fetch the persisted messages.",
            "example": "24830c6c-76d8-43be-ae22-1dfd545421ab"
          },
          "sessionId": {
            "type": "string",
            "format": "uuid",
            "description": "Session (workspace + sandbox) provisioned for the run.",
            "example": "fa9c1516-35f0-4efd-a62c-01af26324e72"
          }
        }
      },
      "ChatRunStatusResponse": {
        "type": "object",
        "required": [
          "runId",
          "status"
        ],
        "description": "Point-in-time status of an asynchronous chat-generation run.",
        "properties": {
          "runId": {
            "type": "string",
            "description": "The durable workflow run id.",
            "example": "wrun_01KVWZNM82NA7XKNEWWHG8VPHJ"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "running",
              "completed",
              "failed",
              "cancelled"
            ],
            "description": "Lifecycle state of the run. Read the produced content via the chat (`chatId` from the start response)."
          },
          "createdAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the workflow run was created; null if unavailable, including for terminal runs.",
            "example": "2026-08-25T19:33:56.000Z"
          },
          "startedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When execution started; null while unavailable or if its timing getter fails, including for terminal runs.",
            "example": "2026-08-25T19:34:16.386Z"
          },
          "completedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the run reached a terminal state; null while unavailable or if its timing getter fails, including for terminal runs.",
            "example": "2026-08-25T20:15:55.000Z"
          },
          "durationMs": {
            "type": [
              "integer",
              "null"
            ],
            "description": "completedAt minus startedAt in milliseconds; null if either timestamp is unavailable, including for terminal runs.",
            "example": 2498614
          }
        }
      },
      "ChatRunNotFoundError": {
        "type": "object",
        "required": [
          "status",
          "error"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "error"
            ]
          },
          "error": {
            "type": "string",
            "example": "Run not found"
          }
        }
      },
      "ResearchUrlsResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success",
              "error"
            ],
            "example": "success"
          },
          "urls": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResearchUrlEntry"
            }
          }
        }
      },
      "ResearchUrlEntry": {
        "type": "object",
        "properties": {
          "domain": {
            "type": "string",
            "description": "Platform name (e.g., spotify, instagram)."
          },
          "url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "ResearchTracksResponse": {
        "type": "object",
        "description": "Artist catalog (tracks view).",
        "properties": {
          "status": {
            "type": "string",
            "example": "success"
          },
          "tracks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResearchCatalogItem"
            }
          }
        }
      },
      "ResearchCatalogItem": {
        "type": "object",
        "description": "Catalog entry. Unfiltered — high-catalog artists include remixes/features.",
        "properties": {
          "title": {
            "type": "string"
          },
          "avatar": {
            "type": "string"
          },
          "release_date": {
            "type": "string",
            "nullable": true
          },
          "site_url": {
            "type": "string"
          },
          "isrcs": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "artists": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResearchArtistMini"
            }
          }
        },
        "additionalProperties": true
      },
      "ResearchArtistMini": {
        "type": "object",
        "description": "Minimal artist reference embedded in catalog/activity payloads.",
        "properties": {
          "name": {
            "type": "string"
          }
        },
        "additionalProperties": true
      },
      "ResearchTrackResponse": {
        "type": "object",
        "description": "Track metadata and audio analysis.",
        "properties": {
          "status": {
            "type": "string",
            "example": "success"
          },
          "result": {
            "type": "string",
            "example": "success"
          },
          "message": {
            "type": "string",
            "example": "Data Retrieved."
          },
          "track_info": {
            "$ref": "#/components/schemas/ResearchTrackInfo"
          },
          "audio_analysis": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResearchAudioAnalysisEntry"
            }
          }
        },
        "additionalProperties": true
      },
      "ResearchAudioAnalysisEntry": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "Audio feature name, e.g. `acousticness`, `tempo`."
          },
          "value": {
            "type": "string",
            "description": "Feature value (string-encoded)."
          }
        }
      },
      "ResearchTrackInfo": {
        "type": "object",
        "description": "Full track record.",
        "properties": {
          "title": {
            "type": "string"
          },
          "avatar": {
            "type": "string"
          },
          "site_url": {
            "type": "string"
          },
          "release_date": {
            "type": "string",
            "nullable": true
          },
          "artists": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResearchArtistMini"
            }
          },
          "is_remix": {
            "type": "boolean"
          },
          "collaborators": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResearchCollaborator"
            }
          },
          "labels": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "distributors": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                }
              },
              "additionalProperties": true
            }
          },
          "genres": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "links": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResearchLink"
            }
          }
        },
        "additionalProperties": true
      },
      "ResearchLink": {
        "type": "object",
        "description": "External platform link.",
        "properties": {
          "source": {
            "type": "string",
            "description": "Platform name, e.g. `spotify`, `apple_music`."
          },
          "external_id": {
            "type": "string",
            "description": "Platform-specific ID."
          },
          "url": {
            "type": "string"
          },
          "isrc": {
            "type": "string",
            "description": "ISRC (tracks only)."
          }
        },
        "additionalProperties": true
      },
      "ResearchCollaborator": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "roles": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "additionalProperties": true
      },
      "ResearchSimilarResponse": {
        "type": "object",
        "description": "Similar artists. Similarity is unweighted (no score/ranking fields).",
        "properties": {
          "status": {
            "type": "string",
            "example": "success"
          },
          "artists": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResearchArtistRef"
            }
          },
          "total": {
            "type": "integer",
            "description": "Only present if the adapter receives a provider total; not guaranteed."
          }
        }
      },
      "ResearchArtistRef": {
        "type": "object",
        "description": "Provider-neutral artist reference.",
        "properties": {
          "id": {
            "type": "string",
            "description": "Provider-neutral artist ID.",
            "example": "wjcgfd9i"
          },
          "name": {
            "type": "string",
            "example": "Drake"
          },
          "avatar": {
            "type": "string",
            "description": "Artist image URL.",
            "nullable": true
          },
          "site_url": {
            "type": "string",
            "description": "Artist page URL.",
            "nullable": true
          }
        },
        "additionalProperties": true
      },
      "ResearchProfileResponse": {
        "type": "object",
        "description": "Full artist profile. For longitudinal platform metrics call `GET /api/research/metrics`.",
        "properties": {
          "status": {
            "type": "string",
            "example": "success"
          },
          "result": {
            "type": "string",
            "example": "success"
          },
          "message": {
            "type": "string",
            "example": "Data Retrieved."
          },
          "artist_info": {
            "$ref": "#/components/schemas/ResearchArtistInfo"
          }
        },
        "additionalProperties": true
      },
      "ResearchArtistInfo": {
        "type": "object",
        "description": "Full artist record — identity, bio, genres, platform links, and related artists.",
        "properties": {
          "name": {
            "type": "string"
          },
          "avatar": {
            "type": "string"
          },
          "site_url": {
            "type": "string"
          },
          "country": {
            "type": "string",
            "description": "ISO country code.",
            "nullable": true
          },
          "bio": {
            "type": "string",
            "nullable": true
          },
          "genres": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "links": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResearchLink"
            }
          },
          "related_artists": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResearchArtistRef"
            }
          }
        },
        "additionalProperties": true
      },
      "ResearchPlaylistsResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success",
              "error"
            ],
            "example": "success"
          },
          "placements": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResearchPlaylistPlacement"
            }
          }
        }
      },
      "ResearchPlaylistPlacement": {
        "type": "object",
        "description": "A playlist the artist currently appears on.",
        "properties": {
          "playlist_id": {
            "type": "string"
          },
          "playlist_name": {
            "type": "string"
          },
          "external_url": {
            "type": "string",
            "description": "Link to the playlist on the platform."
          },
          "followers_total": {
            "type": "string",
            "description": "Human-readable follower count, e.g. \"34.3M\"."
          },
          "image_url": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": true
      },
      "ResearchMilestonesResponse": {
        "type": "object",
        "description": "Milestone activity entries (same shape as career/insights).",
        "properties": {
          "status": {
            "type": "string",
            "example": "success"
          },
          "milestones": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResearchActivity"
            }
          }
        }
      },
      "ResearchActivity": {
        "type": "object",
        "description": "Dated activity event. Shared by career, insights, and milestones.",
        "properties": {
          "source": {
            "type": "string",
            "description": "Platform, e.g. `spotify`."
          },
          "activity_text": {
            "type": "string",
            "description": "Human-readable activity description."
          },
          "activity_type": {
            "type": "string",
            "description": "e.g. `playlist`, `chart`."
          },
          "activity_date": {
            "type": "string",
            "example": "2026-06-04"
          },
          "activity_url": {
            "type": "string",
            "nullable": true
          },
          "activity_avatar": {
            "type": "string",
            "nullable": true
          },
          "activity_tier": {
            "type": "integer",
            "description": "Importance tier (lower = more significant)."
          },
          "track_info": {
            "$ref": "#/components/schemas/ResearchActivityTrackInfo"
          }
        },
        "additionalProperties": true
      },
      "ResearchActivityTrackInfo": {
        "type": "object",
        "description": "Track context attached to an activity entry.",
        "properties": {
          "title": {
            "type": "string"
          },
          "avatar": {
            "type": "string"
          },
          "release_date": {
            "type": "string",
            "nullable": true
          },
          "site_url": {
            "type": "string"
          },
          "artists": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResearchArtistMini"
            }
          }
        },
        "additionalProperties": true
      },
      "ResearchMetricsResponse": {
        "type": "object",
        "description": "Current statistics for the artist on the requested `source`.",
        "properties": {
          "status": {
            "type": "string",
            "example": "success"
          },
          "result": {
            "type": "string",
            "example": "success"
          },
          "message": {
            "type": "string",
            "example": "Data Retrieved."
          },
          "stats": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResearchMetricStat"
            }
          },
          "artist_info": {
            "$ref": "#/components/schemas/ResearchArtistRef"
          },
          "source_ids": {
            "type": "array",
            "description": "Sources included in `stats`.",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "ResearchMetricStat": {
        "type": "object",
        "description": "Per-source current statistics.",
        "properties": {
          "source": {
            "type": "string",
            "description": "Platform the metrics belong to, e.g. `spotify`, `tiktok`."
          },
          "data": {
            "type": "object",
            "description": "Platform-specific metric fields. Keys vary by `source`.",
            "additionalProperties": true,
            "example": {
              "monthly_listeners_current": 99222567,
              "followers_total": 112035525,
              "popularity_current": 100,
              "streams_total": 137582496099,
              "playlists_current": 169220,
              "charts_current": 184
            }
          }
        },
        "additionalProperties": true
      },
      "ResearchLookupResponse": {
        "type": "object",
        "description": "Artist resolved from a Spotify URL or ID.",
        "properties": {
          "status": {
            "type": "string",
            "example": "success"
          },
          "result": {
            "type": "string",
            "example": "success"
          },
          "message": {
            "type": "string",
            "example": "Data Retrieved."
          },
          "artist_info": {
            "$ref": "#/components/schemas/ResearchArtistInfo"
          },
          "id": {
            "type": "string",
            "description": "Opaque Recoup research artist ID when resolved. Reuse it in artist research requests; it is separate from the Recoup artist account UUID and Spotify ID."
          }
        },
        "additionalProperties": true
      },
      "ResearchInsightsResponse": {
        "type": "object",
        "description": "Noteworthy activity highlights.",
        "properties": {
          "status": {
            "type": "string",
            "example": "success"
          },
          "insights": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResearchActivity"
            }
          }
        }
      },
      "ResearchCareerResponse": {
        "type": "object",
        "description": "Career activity feed.",
        "properties": {
          "status": {
            "type": "string",
            "example": "success"
          },
          "career": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResearchActivity"
            }
          }
        }
      },
      "ResearchAudienceResponse": {
        "type": "object",
        "description": "Provider audience payload. Available geographic and demographic fields vary by source and entitlement.",
        "properties": {
          "status": {
            "type": "string",
            "example": "success"
          },
          "result": {
            "type": "string",
            "example": "success"
          },
          "message": {
            "type": "string",
            "example": "Data Retrieved."
          },
          "audience": {
            "type": "array",
            "description": "Provider audience entries; fields vary by requested metric source. Empty data does not establish an absent audience.",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "artist_info": {
            "$ref": "#/components/schemas/ResearchArtistRef"
          },
          "source_ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "ResearchAlbumsResponse": {
        "type": "object",
        "description": "Artist catalog (albums view).",
        "properties": {
          "status": {
            "type": "string",
            "example": "success"
          },
          "albums": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResearchCatalogItem"
            }
          }
        }
      },
      "ResearchSearchResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success",
              "error"
            ],
            "example": "success"
          },
          "results": {
            "type": "array",
            "description": "Matching artists, tracks or labels with provider identifiers and metadata.",
            "items": {
              "$ref": "#/components/schemas/ResearchSearchResult"
            }
          }
        }
      },
      "ResearchSearchResult": {
        "type": "object",
        "description": "Search result. Additional type-specific identifier fields pass through. The set depends on `type` (artists | tracks | labels).",
        "properties": {
          "id": {
            "type": "string",
            "description": "Provider-neutral ID."
          },
          "name": {
            "type": "string"
          },
          "avatar": {
            "type": "string",
            "nullable": true
          },
          "site_url": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": true
      },
      "DeleteTrailingChatAuthenticationErrorResponse": {
        "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": {}
          }
        ]
      }
    },
    "responses": {
      "ResearchDataSourceUnsupported": {
        "description": "The configured research data source does not support this endpoint or data shape.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ResearchErrorResponse"
            },
            "example": {
              "status": "error",
              "error": "Request failed with status 501"
            }
          }
        }
      }
    }
  }
}