# Recoup: marketing context summaries > Recoup provides AI transformation for music funds and rightsholders: AI strategy, custom systems, and team enablement. The platform, Skills, and API are additional ways to use Recoup's technology. Scope: this file opens with the marketing page summaries used by the public content index, then carries the complete platform documentation, page by page, as served at each page's Source URL. The summaries are not the full text of the website, blog, or playbook. For authoritative detail on those, read the canonical page or use the content reading endpoint with an ID from search. ## AI training for music teams Content ID: page:/training Source: https://recoupable.dev/training Representation: Summary of the public page. Follow the source for the complete page. Learn on the work you already do: artist research, release planning, catalog operations, and reporting. We help your team use AI, check its output, and turn useful results into repeatable workflows. Start with a real task. Bring a release brief, a catalog question, or a recurring report. Learn where AI helps and where your judgment matters. Learn to check the work. Give the agent useful context, trace answers to their sources, and spot what needs a human review. Leave with a method to reuse. Capture the instructions, reference material, and review steps so your team can repeat the workflow. We shape the training around your team’s experience and the software you use. Scope, format, and price are agreed before the engagement. Do we need technical experience? No. We adapt the training to the people doing the work, from first-time AI users to teams already working with agents and automations. Do we need to use Recoup Platform? No. Training can use Recoup Platform or the AI tools your team already uses. We agree on the tools and access needed before the session. Can you train us on a system you built? Yes. Training can form part of a custom build and handoff, or focus on improving how your team uses an existing system. What does training cost? It depends on the team, workflows, and depth of support. We agree on the scope, format, and price with you before starting. ## Next steps - [Discuss team training](https://recoupable.dev/start-project?workflow=Team%20training) - [Advisory](https://recoupable.dev/advisory) - [Build](https://recoupable.dev/build) ## Recoup: AI transformation for music funds and rightsholders Content ID: page:/ Source: https://recoupable.dev/ Representation: Summary of the public page. Follow the source for the complete page. We help the people who create, own, and invest in music put AI to work. Advisory: Your team implements. We provide strategy, planning, and coaching. Build + Partner: We build and integrate the systems, train your team, and support adoption. Custom code in a repository your company controls. How to run, maintain, and connect the system. Hands-on training for the people running it. Ownership, licenses, hosting, and platform or third-party costs are agreed in scope. Continued support is separate. We review one workflow and identify a practical first step. Bring the task and tools involved. Implementation is scoped and priced separately. Use our software and skills without a consulting engagement. Research artists, draft content, and run recurring work with your artist context in one place. Open-source playbooks for research, releases, content, and catalogs, installed in a compatible AI agent. Add artist context, research, and content tools to your application through the API, MCP, or CLI. Work includes royalty reporting, investment review, catalog intelligence, and workflow automation. Read the case studies for the full examples. ## Next steps - [Services](https://recoupable.dev/services) - [Plans and pricing](https://recoupable.dev/pricing) - [Our software](https://recoupable.dev/platform) - [Get a Free Audit](https://recoupable.dev/start-project) ## AI strategy, custom systems, and team training Content ID: page:/services Source: https://recoupable.dev/services Representation: Summary of the public page. Follow the source for the complete page. AI strategy: review workflows, data, and tools; agree on priorities, a practical roadmap, project scope, and success measures. Custom systems: build agents, applications, and connections to existing tools, with documented code and a working handoff. Examples include royalty reporting and investment review. Team training: practice on real workflows, capture reusable methods and review practices, and prepare internal owners to keep improving the system. Scope, deliverables, and price are agreed before work begins. Custom code is delivered in a repository the client controls; the agreement defines ownership and third-party component terms. Ongoing support and maintenance can be scoped separately. ## Next steps - [AI advisory](https://recoupable.dev/advisory) - [Custom builds](https://recoupable.dev/build) - [Team training](https://recoupable.dev/training) - [Compare plans](https://recoupable.dev/pricing#plans) - [Discuss a project](https://recoupable.dev/contact) ## AI advisory for music companies Content ID: page:/advisory Source: https://recoupable.dev/advisory Representation: Summary of the public page. Follow the source for the complete page. Recoup reviews recurring work in reporting, research, release planning, and catalog operations. The assessment considers the task, its current cost, the available information, access, and the places where judgment matters. The engagement identifies priorities, dependencies, a focused first project, and success measures. It can cover one question, a department, or a company-wide plan. Advisory can use Recoup, other tools, or custom software. It does not require moving the team onto the Recoup platform. The people doing the work participate in shaping the plan. ## Next steps - [Advisory plan](https://recoupable.dev/pricing#advisory) - [Discuss AI strategy](https://recoupable.dev/start-project?plan=advisory&billing=monthly) - [Custom systems](https://recoupable.dev/build) ## Custom AI systems for music businesses Content ID: page:/build Source: https://recoupable.dev/build Representation: Summary of the public page. Follow the source for the complete page. Recoup builds agents for research, reports, campaign briefs, and recurring tasks; integrations connecting catalog data, royalty statements, CRMs, and internal tools; dashboards with source records and review queues; and full applications with interfaces and backends. Delivery starts with an agreed task, inputs, people, and acceptance standard. Useful pieces are reviewed with the team, tested on real work, documented, and handed over with training. Custom code is delivered in a repository the client controls. Agreements define ownership, component terms, price, maintenance, and responsibilities. Builds may use Recoup or run as standalone software. ## Next steps - [Build + Partner plan](https://recoupable.dev/pricing#partner) - [Discuss a custom system](https://recoupable.dev/start-project?plan=partner&billing=monthly) - [Royalty reporting](https://recoupable.dev/operations) - [Acquisition review](https://recoupable.dev/acquisitions) ## Recoup Platform: a ready-to-use AI workspace for music teams Content ID: page:/platform Source: https://recoupable.dev/platform Representation: Summary of the public page. Follow the source for the complete page. A chat-based agent workspace built for music business operations. Recoup hosts the app and connects the tools, so your team can start working without building its own setup. Work across your artists, catalogs, and releases through chat. For music teams that want AI without a custom build. No development team required. Start with a hosted workspace. Skip the cost of building and maintaining your own AI system. AI built for music operations. Go beyond general chat with tools and context for your artists, catalogs, releases, and campaigns. Your agents. Your branded portal. Give artists and clients a white-labeled portal to work with your agents under your brand. Research: Get artist and audience insights to shape your next campaign. Create content: Turn tracks and release plans into campaign ideas, visuals, and copy. Automate repeat work: Schedule research, content prompts, and reports for your roster or catalog. Hosted by Recoup. Open source. Use the cloud app while Recoup runs the infrastructure. The workspace is built on the Recoup API and MCP, and its code is open source. Already have an AI workspace? Connect Recoup’s tools. Keep using the agent you prefer. Add music workflows with Recoup Skills, or connect it to Recoup’s tools through the API and MCP. Need a custom system? We can build it with you. Work with our team on bespoke agents, integrations, and workflows built around your company. An artist, catalog, or release you’re working on, plus any useful files and notes. Give Recoup a clear task, such as researching an audience or drafting a release campaign. The self-serve Recoup Platform plan includes workspace access and the music skill pack at $99/month. Annual billing saves at least 20%. API and MCP usage is billed separately by usage. Company-specific workflows and integrations can be scoped with the consulting team. ## Next steps - [Open Recoup Platform](https://app.recoupable.dev/) - [Recoup Platform plan](https://recoupable.dev/pricing#platform) - [Explore the app source](https://github.com/recoupable/app) - [Explore Skills](https://recoupable.dev/skills) - [Connect the API or MCP](https://recoupable.dev/developers) - [Custom systems](https://recoupable.dev/build) ## Recoup pricing: Platform, advisory, and custom builds Content ID: page:/pricing Source: https://recoupable.dev/pricing Representation: Summary of the public page. Follow the source for the complete page. Platform: $99/month, or $79/month billed annually at $948. The tools and music skills. You take it from here. Includes: Recoup Platform access; Music skill pack; Artist context and research; Content and recurring workflows. Advisory: $999/month, or $799/month billed annually at $9,588. An expert in your corner. A clear plan for your team. Includes: AI strategy and workflow review; A prioritized action plan; Advice on tools and implementation; Team coaching and adoption. Build + Partner: $9,999/month, or $7,999/month billed annually at $95,988. Your AI build partner. From the first idea to daily use. Includes: Custom agents and applications; Connections to your tools and data; Testing, documentation, and handoff; Team training and ongoing improvements. Annual billing applies a 20% discount, then rounds the monthly equivalent down to a whole dollar. The annual charge is twelve times that rounded amount. The page shows both amounts. Enterprise has a custom scope and price; contact Recoup to discuss the engagement. For advisory and builds, scope, deliverables, and delivery schedule are agreed together. API and MCP are billed separately by usage. Consult the credits documentation for billable operations and usage accounting. The annual subscription discount does not discount usage charges. Recoup Skills remains open source. The Recoup Platform plan bundles a hosted workspace and the music skill pack; an AI client or third-party services may have their own costs. ## Next steps - [Compare plans](https://recoupable.dev/pricing#plans) - [Open Recoup Platform](https://app.recoupable.dev/) - [Discuss advisory](https://recoupable.dev/start-project?plan=advisory&billing=monthly) - [Discuss a build](https://recoupable.dev/start-project?plan=partner&billing=monthly) - [Discuss Enterprise](https://recoupable.dev/start-project?plan=enterprise) - [API and MCP usage](https://recoupable.dev/docs/credits) ## Recoup Skills: music playbooks for your AI Content ID: page:/skills Source: https://recoupable.dev/skills Representation: Summary of the public page. Follow the source for the complete page. Recoup Skills brings music-business instructions, tools, and templates into compatible AI agents. The collection covers artist research, release planning, content, catalog work, and opportunity research. A skill is a reusable method for a job. It complements the tools an agent can access; some skills call the Recoup API or other services. The hosted Recoup app provides a workspace. Skills bring methods into an agent you already use. Installation and available integrations depend on the chosen AI client. ## Next steps - [Recoup Skills repository](https://github.com/recoupable/skills) - [Install in Claude](https://recoupable.dev/docs/claude) - [API authentication](https://recoupable.dev/docs/authentication) ## Recoup REST API, MCP, CLI, and agent tools Content ID: page:/developers Source: https://recoupable.dev/developers Representation: Summary of the public page. Follow the source for the complete page. Recoup exposes a REST API, an authenticated platform MCP server, a CLI, and open-source Skills. The public documentation describes request parameters, responses, and credits. API and MCP are billed by usage, separately from subscriptions. The credits guide explains which operations incur charges and how usage is measured. The documented platform MCP endpoint is https://api.recoupable.dev/mcp. It requires an API key as a Bearer token and provides sandbox tools; it is separate from public website reading and search. The CLI is installed as @recoupable/cli and uses RECOUP_API_KEY. Available commands and current beta limitations are documented. Keep API keys on the server and consult the individual endpoint's authentication requirements. ## Next steps - [API reference](https://recoupable.dev/docs/api-reference) - [Platform MCP setup](https://recoupable.dev/docs/mcp) - [API and MCP pricing](https://recoupable.dev/pricing#usage) - [Usage credits](https://recoupable.dev/docs/credits) - [CLI guide](https://recoupable.dev/docs/cli) - [API key setup](https://recoupable.dev/docs/authentication) ## About Recoup Content ID: page:/about Source: https://recoupable.dev/about Representation: Summary of the public page. Follow the source for the complete page. Recoup was founded by Sidney Swift. It builds tools for artists and teams and custom AI systems for the work inside music companies. The approach is to understand the task before choosing technology, build with the people doing the work, document the system, and help the team take responsibility for it. ## Next steps - [Services](https://recoupable.dev/services) - [Platform](https://recoupable.dev/platform) - [Contact](https://recoupable.dev/contact) ## Talk to Recoup about your company Content ID: page:/contact Source: https://recoupable.dev/contact Representation: Summary of the public page. Follow the source for the complete page. Bring your company's priorities, an existing process, or a part of the business you want to improve. Recoup can discuss where AI may help and a useful starting point. The contact form asks for a name, work email, company, area of interest, and project brief. Use the form or email the address below. Reading this website does not submit an inquiry or book a meeting. ## Next steps - [Project inquiry form](https://recoupable.dev/contact) - [Share a fuller project brief](https://recoupable.dev/start-project) - [Email Recoup](mailto:hi@recoupable.dev) ## Get a free AI audit or discuss a Recoup project Content ID: page:/start-project Source: https://recoupable.dev/start-project Representation: Summary of the public page. Follow the source for the complete page. Without a selected plan, this page requests a free AI audit of one workflow. Recoup follows up to arrange the review. Any implementation is scoped and priced separately. An allowlisted plan selection keeps the paid project inquiry and its billing context. The project inquiry asks for a name, work email, company, company type, area of interest, the work to improve, an initial project budget in USD, and a preferred starting timeframe. Role, phone, company website, and current tools or providers are optional. Budget ranges are planning context, not a rate card. Not decided yet and Just exploring are valid starting points. Recoup agrees scope and price before work begins. The visitor reviews and submits the brief. When direct submission is unavailable, the page prepares an unsent email brief with selectable and copyable text. Reading the page or preparing a brief through an agent does not save a lead or contact Recoup. ## Next steps - [Request a free AI audit](https://recoupable.dev/start-project) - [Shorter contact form](https://recoupable.dev/contact) - [Custom builds](https://recoupable.dev/build) ## Recoup Podcast: how music businesses use AI Content ID: page:/podcast Source: https://recoupable.dev/podcast Representation: Summary of the public page. Follow the source for the complete page. The people running music businesses, on their history in the industry and how they actually use AI today. Thirty to sixty minutes with founders, fund managers, publishers and catalog owners. No pitches. Practical answers you can take back to your team. The $100K playbook of an independent artist w/ Xcelencia, Independent artist and technologist (2024-12-08, 40 min). YouTube: https://www.youtube.com/watch?v=ZXLjcYggrwc. Spotify: https://open.spotify.com/episode/4gIQjPCwCOpNdzoi8HQDjL. Building a more equitable music industry with decentralized networks w/ Steph Guerrero and panel, Steph Guerrero, Watcher.eth, Dragaan, PaperHandPapi (2023-02-13, 54 min). YouTube: https://www.youtube.com/watch?v=-uKg5ekk8pw. Spotify: https://open.spotify.com/episode/5eskBoMuAQYIB5cheRftXc. Growing a music platform: what worked at Mint Songs w/ Nick Merich, Head of Growth, Mint Songs (2022-07-04, 63 min). YouTube: https://www.youtube.com/watch?v=0TFRtda1xHg. Spotify: https://open.spotify.com/episode/2BGKNlumOWCEdpcZMMnh2O. From Grammy-winning producer to building software for the music business w/ Sidney Swift, Cofounder, Recoup (2022-07-18, 60 min). YouTube: https://www.youtube.com/watch?v=z_3qo3YVPWQ. Spotify: https://open.spotify.com/episode/4KCglIfrD8i5JdN2c1gZlF. Connecting the diaspora through music and new income models w/ J Mack Ent., Artist, creator of Wura (2023-02-06, 55 min). YouTube: https://www.youtube.com/watch?v=--3Rv1YVV4o. Spotify: https://open.spotify.com/episode/7yUelf45dXOrFPI7zTNxRB. How the traditional music industry moves into new technology w/ Kantor, Artist (2022-05-16, 59 min). YouTube: https://www.youtube.com/watch?v=y0qslS8vMMs. Spotify: https://open.spotify.com/episode/23ficFzRzJMYbnWV7hB3o3. Run a label, a fund, a publisher or a management company? Come tell your story. ## Next steps - [Watch on YouTube](https://www.youtube.com/playlist?list=PLGjXo1TfTj0w) - [Listen on Spotify](https://open.spotify.com/show/2qOKySbQkldohO7vbSGIYz) - [Listen on Apple Podcasts](https://podcasts.apple.com/us/podcast/recoup-podcast/id1316209308) - [Request an invite](https://recoupable.dev/start-project?workflow=Podcast%20guest) ## AI readiness check for music companies Content ID: page:/audit Source: https://recoupable.dev/audit Representation: Summary of the public page. Follow the source for the complete page. The free readiness check asks about one recurring workflow, frequency, information, access, process documentation, ownership, and current AI use. No email is required. Recommendations focus on arranging information and access, making the job specific, identifying a workflow owner, or scoping a first build. It is a practical starting point, not a certification or predictive score. ## Next steps - [Take the readiness check](https://recoupable.dev/audit) - [AI advisory](https://recoupable.dev/advisory) ## AI workflow ROI planner Content ID: page:/roi Source: https://recoupable.dev/roi Representation: Summary of the public page. Follow the source for the complete page. Inputs include monthly task hours, hourly cost, expected time reduction, ongoing monthly system cost, and one-time setup cost. The planner calculates hours saved, capacity value, monthly net value, first-year value, and payback when applicable. Freed time is capacity, not automatically cash saved. Include preparation, review, correction, and maintenance; test conservative assumptions on real tasks before projecting results. ## Next steps - [Use the workflow planner](https://recoupable.dev/roi) - [Discuss the assumptions](https://recoupable.dev/contact) ## Read Recoup with your AI agent Content ID: page:/agents Source: https://recoupable.dev/agents Representation: Summary of the public page. Follow the source for the complete page. The public agent interface searches and reads the website's published content. Documentation, articles, and playbook chapters are returned as readable Markdown; marketing-page overviews are explicitly labeled as summaries with links to the complete pages. These tools do not access private account or client information, run platform tasks, submit inquiries, or book meetings. Platform actions use the separate authenticated Recoup API and MCP server. ## Next steps - [Agent access and setup](https://recoupable.dev/agents) - [Public documentation](https://recoupable.dev/docs) - [Authenticated platform tools](https://recoupable.dev/developers) ## AI for music catalog acquisitions Content ID: page:/acquisitions Source: https://recoupable.dev/acquisitions Representation: Summary of the public page. Follow the source for the complete page. Recoup can build a workflow that gathers deal documents, financial models, and analyst notes into one review; traces findings to their sources; keeps missing information visible; and prepares questions for the seller. The result is a draft review for the investment team. The service supports preparation and review rather than promising an investment decision or guaranteed outcome. ## Next steps - [Acquisition workflow example](https://recoupable.dev/acquisitions#workflow) - [Discuss acquisition review](https://recoupable.dev/acquisitions/contact) ## AI royalty reporting and catalog operations Content ID: page:/operations Source: https://recoupable.dev/operations Representation: Summary of the public page. Follow the source for the complete page. Recoup can connect royalty statements, receipts, and catalog records; identify differences by payment source; and prepare a report with the underlying records and a focused review list. The example demonstrates why an overall matching total does not prove each source reconciles: offsetting differences can cancel out. Reviewers can inspect records behind individual differences. A project starts with the team's actual files, tools, and recurring reporting process. The scope defines the useful first build and review responsibilities. ## Next steps - [Royalty reporting example](https://recoupable.dev/operations#workflow) - [Discuss catalog operations](https://recoupable.dev/operations/contact) ## Recoup Privacy Policy Content ID: page:/privacy Source: https://recoupable.dev/privacy Representation: Summary of the public page. Follow the source for the complete page. The Privacy Policy describes information collection, usage, sharing, third-party services, security, individual rights and choices, children's privacy, international transfers, and policy changes. This is a navigation summary, not the complete policy. Consult the linked policy for its full text and stated update date. ## Next steps - [Read the complete Privacy Policy](https://recoupable.dev/privacy) ## Recoup Terms of Use Content ID: page:/terms Source: https://recoupable.dev/terms Representation: Summary of the public page. Follow the source for the complete page. The Terms of Use cover service access, registration, content responsibility, ownership, conduct, third-party services, purchases, subscriptions, warranties, liability, arbitration, and other provisions. This is a navigation summary, not the complete agreement. Read the full terms and any applicable supplemental terms before using the service. ## Next steps - [Read the complete Terms of Use](https://recoupable.dev/terms) ## Recoup project stories Content ID: page:/case-studies Source: https://recoupable.dev/case-studies Representation: Summary of the public page. Follow the source for the complete page. These stories describe documented work with music teams. Illustrations show the methods, not client financial data. They do not claim measured revenue gains or ROI. ## Next steps - [Royalty reports. With answers behind the numbers.](https://recoupable.dev/case-studies/royalty-reporting) - [A first draft that knows the deal.](https://recoupable.dev/case-studies/investment-review) - [A catalog brief worth opening.](https://recoupable.dev/case-studies/catalog-intelligence) ## Royalty reports. With answers behind the numbers. Content ID: page:/case-studies/royalty-reporting Source: https://recoupable.dev/case-studies/royalty-reporting Representation: Summary of the public page. Follow the source for the complete page. From chasing a difference to inspecting its source. Royalty statements and cash receipts tell different sides of the same story. Before reporting, the finance team needs to know which sources agree, which payments are still in transit, and which differences need attention. Checking only the grand total can miss the detail: an overpayment in one source can offset a shortfall in another. The review has to go deeper without becoming another spreadsheet rebuilt by hand. The system produces one workbook with overall totals, comparisons by payment source, flagged differences, and timing items. The finance team can inspect the records behind each finding and approve the review. When a source export was corrected, the workflow was run again with the updated input. The team could revise the report without rebuilding the process. Unresolved items stayed visible for the next review. An anonymized account of documented implementation and operating records. Business time savings and recovered royalties have not been independently measured. The illustration shows the method, not a client’s financial data. ## Next steps - [Explore the reporting example](https://recoupable.dev/operations#royalty-example) - [Discuss a similar project](https://recoupable.dev/contact) - [All project stories](https://recoupable.dev/case-studies) ## A first draft that knows the deal. Content ID: page:/case-studies/investment-review Source: https://recoupable.dev/case-studies/investment-review Representation: Summary of the public page. Follow the source for the complete page. From assembling the context to reviewing the draft. A model holds the assumptions. Previous reports establish the house format. Analyst notes capture the questions, and email threads contain details that never made it into the data room. Turning that material into a coherent diligence memo means reconstructing the context each time. A generic chat prompt starts too far away from the team’s actual work. The analyst used the workspace to prepare a diligence draft from prior reports, a model, notes, and email context. That draft became the starting point for their edits. The same workspace helped them work through questions about model changes. The information was already in context when the analyst began reviewing. Checking assumptions, resolving open questions, and making the investment decision stayed with the team. An anonymized account of documented operator use of a Recoup-enabled workspace. Deal names, model values, and customer quotations are kept private. Preparation time and investment outcomes have not been independently measured. ## Next steps - [Explore the investment example](https://recoupable.dev/acquisitions#acquisition-example) - [Discuss a similar project](https://recoupable.dev/contact) - [All project stories](https://recoupable.dev/case-studies) ## A catalog brief worth opening. Content ID: page:/case-studies/catalog-intelligence Source: https://recoupable.dev/case-studies/catalog-intelligence Representation: Summary of the public page. Follow the source for the complete page. From another data export to a useful review list. A catalog can generate movement across streaming, video, social, and discovery platforms. Some changes are meaningful. Others reflect duplicate records, stale sources, or a mismatch in reporting windows. The team needs a short list it can investigate, with enough context to distinguish an opportunity from a data problem. Recurring briefs bring period changes, related track movements, and source checks into one review. The workflow flags data anomalies alongside the items that deserve a closer look. The operator starts with a prepared brief and the questions to investigate. Choosing a campaign or acting on an opportunity stays with the team. An anonymized account of implemented workflows and repeated report outputs. Campaign impact and reader engagement have not been measured. The illustration contains no actual artist or catalog data. ## Next steps - [Explore artist and catalog tools](https://recoupable.dev/platform) - [Discuss a similar project](https://recoupable.dev/contact) - [All project stories](https://recoupable.dev/case-studies) ## Music Videos for Less Than $10 | Free Skill Download Content ID: page:/music-videos Download the free skill. Give your AI agent an idea, then shape the song, the scenes and the finished music video. Free download. No signup to get the skill. Under $10 is the self-serve generation budget. Plan fees and extra takes are separate. [Download skill](/downloads/recoup-music-video.zip) [Make it in Recoup](https://app.recoupable.dev) ## The workflow is yours. Get the instructions behind the process: plan the story, keep characters consistent, animate the scenes and check the final cut. A skill is a set of instructions for your AI agent. - Song, story and scene planning - Characters, locations and visual continuity - Image and motion prompts - Editing, quality checks and budget guidance [Read the skill on GitHub](https://github.com/recoupable/skills/tree/main/skills/recoup-music-video) ## Two songs. Two different worlds. Music videos we made with artists, built around their released recordings. ### Movamos el mundo · Tomás Mika Tomás had shot the original video alone in a field. He asked for a story with a couple, different places and a life together. The video ends in his field. [Watch the video on YouTube](https://www.youtube.com/watch?v=ouLTF_CsDGY) ### Letal Xlug · brauxelion ft. Shisosaloud The rain, neon and game world of his EP, carried into a video for his verse. Built around his recording and approved by the artist. [Watch the video on YouTube](https://www.youtube.com/watch?v=f_GElz8Xc_A) ## From an idea to a finished music video. ### Get the skill Download it for your AI agent, or start in Recoup with the workflow connected. ### Shape the song and story Start with a new song. Choose the look, characters and scenes before spending on animation. ### Make the video Set the budget, review each scene, then render your video. Rework only what needs another take. ### Already have a released track? The downloadable skill currently starts with a generated song. For a video using your own recording, [work with our team](/music-videos#request). ## Your recording. Our help making the video. Send your released song and the world you picture. We agree on scope and a custom quote before production begins. Custom production is quoted separately from the self-serve budget. - A visual direction shaped with you - A scene plan and images to review - An edited video set to your recording - A final cut for you to approve and publish [Get a quote for my song](/music-videos#request) ## A few things to know. ### Is the skill really free? Yes. Download the skill and its reference guide without an email address or Recoup account. Generating songs, images and video uses paid credits. ### What does “less than $10” cover? The self-serve generation budget for a music video. Length, resolution and extra takes affect the total. Recoup plan fees and custom production are separate. ### Can I use my own song? The downloadable skill currently generates a new song. If you want a video for a released recording, send it to our team for a custom quote. ### What do I need to use the skill? An AI agent that can follow the instructions and access Recoup’s generation tools. The file is free; running it requires a Recoup account and generation credits. You can also start directly in Recoup. ### Does the free download include video generation? The skill is free to download. Running it requires a Recoup account and generation credits. The published workflow describes a 15-second starter piece and full-length music videos; available duration depends on your account access. Check your plan before generating. ## Make the video you picture. Start with the free skill. See where the song takes you. [Download skill](/downloads/recoup-music-video.zip) ## Read the complete topic [Search published content](https://recoupable.dev/agent-api/v1/search?query=royalty&type=all&limit=5), then pass a returned ID to https://recoupable.dev/agent-api/v1/read. A response states its representation and provides a continuation offset when applicable. [Tool and content catalog](https://recoupable.dev/agents/catalog.json) | [Documentation](https://recoupable.dev/docs) | [Website OpenAPI](https://recoupable.dev/openapi.json) ## Access and boundaries - This website's /agent-api/v1 endpoints are public. They search and read published content, calculate from supplied assumptions, and return inquiry drafts. They do not access private catalogs or operate a Recoup account. - The platform REST API is separate: https://api.recoupable.dev/api. Programmatic access normally uses x-api-key. Some platform endpoints support a Privy access token as Authorization: Bearer instead; do not send both headers. Follow the endpoint's documentation and [authentication guide](https://recoupable.dev/docs/authentication). - The platform MCP server is https://api.recoupable.dev/mcp. Its connection guide uses Authorization: Bearer with a Recoup API key. See [MCP documentation](https://recoupable.dev/docs/mcp). This website does not host a remote MCP server. - Account creation, messages, uploads, connector actions, and other platform writes are separate operations with their own permissions. A documentation example is not authorization to execute them. - Website tools do not submit an inquiry or book a call. A prepared brief is a draft for review; the visitor completes the [contact form](https://recoupable.dev/contact). - Workflow ROI uses supplied assumptions. Capacity value is not guaranteed cash savings, a quote, or a promise of results. - Never send credentials or private client records to public website search or tools. # Recoup API Documentation Source: https://recoupable.dev/docs Use the Recoup API to build your record label. Access research, content creation, chat, artist analytics, social media, and platform management endpoints. ## Quickest start — one curl call Get a working API key in a single unauthenticated request. No dashboard, no browser, no human in the loop. ```bash export RECOUP_API_KEY=$(curl -s -X POST "https://api.recoupable.dev/api/agents/signup" \ -H "Content-Type: application/json" \ -d '{"email": "agent+'$(date +%s)-$RANDOM'@recoupable.com"}' | jq -r .api_key) ``` `$RECOUP_API_KEY` is now ready to pass in the `x-api-key` header on any request. See the [Agents guide](https://recoupable.dev/docs/agents) for the full signup and verification flow. ## What is Recoup? Recoup is an AI agent platform for smarter song rollouts, unforgettable fan experiences, and lasting artist growth. Empowering music executives with actionable insights and next-gen tools. This is where record labels, musicians, and managers start to build on Recoup AI technology like chat, tasks, agents, and more. ## Base URL All API requests should be made to: ```bash https://api.recoupable.dev/api ``` ## Authentication Most API endpoints are authenticated using an API key passed in the `x-api-key` header. The only exceptions are the [agent onboarding](https://recoupable.dev/docs/agents) endpoints — [`POST /api/agents/signup`](https://recoupable.dev/docs/api-reference/agents/signup) and [`POST /api/agents/verify`](https://recoupable.dev/docs/api-reference/agents/verify) — which are intentionally unauthenticated so agents can obtain their first API key. 1. Navigate to the [API Keys Management Page](https://chat.recoupable.dev/keys) 2. Sign in with your account 3. Create a new API key and copy it immediately (it's only shown once) ```bash curl -X GET "https://api.recoupable.dev/api/artists?accountId=YOUR_ACCOUNT_ID" \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" ``` > **Warning** > Keep your API key secure. Do not share it publicly or commit it to version control. ## Get Started [Agent Signup](https://recoupable.dev/docs/agents) Get an API key in one curl call. The fastest path for AI agents — no dashboard required. [Quickstart](https://recoupable.dev/docs/quickstart) Get your API key and make your first request in minutes. [CLI](https://recoupable.dev/docs/cli) Install and use the Recoup CLI to interact with the platform from your terminal. [Claude](https://recoupable.dev/docs/claude) Install the Recoup plugins in Claude and run the getting-started skill. [MCP Server](https://recoupable.dev/docs/mcp) Connect Recoup to AI assistants via the Model Context Protocol. ## API Sections The API is organized into six main sections. Use these links to jump to the right area. [Research](https://recoupable.dev/docs/api-reference/research/web) Web search, deep research, people search, page extraction and enrichment, upcoming events, and the Apify-backed play-count measurement store. [Content Creation](https://recoupable.dev/docs/api-reference/content/create) Generate images, videos, and captions. Transcribe audio, edit content, upscale media, analyze videos, manage templates, and estimate costs. [Chat](https://recoupable.dev/docs/api-reference/chat/chats) Conversations with artist context. Create, stream, and generate messages. Copy messages, delete trailing messages, and manage chat history. [Social Media](https://recoupable.dev/docs/api-reference/connectors/list) Spotify, Instagram, X (Twitter), and generic social scraping. Search artists, scrape profiles and comments, track trends, and manage OAuth connectors. [Releases](https://recoupable.dev/docs/api-reference/songs/songs) Songs, catalogs, and task management. Analyze songs, manage catalog collections, and schedule recurring tasks with cron-based automation. [Accounts](https://recoupable.dev/docs/api-reference/pulses/update) Accounts, organizations, subscriptions, pulses, notifications, sandboxes, and admin tools. ## Agents [Content Agent](https://recoupable.dev/docs/content-agent) Content creation agent accessible via Slack. Generates images, videos, and captions for artists automatically. [Authentication](https://recoupable.dev/docs/authentication) API key authentication, account-scoped access, and organization-level permissions. ## Quick Reference for LLMs If you are an LLM navigating these docs, here is a summary of the endpoint structure: - **`/api/artists/*`** — Artist management (list, create, socials, socials-scrape, profile) - **`/api/research/*`** — Artist research (search, lookup, profile, metrics, audience, cities, similar, urls, instagram-posts, playlists, albums, track, tracks, career, insights, genres, festivals, web, deep, people, extract, enrich, milestones, venues, rank, charts, radio, discover, curator, playlist) - **`/api/content/*`** — Content creation (create, generate-image, generate-video, generate-caption, transcribe-audio, edit, upscale, analyze-video, templates, validate, estimate) - **`/api/chat/*`** — Chat (chats, artist, messages, messages-copy, messages-trailing-delete, create, update, delete, runs, runs-status, stream, compact) - **`/api/songs/*`** — Songs and catalogs (songs, create, analyze, analyze-presets, catalogs, catalogs-create, catalogs-delete, catalog-songs, catalog-songs-add, catalog-songs-delete) - **`/api/tasks/*`** — Task automation (get, create, update, delete, runs) - **`/api/spotify/*`** — Spotify (search, artist, artist-albums, artist-top-tracks, album) - **`/api/instagram/*`** — Instagram (comments, profiles) - **`/api/x/*`** — X/Twitter (search, trends) - **`/api/connectors/*`** — OAuth connectors (list, authorize, disconnect) - **`/api/accounts/*`** — Accounts (get, id, create, update, add-artist) - **`/api/organizations/*`** — Organizations (list, create, add-artist) - **`/api/sandboxes/*`** — Sandboxes (list, create, snapshot, delete, setup, file, upload) - **`/api/content-agent/*`** — Content agent webhooks (webhook, callback) - **`/api/agents/*`** — Agent onboarding (signup, verify) — no auth required Base URL: `https://api.recoupable.dev/api` [OpenAPI Specification](https://github.com/sweetmantech/docs/blob/main/api-reference/openapi.json) ## Need Help? [Contact Support](mailto:agent@recoupable.dev) Reach out to our team at [agent@recoupable.dev](mailto:agent@recoupable.dev) for assistance with the Recoup API. # Quickstart Source: https://recoupable.dev/docs/quickstart Get a Recoup API key in one call and make your first request — no browser, no dashboard. ## Quickest start Sign up your agent and get an API key in a single API call — no dashboard, no browser, no human in the loop. This one-liner signs up a fresh `agent+` address and exports the returned key to `$RECOUP_API_KEY`: ```bash export RECOUP_API_KEY=$(curl -s -X POST "https://api.recoupable.dev/api/agents/signup" \ -H "Content-Type: application/json" \ -d '{"email": "agent+'$(date +%s)-$RANDOM'@recoupable.com"}' | jq -r .api_key) ``` Verify it worked: ```bash curl -H "x-api-key: $RECOUP_API_KEY" https://api.recoupable.dev/api/accounts/id ``` > **Tip** > The `agent+{timestamp}@recoupable.com` shape is the fastest path for agents — it guarantees a fresh `agent+` address and returns an API key instantly without email verification. For the full signup + email-verification flow, see the [Agents guide](https://recoupable.dev/docs/agents). ## Base URL All API requests should be made to: ```bash https://api.recoupable.dev/api ``` ## Your First Request Once you have an API key, include it in the `x-api-key` header on every request. Here's a simple call that retrieves your scheduled tasks: ```bash cURL curl -X GET "https://api.recoupable.dev/api/tasks" \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" ``` ```python Python import requests headers = { "Content-Type": "application/json", "x-api-key": "YOUR_API_KEY" } response = requests.get( "https://api.recoupable.dev/api/tasks", headers=headers ) print(response.json()) ``` ```javascript JavaScript const response = await fetch("https://api.recoupable.dev/api/tasks", { headers: { "Content-Type": "application/json", "x-api-key": "YOUR_API_KEY", }, }); const data = await response.json(); console.log(data); ``` ```typescript TypeScript interface Task { id: string; title: string; prompt: string; schedule: string; account_id: string; artist_account_id: string; enabled: boolean; } interface TasksResponse { status: "success" | "error"; tasks: Task[]; } const response = await fetch("https://api.recoupable.dev/api/tasks", { headers: { "Content-Type": "application/json", "x-api-key": "YOUR_API_KEY", }, }); const data: TasksResponse = await response.json(); console.log(data.tasks); ``` **Example Response:** ```json { "status": "success", "tasks": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "title": "Daily Fan Report", "prompt": "Generate a summary of new fans from the past 24 hours", "schedule": "0 9 * * *", "account_id": "123e4567-e89b-12d3-a456-426614174000", "artist_account_id": "987fcdeb-51a2-3b4c-d5e6-789012345678", "enabled": true } ] } ``` > **Info** > For full documentation on the Tasks API including filtering options, see the [Tasks API Reference](https://recoupable.dev/docs/api-reference/tasks/get). ## Prefer the dashboard? If you're a human building an integration, you can also create API keys from the web console instead of the signup endpoint: 1. Navigate to the [Recoup API Keys Management Page](https://chat.recoupable.dev/keys) 2. Sign in with your account 3. Enter a descriptive name (e.g. "Production Server") 4. Click **Create API Key** > **Warning** > Copy and securely store your API key immediately — it will only be shown once. ## Next Steps With your API key ready, you can now: [Artist Data](https://recoupable.dev/docs/api-reference/artists/socials) Fetch artists' social accounts and follower counts. [Fan Analytics](https://recoupable.dev/docs/api-reference/fans/get) Access fan data across all connected social platforms. [Chat API](https://recoupable.dev/docs/api-reference/chat/chats) Build AI-powered conversations with artist context. [Task Management](https://recoupable.dev/docs/api-reference/tasks/get) Schedule and automate recurring tasks. ## Support If you need help or have questions about the API, please contact our support team at [agent@recoupable.dev](mailto:agent@recoupable.dev). # CLI Source: https://recoupable.dev/docs/cli Install the Recoup CLI and interact with the platform from your terminal. The Recoup CLI (`@recoupable/cli`) wraps the Recoup API for terminal-first workflows. It's available as a global npm package and comes pre-installed in sandbox environments. > **Note** > **The CLI is in beta.** The commands listed below are what's shipped today (v0.1.13). For research workflows and most content operations, call the [REST API](https://recoupable.dev/docs/api-reference) directly — the CLI is gradually catching up. > > **Agents:** if a command isn't listed below, don't retry — fall back to the corresponding REST endpoint linked under each command. ## Install ```bash npm install -g @recoupable/cli ``` ## Authenticate Set your API key as an environment variable: ```bash export RECOUP_API_KEY=your-api-key ``` Verify it works: ```bash recoup whoami ``` > **Tip** > Get an API key from the [API Keys page](https://chat.recoupable.dev/keys) or use the [agent signup](https://recoupable.dev/docs/agents) for instant key generation. ## Configuration | Variable | Required | Default | Description | | --- | --- | --- | --- | | `RECOUP_API_KEY` | Yes | — | Your Recoup API key | | `RECOUP_API_URL` | No | `https://api.recoupable.dev` | API base URL override | All commands support `--json` for machine-readable output and `--help` for usage info. --- ## Commands ### whoami Show the authenticated account. See [`GET /api/accounts/id`](https://recoupable.dev/docs/api-reference/accounts/id). ```bash recoup whoami recoup whoami --json ``` ### orgs List organizations. See [`GET /api/organizations`](https://recoupable.dev/docs/api-reference/organizations/list). ```bash recoup orgs list recoup orgs list --account ``` ### artists List artists. See [`GET /api/artists`](https://recoupable.dev/docs/api-reference/artists/list). ```bash recoup artists list recoup artists list --org recoup artists list --account ``` ### emails Send an email to the authenticated account. See [`POST /api/emails`](https://recoupable.dev/docs/api-reference/emails/create). ```bash recoup emails --subject "Pulse Report" --text "Here's your weekly summary." recoup emails --subject "Update" --html "

Report

Details here.

" ``` | Flag | Required | Description | | --- | --- | --- | | `--subject ` | Yes | Email subject line | | `--text ` | No | Plain text or Markdown body | | `--html ` | No | Raw HTML body (takes precedence over `--text`) | | `--cc ` | No | CC recipient (repeatable) | | `--room-id ` | No | Room ID for a chat link in the email footer | | `--account ` | No | Send to a specific account (org keys only) | ### chats Manage chats. See [`GET /api/chats`](https://recoupable.dev/docs/api-reference/chat/chats) and [`POST /api/chats`](https://recoupable.dev/docs/api-reference/chat/create). ```bash recoup chats list recoup chats create --name "My Topic" recoup chats create --name "My Topic" --artist ``` ### sandboxes Manage sandboxes. See [`GET /api/sandboxes`](https://recoupable.dev/docs/api-reference/sandboxes/list) and [`POST /api/sandboxes`](https://recoupable.dev/docs/api-reference/sandboxes/create). ```bash recoup sandboxes list recoup sandboxes create recoup sandboxes create --command "ls -la" ``` ### tasks Check background task status. See [`GET /api/tasks/runs`](https://recoupable.dev/docs/api-reference/tasks/runs). ```bash recoup tasks status --run ``` ### songs Run AI music analysis. See [`POST /api/songs/analyze`](https://recoupable.dev/docs/api-reference/songs/analyze) and [`GET /api/songs/analyze/presets`](https://recoupable.dev/docs/api-reference/songs/analyze-presets). ```bash recoup songs presets recoup songs analyze --preset catalog_metadata --audio https://example.com/track.mp3 recoup songs analyze --prompt "Describe the production style" --audio https://example.com/track.mp3 ``` One of `--preset` or `--prompt` is required. The other flags are optional. | Flag | Required | Description | | --- | --- | --- | | `--preset ` | Conditional | Curated analysis preset. Required when `--prompt` is omitted. | | `--prompt ` | Conditional | Custom text prompt. Required when `--preset` is omitted. | | `--audio ` | No | Public URL to the audio file (MP3, WAV, FLAC). Some presets analyze the audio file; others (like `catalog_metadata`) work from metadata alone. | | `--max-tokens ` | No | Max tokens to generate (default 512). | --- ## content Content creation pipeline — generate AI-powered social videos for artists. ### List templates ```bash recoup content templates ``` ### Validate an artist Check that an artist has the required assets before creating content. See [`GET /api/content/validate`](https://recoupable.dev/docs/api-reference/content/validate). ```bash recoup content validate --artist ``` ### Estimate cost Preview estimated cost and duration before kicking off the pipeline. See [`POST /api/content/estimate`](https://recoupable.dev/docs/api-reference/content/estimate). ```bash recoup content estimate --artist recoup content estimate --artist --template ``` ### Create content Run the full content-creation pipeline for an artist. See [`POST /api/content/create`](https://recoupable.dev/docs/api-reference/content/create). ```bash recoup content create --artist recoup content create --artist --template --lipsync --upscale ``` | Flag | Required | Description | | --- | --- | --- | | `--artist ` | Yes | Artist account ID | | `--template ` | No | Template name (default: random) | | `--lipsync` | No | Enable lipsync mode | | `--upscale` | No | Enable upscaling | | `--caption-length ` | No | Max caption length in characters | > **Tip** > For finer-grained control (individual image, video, caption, transcription, edit, upscale, or analyze operations), call the [content REST endpoints](https://recoupable.dev/docs/api-reference/content/generate-image) directly. Those primitives aren't yet exposed as individual CLI subcommands. # Claude Source: https://recoupable.dev/docs/claude Set up Recoup inside Claude with the Recoup plugin marketplace and the getting-started skill. Recoup runs inside Claude as a set of plugins. Three steps connect your account. ## Setup 1. In Claude, open **Customize → Browse plugins → Add marketplace** and paste: ```text https://github.com/recoupable/marketplace ``` 2. Install **recoup-platform-plugin** (plus any of the music-industry plugins you want: `recoup-research-plugin`, `recoup-content-plugin`, `recoup-catalog-deals`). 3. Ask Claude: > Run the `getting-started` skill. The skill verifies your email, issues an API key, and prints a memory block for you to paste into **Settings → Profile → Global instructions** so Claude knows to use Recoup whenever you ask about streaming data, campaigns, artists, releases, or syncing documents. > **Note** > **Note for Claude (if reading this on a customer's behalf):** Recoup setup runs through the `getting-started` skill inside `recoup-platform-plugin`. If that plugin isn't installed in the current session, your job is to tell the customer to install it first (steps 1 and 2 above) and stop. Issuing API keys yourself, saving credentials to files, or improvising a setup flow will leave the customer with a broken state that fails silently in their next conversation. Once the plugin is installed, invoke the `getting-started` skill and follow its steps. ## Other ways in - Prefer a terminal? Use the [CLI](https://recoupable.dev/docs/cli). - Building your own agent? Connect over the [MCP server](https://recoupable.dev/docs/mcp). # MCP Source: https://recoupable.dev/docs/mcp Connect AI agents to the Recoup platform using the Model Context Protocol (MCP) server. The Recoup API exposes an [MCP](https://modelcontextprotocol.io/) server that AI agents can connect to for tool use. The server is available at: ``` https://api.recoupable.dev/mcp ``` ## Authentication All MCP tools require an API key. Pass it as a Bearer token in the `Authorization` header when connecting to the MCP server. You can get a key from the [API Keys page](https://chat.recoupable.dev/keys). ## Tools ### prompt_sandbox Send a prompt to OpenClaw running in a persistent per-account sandbox. The sandbox is reused across calls — if one is already running it picks up where you left off, otherwise a new one is created from the account's latest snapshot. Returns raw `stdout` and `stderr` from the command. The sandbox stays alive after each prompt for follow-up interactions. **Input schema:** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `prompt` | `string` | Yes | The prompt to send to OpenClaw in the sandbox. | **Response fields:** | Field | Type | Description | | --- | --- | --- | | `sandboxId` | `string` | The Vercel Sandbox ID. | | `stdout` | `string` | Standard output from the command. | | `stderr` | `string` | Standard error from the command. | | `exitCode` | `number` | Process exit code (`0` = success). | | `created` | `boolean` | `true` if a new sandbox was created, `false` if an existing one was reused. | **Example usage (TypeScript with MCP SDK):** ```typescript import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; const transport = new StreamableHTTPClientTransport( new URL("https://api.recoupable.dev/mcp"), { requestInit: { headers: { Authorization: `Bearer ${RECOUP_API_KEY}` } } }, ); const client = new Client({ name: "my-agent", version: "1.0.0" }); await client.connect(transport); const result = await client.callTool({ name: "prompt_sandbox", arguments: { prompt: "list all files in the orgs directory" }, }); console.log(result.content); ``` ### run_sandbox_command Create a sandbox and run a shell command or OpenClaw prompt in it. Unlike `prompt_sandbox`, this creates a **new sandbox each call** and runs the command asynchronously via a background task. Returns a sandbox ID and run ID to track progress. See [`POST /api/sandboxes`](https://recoupable.dev/docs/api-reference/sandboxes/create) for the equivalent REST endpoint. **Input schema:** | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `command` | `string` | No | Shell command to run. Cannot be used with `prompt`. | | `args` | `string[]` | No | Arguments for the command. | | `cwd` | `string` | No | Working directory for the command. | | `prompt` | `string` | No | OpenClaw prompt. Cannot be used with `command`. | | `account_id` | `string` | No | Target a specific account (org API keys only). | # Authentication Source: https://recoupable.dev/docs/authentication How authentication works in the Recoup API — API keys, access tokens, and organization access control. ## Overview Every request to the Recoup API must be authenticated using exactly one of two mechanisms: | Method | Header | Use case | | --- | --- | --- | | API Key | `x-api-key` | Server-to-server integrations | | Access Token | `Authorization: Bearer ` | Frontend apps authenticated via Privy | Providing both headers in the same request will result in a `401` error. > **Note** > Agent onboarding endpoints (`POST /api/agents/signup` and `POST /api/agents/verify`) are **unauthenticated** — they exist so agents can obtain their first API key. See the [Agents guide](https://recoupable.dev/docs/agents) for details. --- ## API Keys API keys are the primary way to authenticate programmatic access to the Recoup API. All API keys are **personal keys** — they are always tied to the account that created them. ### Creating an API Key 1. Navigate to [chat.recoupable.dev/keys](https://chat.recoupable.dev/keys) 2. Enter a descriptive name (e.g. `"Production Server"`) 3. Click **Create API Key** > **Warning** > Copy your API key immediately — it is only shown once. Keys are stored as a secure HMAC-SHA256 hash and cannot be retrieved after creation. ### Using an API Key Pass your key in the `x-api-key` header: ```bash curl -X GET "https://api.recoupable.dev/api/tasks" \ -H "x-api-key: YOUR_API_KEY" ``` ### Access to Organizations If your account belongs to one or more organizations, your API key can access data across those organizations by passing an `account_id` parameter on supported endpoints. This lets you filter to any account within an organization your key has access to. - **No org membership** — the key can only access its own account's data - **Org member** — the key can pass `account_id` to filter to any account within that organization > **Info** > Org membership is determined by the account's [organizations](https://recoupable.dev/docs/api-reference/organizations/list). An account gains access to an org when it is added as a member. --- ## Access Tokens (Privy) If you're building a frontend application that authenticates users via [Privy](https://privy.io/), you can pass the user's Privy JWT as a Bearer token instead of an API key. ```bash curl -X GET "https://api.recoupable.dev/api/tasks" \ -H "Authorization: Bearer YOUR_PRIVY_JWT" ``` The API validates the token against Privy, extracts the user's email, and resolves it to the corresponding Recoup account. Bearer tokens always authenticate as a personal account — they cannot act on behalf of an organization. --- ## How We Verify Access on API Calls Every authenticated request goes through `validateAuthContext`, which enforces the following access rules: ### API Key or Bearer Token By default, requests access the key owner's own account. When `account_id` is provided: ``` Request includes account_id override? ├── Same as key owner → Allowed (self-access) ├── Key owner is a member of an org that contains account_id → Allowed └── No matching org membership → 403 Forbidden ``` Membership is verified by checking the key owner's [organizations](https://recoupable.dev/docs/api-reference/organizations/list) for a record linking the account to the target account's organization. > **Note** > The Recoup internal admin organization has universal access to all accounts. ### Organization Access via `organization_id` Some endpoints accept an `organization_id` parameter. When provided, the API additionally validates that the authenticated account is either: - A **member** of the organization, or - The **organization account itself** --- ## Error Responses | Status | Cause | | --- | --- | | `401` | Missing or invalid credentials, or both `x-api-key` and `Authorization` headers provided | | `403` | Valid credentials but insufficient access to the requested `account_id` or `organization_id` | --- ## Security Notes - API keys are **never stored in plaintext** — only an HMAC-SHA256 hash (keyed with your project secret) is persisted in the database - **Never include `account_id` in your API key creation request** — the account is always derived from your authenticated credentials - Rotate keys immediately if compromised via the [API Keys Management Page](https://chat.recoupable.dev/keys) # Credits Source: https://recoupable.dev/docs/credits How Recoup credits work — what's billed, how to check your balance, and how to upgrade. Some Recoup endpoints are billed in **credits** — primarily endpoints that hit external data providers, run AI inference, or generate content. The rest of the API is free at the API layer. --- ## What's billed | Family | Billed? | Notes | | --- | --- | --- | | **Research** ([`/api/research/*`](https://recoupable.dev/docs/api-reference/research/web)) | Yes | Each successful call deducts credits. Costs vary by endpoint and parameters (e.g. [`enrich`](https://recoupable.dev/docs/api-reference/research/enrich) charges by processor tier; [`extract`](https://recoupable.dev/docs/api-reference/research/extract) charges by URL count). | | **Content generation** ([`/api/image/generate`](https://recoupable.dev/docs/api-reference/image/generation)) | Yes | Image generation is priced per call. | | **AI Chat — streaming** ([`POST /api/chat`](https://recoupable.dev/docs/api-reference/chat/workflow)) | Yes | Variable cost based on model token usage, with a per-request minimum. | | **Music analysis** ([`POST /api/songs/analyze`](https://recoupable.dev/docs/api-reference/songs/analyze)) | Yes | $0.05 per model call plus $0.001166 per second of inference time; `full_report` is the sum of its 13 calls. | | **Social scrape** ([`/api/socials/{id}/scrape`](https://recoupable.dev/docs/api-reference/social/scrape), [`/api/artist/socials/scrape`](https://recoupable.dev/docs/api-reference/artist/socials-scrape)) | Yes | $0.05, plus $0.01 per post requested via `posts` — per social profile scraped. | | **Everything else** | Free at the API layer | [Artist CRUD](https://recoupable.dev/docs/api-reference/artists/list), [sandboxes](https://recoupable.dev/docs/api-reference/sandboxes/list), [sessions](https://recoupable.dev/docs/api-reference/sessions/get), [scheduled tasks](https://recoupable.dev/docs/api-reference/tasks/get), [account/org management](https://recoupable.dev/docs/api-reference/accounts/id), [agent signup](https://recoupable.dev/docs/api-reference/agents/signup), [Spotify proxies](https://recoupable.dev/docs/api-reference/spotify/search), etc. Subscription gating may still apply. | Failed calls (4xx / 5xx) do **not** deduct credits. Deduction happens only after the upstream call succeeds. --- ## What a credit is worth **Balances and prices are US dollars.** The ledger stores amounts as integer micro-dollars, the same 6-decimal unit as USDC: `1,000,000 = $1.00`, so it can carry sub-cent charges (a $0.002/s provider rate prices exactly). Every credit field the API returns is that integer; divide by 1,000,000 to display it as currency. Example: [`GET /api/accounts/{id}/credits`](https://recoupable.dev/docs/api-reference/accounts/credits-get). --- ## Check your balance ```bash curl -sS https://api.recoupable.dev/api/accounts/$ACCOUNT_ID/credits \ -H "x-api-key: $RECOUP_API_KEY" ``` Response shape: ```json { "account_id": "acc_…", "remaining_credits": 292450000, "total_credits": 300000000, "used_credits": 7550000, "is_pro": true, "plan": "pro", "task_limit": null, "min_cadence_minutes": 60, "timestamp": "2026-04-24T17:50:43.475" } ``` `plan`, `task_limit`, and `min_cadence_minutes` describe the [plan](https://recoupable.dev/docs/credits#subscription) the balance belongs to. `total_credits` is your plan-derived monthly allotment. `remaining_credits` can exceed `total_credits` after a top-up or an admin grant, and `used_credits` clamps to 0 in that case. Full schema at [Get Account Credits](https://recoupable.dev/docs/api-reference/accounts/credits-get). To see what consumed the balance, list the charges line by line with [`GET /api/accounts/{id}/usage`](https://recoupable.dev/docs/api-reference/accounts/usage-get): one item per deduction, newest first, each with the amount in micro-dollars and as a dollar string, plus the total for the period. API charges name the endpoint that billed them in `model_id` (for example `POST /api/artist/socials/scrape`), so a list of charges reads as a list of calls. --- ## Subscription For Platform, Advisory, Build + Partner, and Enterprise options, see [Recoup pricing](https://recoupable.dev/pricing). API and MCP usage is billed separately through credits. Your account's current credit allowance and scheduled-task limits are returned by [Get Account Credits](https://recoupable.dev/docs/api-reference/accounts/credits-get). Use `total_credits`, `task_limit`, and `min_cadence_minutes` from that response rather than inferring limits from a public plan name. The API may return account identifiers such as `free`, `starter`, or `pro`. These are documented API values; they are not the names of consulting engagements. The request and response schemas define the values accepted by each endpoint. ### Task limits [`POST /api/tasks`](https://recoupable.dev/docs/api-reference/tasks/create) and [`PATCH /api/tasks`](https://recoupable.dev/docs/api-reference/tasks/update) [check the plan](https://recoupable.dev/docs/api-reference/accounts/credits-get) before writing. Creating a task past `task_limit`, re-enabling a disabled task past it, or saving a cron whose consecutive runs are closer together than `min_cadence_minutes` returns **HTTP 402** with `error: "plan_limit"`: ```json { "status": "error", "error": "plan_limit", "limit": "task_count", "message": "Free includes 1 task. Starter includes 3, Pro is unlimited.", "plan": "free", "task_limit": 1, "min_cadence_minutes": 10080, "current_task_count": 1, "billingUrl": "https://app.recoupable.dev/plan" } ``` `limit` is `task_count` or `min_cadence`; `current_task_count` excludes the task being created or updated. Edits that only touch `title`, `prompt`, or `model` never hit the gate. ### Monthly refill The refill is a **floor, not an assignment.** It raises `remaining_credits` up to your plan's monthly total and never lowers it. A balance already above the plan total, whether from a [top-up](https://recoupable.dev/docs/credits#one-time-top-ups) or an admin grant, is left exactly as it is, so credits you bought or were granted are never taken away by the calendar. The refill is lazy rather than scheduled: it applies on the next read of [`GET /api/accounts/{id}/credits`](https://recoupable.dev/docs/api-reference/accounts/credits-get) once your credits row is more than a month old. The balance you read is always the refilled one. ### Manage your subscription Sign in to your [account billing page](https://app.recoupable.dev/plan) to review your subscription, price, and current terms. For programmatic checkout, use the accepted request values in [Create Subscription Session](https://recoupable.dev/docs/api-reference/subscriptions/sessions-create) and open its returned checkout URL. Review the price and billing terms before completing checkout. Advisory and custom-build engagements begin with a conversation about scope. Their website plan selection does not create an API subscription or complete a payment. ### Check your tier ```bash curl -sS https://api.recoupable.dev/api/accounts/$ACCOUNT_ID/subscription \ -H "x-api-key: $RECOUP_API_KEY" ``` Get `$ACCOUNT_ID` from [`GET /api/accounts/id`](https://recoupable.dev/docs/api-reference/accounts/id) if you don't already have it. Response includes `isPro` (boolean), `status`, `plan`, and `source` (whether the subscription comes from the account itself or an organization the account belongs to). Full schema at [Get Subscription](https://recoupable.dev/docs/api-reference/accounts/subscription-get). ### One-time top-ups You can purchase credits any time via [`POST /api/credits/sessions`](https://recoupable.dev/docs/api-reference/credits/sessions-create). The endpoint adapts to what's on the account: - **Card on file → silent auto-charge.** Recoup charges your saved Stripe card off-session and returns `paymentIntentId`, `creditsPurchased`, and `totalCents`. Credits land within seconds. - **No card, or Stripe declines the saved card → Stripe Checkout fallback.** The response contains a Checkout `url` you open in the browser. When Stripe specifically declined a saved card, the response also includes a `declineReason` (e.g. `insufficient_funds`, `expired_card`) so you can explain *why* before sending the customer to update billing. ```bash curl -sS -X POST https://api.recoupable.dev/api/credits/sessions \ -H "x-api-key: $RECOUP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"credits": 1000000, "successUrl": "https://chat.recoupable.dev/credits/success"}' ``` Full request/response schema at [Create Credits Top-Up Session](https://recoupable.dev/docs/api-reference/credits/sessions-create). ### Check which card will be charged Before triggering a silent off-session charge, inspect the default payment method on file: ```bash curl -sS https://api.recoupable.dev/api/accounts/$ACCOUNT_ID/payment-method \ -H "x-api-key: $RECOUP_API_KEY" ``` Response shape: ```json { "account_id": "acc_…", "card": { "brand": "visa", "last4": "4242", "exp_month": 12, "exp_year": 2026, "funding": "credit" } } ``` `card` is `null` when no payment method has been saved yet — the next top-up call will route through a checkout session to collect one. Expired cards are still returned (with their original `exp_month` / `exp_year`); callers should compare against the current date and warn the customer, since an off-session charge against an expired card will decline. Full schema at [Get Default Payment Method](https://recoupable.dev/docs/api-reference/accounts/payment-method-get). --- ## Running out of credits Every billed API request runs a credit gate before it executes. If `remaining_credits` doesn't cover the request's cost, the request stops there and returns **HTTP 402**. Nothing is charged and no Stripe object is created. The decision tree: 1. **Enough credits?** → Request proceeds, credits deducted on success. 2. **Short?** → Request returns **HTTP 402** with the balance, the cost, and a link to billing. A card on the account is never charged on its own. Charging happens only when the account asks to buy credits, through [`POST /api/credits/sessions`](https://recoupable.dev/docs/api-reference/credits/sessions-create), or when the account has turned on auto top-up. Saving a card ahead of time via [`POST /api/accounts/{id}/payment-method`](https://recoupable.dev/docs/api-reference/accounts/payment-method-create) makes a one-time purchase a single call instead of a browser round-trip; it does not authorize a charge by itself. Your plan, card, and every payment are on [`/api/accounts/{id}/subscription`](https://recoupable.dev/docs/api-reference/accounts/subscription-get), [`/api/accounts/{id}/payment-method`](https://recoupable.dev/docs/api-reference/accounts/payment-method-get), and [`/api/accounts/{id}/payments`](https://recoupable.dev/docs/api-reference/accounts/payments-get); pass an organization id as `{id}` to read the organization's billing. ### Auto top-up (opt-in) Auto top-up is off for every account until it is turned on with [`PUT /api/accounts/{id}/auto-top-up`](https://recoupable.dev/docs/api-reference/accounts/auto-top-up-update), which needs three things chosen by the account: `enabled`, the `amountCents` to buy each time (5.00 to 1,000.00 USD), and the `thresholdCents` balance that triggers it. Both are USD cents; the `remaining_credits` balance above is in credit micro-dollars, so divide it by 10,000 to compare. Turning it on requires a card on file. Once on, the first credit deduction that leaves the balance below the threshold charges the card for the amount, grants the credits, records a usage event, and emails a receipt. Guardrails: at most one top-up per account per 10 minutes; a card decline turns auto top-up off, records the decline message as `lastError` (returned by [`GET /api/accounts/{id}/auto-top-up`](https://recoupable.dev/docs/api-reference/accounts/auto-top-up-get)), and emails the account instead of retrying; removing the card turns it off. Auto top-up never creates a checkout session and never touches an invoiced (enterprise) plan. Saving a card does not turn auto top-up on by itself. --- ## 402 Payment Required When the gate comes up short, billed endpoints return **HTTP 402** with a unified body: ```json { "error": "insufficient_credits", "remaining_credits": 120000, "required_credits": 1000000, "billingUrl": "https://app.recoupable.dev" } ``` | Field | When it appears | Meaning | | --- | --- | --- | | `error` | Always | Literally `"insufficient_credits"` | | `remaining_credits` | Always | Account balance at the moment of the failed request | | `required_credits` | Always | What this endpoint needed | | `billingUrl` | Always | Static link to the Recoup app, where a human can save a card and buy credits | `billingUrl` is a constant, not a freshly minted Stripe Checkout Session. Retrying a credit-gated endpoint returns the same URL every time and creates nothing, so an unattended client that keeps hitting the gate is safe to leave running. **How to react:** - **Browser-driven UI** (e.g., the Recoup chat app): send the customer to `billingUrl`, or call [`POST /api/credits/sessions`](https://recoupable.dev/docs/api-reference/credits/sessions-create) inline and open the `url` it returns. - **Programmatic / LLM-driven client**: report `remaining_credits` and `required_credits`, and surface `billingUrl` as the link a human needs to visit. Do not treat a 402 as retryable; the balance will not change on its own until someone buys credits or the monthly refill lands. --- ## Cost per endpoint Current as of this revision of the page. The authoritative source is the per-endpoint reference docs and the `required_credits` field on any 402 response. | Endpoint | Cost | | --- | --- | | [`POST /api/chat`](https://recoupable.dev/docs/api-reference/chat/workflow) (streaming) | Variable, priced from the model's token usage; see `required_credits` on a 402 | | [`GET /api/research/*`](https://recoupable.dev/docs/api-reference/research/web) (artist & non-artist research) | $0.05 per call | | [`POST /api/research/people`](https://recoupable.dev/docs/api-reference/research/people) | $0.05 per call | | [`POST /api/research/web`](https://recoupable.dev/docs/api-reference/research/web) | $0.01 per call | | [`POST /api/research/extract`](https://recoupable.dev/docs/api-reference/research/extract) | $0.05 × number of URLs | | [`POST /api/research/enrich`](https://recoupable.dev/docs/api-reference/research/enrich) | $0.05 / $0.10 / $0.25 (base / core / ultra processor) | | [`POST /api/research/deep`](https://recoupable.dev/docs/api-reference/research/deep) | $0.25 per call | | [`POST /api/image/generate`](https://recoupable.dev/docs/api-reference/image/generation) | Per-call price; see endpoint reference | | [`POST /api/songs/analyze`](https://recoupable.dev/docs/api-reference/songs/analyze) | `$0.05 + $0.001166 × elapsed_seconds` per model call; `full_report` = 13 calls | # Create a New Artist Source: https://recoupable.dev/docs/workflows/create-artist End-to-end workflow to create, research and enrich a new artist account in a single session. This is the canonical recipe used internally by Recoup's chat agent. Follow it step-by-step to bring a new artist account up to "researched + enriched" parity from a sandbox or any external agent. The chain is 8 sequential API calls. Long deterministic chains executed from prose memory tend to drop steps — the agent reads the doc once, runs a couple of calls, and forgets the rest. To prevent that from a sandbox, **drive the work from a checklist file**: scaffold the artist's `RECOUP.md` with one checkbox per step before any API call, then tick each box and persist captured values back to the frontmatter as you go. The file becomes the workflow state, and a fresh turn can resume by reading it. ## Prerequisites - `$RECOUP_ACCESS_TOKEN` — Bearer token for `api.recoupable.dev` - `$RECOUP_ORG_ID` — the org the artist should belong to (recommended in sandboxes) - An artist name to create (e.g. `ARTIST_NAME="The Weeknd"`) The flow has three phases, run from a single checklist file: 1. **Create + identify** — `POST /api/artists`, then find the canonical Spotify match 2. **Enrich** — `PATCH` the artist with image/label/socials, then run a web search for narrative context and additional socials 3. **Synthesize + persist** — generate a knowledge-base report, save it (RECOUP.md tree or hosted URL), then optionally `PATCH` the `knowledges` array ## Step 0: Scaffold the workspace BEFORE any API call Pick a slug, make the directory, and write the initial `RECOUP.md` template — frontmatter holds the values the chain captures (filled as you go); body holds the unchecked steps: ```bash ARTIST_SLUG=$(echo "$ARTIST_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]\+/-/g; s/^-//; s/-$//') ARTIST_DIR="artists/$ARTIST_SLUG" mkdir -p "$ARTIST_DIR" cat > "$ARTIST_DIR/RECOUP.md" < **Note** > The Songs API also supports POST requests for bulk create/fetch operations. See the [Create Songs](https://recoupable.dev/docs/api-reference/songs/create) endpoint. ## GET /api/songs Full OpenAPI specification: https://recoupable.dev/docs/spec/releases.json ## Authentication The supplied specification does not declare an OpenAPI security object for this operation. Its declared headers and parameters still apply. Consult the authentication guide and the full specification before calling it. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Releases", "description": "API documentation for the Recoup platform - an AI agent platform for the music industry", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "paths": { "/api/songs": { "get": { "description": "Retrieve songs from the database with optional filtering by ISRC (International Standard Recording Code) or artist account. This endpoint joins the songs table with song_artists and accounts tables to provide comprehensive song information.", "parameters": [ { "name": "isrc", "in": "query", "description": "International Standard Recording Code to filter by specific song", "required": false, "schema": { "type": "string" } }, { "name": "artist_account_id", "in": "query", "description": "Artist account ID to filter songs by artist", "required": false, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Songs retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SongsResponse" } } } }, "400": { "description": "Bad request - invalid parameters", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SongsErrorResponse" } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SongsErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SongsErrorResponse" } } } } } } } }, "components": { "schemas": { "SongsResponse": { "type": "object", "description": "Response containing songs data", "properties": { "status": { "type": "string", "enum": [ "success", "error" ], "description": "Status of the request" }, "songs": { "type": "array", "items": { "$ref": "#/components/schemas/Song" }, "description": "Array of song objects with artist information" }, "error": { "type": "string", "description": "Error message (only present if status is 'error')" } } }, "SongsErrorResponse": { "type": "object", "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Status of the request" }, "error": { "type": "string", "description": "Error message describing what went wrong" } } }, "Song": { "type": "object", "description": "A song with its metadata and associated artists", "properties": { "isrc": { "type": "string", "description": "International Standard Recording Code (primary key)" }, "name": { "type": "string", "description": "Name of the song" }, "album": { "type": "string", "description": "Name of the album the song belongs to" }, "notes": { "type": "string", "description": "Notes for the song" }, "updated_at": { "type": "string", "format": "date-time", "description": "ISO timestamp of when the song data was last updated" }, "artists": { "type": "array", "items": { "$ref": "#/components/schemas/SongArtist" }, "description": "Array of artist objects associated with this song" } } }, "SongArtist": { "type": "object", "description": "Artist associated with a song", "properties": { "id": { "type": "string", "format": "uuid", "description": "Unique identifier for the artist account" }, "name": { "type": "string", "nullable": true, "description": "Name of the artist (can be null)" }, "timestamp": { "type": "integer", "nullable": true, "description": "Timestamp associated with the artist account (can be null)" } } } } } } ``` # Create Songs Source: https://recoupable.dev/docs/api-reference/songs/create Bulk create or fetch songs by ISRC. For each song, the API attempts to look up metadata via internal search. If no data is found, optional fallback fields (name, album, notes, artists) are used. > **Note** > This endpoint performs bulk create/fetch operations. For each song, the API attempts to look up metadata via internal search. If no data is found, optional fallback fields (name, album, notes, artists) are used. ## POST /api/songs Full OpenAPI specification: https://recoupable.dev/docs/spec/releases.json ## Authentication The supplied specification does not declare an OpenAPI security object for this operation. Its declared headers and parameters still apply. Consult the authentication guide and the full specification before calling it. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Releases", "description": "API documentation for the Recoup platform - an AI agent platform for the music industry", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "paths": { "/api/songs": { "post": { "description": "Bulk create or fetch songs by ISRC. For each song, the API attempts to look up metadata via internal search. If no data is found, optional fallback fields (name, album, notes, artists) are used.", "requestBody": { "description": "Array of songs to create or fetch", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateSongsRequest" } } } }, "responses": { "200": { "description": "Songs created or fetched successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SongsResponse" } } } }, "400": { "description": "Bad request - invalid parameters", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SongsErrorResponse" } } } } } } } }, "components": { "schemas": { "CreateSongsRequest": { "type": "object", "required": [ "songs" ], "properties": { "songs": { "type": "array", "items": { "$ref": "#/components/schemas/CreateSongInput" }, "description": "Array of song inputs for bulk create/fetch" } } }, "SongsResponse": { "type": "object", "description": "Response containing songs data", "properties": { "status": { "type": "string", "enum": [ "success", "error" ], "description": "Status of the request" }, "songs": { "type": "array", "items": { "$ref": "#/components/schemas/Song" }, "description": "Array of song objects with artist information" }, "error": { "type": "string", "description": "Error message (only present if status is 'error')" } } }, "SongsErrorResponse": { "type": "object", "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Status of the request" }, "error": { "type": "string", "description": "Error message describing what went wrong" } } }, "CreateSongInput": { "type": "object", "required": [ "isrc" ], "properties": { "isrc": { "type": "string", "description": "International Standard Recording Code of the song to create or fetch" }, "name": { "type": "string", "description": "Optional. Song name, applied only if internal search cannot find valid info" }, "album": { "type": "string", "description": "Optional. Album name, applied only if internal search cannot find valid info" }, "notes": { "type": "string", "description": "Optional. Notes for the song, applied only if internal search cannot find valid info" }, "artists": { "type": "array", "items": { "type": "string" }, "description": "Optional array of artist names, applied only if internal search cannot find valid info" } } }, "Song": { "type": "object", "description": "A song with its metadata and associated artists", "properties": { "isrc": { "type": "string", "description": "International Standard Recording Code (primary key)" }, "name": { "type": "string", "description": "Name of the song" }, "album": { "type": "string", "description": "Name of the album the song belongs to" }, "notes": { "type": "string", "description": "Notes for the song" }, "updated_at": { "type": "string", "format": "date-time", "description": "ISO timestamp of when the song data was last updated" }, "artists": { "type": "array", "items": { "$ref": "#/components/schemas/SongArtist" }, "description": "Array of artist objects associated with this song" } } }, "SongArtist": { "type": "object", "description": "Artist associated with a song", "properties": { "id": { "type": "string", "format": "uuid", "description": "Unique identifier for the artist account" }, "name": { "type": "string", "nullable": true, "description": "Name of the artist (can be null)" }, "timestamp": { "type": "integer", "nullable": true, "description": "Timestamp associated with the artist account (can be null)" } } } } } } ``` # Analyze Songs Source: https://recoupable.dev/docs/api-reference/songs/analyze Analyze music using a state-of-the-art Audio Language Model ([Music Flamingo](https://www.universalmusic.com/universal-music-group-to-transform-music-experience-for-billions-of-fans-with-nvidia-ai/)) that listens directly to the audio waveform. ## POST /api/songs/analyze Full OpenAPI specification: https://recoupable.dev/docs/spec/releases.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Releases", "description": "API documentation for the Recoup platform - an AI agent platform for the music industry", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "paths": { "/api/songs/analyze": { "post": { "description": "Analyze music using a state-of-the-art Audio Language Model ([Music Flamingo](https://www.universalmusic.com/universal-music-group-to-transform-music-experience-for-billions-of-fans-with-nvidia-ai/)) that listens directly to the audio waveform.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "requestBody": { "description": "Music analysis request", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SongAnalyzeRequest" } } } }, "responses": { "200": { "description": "Music analysis completed successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SongAnalyzeResponse" } } } }, "400": { "description": "Bad request — missing or invalid fields, including a missing `audio_url` (`missing_fields: [\"audio_url\"]`) or both `preset` and `prompt` in one request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SongAnalyzeErrorResponse" } } } }, "401": { "description": "Unauthorized — invalid or missing API key / Bearer token", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SongAnalyzeErrorResponse" } } } }, "402": { "description": "Insufficient credits — the balance cannot cover the base price of this request (`$0.05` per model call, `$0.65` for `full_report`). The model is not called, nothing is charged, and no Stripe object is created. The body includes a static `billingUrl`.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SongAnalyzeInsufficientCreditsResponse" } } } }, "422": { "description": "Unverifiable audio. Before the model is called, `audio_url` is checked with a `HEAD` request (a ranged `GET` for hosts that reject `HEAD`). It must answer 2xx within 10 seconds with an `audio/*` or `application/octet-stream` content type. When it does not, the model is not called and nothing is charged.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SongAnalyzeAudioUrlErrorResponse" } } } }, "500": { "description": "Server error — upstream model unavailable or inference failed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SongAnalyzeErrorResponse" } } } } } } } }, "components": { "schemas": { "SongAnalyzeRequest": { "type": "object", "description": "Provide exactly one of `preset` or `prompt`, plus `audio_url`. Use `preset` for structured analysis workflows, or `prompt` for free-form questions about the audio.", "required": [ "audio_url" ], "properties": { "preset": { "type": "string", "enum": [ "catalog_metadata", "mood_tags", "lyric_transcription", "mix_feedback", "song_description", "music_theory", "similar_artists", "sample_detection", "sync_brief_match", "audience_profile", "content_advisory", "playlist_pitch", "artist_development_notes", "full_report" ], "description": "Name of a curated analysis preset. Use instead of prompt for structured, optimized output. The 'full_report' preset runs all 13 presets in parallel and returns a comprehensive report. See [List Analyze Presets](/docs/api-reference/songs/analyze-presets) for the full list of available presets.", "example": "catalog_metadata" }, "prompt": { "type": "string", "minLength": 1, "maxLength": 24000, "description": "Text prompt or question about the music", "example": "Describe the genre, tempo, and mood of this track." }, "audio_url": { "type": "string", "format": "uri", "description": "Public URL to an audio file (MP3, WAV, or FLAC, up to 20 minutes). Required in every mode, including a custom `prompt`. Verified before the model is called: the URL must answer a `HEAD` request (or a ranged `GET`) with 2xx within 10 seconds and an `audio/*` or `application/octet-stream` content type, else the request returns 422 and nothing is charged.", "example": "https://example.com/song.mp3" }, "max_new_tokens": { "type": "integer", "minimum": 1, "maximum": 2048, "default": 512, "description": "Maximum number of tokens to generate", "example": 512 }, "temperature": { "type": "number", "minimum": 0, "maximum": 2, "default": 1, "description": "Controls output creativity — higher values produce more varied responses", "example": 0.7 }, "top_p": { "type": "number", "minimum": 0, "maximum": 1, "default": 1, "description": "Nucleus sampling probability cutoff", "example": 0.9 }, "do_sample": { "type": "boolean", "default": false, "description": "Enable sampling (set true when using temperature or top_p)", "example": false } } }, "SongAnalyzeResponse": { "type": "object", "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Request status" }, "preset": { "type": "string", "description": "Preset used for analysis, when applicable", "example": "catalog_metadata" }, "response": { "description": "Model output for single-preset or custom-prompt analysis. May be plain text or structured JSON depending on the preset." }, "report": { "type": "object", "description": "Full report payload returned only when using the `full_report` preset" }, "elapsed_seconds": { "type": "number", "format": "float", "description": "Inference time in seconds" } } }, "SongAnalyzeErrorResponse": { "type": "object", "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Error status" }, "missing_fields": { "type": "array", "description": "Path to the first invalid or missing field when validation fails", "items": { "type": "string" } }, "error": { "type": "string", "description": "Error message describing what went wrong" } } }, "SongAnalyzeInsufficientCreditsResponse": { "type": "object", "description": "Returned (402) when the account lacks credits for the analysis. Nothing is charged and no Stripe object is created. Credits must be bought explicitly via [`POST /api/credits/sessions`](/docs/api-reference/credits/sessions-create).", "properties": { "error": { "type": "string", "enum": [ "insufficient_credits" ] }, "remaining_credits": { "type": "integer", "description": "The account's balance, in credits (micro-dollars: 1,000,000 = `$1.00`).", "example": 0 }, "required_credits": { "type": "integer", "description": "The base price of this request, in credits: 50000 (`$0.05`) for a single preset or custom prompt, 650000 (`$0.65`) for `full_report`. The per-second inference charge is added after the model returns.", "example": 50000 }, "billingUrl": { "type": "string", "description": "Static link to the Recoup app, where a human can save a card and buy credits. It is a constant, not a freshly minted Stripe Checkout Session, so a credit-gated endpoint that keeps returning 402 creates nothing. To buy credits programmatically, call [`POST /api/credits/sessions`](/docs/api-reference/credits/sessions-create).", "example": "https://app.recoupable.dev" } }, "required": [ "error", "remaining_credits", "required_credits", "billingUrl" ] }, "SongAnalyzeAudioUrlErrorResponse": { "type": "object", "description": "Returned (422) when `audio_url` could not be verified. The model is not called and nothing is charged.", "properties": { "status": { "type": "string", "enum": [ "error" ] }, "error": { "type": "string", "enum": [ "audio_url_unreachable", "audio_url_not_audio" ], "description": "`audio_url_unreachable`: the URL did not answer 2xx within 10 seconds. `audio_url_not_audio`: it answered, but with a content type that is not `audio/*` or `application/octet-stream`.", "example": "audio_url_not_audio" }, "message": { "type": "string", "description": "What the check observed, for the caller's logs.", "example": "audio_url answered 200 with content type text/html" } }, "required": [ "status", "error", "message" ] } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # List Analyze Presets Source: https://recoupable.dev/docs/api-reference/songs/analyze-presets Lists all available music analysis presets. Each preset is a curated prompt with optimized generation parameters for a specific use case (e.g. catalog metadata enrichment, sync licensing analysis, audience profiling). Requires authentication via API key or Bearer token. ## GET /api/songs/analyze/presets Full OpenAPI specification: https://recoupable.dev/docs/spec/releases.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Releases", "description": "API documentation for the Recoup platform - an AI agent platform for the music industry", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "paths": { "/api/songs/analyze/presets": { "get": { "description": "Lists all available music analysis presets. Each preset is a curated prompt with optimized generation parameters for a specific use case (e.g. catalog metadata enrichment, sync licensing analysis, audience profiling). Requires authentication via API key or Bearer token.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "responses": { "200": { "description": "Presets listed successfully", "content": { "application/json": { "schema": { "type": "object", "properties": { "status": { "type": "string", "enum": [ "success" ] }, "presets": { "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string", "description": "Preset identifier to pass in the analyze request" }, "label": { "type": "string", "description": "Human-readable preset name" }, "description": { "type": "string", "description": "What this preset does" }, "requiresAudio": { "type": "boolean", "description": "Always `true`: every analyze request requires `audio_url`, whichever preset or prompt it uses. Kept on the response for compatibility.", "example": true }, "responseFormat": { "type": "string", "enum": [ "json", "text" ], "description": "Expected response format" } } } } } } } } }, "401": { "description": "Unauthorized — invalid or missing API key / Bearer token", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SongAnalyzeErrorResponse" } } } } } } } }, "components": { "schemas": { "SongAnalyzeErrorResponse": { "type": "object", "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Error status" }, "missing_fields": { "type": "array", "description": "Path to the first invalid or missing field when validation fails", "items": { "type": "string" } }, "error": { "type": "string", "description": "Error message describing what went wrong" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Get Catalogs Source: https://recoupable.dev/docs/api-reference/songs/catalogs Retrieve the catalogs visible to a specific account. Returns the catalogs the account owns directly, plus the catalogs owned by any organization the account belongs to - so every member of an organization sees that organization's catalogs without passing the organization's id. A catalog owned both directly and through an organization is returned once. An account that belongs to no organization receives only its own catalogs. Create an organization-owned catalog with [Create catalog](/docs/api-reference/songs/catalogs-create) or [Run valuation](/docs/api-reference/songs/valuation-run). ## GET /api/accounts/{id}/catalogs Full OpenAPI specification: https://recoupable.dev/docs/spec/releases.json ## Authentication The supplied specification does not declare an OpenAPI security object for this operation. Its declared headers and parameters still apply. Consult the authentication guide and the full specification before calling it. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Releases", "description": "API documentation for the Recoup platform - an AI agent platform for the music industry", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "paths": { "/api/accounts/{id}/catalogs": { "get": { "description": "Retrieve the catalogs visible to a specific account. Returns the catalogs the account owns directly, plus the catalogs owned by any organization the account belongs to - so every member of an organization sees that organization's catalogs without passing the organization's id. A catalog owned both directly and through an organization is returned once. An account that belongs to no organization receives only its own catalogs. Create an organization-owned catalog with [Create catalog](/docs/api-reference/songs/catalogs-create) or [Run valuation](/docs/api-reference/songs/valuation-run).\n\nEach catalog carries its estimated value band and its owner, so a list of catalogs can be ranked by worth and a member can tell an organization's catalog from their own. `valuation` is null for a catalog with no measured songs; `owner.image` is null when the owner account has no avatar.", "parameters": [ { "name": "id", "in": "path", "description": "The unique identifier of the account to query. The catalogs of every organization this account belongs to are included in the response.", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Catalogs retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogsResponse" } } } }, "400": { "description": "Bad request - missing or invalid account id", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogsErrorResponse" } } } }, "401": { "description": "Unauthorized - missing or invalid authentication", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogsErrorResponse" } } } }, "403": { "description": "Forbidden - caller lacks access to the target account", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogsErrorResponse" } } } }, "404": { "description": "Account not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogsErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogsErrorResponse" } } } } } } } }, "components": { "schemas": { "CatalogsResponse": { "type": "object", "description": "Response containing catalogs data", "properties": { "status": { "type": "string", "enum": [ "success", "error" ], "description": "Status of the request" }, "catalogs": { "type": "array", "items": { "$ref": "#/components/schemas/CatalogListItem" }, "description": "Array of catalog objects, each with its estimated value and its owner" }, "error": { "type": "string", "description": "Error message (only present if status is 'error')" } } }, "CatalogsErrorResponse": { "type": "object", "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Status of the request" }, "error": { "type": "string", "description": "Error message describing what went wrong" } } }, "CatalogListItem": { "type": "object", "description": "A catalog as returned by [Get catalogs](/docs/api-reference/songs/catalogs): the catalog plus its estimated value and its owner. Only this list carries those two fields - the create and rename responses return the bare catalog.", "required": [ "id", "name", "created_at", "updated_at", "measured_song_count", "valuation", "owner" ], "properties": { "id": { "type": "string", "format": "uuid", "description": "Unique identifier for the catalog" }, "name": { "type": "string", "description": "Name of the catalog" }, "created_at": { "type": "string", "format": "date-time", "description": "ISO timestamp of when the catalog was created" }, "updated_at": { "type": "string", "format": "date-time", "description": "ISO timestamp of when the catalog was last updated" }, "measured_song_count": { "type": "integer", "description": "Songs in the catalog with at least one play-count measurement. 0 for a catalog that was never measured, in which case valuation is null." }, "valuation": { "oneOf": [ { "$ref": "#/components/schemas/ValuationBand" }, { "type": "null" } ], "description": "Estimated value band - the same schema and model as [Get catalog measurements](/docs/api-reference/songs/catalog-measurements) returns, so a list and the report it opens cannot disagree. Null when nothing in the catalog has been measured; clients should say so rather than render $0." }, "owner": { "$ref": "#/components/schemas/CatalogOwner" } } }, "ValuationBand": { "type": "object", "description": "Estimated catalog value in USD, derived at read time from the latest measurements - the same model as the recoupable.dev valuation card. Annual run-rate = lifetime streams / catalog age (lifetime-average proxy), converted to net label share (all-DSP gross-up 1.25/1.4/1.6, 15% distribution fee, 25% royalty share, $0.0035 per Spotify stream) and multiplied by a 10x/13x/16x master-catalog market multiple.", "properties": { "low": { "type": "number", "description": "Low estimate in USD (1.25x gross-up, 10x multiple)" }, "mid": { "type": "number", "description": "Central estimate in USD (1.4x gross-up, 13x multiple)" }, "high": { "type": "number", "description": "High estimate in USD (1.6x gross-up, 16x multiple)" } } }, "CatalogOwner": { "type": "object", "description": "The account a catalog belongs to. A catalog owned both directly and through an organization reports the organization - that is the ownership worth surfacing to a member.", "required": [ "id", "name", "image", "is_organization" ], "properties": { "id": { "type": "string", "format": "uuid", "description": "Owner account id" }, "name": { "type": "string", "nullable": true, "description": "Owner display name, null when the account has none" }, "image": { "type": "string", "nullable": true, "description": "Owner avatar URL from account_info, null when the account has no image. Clients should fall back to initials rather than render a broken image." }, "is_organization": { "type": "boolean", "description": "True when the owner is an organization the caller belongs to, false when it is a personal account" } } } } } } ``` # Create Catalog Source: https://recoupable.dev/docs/api-reference/songs/catalogs-create Create a catalog, owned by the calling account or - with organization_id - by one of the caller's organizations, in which case every member of that organization sees it via [Get Catalogs](/docs/api-reference/songs/catalogs). When materializing from a valuation snapshot, the endpoint also attaches the snapshot's canonical artist to the caller's roster: the measured songs' existing song-artist links are resolved to the dominant artist account, which is added to the caller's roster if not already present. The attach also runs on idempotent re-claims of an already-materialized snapshot. The roster attach always targets the calling account, even when the catalog is owned by an organization. ## POST /api/catalogs Full OpenAPI specification: https://recoupable.dev/docs/spec/releases.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Releases", "description": "API documentation for the Recoup platform - an AI agent platform for the music industry", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "paths": { "/api/catalogs": { "post": { "description": "Create a catalog, owned by the calling account or - with organization_id - by one of the caller's organizations, in which case every member of that organization sees it via [Get Catalogs](/docs/api-reference/songs/catalogs). When materializing from a valuation snapshot, the endpoint also attaches the snapshot's canonical artist to the caller's roster: the measured songs' existing song-artist links are resolved to the dominant artist account, which is added to the caller's roster if not already present. The attach also runs on idempotent re-claims of an already-materialized snapshot. The roster attach always targets the calling account, even when the catalog is owned by an organization.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "requestBody": { "description": "Catalog to create. Provide name, snapshot, or both - at least one is required.", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateCatalogRequest" } } } }, "responses": { "200": { "description": "Catalog created, or the existing catalog returned when re-materializing the same snapshot (idempotent)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateCatalogResponse" } } } }, "400": { "description": "Bad request - neither name nor snapshot provided, or invalid snapshot", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogSongsErrorResponse" } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogSongsErrorResponse" } } } }, "403": { "description": "Forbidden - the snapshot belongs to a different account, or the caller is not a member of the organization named by organization_id", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogSongsErrorResponse" } } } }, "404": { "description": "Not found - no snapshot exists for the supplied snapshot", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogSongsErrorResponse" } } } } } } } }, "components": { "schemas": { "CreateCatalogRequest": { "type": "object", "description": "Request body for creating a catalog. At least one of name or snapshot must be supplied. The catalog is owned by the authenticated account unless organization_id names an organization to own it instead.", "properties": { "name": { "type": "string", "description": "Optional. Display name for the catalog. If omitted when snapshot is supplied, a name is derived from the source run." }, "snapshot": { "type": "string", "format": "uuid", "description": "Optional. ID of a completed playcount snapshot (valuation run) owned by the authenticated account. Its measured ISRCs are added to the new catalog as catalog songs. Create one with [Create measurement job](/docs/api-reference/research/measurement-jobs)." }, "organization_id": { "type": "string", "format": "uuid", "description": "Optional. ID of an organization to own the catalog instead of the calling account. Every member of that organization then sees the catalog in their own [Get Catalogs](/docs/api-reference/songs/catalogs) response, without passing the organization's id. The caller must be a member of the organization; otherwise the request is rejected with 403. Omit to own the catalog personally, which is the default." } } }, "CreateCatalogResponse": { "type": "object", "description": "Response returned after creating, or idempotently re-fetching, a catalog", "properties": { "status": { "type": "string", "enum": [ "success", "error" ], "description": "Status of the request" }, "catalog": { "$ref": "#/components/schemas/Catalog" }, "songs_added": { "type": "integer", "description": "Number of catalog songs materialized from the source. 0 when no snapshot was supplied, or when the run was already materialized (idempotent re-claim)." }, "error": { "type": "string", "description": "Error message (only present if status is 'error')" } } }, "CatalogSongsErrorResponse": { "type": "object", "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Status of the request" }, "error": { "type": "string", "description": "Error message describing what went wrong" } } }, "Catalog": { "type": "object", "description": "A catalog with its metadata", "properties": { "id": { "type": "string", "format": "uuid", "description": "Unique identifier for the catalog" }, "name": { "type": "string", "description": "Name of the catalog" }, "created_at": { "type": "string", "format": "date-time", "description": "ISO timestamp of when the catalog was created" }, "updated_at": { "type": "string", "format": "date-time", "description": "ISO timestamp of when the catalog was last updated" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Rename Catalog Source: https://recoupable.dev/docs/api-reference/songs/catalog-rename Rename a catalog. The only mutable field is the display name — catalog membership is managed through [Add catalog songs](/docs/api-reference/songs/catalog-songs-add) and [Remove catalog songs](/docs/api-reference/songs/catalog-songs-delete). ## PATCH /api/catalogs/{catalogId} Full OpenAPI specification: https://recoupable.dev/docs/spec/releases.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Releases", "description": "API documentation for the Recoup platform - an AI agent platform for the music industry", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "paths": { "/api/catalogs/{catalogId}": { "patch": { "description": "Rename a catalog. The only mutable field is the display name — catalog membership is managed through [Add catalog songs](/docs/api-reference/songs/catalog-songs-add) and [Remove catalog songs](/docs/api-reference/songs/catalog-songs-delete).\n\nCatalogs created by [Run valuation](/docs/api-reference/songs/valuation-run) are named after the measured artist. Catalogs created before that shipped are all named `Valuation Catalog` — this endpoint is how you give them real names.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "parameters": [ { "name": "catalogId", "in": "path", "description": "ID of the catalog to rename.", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "description": "The new catalog name.", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdateCatalogRequest" } } } }, "responses": { "200": { "description": "Catalog renamed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdateCatalogResponse" } } } }, "400": { "description": "Bad request - catalogId is not a valid UUID, or name is missing or empty", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogsErrorResponse" } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogsErrorResponse" } } } }, "404": { "description": "Catalog not found - no catalog with this ID is visible to the caller. A catalog that exists but belongs to neither the authenticated account nor one of its organizations returns 404, not 403: the same visibility rule the catalog read paths use, so a catalog you cannot see is indistinguishable from one that does not exist.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogsErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogsErrorResponse" } } } } } } } }, "components": { "schemas": { "UpdateCatalogRequest": { "type": "object", "description": "Request body for renaming a catalog.", "required": [ "name" ], "properties": { "name": { "type": "string", "minLength": 1, "description": "New display name for the catalog. Must not be empty." } } }, "UpdateCatalogResponse": { "type": "object", "description": "Response returned after renaming a catalog", "required": [ "status", "catalog" ], "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the request" }, "catalog": { "$ref": "#/components/schemas/Catalog" } } }, "CatalogsErrorResponse": { "type": "object", "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Status of the request" }, "error": { "type": "string", "description": "Error message describing what went wrong" } } }, "Catalog": { "type": "object", "description": "A catalog with its metadata", "properties": { "id": { "type": "string", "format": "uuid", "description": "Unique identifier for the catalog" }, "name": { "type": "string", "description": "Name of the catalog" }, "created_at": { "type": "string", "format": "date-time", "description": "ISO timestamp of when the catalog was created" }, "updated_at": { "type": "string", "format": "date-time", "description": "ISO timestamp of when the catalog was last updated" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Delete Catalog Source: https://recoupable.dev/docs/api-reference/songs/catalog-delete Delete a catalog. Removes the catalog, its song membership (catalog_songs), its ownership links (account_catalogs) and its stored valuation history (catalog_valuations). ## DELETE /api/catalogs/{catalogId} Full OpenAPI specification: https://recoupable.dev/docs/spec/releases.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Releases", "description": "API documentation for the Recoup platform - an AI agent platform for the music industry", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "paths": { "/api/catalogs/{catalogId}": { "delete": { "description": "Delete a catalog. Removes the catalog, its song membership (catalog_songs), its ownership links (account_catalogs) and its stored valuation history (catalog_valuations).\n\nThe measurement that produced the catalog is **not** deleted. A playcount snapshot is a metered capture that spent credits and cannot be reproduced for a past date, while a catalog is a label over songs that can be rebuilt from it in one call — so deleting a catalog only clears the snapshot's reference to it. The snapshot then reads as unclaimed, and [Create catalog](/docs/api-reference/songs/catalogs-create) can re-materialize it under a correct name. The released snapshot IDs are returned so that recovery does not require hunting for them.\n\nUse this for genuine duplicates. To fix a catalog's name, use [Rename catalog](/docs/api-reference/songs/catalog-rename) instead — it keeps the catalog's measurements and valuation history intact.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "parameters": [ { "name": "catalogId", "in": "path", "description": "ID of the catalog to delete.", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Catalog deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeleteCatalogResponse" } } } }, "400": { "description": "Bad request - catalogId is not a valid UUID", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogsErrorResponse" } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogsErrorResponse" } } } }, "404": { "description": "Catalog not found - no catalog with this ID is visible to the caller. A catalog that exists but belongs to neither the authenticated account nor one of its organizations returns 404, not 403: the same visibility rule the catalog read paths use, so a catalog you cannot see is indistinguishable from one that does not exist.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogsErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogsErrorResponse" } } } } } } } }, "components": { "schemas": { "DeleteCatalogResponse": { "type": "object", "description": "Response returned after deleting a catalog", "required": [ "status", "catalog_id", "released_snapshot_ids" ], "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the request" }, "catalog_id": { "type": "string", "format": "uuid", "description": "ID of the deleted catalog" }, "released_snapshot_ids": { "type": "array", "description": "IDs of the playcount snapshots that pointed at this catalog. Those snapshots still exist - only their catalog reference was cleared - so each can be re-materialized into a new, correctly named catalog with Create catalog. Empty when the catalog was not created from a measurement run.", "items": { "type": "string", "format": "uuid" } } } }, "CatalogsErrorResponse": { "type": "object", "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Status of the request" }, "error": { "type": "string", "description": "Error message describing what went wrong" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Run Valuation Source: https://recoupable.dev/docs/api-reference/songs/valuation-run Run valuation ## POST /api/valuation Full OpenAPI specification: https://recoupable.dev/docs/spec/releases.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Releases", "description": "API documentation for the Recoup platform - an AI agent platform for the music industry", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "paths": { "/api/valuation": { "post": { "summary": "Run valuation", "description": "Generate a catalog from a Spotify artist in one call. Resolves the artist's releases, captures current Spotify play counts (spending the account's credits), materializes a catalog from the resulting snapshot (idempotent - see [Create catalog](/docs/api-reference/songs/catalogs-create)), and returns the catalog with its estimated value band. The catalog is owned by the calling account unless organization_id names one of the caller's organizations to own it instead, in which case every member of that organization sees it. The calling account is always taken from the credentials, never the body, and it is the account whose credits are spent. The searched artist is also linked to the caller's roster (so a funnel signup lands with a populated `GET /api/artists` it can confirm); when the catalog's songs already resolve a canonical artist that one is used, otherwise the searched Spotify artist is linked directly. Synchronous: the request waits for the capture to land (typically under two minutes). Each run also persists a row in the catalog's valuation history, readable via [Get Catalog Valuations](/docs/api-reference/songs/catalog-valuations).", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "requestBody": { "description": "The Spotify artist to value.", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RunValuationRequest" } } } }, "responses": { "200": { "description": "Catalog created (or idempotently re-fetched) with its estimated value band.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RunValuationResponse" } } } }, "400": { "description": "Bad request - missing or invalid spotify_artist_id", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogSongsErrorResponse" } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogSongsErrorResponse" } } } }, "402": { "description": "Insufficient credits to measure the catalog", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogSongsErrorResponse" } } } }, "403": { "description": "Forbidden - the caller is not a member of the organization named by organization_id", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogSongsErrorResponse" } } } }, "404": { "description": "Not found - no releases found for the Spotify artist", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogSongsErrorResponse" } } } }, "504": { "description": "The measurement did not complete within the wait window - retry shortly", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogSongsErrorResponse" } } } } } } } }, "components": { "schemas": { "RunValuationRequest": { "type": "object", "required": [ "spotify_artist_id" ], "properties": { "spotify_artist_id": { "type": "string", "description": "The Spotify artist id to value (e.g. from a Spotify artist search). The endpoint resolves this artist's releases, measures them, and builds the catalog." }, "organization_id": { "type": "string", "format": "uuid", "description": "Optional. ID of an organization to own the resulting catalog instead of the calling account. Every member of that organization then sees it in their own [Get Catalogs](/docs/api-reference/songs/catalogs) response. The caller must be a member of the organization; otherwise the request is rejected with 403. Credits are still spent by the calling account, and the searched artist is still linked to the caller's roster. Omit to own the catalog personally, which is the default." }, "account_id": { "type": "string", "format": "uuid", "description": "Optional. Act on behalf of this account (UUID). Only applicable when the authenticated account has access to the target account, for example an organization key acting for a member account. Every side effect of the run - the playcount snapshot, the catalog claim, the roster attach, and the report email - is scoped to this account instead of the caller. Omit to run for the caller's own account, which is the default." } } }, "RunValuationResponse": { "type": "object", "description": "Result of a valuation run: the materialized catalog and its estimated value band.", "properties": { "status": { "type": "string", "enum": [ "success", "error" ], "description": "Status of the request" }, "catalog": { "$ref": "#/components/schemas/Catalog" }, "band": { "$ref": "#/components/schemas/ValuationBand" }, "songs_measured": { "type": "integer", "description": "Number of tracks with a captured play count that were materialized into the catalog." }, "error": { "type": "string", "description": "Error message (only present if status is 'error')" } } }, "CatalogSongsErrorResponse": { "type": "object", "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Status of the request" }, "error": { "type": "string", "description": "Error message describing what went wrong" } } }, "Catalog": { "type": "object", "description": "A catalog with its metadata", "properties": { "id": { "type": "string", "format": "uuid", "description": "Unique identifier for the catalog" }, "name": { "type": "string", "description": "Name of the catalog" }, "created_at": { "type": "string", "format": "date-time", "description": "ISO timestamp of when the catalog was created" }, "updated_at": { "type": "string", "format": "date-time", "description": "ISO timestamp of when the catalog was last updated" } } }, "ValuationBand": { "type": "object", "description": "Estimated catalog value in USD, derived at read time from the latest measurements - the same model as the recoupable.dev valuation card. Annual run-rate = lifetime streams / catalog age (lifetime-average proxy), converted to net label share (all-DSP gross-up 1.25/1.4/1.6, 15% distribution fee, 25% royalty share, $0.0035 per Spotify stream) and multiplied by a 10x/13x/16x master-catalog market multiple.", "properties": { "low": { "type": "number", "description": "Low estimate in USD (1.25x gross-up, 10x multiple)" }, "mid": { "type": "number", "description": "Central estimate in USD (1.4x gross-up, 13x multiple)" }, "high": { "type": "number", "description": "High estimate in USD (1.6x gross-up, 16x multiple)" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Get Runs Source: https://recoupable.dev/docs/api-reference/songs/runs Get runs ## GET /api/runs Full OpenAPI specification: https://recoupable.dev/docs/spec/releases.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Releases", "description": "API documentation for the Recoup platform - an AI agent platform for the music industry", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "paths": { "/api/runs": { "get": { "summary": "Get runs", "description": "Get background runs, newest first. A run is the generic status resource for long-running work. `kind` selects the run type (`valuation` and `music` today); future kinds are new enum values here, never new endpoints.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "parameters": [ { "name": "kind", "in": "query", "required": true, "schema": { "type": "string", "enum": [ "valuation", "music" ] }, "description": "Required. The run type to list. Unknown kinds are rejected with 400." }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1, "maximum": 20, "default": 1 }, "description": "Optional. Maximum runs to return, newest first. Defaults to 1 (the latest run)." } ], "responses": { "200": { "description": "The calling account's runs of the requested kind, newest first. Empty when the account has never run one.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GetRunsResponse" } } } }, "400": { "description": "Missing or unknown kind, or invalid limit.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogSongsErrorResponse" } } } }, "401": { "description": "Missing or invalid credentials.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogSongsErrorResponse" } } } } } } } }, "components": { "schemas": { "GetRunsResponse": { "type": "object", "description": "The calling account's runs of the requested kind, newest first.", "properties": { "status": { "type": "string", "enum": [ "success", "error" ], "description": "Status of the request" }, "runs": { "type": "array", "description": "Runs, newest first. Empty when the account has never run one of this kind. The item shape follows the requested `kind`.", "items": { "oneOf": [ { "title": "Valuation run", "$ref": "#/components/schemas/ValuationRun" }, { "title": "Music run", "$ref": "#/components/schemas/MusicRun" } ] } }, "error": { "type": "string", "description": "Error message (only present if status is 'error')" } } }, "CatalogSongsErrorResponse": { "type": "object", "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Status of the request" }, "error": { "type": "string", "description": "Error message describing what went wrong" } } }, "ValuationRun": { "type": "object", "description": "One background run. `id` is opaque; `state` is a domain phase, not a storage value: `queued` (capture accepted, not yet scraping), `measuring` (capture in flight, or finished moments ago and being claimed), `claimed` (catalog materialized - `result.catalog_id` is set), `failed` (the capture finished but no catalog was claimed, or the capture itself failed).", "properties": { "id": { "type": "string", "format": "uuid", "description": "Opaque run id. Do not infer anything from its format; it is stable for polling a single run across requests." }, "kind": { "type": "string", "enum": [ "valuation" ], "description": "The run type." }, "state": { "type": "string", "enum": [ "queued", "measuring", "claimed", "failed" ], "description": "Domain phase of the run." }, "album_count": { "type": "integer", "description": "Number of releases in the run's capture scope." }, "created_at": { "type": "string", "format": "date-time", "description": "When the run was created." }, "result": { "type": "object", "nullable": true, "description": "Set once the run is claimed; null before that.", "properties": { "catalog_id": { "type": "string", "format": "uuid", "description": "The materialized catalog. Read its value band via [Get Catalog Measurements](/docs/api-reference/songs/catalog-measurements)." } } } } }, "MusicRun": { "type": "object", "description": "One music generation, surfaced as a run. `id` is the generation's id: read the full record, including its logs, with [Get Music Generation](/docs/api-reference/music/get).", "properties": { "id": { "type": "string", "format": "uuid", "description": "The generation's id." }, "kind": { "type": "string", "enum": [ "music" ], "description": "The run type." }, "state": { "type": "string", "enum": [ "queued", "generating", "complete", "failed" ], "description": "Domain phase of the run: `queued` before the workflow picks it up, `generating` while the model renders, then `complete` or `failed`." }, "created_at": { "type": "string", "format": "date-time", "description": "When the generation was created." }, "result": { "type": "object", "nullable": true, "description": "Set once the generation completes; null before that.", "properties": { "generation_id": { "type": "string", "format": "uuid", "description": "The completed generation." }, "audio_url": { "type": "string", "description": "Playable audio URL." } } } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Get Catalog Measurements Source: https://recoupable.dev/docs/api-reference/songs/catalog-measurements Get the latest play counts and a derived valuation band for a catalog. Measurements are captured by [Create measurement job](/docs/api-reference/research/measurement-jobs) runs; the band is computed at read time from the latest capture per song. ## GET /api/catalogs/{catalogId}/measurements Full OpenAPI specification: https://recoupable.dev/docs/spec/releases.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Releases", "description": "API documentation for the Recoup platform - an AI agent platform for the music industry", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "paths": { "/api/catalogs/{catalogId}/measurements": { "get": { "description": "Get the latest play counts and a derived valuation band for a catalog. Measurements are captured by [Create measurement job](/docs/api-reference/research/measurement-jobs) runs; the band is computed at read time from the latest capture per song.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "parameters": [ { "name": "catalogId", "in": "path", "description": "The unique identifier of the catalog to read measurements for. The catalog must belong to the authenticated account. Malformed (non-uuid) values are rejected with 400.", "required": true, "schema": { "type": "string", "format": "uuid" } }, { "name": "account_id", "in": "query", "description": "Act on behalf of this account (UUID). Only applicable when the authenticated account has access to the target account, for example an organization key acting for a member account. The catalog ownership check then runs against this account instead of the caller. Omitted: the caller's own account.", "required": false, "schema": { "type": "string", "format": "uuid" } }, { "name": "artist_account_id", "in": "query", "description": "Optional artist account to scope the read to. When present, measurements and the valuation band cover only the catalog's songs linked to this artist account (via song_artists); when absent, the whole catalog. A song linked to multiple artists counts for each of them. An artist with no linked measured songs in the catalog yields measured_song_count 0 and an empty measurements array. Malformed (non-uuid) values are rejected with 400.", "required": false, "schema": { "type": "string", "format": "uuid" } }, { "name": "page", "in": "query", "description": "Page number of the measurements array (default 1). Pagination only windows the measurements rows - the aggregates always cover the entire scope. Invalid values are rejected with 400.", "required": false, "schema": { "type": "integer", "minimum": 1, "default": 1 } }, { "name": "limit", "in": "query", "description": "Number of measurement rows per page (default 50, max 100). Invalid values are rejected with 400.", "required": false, "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 } } ], "responses": { "200": { "description": "Latest per-song measurements and the derived valuation band", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogMeasurementsResponse" } } } }, "400": { "description": "Bad request - malformed catalogId or artist_account_id, or invalid page/limit", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogSongsErrorResponse" } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogSongsErrorResponse" } } } }, "404": { "description": "Not found - the catalog does not exist or belongs to a different account", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogSongsErrorResponse" } } } } } } } }, "components": { "schemas": { "CatalogMeasurementsResponse": { "type": "object", "description": "Latest per-song play counts for a catalog plus the valuation band derived from them", "properties": { "status": { "type": "string", "enum": [ "success", "error" ], "description": "Status of the request" }, "measurements": { "type": "array", "items": { "$ref": "#/components/schemas/CatalogTrackMeasurement" }, "description": "One page of latest-per-ISRC measurements (the requested page/limit window), sorted by play count descending. One entry per measured song: the newest capture per ISRC; songs never measured are omitted. Use measured_song_count / pagination for the size of the full set - the aggregates are NOT limited to this page." }, "pagination": { "$ref": "#/components/schemas/CatalogMeasurementsPagination" }, "measured_song_count": { "type": "integer", "description": "Total number of measured songs in scope (the whole catalog, or the artist subset when filtered), computed over the entire scope regardless of pagination - no row cap" }, "valuation": { "$ref": "#/components/schemas/ValuationBand" }, "total_streams": { "type": "integer", "description": "Sum of the latest play counts across ALL measured songs in scope, computed in a single SQL aggregate regardless of pagination - no row cap" }, "artist_account_id": { "type": "string", "format": "uuid", "nullable": true, "description": "Echoes the applied artist_account_id filter: the uuid when the response was scoped to that artist, null when the response covers the whole catalog. Clients that request an artist scope should verify this echo before treating the numbers as artist-scoped." }, "catalog_age_years": { "type": "integer", "description": "Catalog age in years used for the annual run-rate: from the earliest Spotify release date of the source run's albums, minimum 1; defaults to 5 when no release date is resolvable." }, "error": { "type": "string", "description": "Error message (only present if status is 'error')" } } }, "CatalogSongsErrorResponse": { "type": "object", "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Status of the request" }, "error": { "type": "string", "description": "Error message describing what went wrong" } } }, "CatalogTrackMeasurement": { "type": "object", "description": "The most recent Spotify play-count measurement for one song (ISRC) in the catalog", "properties": { "isrc": { "type": "string", "description": "ISRC of the song" }, "title": { "type": "string", "nullable": true, "description": "Song title, when known" }, "playcount": { "type": "integer", "description": "Latest Spotify platform-displayed play count" }, "measured_at": { "type": "string", "format": "date-time", "description": "ISO timestamp of when the play count was captured" } } }, "CatalogMeasurementsPagination": { "type": "object", "description": "Pagination metadata for the measurements array - the same envelope as catalog songs", "properties": { "total_count": { "type": "integer", "description": "Total number of measured songs in scope (equals measured_song_count)" }, "page": { "type": "integer", "description": "Current page number" }, "limit": { "type": "integer", "description": "Number of measurement rows per page" }, "total_pages": { "type": "integer", "description": "Total number of pages available" } } }, "ValuationBand": { "type": "object", "description": "Estimated catalog value in USD, derived at read time from the latest measurements - the same model as the recoupable.dev valuation card. Annual run-rate = lifetime streams / catalog age (lifetime-average proxy), converted to net label share (all-DSP gross-up 1.25/1.4/1.6, 15% distribution fee, 25% royalty share, $0.0035 per Spotify stream) and multiplied by a 10x/13x/16x master-catalog market multiple.", "properties": { "low": { "type": "number", "description": "Low estimate in USD (1.25x gross-up, 10x multiple)" }, "mid": { "type": "number", "description": "Central estimate in USD (1.4x gross-up, 13x multiple)" }, "high": { "type": "number", "description": "High estimate in USD (1.6x gross-up, 16x multiple)" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Get Catalog Valuations Source: https://recoupable.dev/docs/api-reference/songs/catalog-valuations Get the persisted valuation history for a catalog, latest first. A row is written each time a valuation band is computed for the whole catalog (valuation runs and measurement reads persist at most one row per catalog per day). Use limit=1 for the current value. History is what makes week-over-week deltas possible. ## GET /api/catalogs/{catalogId}/valuations Full OpenAPI specification: https://recoupable.dev/docs/spec/releases.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Releases", "description": "API documentation for the Recoup platform - an AI agent platform for the music industry", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "paths": { "/api/catalogs/{catalogId}/valuations": { "get": { "description": "Get the persisted valuation history for a catalog, latest first. A row is written each time a valuation band is computed for the whole catalog (valuation runs and measurement reads persist at most one row per catalog per day). Use limit=1 for the current value. History is what makes week-over-week deltas possible.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "parameters": [ { "name": "catalogId", "in": "path", "description": "The unique identifier of the catalog. The catalog must belong to the authenticated account. Malformed (non-uuid) values are rejected with 400.", "required": true, "schema": { "type": "string", "format": "uuid" } }, { "name": "limit", "in": "query", "description": "Maximum number of valuation rows to return, latest first (default 30, max 100). limit=1 returns the current value. Invalid values are rejected with 400.", "required": false, "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 30 } } ], "responses": { "200": { "description": "The catalog's persisted valuation rows, latest first. Empty when no valuation has been persisted yet.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogValuationsResponse" } } } }, "400": { "description": "Malformed catalogId or limit", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "Missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "Catalog not found or not owned by the authenticated account", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "CatalogValuationsResponse": { "type": "object", "required": [ "status", "valuations" ], "properties": { "status": { "type": "string", "example": "success" }, "valuations": { "type": "array", "description": "Persisted valuation rows, latest first.", "items": { "type": "object", "required": [ "low", "mid", "high", "measured_song_count", "total_streams", "measured_at" ], "properties": { "low": { "type": "number", "description": "Low end of the estimated catalog value band, USD." }, "mid": { "type": "number", "description": "Midpoint of the estimated catalog value band, USD." }, "high": { "type": "number", "description": "High end of the estimated catalog value band, USD." }, "measured_song_count": { "type": "integer", "description": "Songs measured in the capture this valuation was computed from." }, "total_streams": { "type": "integer", "description": "Whole-catalog lifetime stream total at measurement time." }, "measured_at": { "type": "string", "format": "date-time", "description": "When the underlying measurement was taken." } } } } } }, "Error": { "required": [ "error", "message" ], "type": "object", "properties": { "error": { "type": "integer", "format": "int32" }, "message": { "type": "string" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Get Catalog Songs Source: https://recoupable.dev/docs/api-reference/songs/catalog-songs Retrieve songs within a specific catalog with pagination support. This endpoint joins catalog_songs with songs, song_artists, and accounts to provide comprehensive song information for a given catalog. > **Note** > This endpoint supports pagination. The Catalog Songs API also supports POST for adding songs and DELETE for removing songs. See the [Add Catalog Songs](https://recoupable.dev/docs/api-reference/songs/catalog-songs-add) and [Remove Catalog Songs](https://recoupable.dev/docs/api-reference/songs/catalog-songs-delete) endpoints. ## GET /api/catalogs/songs Full OpenAPI specification: https://recoupable.dev/docs/spec/releases.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Releases", "description": "API documentation for the Recoup platform - an AI agent platform for the music industry", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "paths": { "/api/catalogs/songs": { "get": { "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "description": "Retrieve songs within a specific catalog with pagination support. This endpoint joins catalog_songs with songs, song_artists, and accounts to provide comprehensive song information for a given catalog.", "parameters": [ { "name": "catalog_id", "in": "query", "description": "The unique identifier of the catalog to query songs for", "required": true, "schema": { "type": "string", "format": "uuid" } }, { "name": "artistName", "in": "query", "description": "Optional. Filters songs to only include those with matching artist name", "required": false, "schema": { "type": "string" } }, { "name": "page", "in": "query", "description": "Page number for pagination (default: 1)", "required": false, "schema": { "type": "integer", "default": 1 } }, { "name": "limit", "in": "query", "description": "Number of songs per page (default: 20, max: 100)", "required": false, "schema": { "type": "integer", "default": 20, "maximum": 100 } } ], "responses": { "200": { "description": "Catalog songs retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogSongsResponse" } } } }, "400": { "description": "Bad request - missing or invalid catalog_id", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogSongsErrorResponse" } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogSongsErrorResponse" } } } }, "403": { "description": "Forbidden - the catalog does not belong to the authenticated account", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogSongsErrorResponse" } } } }, "404": { "description": "Catalog not found or no songs in catalog", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogSongsErrorResponse" } } } } } } } }, "components": { "schemas": { "CatalogSongsResponse": { "type": "object", "description": "Response containing catalog songs data with pagination", "properties": { "status": { "type": "string", "enum": [ "success", "error" ], "description": "Status of the request" }, "songs": { "type": "array", "items": { "$ref": "#/components/schemas/CatalogSong" }, "description": "Array of song objects with artist information" }, "pagination": { "$ref": "#/components/schemas/CatalogSongsPagination", "description": "Pagination metadata for the response" }, "error": { "type": "string", "description": "Error message (only present if status is 'error')" } } }, "CatalogSongsErrorResponse": { "type": "object", "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Status of the request" }, "error": { "type": "string", "description": "Error message describing what went wrong" } } }, "CatalogSong": { "type": "object", "description": "A song within a catalog with its metadata and associated artists", "properties": { "catalog_id": { "type": "string", "format": "uuid", "description": "Catalog ID this song entry is associated with" }, "isrc": { "type": "string", "description": "International Standard Recording Code (primary key)" }, "name": { "type": "string", "description": "Name of the song" }, "album": { "type": "string", "description": "Name of the album the song belongs to" }, "lyrics": { "type": "string", "description": "Full lyrics of the song" }, "updated_at": { "type": "string", "format": "date-time", "description": "ISO timestamp of when the song data was last updated" }, "artists": { "type": "array", "items": { "$ref": "#/components/schemas/SongArtist" }, "description": "Array of artist objects associated with this song" } } }, "CatalogSongsPagination": { "type": "object", "description": "Pagination metadata for catalog songs response", "properties": { "total_count": { "type": "integer", "description": "Total number of songs in the catalog" }, "page": { "type": "integer", "description": "Current page number" }, "limit": { "type": "integer", "description": "Number of songs per page" }, "total_pages": { "type": "integer", "description": "Total number of pages available" } } }, "SongArtist": { "type": "object", "description": "Artist associated with a song", "properties": { "id": { "type": "string", "format": "uuid", "description": "Unique identifier for the artist account" }, "name": { "type": "string", "nullable": true, "description": "Name of the artist (can be null)" }, "timestamp": { "type": "integer", "nullable": true, "description": "Timestamp associated with the artist account (can be null)" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Add Catalog Songs Source: https://recoupable.dev/docs/api-reference/songs/catalog-songs-add Batch add songs to a catalog by ISRC. For each song, the API attempts to look up metadata via internal search. If no data is found, optional fallback fields (name, album, notes, artists) are used. > **Note** > For each song, the API attempts to look up metadata via internal search. If no data is found, optional fallback fields (name, album, notes, artists) are used. ## POST /api/catalogs/songs Full OpenAPI specification: https://recoupable.dev/docs/spec/releases.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Releases", "description": "API documentation for the Recoup platform - an AI agent platform for the music industry", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "paths": { "/api/catalogs/songs": { "post": { "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "description": "Batch add songs to a catalog by ISRC. For each song, the API attempts to look up metadata via internal search. If no data is found, optional fallback fields (name, album, notes, artists) are used.", "requestBody": { "description": "Array of songs to add to catalog", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AddCatalogSongsRequest" } } } }, "responses": { "200": { "description": "Songs added to catalog successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogSongsResponse" } } } }, "400": { "description": "Bad request - missing required fields", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogSongsErrorResponse" } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogSongsErrorResponse" } } } }, "403": { "description": "Forbidden - the catalog does not belong to the authenticated account", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogSongsErrorResponse" } } } } } } } }, "components": { "schemas": { "AddCatalogSongsRequest": { "type": "object", "required": [ "songs" ], "properties": { "songs": { "type": "array", "items": { "$ref": "#/components/schemas/AddCatalogSongInput" }, "description": "Array of songs for batch updates" } } }, "CatalogSongsResponse": { "type": "object", "description": "Response containing catalog songs data with pagination", "properties": { "status": { "type": "string", "enum": [ "success", "error" ], "description": "Status of the request" }, "songs": { "type": "array", "items": { "$ref": "#/components/schemas/CatalogSong" }, "description": "Array of song objects with artist information" }, "pagination": { "$ref": "#/components/schemas/CatalogSongsPagination", "description": "Pagination metadata for the response" }, "error": { "type": "string", "description": "Error message (only present if status is 'error')" } } }, "CatalogSongsErrorResponse": { "type": "object", "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Status of the request" }, "error": { "type": "string", "description": "Error message describing what went wrong" } } }, "AddCatalogSongInput": { "type": "object", "required": [ "catalog_id", "isrc" ], "properties": { "catalog_id": { "type": "string", "format": "uuid", "description": "Catalog ID to which the song will be added" }, "isrc": { "type": "string", "description": "Song ISRC to associate to the catalog" }, "name": { "type": "string", "description": "Optional. Applied only if internal search cannot find valid info for ISRC" }, "album": { "type": "string", "description": "Optional. Applied only if internal search cannot find valid info for ISRC" }, "notes": { "type": "string", "description": "Optional. Applied only if internal search cannot find valid info for ISRC" }, "artists": { "type": "array", "items": { "type": "string" }, "description": "Optional array of artist names. Applied only if internal search lacks info" } } }, "CatalogSong": { "type": "object", "description": "A song within a catalog with its metadata and associated artists", "properties": { "catalog_id": { "type": "string", "format": "uuid", "description": "Catalog ID this song entry is associated with" }, "isrc": { "type": "string", "description": "International Standard Recording Code (primary key)" }, "name": { "type": "string", "description": "Name of the song" }, "album": { "type": "string", "description": "Name of the album the song belongs to" }, "lyrics": { "type": "string", "description": "Full lyrics of the song" }, "updated_at": { "type": "string", "format": "date-time", "description": "ISO timestamp of when the song data was last updated" }, "artists": { "type": "array", "items": { "$ref": "#/components/schemas/SongArtist" }, "description": "Array of artist objects associated with this song" } } }, "CatalogSongsPagination": { "type": "object", "description": "Pagination metadata for catalog songs response", "properties": { "total_count": { "type": "integer", "description": "Total number of songs in the catalog" }, "page": { "type": "integer", "description": "Current page number" }, "limit": { "type": "integer", "description": "Number of songs per page" }, "total_pages": { "type": "integer", "description": "Total number of pages available" } } }, "SongArtist": { "type": "object", "description": "Artist associated with a song", "properties": { "id": { "type": "string", "format": "uuid", "description": "Unique identifier for the artist account" }, "name": { "type": "string", "nullable": true, "description": "Name of the artist (can be null)" }, "timestamp": { "type": "integer", "nullable": true, "description": "Timestamp associated with the artist account (can be null)" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Remove Catalog Songs Source: https://recoupable.dev/docs/api-reference/songs/catalog-songs-delete Batch remove songs from a catalog by ISRC. Deletes the relationship in catalog_songs for each catalog_id and ISRC pair. > **Note** > This endpoint removes the relationship in `catalog_songs` for each `catalog_id` and ISRC pair. The song data itself is not deleted. ## DELETE /api/catalogs/songs Full OpenAPI specification: https://recoupable.dev/docs/spec/releases.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Releases", "description": "API documentation for the Recoup platform - an AI agent platform for the music industry", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "paths": { "/api/catalogs/songs": { "delete": { "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "description": "Batch remove songs from a catalog by ISRC. Deletes the relationship in catalog_songs for each catalog_id and ISRC pair.", "requestBody": { "description": "Array of songs to remove from catalog", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeleteCatalogSongsRequest" } } } }, "responses": { "200": { "description": "Songs removed from catalog successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogSongsResponse" } } } }, "400": { "description": "Bad request - missing catalog_id or isrc", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogSongsErrorResponse" } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogSongsErrorResponse" } } } }, "403": { "description": "Forbidden - the catalog does not belong to the authenticated account", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatalogSongsErrorResponse" } } } } } } } }, "components": { "schemas": { "DeleteCatalogSongsRequest": { "type": "object", "required": [ "songs" ], "properties": { "songs": { "type": "array", "items": { "$ref": "#/components/schemas/DeleteCatalogSongInput" }, "description": "Array of songs for batch deletes" } } }, "CatalogSongsResponse": { "type": "object", "description": "Response containing catalog songs data with pagination", "properties": { "status": { "type": "string", "enum": [ "success", "error" ], "description": "Status of the request" }, "songs": { "type": "array", "items": { "$ref": "#/components/schemas/CatalogSong" }, "description": "Array of song objects with artist information" }, "pagination": { "$ref": "#/components/schemas/CatalogSongsPagination", "description": "Pagination metadata for the response" }, "error": { "type": "string", "description": "Error message (only present if status is 'error')" } } }, "CatalogSongsErrorResponse": { "type": "object", "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Status of the request" }, "error": { "type": "string", "description": "Error message describing what went wrong" } } }, "DeleteCatalogSongInput": { "type": "object", "required": [ "catalog_id", "isrc" ], "properties": { "catalog_id": { "type": "string", "format": "uuid", "description": "Catalog ID from which the song will be removed" }, "isrc": { "type": "string", "description": "Song ISRC to remove from the catalog" } } }, "CatalogSong": { "type": "object", "description": "A song within a catalog with its metadata and associated artists", "properties": { "catalog_id": { "type": "string", "format": "uuid", "description": "Catalog ID this song entry is associated with" }, "isrc": { "type": "string", "description": "International Standard Recording Code (primary key)" }, "name": { "type": "string", "description": "Name of the song" }, "album": { "type": "string", "description": "Name of the album the song belongs to" }, "lyrics": { "type": "string", "description": "Full lyrics of the song" }, "updated_at": { "type": "string", "format": "date-time", "description": "ISO timestamp of when the song data was last updated" }, "artists": { "type": "array", "items": { "$ref": "#/components/schemas/SongArtist" }, "description": "Array of artist objects associated with this song" } } }, "CatalogSongsPagination": { "type": "object", "description": "Pagination metadata for catalog songs response", "properties": { "total_count": { "type": "integer", "description": "Total number of songs in the catalog" }, "page": { "type": "integer", "description": "Current page number" }, "limit": { "type": "integer", "description": "Number of songs per page" }, "total_pages": { "type": "integer", "description": "Total number of pages available" } } }, "SongArtist": { "type": "object", "description": "Artist associated with a song", "properties": { "id": { "type": "string", "format": "uuid", "description": "Unique identifier for the artist account" }, "name": { "type": "string", "nullable": true, "description": "Name of the artist (can be null)" }, "timestamp": { "type": "integer", "nullable": true, "description": "Timestamp associated with the artist account (can be null)" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Generate a Music Video Source: https://recoupable.dev/docs/workflows/generate-music-video End-to-end workflow to produce a vertical, social-ready music video — image, motion, caption — from an artist and a song. This is the canonical recipe used internally by Recoup's `create-content` background task. Two paths are documented below: the **async pipeline** that an LLM agent should use, and the **manual recipe** for humans (or for cases where you want to swap a single step). ## Hand it to your LLM The fastest way to use this guide is to point an LLM at it directly — every page on this site is also served as raw markdown at the same path with a `.md` suffix, so models can fetch and follow it without parsing HTML: ``` Generate a music video for The Weeknd – "Blinding Lights" using https://docs.recoupable.dev/workflows/generate-music-video.md ``` Swap in your own artist and song. The LLM will resolve the artist, kick off the async pipeline, poll until done, and pull back the final video URL. ## Running as an agent? Use the async pipeline `POST /api/content/video` is synchronous and routinely takes 60–180s. Most agent shells (Claude Cowork, OpenAI tool calls, etc.) cap a single command at 30–60s and kill background processes when the shell exits — the manual recipe below is effectively un-runnable from those environments. Use the async path instead — same five steps, run server-side: ```bash # Trigger RUN_IDS=$(curl -sS -X POST "https://api.recoupable.dev/api/content/create" \ -H "x-api-key: $RECOUP_API_KEY" -H "Content-Type: application/json" \ -d "$(jq -n --arg artist "$ARTIST_ACCOUNT_ID" --arg template "$TEMPLATE" \ '{artist_account_id: $artist, template: $template}')" \ | jq -r '.runIds[]') # Poll (every ~10s) until COMPLETED / FAILED / CANCELED / CRASHED RUN_ID=$(echo "$RUN_IDS" | head -1) until STATUS=$(curl -sS "https://api.recoupable.dev/api/tasks/runs?runId=$RUN_ID" \ -H "x-api-key: $RECOUP_API_KEY" \ | jq -r '.runs[0].status') && \ [[ "$STATUS" =~ ^(COMPLETED|FAILED|CANCELED|CRASHED)$ ]]; do sleep 10 done # Read the output curl -sS "https://api.recoupable.dev/api/tasks/runs?runId=$RUN_ID" \ -H "x-api-key: $RECOUP_API_KEY" \ | jq '.runs[0].output' # -> { videoSourceUrl, imageUrl, captionText, template, lipsync, audio: {...} } ``` Polling fits inside short shell timeouts and survives session restarts. See [Tasks Runs](https://recoupable.dev/docs/api-reference/tasks/runs) for the full status enum (`QUEUED`, `EXECUTING`, `COMPLETED`, `FAILED`, `CANCELED`, `CRASHED`, etc.) and the `CreateContentRunOutput` schema. ### Resolving `$ARTIST_ACCOUNT_ID` `POST /api/content/create` needs the artist's `account_id`. Three calls: ```bash ORG_ID=$(curl -sS "https://api.recoupable.dev/api/organizations" \ -H "x-api-key: $RECOUP_API_KEY" | jq -r '.organizations[0].id') ARTIST_ACCOUNT_ID=$(curl -sS "https://api.recoupable.dev/api/artists?org_id=$ORG_ID" \ -H "x-api-key: $RECOUP_API_KEY" \ | jq -r --arg name "$ARTIST_NAME" '.artists[] | select(.name == $name) | .account_id') ``` The artist record exposes both `id` and `account_id` (both UUIDs). Use **`account_id`** — `id` is the artist row's primary key, `account_id` is the underlying account that owns it. The two are easy to swap; you'll get a 404 from `/api/content/create` if you pass the wrong one. ## Where the song lives Step 5 (and the async pipeline's lipsync mode) need a `song.mp3`. **Don't assume one exists, and don't assume the user has one locally.** Walk the agent through this fallback chain: 1. **Check the artist's sandbox repo first.** Each Recoup account has a backing GitHub repo. If the user has imported songs through Recoup, they live at predictable paths: ``` .openclaw/workspace/orgs/{org-slug}/artists/{artist-slug}/songs/{song-slug}/{song-slug}.mp3 /lyrics.json /clips.json ``` Discover the repo with [`GET /api/sandboxes`](https://recoupable.dev/docs/api-reference/sandboxes/list) (returns `github_repo` and a `filetree`); fetch a file with [`GET /api/sandboxes/file?path=…`](https://recoupable.dev/docs/api-reference/sandboxes/get-file). **Binary files (`.mp3`, `.png`, `.mp4`) come back base64-encoded in the `content` field — decode before writing to disk.** 2. **If no song is in the sandbox, ask the user how to proceed.** Two options to offer: - *"Want me to fetch the audio from YouTube?"* — agent downloads via `yt-dlp` (or equivalent), saves locally; user is responsible for any rights / DSP-licensing implications. - *"Want to supply the song yourself?"* — user uploads / drops a path; agent reads from there. Don't pick a path silently. The cost of fetching the wrong song from YouTube (or fetching one at all) is enough that the user should make the call. 3. **Don't fall back to "use a placeholder track."** A music video without the song is not a deliverable. ## Manual recipe (humans + targeted overrides) The rest of this page walks the same steps you can run by hand or call individually if you want to swap a single stage (different prompt for image, different motion, different caption length). ### Prerequisites - An auth credential for `api.recoupable.dev`. Two options — pick one and use it for every call below: - **API key** (`recoup_sk_…`, recommended for sandbox / agent use): pass as `-H "x-api-key: $RECOUP_API_KEY"`. - One-shot agent: `POST /api/agents/signup` with an `agent+{unique}@recoupable.com` email returns the key immediately. - Real-email signup: same endpoint with a real email mails a 6-digit code; complete with `POST /api/agents/verify`. See [Agents](https://recoupable.dev/docs/agents). - **Privy access token** (for end-user flows in chat/UI): pass as `-H "Authorization: Bearer $RECOUP_ACCESS_TOKEN"`. - The examples below use `x-api-key`. Substitute `Authorization: Bearer …` if you're using a Privy token. - `$ARTIST_NAME`, `$SONG_TITLE`, `$SONG_LYRICS_CLIP` (a 1–2 sentence mood snippet) - `$REFERENCE_IMAGE_URL` *(optional)* — an artist photo or album cover to seed the image; if your template's purpose is "show this exact image" (e.g. `album-record-store`), set this and skip image generation in step 2 - A `song.mp3` for step 5. **Don't ask the user for a local file** — fetch from the artist's repo via `/api/sandboxes/file`. - `ffmpeg` installed locally for step 5 ### Step 0: Scaffold the workspace BEFORE any API call The `VIDEO.md` checklist *is* the workflow state — tick boxes and persist values back to the frontmatter as you go. To resume later, find the first unchecked box. ```bash VIDEO_SLUG=$(echo "$SONG_TITLE" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-//; s/-$//') VIDEO_DIR="videos/$VIDEO_SLUG" mkdir -p "$VIDEO_DIR" cat > "$VIDEO_DIR/VIDEO.md" < "$VIDEO_DIR/caption.txt" # Single-line filter graph — newlines inside -filter_complex are literal characters and break the [v] label ffmpeg -y \ -stream_loop -1 -i "$VIDEO_DIR/clip.mp4" \ -i "$SONG_PATH" \ -filter_complex "[0:v]crop=ih*9/16:ih,scale=1080:1920,drawtext=fontfile=$CAPTION_FONT:textfile=$VIDEO_DIR/caption.txt:fontcolor=$CAPTION_COLOR:fontsize=$CAPTION_FONT_SIZE:x=(w-text_w)/2:y=h-text_h-120:box=1:boxcolor=$CAPTION_STROKE@0.5:boxborderw=20[v]" \ -map "[v]" -map "1:a" \ -shortest -c:v libx264 -c:a aac -pix_fmt yuv420p \ "$FINAL_PATH" ``` **After:** write `finalVideoPath`, tick the box. With every box ticked, the music video is complete. ## Step 6: Publish (optional) Once the MP4 is rendered, push it to the artist's socials with the [Connectors API](https://recoupable.dev/docs/api-reference/connectors/list). One heads-up worth knowing: - **TikTok URL ownership.** `TIKTOK_PUBLISH_VIDEO` (pull-from-URL mode) requires the source domain be verified in the TikTok dev portal. `fal.media` URLs will fail with `url_ownership_unverified` — use `TIKTOK_UPLOAD_VIDEO` instead, which accepts the same URL and uploads server-side. --- The checklist is the source of truth — if a box isn't ticked, treat the step as not run. # Create Content Source: https://recoupable.dev/docs/api-reference/content/create Trigger the content creation pipeline for an artist. Provide `artist_account_id` to identify the target artist. Validates the artist has all required files (face guide, songs) unless overridden via `songs` URLs or `images`, then triggers a background task that generates a short-form video. Returns `runIds` — an array of run IDs that can each be polled via [GET /api/tasks/runs](/docs/api-reference/tasks/runs). ## POST /api/content/create Full OpenAPI specification: https://recoupable.dev/docs/spec/content.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Content", "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/content/create": { "post": { "description": "Trigger the content creation pipeline for an artist. Provide `artist_account_id` to identify the target artist. Validates the artist has all required files (face guide, songs) unless overridden via `songs` URLs or `images`, then triggers a background task that generates a short-form video. Returns `runIds` — an array of run IDs that can each be polled via [GET /api/tasks/runs](/docs/api-reference/tasks/runs).", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "requestBody": { "description": "Content creation parameters including the target artist and optional template/workflow settings", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentCreateRequest" } } } }, "responses": { "202": { "description": "Pipeline triggered successfully. Returns `runIds` — an array of run IDs. Poll each via [GET /api/tasks/runs](/docs/api-reference/tasks/runs) to check progress.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentCreateResponse" } } } }, "400": { "description": "Validation failed — missing artist identifier, artist is missing required files, or template not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentCreateErrorResponse" } } } }, "401": { "description": "Unauthorized — invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentErrorResponse" } } } }, "404": { "description": "Artist not found — the provided artist_account_id does not match any artist", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentErrorResponse" } } } } } } } }, "components": { "schemas": { "ContentCreateRequest": { "type": "object", "description": "Parameters for triggering the content creation pipeline.", "properties": { "artist_account_id": { "type": "string", "format": "uuid", "description": "UUID of the artist account to create content for. Use [GET /api/artists](/docs/api-reference/artists/list) to find artist account IDs.", "example": "1873859c-dd37-4e9a-9bac-80d3558527a9" }, "template": { "type": "string", "description": "Optional template ID for content generation. Defines the visual style, scene, and prompt configuration. When omitted, the pipeline runs in malleable mode using only the params you provide. See GET /api/content/templates for available options.", "example": "artist-caption-stage" }, "lipsync": { "type": "boolean", "description": "Whether to generate video with lip-synced audio. When `true`, uses an audio-to-video model that bakes audio into the video for lip movement. When `false`, generates video from the image alone and overlays audio in post. If omitted, the template's default workflow is used.", "example": false }, "caption_length": { "type": "string", "enum": [ "none", "short", "medium", "long" ], "description": "Controls the length of the generated caption text. `none` skips caption generation. `short` produces 1-2 lines (punchy, minimal). `medium` produces 1-2 sentences. `long` produces a paragraph (stream of consciousness style). Defaults to `none`.", "default": "none", "example": "none" }, "upscale": { "type": "boolean", "description": "Whether to upscale the generated image and video for higher resolution and detail. Adds approximately 2 minutes to the pipeline. Defaults to `false`.", "default": false, "example": false }, "batch": { "type": "integer", "minimum": 1, "maximum": 30, "description": "Number of videos to generate in parallel. Each video independently selects a random reference image, song clip, and mood variation. The response always returns `runIds` as an array. Defaults to `1`.", "default": 1, "example": 1 }, "songs": { "type": "array", "items": { "type": "string" }, "description": "Optional list of song slugs or public URLs to use for the audio track. Song slugs match filenames without extension from the artist's `songs/` directory (e.g. `\"hiccups\"` for `hiccups.mp3`). Public URLs (e.g. `\"https://example.com/my-song.mp3\"`) are downloaded, transcribed, and clipped directly — bypassing the Git repo. When omitted, all songs in the artist's repo are eligible.", "example": [ "hiccups", "https://example.com/unreleased-track.mp3" ] }, "images": { "type": "array", "items": { "type": "string", "format": "uri" }, "description": "Optional list of public image URLs to use as face guides instead of the artist's default `face-guide.png` from their GitHub repo. The first image is used as the primary face guide. Useful when the caller wants to override the default face reference.", "example": [ "https://example.com/face.png" ] } }, "required": [ "artist_account_id" ] }, "ContentCreateResponse": { "type": "object", "required": [ "runIds", "status", "artist_account_id" ], "description": "Confirmation that the content creation pipeline has been triggered. Always returns `runIds` as an array — even for a single run, it contains one element.", "properties": { "runIds": { "type": "array", "items": { "type": "string" }, "description": "Array of run IDs. Poll each via [GET /api/tasks/runs](/docs/api-reference/tasks/runs). For single runs this contains one element.", "example": [ "run_abc123def456" ] }, "status": { "type": "string", "enum": [ "triggered" ], "description": "Indicates the pipeline has been triggered" }, "artist_account_id": { "type": "string", "format": "uuid", "description": "UUID of the artist account the pipeline is running for", "example": "1873859c-dd37-4e9a-9bac-80d3558527a9" }, "template": { "type": [ "string", "null" ], "description": "Template ID when a preset pipeline is used; null in malleable mode.", "example": null }, "lipsync": { "type": "boolean", "description": "Whether lip-sync mode is enabled", "example": false }, "failed": { "type": "integer", "description": "Number of triggers that failed. Only present when some triggers failed.", "example": 0 } } }, "ContentCreateErrorResponse": { "type": "object", "required": [ "error" ], "description": "Returned when the artist is missing required files or the template is not found. Includes actionable instructions for resolving each issue.", "properties": { "error": { "type": "string", "description": "Human-readable error summary", "example": "Artist 'new-artist' is not ready for content creation" }, "ready": { "type": "boolean", "description": "Always `false` when this error is returned", "example": false }, "missing": { "type": "array", "description": "List of missing files with severity and fix instructions. Only present when the artist fails validation.", "items": { "$ref": "#/components/schemas/ContentMissingFile" } }, "available_templates": { "type": "array", "description": "List of valid template names. Only present when the requested template was not found.", "items": { "type": "string" }, "example": [ "artist-caption-bedroom", "artist-caption-outside", "artist-caption-stage" ] } } }, "ContentErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "description": "Error message describing what went wrong", "example": "Unauthorized" } } }, "ContentMissingFile": { "type": "object", "required": [ "file", "severity", "description", "fix" ], "description": "A missing file that prevents or degrades content creation.", "properties": { "file": { "type": "string", "description": "Relative path of the missing file within the artist directory", "example": "context/images/face-guide.png" }, "severity": { "type": "string", "enum": [ "required", "recommended" ], "description": "`required` means the pipeline will fail without this file. `recommended` means the pipeline will run but output quality is degraded.", "example": "required" }, "description": { "type": "string", "description": "What this file is used for in the pipeline", "example": "Face guide image used for AI image generation" }, "fix": { "type": "string", "description": "Actionable instructions for creating or adding the missing file", "example": "Generate a face guide using fal-ai/nano-banana-pro/edit with 2-3 reference photos of the artist" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Generate Image Source: https://recoupable.dev/docs/api-reference/content/generate-image Generate an image from a text prompt. Pass `image_urls` to edit those images with the prompt instead of generating a new one. Returns the image URL. ## POST /api/content/image Full OpenAPI specification: https://recoupable.dev/docs/spec/content.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Content", "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/content/image": { "post": { "description": "Generate an image from a text prompt. Pass `image_urls` to edit those images with the prompt instead of generating a new one. Returns the image URL.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "requestBody": { "description": "Image generation parameters", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentCreateImageRequest" } } } }, "responses": { "200": { "description": "Image generated successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentCreateImageResponse" } } } }, "400": { "description": "Validation failed — invalid or missing request body fields", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentErrorResponse" } } } }, "401": { "description": "Unauthorized — invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentErrorResponse" } } } }, "402": { "description": "Insufficient credits. Charged per generated image at the pinned model's rate ($0.01 per image). A free-tier account without sufficient credits receives a 402.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentErrorResponse" } } } } } } } }, "components": { "schemas": { "ContentCreateImageRequest": { "type": "object", "properties": { "prompt": { "type": "string", "description": "Text prompt used to generate or edit the image" }, "image_urls": { "type": "array", "items": { "type": "string", "format": "uri" }, "minItems": 1, "maxItems": 10, "description": "Reference images to edit (1-10 HTTP(S) URLs). When provided, edits these images with `prompt` instead of generating a new one." }, "num_images": { "type": "integer", "minimum": 1, "maximum": 4, "default": 1, "description": "Number of images to generate (1-4)" }, "aspect_ratio": { "type": "string", "enum": [ "21:9", "16:9", "4:3", "3:2", "1:1", "2:3", "3:4", "9:16", "9:21" ], "description": "Output aspect ratio. If omitted, the model chooses automatically based on the request." }, "output_format": { "type": "string", "enum": [ "jpeg", "png", "webp" ], "default": "webp", "description": "Format of the generated image" }, "sync_mode": { "type": "boolean", "default": false, "description": "If true, the image is returned as a data URI and is not persisted in the request history" } } }, "ContentCreateImageResponse": { "type": "object", "required": [ "imageUrl", "images" ], "properties": { "imageUrl": { "type": "string", "format": "uri", "description": "URL of the first generated image" }, "images": { "type": "array", "items": { "type": "string", "format": "uri" }, "description": "URLs of all generated images. Contains one element when num_images is 1." } } }, "ContentErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "description": "Error message describing what went wrong", "example": "Unauthorized" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Generate Video Source: https://recoupable.dev/docs/api-reference/content/generate-video Generate a video from a text prompt, optionally animating a still image (`image_url`) or transitioning between two images (`image_url` + `end_image_url`). ## POST /api/content/video Full OpenAPI specification: https://recoupable.dev/docs/spec/content.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Content", "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/content/video": { "post": { "description": "Generate a video from a text prompt, optionally animating a still image (`image_url`) or transitioning between two images (`image_url` + `end_image_url`).", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "requestBody": { "description": "Video generation parameters", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentCreateVideoRequest" } } } }, "responses": { "200": { "description": "Video generated successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentCreateVideoResponse" } } } }, "400": { "description": "Validation failed — invalid or missing request body fields", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentErrorResponse" } } } }, "401": { "description": "Unauthorized — invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentErrorResponse" } } } }, "402": { "description": "Insufficient credits. Charged per second of output at the pinned model's rate ($0.08/s, MiniMax H3 Max). A free-tier account without sufficient credits receives a 402.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentErrorResponse" } } } } } } } }, "components": { "schemas": { "ContentCreateVideoRequest": { "type": "object", "required": [ "prompt", "prompt_expansion_mode" ], "properties": { "prompt": { "type": "string", "minLength": 1, "maxLength": 50000, "description": "Text prompt for video generation" }, "prompt_expansion_mode": { "type": "string", "default": "balanced", "enum": [ "balanced", "quality" ], "description": "How much effort to spend rewriting the prompt before generation. `balanced` returns in about a second. `quality` spends up to ~30s on a richer prompt." }, "image_url": { "type": "string", "format": "uri", "description": "URL of the image to use as the first frame. When provided, the output aspect ratio follows this image. Generate an image first via POST /api/content/image if needed." }, "end_image_url": { "type": "string", "format": "uri", "description": "Optional URL of the image to use as the last frame, for first-to-last keyframe generation" }, "duration": { "type": "integer", "minimum": 5, "maximum": 15, "default": 5, "description": "Duration of the generated video, in seconds" }, "resolution": { "type": "string", "enum": [ "480P", "768P" ], "default": "768P", "description": "The native generation resolution of the video" }, "seed": { "type": "integer", "description": "Random seed. A random seed is selected when omitted." }, "enable_safety_checker": { "type": "boolean", "default": true, "description": "If true, the safety checker will be enabled" }, "sync_mode": { "type": "boolean", "default": false, "description": "Return the generated video as base64 instead of a CDN URL" } } }, "ContentCreateVideoResponse": { "type": "object", "required": [ "videoUrl" ], "properties": { "videoUrl": { "type": "string", "format": "uri", "description": "URL of the generated video" } } }, "ContentErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "description": "Error message describing what went wrong", "example": "Unauthorized" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Generate Caption Source: https://recoupable.dev/docs/api-reference/content/generate-caption Generate a short caption from a topic. Returns the text content and default styling (font, color, size). ## POST /api/content/caption Full OpenAPI specification: https://recoupable.dev/docs/spec/content.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Content", "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/content/caption": { "post": { "description": "Generate a short caption from a topic. Returns the text content and default styling (font, color, size).", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "requestBody": { "description": "Text generation parameters", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentCreateTextRequest" } } } }, "responses": { "200": { "description": "Text generated successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentCreateTextResponse" } } } }, "400": { "description": "Validation failed — invalid or missing request body fields", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentErrorResponse" } } } }, "401": { "description": "Unauthorized — invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentErrorResponse" } } } } } } } }, "components": { "schemas": { "ContentCreateTextRequest": { "type": "object", "required": [ "topic" ], "properties": { "template": { "type": "string", "description": "Optional template ID. When provided, injects the template's caption guide (tone, rules, formats) and examples into the LLM prompt. Caller's topic is still required. See [GET /api/content/templates](/docs/api-reference/content/templates) for available options." }, "topic": { "type": "string", "description": "The subject or theme for caption generation" }, "length": { "type": "string", "enum": [ "short", "medium", "long" ], "default": "short", "description": "Desired text length" } } }, "ContentCreateTextResponse": { "type": "object", "required": [ "content", "color", "borderColor", "maxFontSize" ], "properties": { "content": { "type": "string", "description": "Generated on-screen text content" }, "font": { "type": [ "string", "null" ], "description": "Font name for the text, or null for default" }, "color": { "type": "string", "description": "Text color as a CSS color value", "example": "white" }, "borderColor": { "type": "string", "description": "Text border/stroke color as a CSS color value", "example": "black" }, "maxFontSize": { "type": "number", "description": "Maximum font size in pixels", "example": 42 } } }, "ContentErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "description": "Error message describing what went wrong", "example": "Unauthorized" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Transcribe Audio Source: https://recoupable.dev/docs/api-reference/content/transcribe-audio Transcribe audio into text with word-level timestamps. Pass one or more audio file URLs in `audio_urls`. Returns the full transcript and an array of timed segments. ## POST /api/content/transcribe Full OpenAPI specification: https://recoupable.dev/docs/spec/content.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Content", "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/content/transcribe": { "post": { "description": "Transcribe audio into text with word-level timestamps. Pass one or more audio file URLs in `audio_urls`. Returns the full transcript and an array of timed segments.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "requestBody": { "description": "Audio transcription parameters", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentCreateAudioRequest" } } } }, "responses": { "200": { "description": "Song transcribed successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentCreateAudioResponse" } } } }, "400": { "description": "Validation failed — invalid or missing request body fields", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentErrorResponse" } } } }, "401": { "description": "Unauthorized — invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentErrorResponse" } } } } } } } }, "components": { "schemas": { "ContentCreateAudioRequest": { "type": "object", "required": [ "audio_urls" ], "properties": { "audio_urls": { "type": "array", "items": { "type": "string", "format": "uri" }, "minItems": 1, "description": "Audio file URLs to transcribe" }, "language": { "type": "string", "default": "en", "description": "Language code for transcription (e.g. en, es, fr). Defaults to en." }, "chunk_level": { "type": "string", "enum": [ "none", "segment", "word" ], "default": "word", "description": "Granularity of timestamp chunks. Defaults to word-level." }, "diarize": { "type": "boolean", "default": false, "description": "Enable speaker diarization. Defaults to false." }, "model": { "type": "string", "description": "fal.ai model ID. Defaults to fal-ai/whisper" } } }, "ContentCreateAudioResponse": { "type": "object", "required": [ "audioUrl", "fullLyrics", "segments", "segmentCount" ], "properties": { "audioUrl": { "type": "string", "description": "URL of the transcribed audio" }, "fullLyrics": { "type": "string", "description": "Complete transcribed lyrics as a single string" }, "segments": { "type": "array", "items": { "$ref": "#/components/schemas/ContentCreateAudioSegment" }, "description": "Timestamped lyric segments" }, "segmentCount": { "type": "number", "description": "Total number of segments returned" } } }, "ContentErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "description": "Error message describing what went wrong", "example": "Unauthorized" } } }, "ContentCreateAudioSegment": { "type": "object", "required": [ "start", "end", "text" ], "properties": { "start": { "type": "number", "description": "Segment start time in seconds" }, "end": { "type": "number", "description": "Segment end time in seconds" }, "text": { "type": "string", "description": "Transcribed text for this segment" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Edit Content Source: https://recoupable.dev/docs/api-reference/content/edit Apply ffmpeg edits to a video — trim, crop, resize, or overlay text. Pass a `template` for a preset edit pipeline, or build your own with an `operations` array. ## PATCH /api/content Full OpenAPI specification: https://recoupable.dev/docs/spec/content.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Content", "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/content": { "patch": { "description": "Apply ffmpeg edits to a video — trim, crop, resize, or overlay text. Pass a `template` for a preset edit pipeline, or build your own with an `operations` array.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "requestBody": { "description": "Edit parameters", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentCreateEditRequest" } } } }, "responses": { "202": { "description": "Edit task triggered successfully. Poll via [GET /api/tasks/runs](/docs/api-reference/tasks/runs) using the returned `runId`.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentCreateEditResponse" } } } }, "400": { "description": "Validation failed — invalid or missing request body fields", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentErrorResponse" } } } }, "401": { "description": "Unauthorized — invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentErrorResponse" } } } } } } } }, "components": { "schemas": { "ContentCreateEditRequest": { "type": "object", "required": [ "video_url" ], "description": "Requires a video URL. Operations are applied sequentially via ffmpeg.", "properties": { "video_url": { "type": "string", "format": "uri", "description": "Input video URL (required)" }, "template": { "type": "string", "description": "Template name for deterministic edit config. If provided, operations are read from the template. See [GET /api/content/templates](/docs/api-reference/content/templates) for available options." }, "operations": { "type": "array", "description": "Array of edit operations to apply in order. Required if template is not provided.", "items": { "type": "object", "required": [ "type" ], "properties": { "type": { "type": "string", "enum": [ "trim", "crop", "resize", "overlay_text" ], "description": "Operation type" }, "start": { "type": "number", "description": "Start time in seconds (trim)" }, "duration": { "type": "number", "description": "Duration in seconds (trim)" }, "aspect": { "type": "string", "description": "Aspect ratio e.g. 9:16 (crop)" }, "width": { "type": "integer", "description": "Width in pixels (crop/resize)" }, "height": { "type": "integer", "description": "Height in pixels (crop/resize)" }, "content": { "type": "string", "description": "Text content (overlay_text)" }, "font": { "type": "string", "description": "Font name (overlay_text)" }, "color": { "type": "string", "description": "Text color (overlay_text)" }, "stroke_color": { "type": "string", "description": "Text stroke color (overlay_text)" }, "max_font_size": { "type": "number", "description": "Maximum font size (overlay_text)" }, "position": { "type": "string", "enum": [ "top", "center", "bottom" ], "description": "Text position (overlay_text)" } } } }, "output_format": { "type": "string", "enum": [ "mp4", "webm", "mov" ], "default": "mp4", "description": "Output format" } } }, "ContentCreateEditResponse": { "type": "object", "required": [ "runId", "status" ], "properties": { "runId": { "type": "string", "description": "Background task run ID. Poll via [GET /api/tasks/runs](/docs/api-reference/tasks/runs) to check progress." }, "status": { "type": "string", "enum": [ "triggered" ], "description": "Status of the edit task" } } }, "ContentErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "description": "Error message describing what went wrong", "example": "Unauthorized" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Upscale Content Source: https://recoupable.dev/docs/api-reference/content/upscale Upscale an image or video to higher resolution. Pass the URL and specify the type. Returns the upscaled URL. ## POST /api/content/upscale Full OpenAPI specification: https://recoupable.dev/docs/spec/content.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Content", "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/content/upscale": { "post": { "description": "Upscale an image or video to higher resolution. Pass the URL and specify the type. Returns the upscaled URL.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "requestBody": { "description": "Upscale parameters", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentCreateUpscaleRequest" } } } }, "responses": { "200": { "description": "Media upscaled successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentCreateUpscaleResponse" } } } }, "400": { "description": "Validation failed — invalid or missing request body fields", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentErrorResponse" } } } }, "401": { "description": "Unauthorized — invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentErrorResponse" } } } } } } } }, "components": { "schemas": { "ContentCreateUpscaleRequest": { "type": "object", "required": [ "url", "type" ], "properties": { "url": { "type": "string", "format": "uri", "description": "URL of the image or video to upscale" }, "type": { "type": "string", "enum": [ "image", "video" ], "description": "Whether the input is an image or video" }, "upscale_factor": { "type": "number", "minimum": 1, "maximum": 4, "default": 2, "description": "Upscale multiplier (1-4x). Defaults to 2x." }, "target_resolution": { "type": "string", "enum": [ "720p", "1080p", "1440p", "2160p" ], "description": "Target output resolution. When provided, overrides upscale_factor with a resolution-based upscale." } } }, "ContentCreateUpscaleResponse": { "type": "object", "required": [ "url" ], "properties": { "url": { "type": "string", "format": "uri", "description": "URL of the upscaled image or video" } } }, "ContentErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "description": "Error message describing what went wrong", "example": "Unauthorized" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Analyze Video Source: https://recoupable.dev/docs/api-reference/content/analyze-video Analyze a video and answer questions about it. Pass a video URL and a text prompt — for example, "Describe what happens" or "Rate the visual quality 1-10." Returns the generated text. ## POST /api/content/analyze Full OpenAPI specification: https://recoupable.dev/docs/spec/content.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Content", "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/content/analyze": { "post": { "description": "Analyze a video and answer questions about it. Pass a video URL and a text prompt — for example, \"Describe what happens\" or \"Rate the visual quality 1-10.\" Returns the generated text.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "requestBody": { "description": "Video analysis parameters", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentCreateAnalyzeRequest" } } } }, "responses": { "200": { "description": "Video analyzed successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentCreateAnalyzeResponse" } } } }, "400": { "description": "Validation failed — invalid or missing request body fields", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentErrorResponse" } } } }, "401": { "description": "Unauthorized — invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentErrorResponse" } } } }, "502": { "description": "Video analysis failed upstream", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentErrorResponse" } } } } } } } }, "components": { "schemas": { "ContentCreateAnalyzeRequest": { "type": "object", "required": [ "video_url", "prompt" ], "properties": { "video_url": { "type": "string", "format": "uri", "description": "Publicly accessible URL of the video to analyze. Supported formats: MP4, MOV, AVI, and other FFmpeg-compatible formats. Maximum duration: 1 hour." }, "prompt": { "type": "string", "maxLength": 2000, "description": "A text prompt that guides the analysis. Can be instructive, descriptive, or phrased as a question. Examples: \"Describe the key moments in this video\", \"Generate 5 SEO keywords for this video\"." }, "temperature": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.2, "description": "Controls the randomness of the text output. Lower values produce more focused results. Defaults to `0.2`." }, "max_tokens": { "type": "integer", "minimum": 1, "maximum": 4096, "description": "Maximum number of tokens to generate. If omitted, uses the model default." } } }, "ContentCreateAnalyzeResponse": { "type": "object", "required": [ "text" ], "properties": { "text": { "type": "string", "description": "The generated analysis text based on the video and prompt." }, "finish_reason": { "type": "string", "enum": [ "stop", "length" ], "nullable": true, "description": "`stop` if the generation completed normally. `length` if truncated at the token limit." }, "usage": { "type": "object", "nullable": true, "properties": { "output_tokens": { "type": "integer", "description": "Number of tokens in the generated text." } } } } }, "ContentErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "description": "Error message describing what went wrong", "example": "Unauthorized" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # List Templates Source: https://recoupable.dev/docs/api-reference/content/templates List all available content creation templates. Templates are optional — every content primitive works without one. When you do use a template, it provides a complete creative recipe: image prompts, video motion config, caption style rules, and edit operations. Returns template ID and description only — enough to pick the right one. ## GET /api/content/templates Full OpenAPI specification: https://recoupable.dev/docs/spec/content.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Content", "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/content/templates": { "get": { "description": "List all available content creation templates. Templates are optional — every content primitive works without one. When you do use a template, it provides a complete creative recipe: image prompts, video motion config, caption style rules, and edit operations. Returns template ID and description only — enough to pick the right one.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "responses": { "200": { "description": "Templates retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentTemplatesResponse" } } } }, "401": { "description": "Unauthorized — invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentErrorResponse" } } } } } } } }, "components": { "schemas": { "ContentTemplatesResponse": { "type": "object", "required": [ "templates" ], "description": "List of available content creation templates.", "properties": { "templates": { "type": "array", "items": { "$ref": "#/components/schemas/ContentTemplate" } } } }, "ContentErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "description": "Error message describing what went wrong", "example": "Unauthorized" } } }, "ContentTemplate": { "type": "object", "required": [ "id", "description" ], "description": "A content creation template — a complete creative recipe defining visual style, composition, caption rules, and edit operations. Templates are optional; all primitives work without one.", "properties": { "id": { "type": "string", "description": "Template identifier. Pass this as the template field in content primitive requests or POST /api/content/create.", "example": "artist-caption-bedroom" }, "description": { "type": "string", "description": "Human-readable description of the template's visual style", "example": "Moody bedroom selfie. Artist on camera with deadpan expression, purple LED lighting, dark room. Short blunt captions in lowercase. Vertical 9:16 video, 8 seconds. Best for: introspective songs, vulnerable moments, daily content. Requires: face image, audio." } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Get Template Detail Source: https://recoupable.dev/docs/api-reference/content/template-detail Get the full configuration for a specific content creation template. Returns the complete creative recipe including image prompts, video motion config, caption style rules, and edit operations. ## GET /api/content/templates/{id} Full OpenAPI specification: https://recoupable.dev/docs/spec/content.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Content", "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/content/templates/{id}": { "get": { "description": "Get the full configuration for a specific content creation template. Returns the complete creative recipe including image prompts, video motion config, caption style rules, and edit operations.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "parameters": [ { "name": "id", "in": "path", "description": "Template identifier", "required": true, "schema": { "type": "string", "example": "artist-caption-bedroom" } } ], "responses": { "200": { "description": "Template detail retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentTemplateDetail" } } } }, "401": { "description": "Unauthorized — invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentErrorResponse" } } } }, "404": { "description": "Template not found", "content": { "application/json": { "schema": { "type": "object", "properties": { "status": { "type": "string", "example": "error" }, "error": { "type": "string", "example": "Template not found" } } } } } } } } } }, "components": { "schemas": { "ContentTemplateDetail": { "type": "object", "required": [ "id", "description" ], "description": "Full configuration for a content creation template including image prompts, video motion config, caption style rules, and edit operations.", "properties": { "id": { "type": "string", "description": "Template identifier", "example": "artist-caption-bedroom" }, "description": { "type": "string", "description": "Human-readable description of the template", "example": "Moody bedroom selfie. Artist on camera with deadpan expression, purple LED lighting, dark room." }, "image": { "type": "object", "description": "Image generation configuration", "properties": { "prompt": { "type": "string", "description": "Default image prompt used when no caller prompt is provided" }, "reference_images": { "type": "array", "items": { "type": "string", "format": "uri" }, "description": "Reference image URLs for style conditioning" }, "style_rules": { "type": "object", "description": "Style constraints (lighting, colors, composition)" } } }, "video": { "type": "object", "description": "Video generation configuration", "properties": { "moods": { "type": "array", "items": { "type": "string" }, "description": "Mood descriptors for motion prompt generation" }, "movements": { "type": "array", "items": { "type": "string" }, "description": "Camera/subject movement descriptors" } } }, "caption": { "type": "object", "description": "Caption generation configuration", "properties": { "guide": { "type": "object", "description": "Caption style guide", "properties": { "tone": { "type": "string", "description": "Voice and tone direction" }, "rules": { "type": "array", "items": { "type": "string" }, "description": "Style rules for caption generation" }, "formats": { "type": "array", "items": { "type": "string" }, "description": "Allowed caption formats" } } }, "examples": { "type": "array", "items": { "type": "string" }, "description": "Example captions for few-shot prompting" } } }, "edit": { "type": "object", "description": "Post-processing edit operations", "properties": { "operations": { "type": "array", "description": "Default edit operations applied during post-processing", "items": { "type": "object", "required": [ "type" ], "properties": { "type": { "type": "string", "enum": [ "trim", "crop", "resize", "overlay_text", "mux_audio" ], "description": "Operation type" }, "start": { "type": "number", "description": "Start time in seconds (trim)" }, "duration": { "type": "number", "description": "Duration in seconds (trim)" }, "aspect": { "type": "string", "description": "Aspect ratio e.g. 9:16 (crop)" }, "width": { "type": "integer", "description": "Width in pixels (crop/resize)" }, "height": { "type": "integer", "description": "Height in pixels (crop/resize)" }, "content": { "type": "string", "description": "Text content (overlay_text)" }, "font": { "type": "string", "description": "Font name (overlay_text)" }, "color": { "type": "string", "description": "Text color (overlay_text)" }, "stroke_color": { "type": "string", "description": "Text stroke color (overlay_text)" }, "max_font_size": { "type": "number", "description": "Maximum font size (overlay_text)" }, "position": { "type": "string", "enum": [ "top", "center", "bottom" ], "description": "Text position (overlay_text)" }, "audio_url": { "type": "string", "format": "uri", "description": "Audio URL to mux (mux_audio)" }, "replace": { "type": "boolean", "description": "Replace existing audio (mux_audio)" } } } } } } } }, "ContentErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "description": "Error message describing what went wrong", "example": "Unauthorized" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Validate Artist Source: https://recoupable.dev/docs/api-reference/content/validate Check whether an artist has all the required files to run the content creation pipeline. Returns a structured report of each required and recommended file with its status. Required files must be present or the pipeline will fail. Recommended files improve output quality but are not strictly necessary. Provide `artist_account_id` as a query parameter. ## GET /api/content/validate Full OpenAPI specification: https://recoupable.dev/docs/spec/content.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Content", "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/content/validate": { "get": { "description": "Check whether an artist has all the required files to run the content creation pipeline. Returns a structured report of each required and recommended file with its status. Required files must be present or the pipeline will fail. Recommended files improve output quality but are not strictly necessary. Provide `artist_account_id` as a query parameter.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "parameters": [ { "name": "artist_account_id", "in": "query", "description": "UUID of the artist account to validate. Use [GET /api/artists](/docs/api-reference/artists/list) to find artist account IDs.", "required": true, "schema": { "type": "string", "format": "uuid", "example": "1873859c-dd37-4e9a-9bac-80d3558527a9" } } ], "responses": { "200": { "description": "Validation completed. Check the `ready` field to determine if the artist can run the pipeline.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentValidateResponse" } } } }, "400": { "description": "Bad request — artist_account_id is required", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentErrorResponse" } } } }, "401": { "description": "Unauthorized — invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentErrorResponse" } } } }, "404": { "description": "Artist not found — the provided artist_account_id does not match any artist", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentErrorResponse" } } } } } } } }, "components": { "schemas": { "ContentValidateResponse": { "type": "object", "required": [ "ready", "artist_account_id" ], "description": "Validation report for an artist's content creation readiness.", "properties": { "ready": { "type": "boolean", "description": "`true` if the artist has all required files and the pipeline can run. `false` if required files are missing.", "example": true }, "artist_account_id": { "type": "string", "format": "uuid", "description": "UUID of the artist account that was validated", "example": "1873859c-dd37-4e9a-9bac-80d3558527a9" }, "songs": { "type": "integer", "description": "Number of songs found in the artist's `songs/` directory", "example": 17 }, "templates": { "type": "array", "description": "Available templates that can be used with this artist", "items": { "type": "string" }, "example": [ "artist-caption-bedroom", "artist-caption-outside", "artist-caption-stage" ] }, "checks": { "type": "object", "description": "Per-file validation results. Only present when `ready` is `true`.", "properties": { "face_guide": { "$ref": "#/components/schemas/ContentValidationCheck" }, "artist_context": { "$ref": "#/components/schemas/ContentValidationCheck" }, "audience_context": { "$ref": "#/components/schemas/ContentValidationCheck" }, "songs": { "allOf": [ { "$ref": "#/components/schemas/ContentValidationCheck" }, { "type": "object", "properties": { "count": { "type": "integer", "description": "Number of songs found", "example": 17 } } } ] } } }, "missing": { "type": "array", "description": "List of missing files with severity and fix instructions. Only present when `ready` is `false`.", "items": { "$ref": "#/components/schemas/ContentMissingFile" } } } }, "ContentErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "description": "Error message describing what went wrong", "example": "Unauthorized" } } }, "ContentValidationCheck": { "type": "object", "required": [ "status" ], "description": "Status of a single validation check.", "properties": { "status": { "type": "string", "enum": [ "ok", "missing", "warning" ], "description": "Whether the file was found", "example": "ok" } } }, "ContentMissingFile": { "type": "object", "required": [ "file", "severity", "description", "fix" ], "description": "A missing file that prevents or degrades content creation.", "properties": { "file": { "type": "string", "description": "Relative path of the missing file within the artist directory", "example": "context/images/face-guide.png" }, "severity": { "type": "string", "enum": [ "required", "recommended" ], "description": "`required` means the pipeline will fail without this file. `recommended` means the pipeline will run but output quality is degraded.", "example": "required" }, "description": { "type": "string", "description": "What this file is used for in the pipeline", "example": "Face guide image used for AI image generation" }, "fix": { "type": "string", "description": "Actionable instructions for creating or adding the missing file", "example": "Generate a face guide using fal-ai/nano-banana-pro/edit with 2-3 reference photos of the artist" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Estimate Cost Source: https://recoupable.dev/docs/api-reference/content/estimate Estimate the cost of running the content creation pipeline. Calculates per-step and per-video costs based on current pricing. Supports comparing multiple workflow profiles (e.g., premium vs. budget) and projecting batch costs. This endpoint is informational only — it does not trigger any pipeline execution or spend credits. ## GET /api/content/estimate Full OpenAPI specification: https://recoupable.dev/docs/spec/content.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Content", "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/content/estimate": { "get": { "description": "Estimate the cost of running the content creation pipeline. Calculates per-step and per-video costs based on current pricing. Supports comparing multiple workflow profiles (e.g., premium vs. budget) and projecting batch costs. This endpoint is informational only — it does not trigger any pipeline execution or spend credits.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "parameters": [ { "name": "lipsync", "in": "query", "description": "Which workflow to estimate. `false` for image-to-video, `true` for audio-to-video. If omitted, estimates the default image-to-video workflow.", "required": false, "schema": { "type": "boolean", "example": false } }, { "name": "batch", "in": "query", "description": "Number of videos to project costs for. Use this to answer questions like \"how much would 30 videos cost?\"", "required": false, "schema": { "type": "integer", "minimum": 1, "default": 1, "example": 1 } }, { "name": "compare", "in": "query", "description": "When `true`, returns estimates for all available workflow profiles (premium, budget, mid) for side-by-side comparison.", "required": false, "schema": { "type": "boolean", "default": false, "example": false } } ], "responses": { "200": { "description": "Cost estimate calculated successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentEstimateResponse" } } } }, "401": { "description": "Unauthorized — invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentErrorResponse" } } } } } } } }, "components": { "schemas": { "ContentEstimateResponse": { "type": "object", "required": [ "workflows" ], "description": "Cost estimates based on current pricing. When `compare` is `false`, the `workflows` array contains a single entry. When `true`, it contains all available profiles.", "properties": { "workflows": { "type": "array", "description": "One or more workflow cost breakdowns", "items": { "$ref": "#/components/schemas/ContentWorkflowEstimate" } }, "comparison": { "description": "Side-by-side summary. Only present when `compare` is `true` and multiple workflows are returned.", "$ref": "#/components/schemas/ContentEstimateComparison" }, "batch": { "type": "object", "description": "Batch cost projection. Only present when `batch` > 1.", "properties": { "count": { "type": "integer", "description": "Number of videos in the batch", "example": 30 }, "cheapestTotal": { "type": "number", "format": "float", "description": "Total cost for the batch using the cheapest workflow", "example": 5.4 }, "mostExpensiveTotal": { "type": "number", "format": "float", "description": "Total cost for the batch using the most expensive workflow", "example": 28.5 } } } } }, "ContentErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "description": "Error message describing what went wrong", "example": "Unauthorized" } } }, "ContentWorkflowEstimate": { "type": "object", "required": [ "name", "perVideo", "steps" ], "description": "Cost breakdown for a single workflow profile.", "properties": { "name": { "type": "string", "description": "Workflow profile name", "example": "Current (image-to-video)" }, "perVideo": { "type": "number", "format": "float", "description": "Total estimated cost per video in USD", "example": 0.82 }, "steps": { "type": "array", "description": "Per-step cost breakdown showing where the money goes", "items": { "$ref": "#/components/schemas/ContentStepEstimate" } }, "costBreakdown": { "type": "object", "description": "Summary of the biggest cost driver", "properties": { "mostExpensiveStep": { "type": "string", "description": "Name of the step that costs the most", "example": "Generate Video" }, "mostExpensivePercent": { "type": "integer", "description": "Percentage of total cost from the most expensive step", "example": 68 } } } } }, "ContentEstimateComparison": { "type": "object", "required": [ "cheapest", "cheapestPerVideo", "mostExpensive", "mostExpensivePerVideo", "savingsPercent" ], "description": "Summary comparing the cheapest and most expensive workflow options.", "properties": { "cheapest": { "type": "string", "description": "Name of the cheapest workflow profile", "example": "Budget (no upscale, LTX video)" }, "cheapestPerVideo": { "type": "number", "format": "float", "description": "Cost per video for the cheapest workflow", "example": 0.18 }, "mostExpensive": { "type": "string", "description": "Name of the most expensive workflow profile", "example": "Current (audio-to-video)" }, "mostExpensivePerVideo": { "type": "number", "format": "float", "description": "Cost per video for the most expensive workflow", "example": 0.95 }, "savingsPercent": { "type": "integer", "description": "Percentage savings between the cheapest and most expensive workflows", "example": 81 } } }, "ContentStepEstimate": { "type": "object", "required": [ "name", "model", "cost", "note" ], "description": "Cost details for a single pipeline step.", "properties": { "name": { "type": "string", "description": "Pipeline step name", "example": "Generate Image" }, "model": { "type": "string", "description": "The AI model or service used for this step", "example": "fal-ai/nano-banana-pro/edit" }, "cost": { "type": "number", "format": "float", "description": "Estimated cost for this step in USD", "example": 0.04 }, "unit": { "type": "string", "description": "Billing unit type (e.g., `images`, `seconds`, `megapixels`). Not present for free steps.", "example": "images" }, "unitPrice": { "type": "number", "format": "float", "description": "Price per billing unit in USD. Not present for free steps.", "example": 0.04 }, "note": { "type": "string", "description": "Human-readable calculation detail", "example": "1 call" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Generate Image Source: https://recoupable.dev/docs/api-reference/image/generation Generate high-quality images using AI models. Images are automatically stored on Arweave and include In Process moment metadata for provenance and ownership tracking. ## GET /api/image/generate Full OpenAPI specification: https://recoupable.dev/docs/spec/content.json ## Authentication The supplied specification does not declare an OpenAPI security object for this operation. Its declared headers and parameters still apply. Consult the authentication guide and the full specification before calling it. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Content", "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/image/generate": { "get": { "description": "Generate high-quality images using AI models. Images are automatically stored on Arweave and include In Process moment metadata for provenance and ownership tracking.", "parameters": [ { "name": "prompt", "in": "query", "description": "Text description of the image you want to generate", "required": true, "schema": { "type": "string" } }, { "name": "account_id", "in": "query", "description": "The unique identifier of the account generating the image", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Image generated successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ImageGenerationResponse" } } } }, "400": { "description": "Bad request - missing required parameters or invalid input", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ImageGenerationErrorResponse" } } } } } } } }, "components": { "schemas": { "ImageGenerationResponse": { "type": "object", "description": "Response from the image generation endpoint, extending Experimental_GenerateImageResult from the AI SDK", "properties": { "images": { "type": "array", "items": { "$ref": "#/components/schemas/GeneratedImage" }, "description": "Array of generated image objects" }, "warnings": { "type": "array", "items": { "type": "string" }, "description": "Array of warning messages, if any" }, "responses": { "type": "array", "items": { "$ref": "#/components/schemas/ImageResponseMeta" }, "description": "Array of response metadata from the AI provider" }, "providerMetadata": { "$ref": "#/components/schemas/ImageProviderMetadata" }, "usage": { "$ref": "#/components/schemas/ImageUsage" }, "imageUrl": { "type": "string", "description": "Permanent Arweave URL where the image is stored" }, "arweaveResult": { "$ref": "#/components/schemas/ArweaveTransaction" }, "moment": { "$ref": "#/components/schemas/InProcessMoment" } } }, "ImageGenerationErrorResponse": { "type": "object", "properties": { "error": { "type": "string", "description": "Error message describing what went wrong" } } }, "GeneratedImage": { "type": "object", "description": "A generated image file from the AI model", "properties": { "base64": { "type": "string", "description": "Image file as a base64 encoded string" }, "uint8Array": { "type": "array", "items": { "type": "integer" }, "description": "Image file as a Uint8Array (represented as array of numbers in JSON)" }, "mediaType": { "type": "string", "description": "The IANA media type of the file (e.g., 'image/png', 'image/jpeg')" } } }, "ImageResponseMeta": { "type": "object", "description": "Response metadata from the AI provider", "properties": { "finishReason": { "type": "string", "description": "Reason the generation finished (e.g., 'stop')" } } }, "ImageProviderMetadata": { "type": "object", "description": "Metadata from the AI provider about the generation", "properties": { "model": { "type": "string", "description": "The AI model used for generation (e.g., 'dall-e-3', 'gpt-image-1')" }, "size": { "type": "string", "description": "The size of the generated image (e.g., '1024x1024')" } } }, "ImageUsage": { "type": "object", "description": "Token usage information for the image generation", "properties": { "promptTokens": { "type": "integer", "description": "Number of tokens used in the prompt" }, "completionTokens": { "type": "integer", "description": "Number of tokens used in the completion (typically 0 for image generation)" }, "totalTokens": { "type": "integer", "description": "Total tokens used" } } }, "ArweaveTransaction": { "type": "object", "description": "Arweave transaction object for the stored image", "properties": { "id": { "type": "string", "description": "Unique identifier for the Arweave transaction" }, "last_tx": { "type": "string", "description": "Last transaction reference" }, "owner": { "type": "string", "description": "Owner address of the transaction" }, "tags": { "type": "array", "items": { "type": "object" }, "description": "Tags associated with the transaction" }, "target": { "type": "string", "description": "Target address (empty for data transactions)" }, "quantity": { "type": "string", "description": "Amount transferred (typically '0' for data transactions)" }, "data": { "type": "string", "description": "Transaction data (may be empty in response)" }, "reward": { "type": "string", "description": "Mining reward for the transaction" }, "signature": { "type": "string", "description": "Transaction signature" }, "format": { "type": "integer", "description": "Transaction format version" } } }, "InProcessMoment": { "type": "object", "description": "In Process moment metadata for provenance and ownership tracking", "properties": { "contractAddress": { "type": "string", "description": "Smart contract address for the moment" }, "tokenId": { "type": "string", "description": "Token ID of the minted moment" }, "hash": { "type": "string", "description": "Transaction hash of the moment mint" }, "chainId": { "type": "integer", "description": "Chain ID (e.g., 8453 for Base)" } } } } } } ``` # Transcribe Audio Source: https://recoupable.dev/docs/api-reference/transcribe/audio Transcribe audio files using OpenAI Whisper. The API saves both the original audio file and the generated markdown transcript to the customer's files in Supabase Storage. The owner account is derived from your credentials; you must have access to the specified artist_account_id. ## POST /api/transcribe Full OpenAPI specification: https://recoupable.dev/docs/spec/content.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Content", "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/transcribe": { "post": { "description": "Transcribe audio files using OpenAI Whisper. The API saves both the original audio file and the generated markdown transcript to the customer's files in Supabase Storage. The owner account is derived from your credentials; you must have access to the specified artist_account_id.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "requestBody": { "description": "Audio transcription request", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TranscribeAudioRequest" } } } }, "responses": { "200": { "description": "Audio transcribed successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TranscribeAudioResponse" } } } }, "400": { "description": "Bad request - missing required fields or invalid audio URL", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TranscribeAudioErrorResponse" } } } }, "401": { "description": "Missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TranscribeAudioErrorResponse" } } } }, "403": { "description": "Caller has no access to the specified artist_account_id", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TranscribeAudioErrorResponse" } } } }, "413": { "description": "Audio file exceeds the 25MB limit", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TranscribeAudioErrorResponse" } } } }, "429": { "description": "Rate limit exceeded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TranscribeAudioErrorResponse" } } } }, "500": { "description": "Server error - OpenAI API key not configured", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TranscribeAudioErrorResponse" } } } } } } } }, "components": { "schemas": { "TranscribeAudioRequest": { "type": "object", "required": [ "audio_url", "artist_account_id" ], "properties": { "audio_url": { "type": "string", "description": "Public URL to the audio file (mp3, wav, m4a, webm)", "example": "https://example.com/song.mp3" }, "account_id": { "type": "string", "format": "uuid", "deprecated": true, "description": "Deprecated and ignored. The owner account is derived from your credentials. Sending this field is harmless but no longer affects attribution.", "example": "550e8400-e29b-41d4-a716-446655440000" }, "artist_account_id": { "type": "string", "format": "uuid", "description": "Artist account ID for file storage", "example": "550e8400-e29b-41d4-a716-446655440001" }, "title": { "type": "string", "description": "Optional title for the audio and transcription files", "example": "My Song" }, "include_timestamps": { "type": "boolean", "description": "Whether to include timestamps in the markdown transcript", "default": false } } }, "TranscribeAudioResponse": { "type": "object", "required": [ "success", "audioFile", "transcriptFile", "text" ], "properties": { "success": { "type": "boolean", "description": "Whether the transcription was successful" }, "audioFile": { "$ref": "#/components/schemas/TranscribeFileInfo", "description": "Information about the saved audio file" }, "transcriptFile": { "$ref": "#/components/schemas/TranscribeFileInfo", "description": "Information about the saved transcript file" }, "text": { "type": "string", "description": "The full transcription text" }, "language": { "type": "string", "description": "Detected language code (e.g., 'en', 'es', 'fr')" } } }, "TranscribeAudioErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "description": "Error message describing what went wrong" } } }, "TranscribeFileInfo": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid", "description": "UUID of the file record in the database" }, "fileName": { "type": "string", "description": "Name of the saved file" }, "storageKey": { "type": "string", "description": "Storage path in Supabase Storage" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Generate Music Source: https://recoupable.dev/docs/api-reference/music/generate Generate music ## POST /api/music Full OpenAPI specification: https://recoupable.dev/docs/spec/content.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Content", "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/music": { "post": { "summary": "Generate music", "description": "Start a song generation with MiniMax Music 3. Generation runs in the background and takes roughly one to two minutes, so this returns **202** immediately with a `pending` generation. Poll [Get Music Generation](/docs/api-reference/music/get) until `status` is `completed` or `failed`; `audio_url` is populated on completion. Credits are checked before the model is called and deducted only when a generation completes, so a failed generation is never charged.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicGenerationRequest" } } } }, "responses": { "202": { "description": "Generation accepted and queued.", "headers": { "Location": { "schema": { "type": "string" }, "description": "URL of the created generation." } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicGenerationCreateResponse" } } } }, "400": { "description": "Validation failed. `missing_fields` names the first offending field.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicGenerationErrorResponse" } } } }, "401": { "description": "Unauthorized. Provide exactly one of `x-api-key` or `Authorization: Bearer`.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicGenerationErrorResponse" } } } }, "402": { "description": "Insufficient credits. No generation is created and the model is not called.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicGenerationErrorResponse" } } } }, "403": { "description": "Access denied to the specified `account_id` or `organization_id`.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicGenerationErrorResponse" } } } }, "500": { "description": "Internal server error.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicGenerationErrorResponse" } } } } } } } }, "components": { "schemas": { "MusicGenerationRequest": { "type": "object", "required": [ "prompt", "lyrics" ], "properties": { "prompt": { "type": "string", "description": "Music description: style, mood, vocals, instrumentation and arrangement.", "example": "Genre: acoustic pop. BPM: 96. Key: C major. Warm and intimate, building gently into the chorus." }, "lyrics": { "type": "string", "description": "The lyrics to sing. Structure tags such as `[intro]`, `[verse]`, `[chorus]` and `[outro]` must each be on their own line; text on the same line as a leading tag is dropped by the model.", "example": "[verse]\nMorning light filtering through the pine\n[chorus]\nSoftly the world begins to breathe" }, "duration": { "type": "number", "minimum": 10, "maximum": 300, "default": 60, "description": "Upper bound on the generated audio length in seconds. The model may stop earlier; the actual length comes back as `duration_seconds`." }, "seed": { "type": "integer", "description": "Seed for reproducibility. Omit for a random seed." }, "num_inference_steps": { "type": "integer", "minimum": 1, "maximum": 100, "default": 30, "description": "Flow-matching steps per denoising chunk. More steps improve quality at the cost of speed." }, "guidance_scale": { "type": "number", "minimum": 0, "maximum": 20, "default": 1.7, "description": "Classifier-free guidance scale of the flow-matching stage." }, "account_id": { "type": "string", "format": "uuid", "description": "Optional. Generate on behalf of another account you can access (self, shared organization, or admin). Defaults to the calling account. Organizations are accounts, so this is also how an organization generation is scoped: send the organization's account id." } } }, "MusicGenerationCreateResponse": { "type": "object", "description": "The accepted generation. Poll it until `status` is terminal.", "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the request" }, "generation": { "$ref": "#/components/schemas/MusicGeneration" } } }, "MusicGenerationErrorResponse": { "type": "object", "required": [ "status", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Always `error`" }, "missing_fields": { "type": "array", "description": "JSON path segments to the first field that failed validation, e.g. `[\"lyrics\"]`", "items": { "oneOf": [ { "type": "string" }, { "type": "integer" } ] } }, "error": { "type": "string", "description": "Human-readable error message" } } }, "MusicGeneration": { "type": "object", "description": "One music generation.", "properties": { "id": { "type": "string", "format": "uuid", "description": "The generation's id. Poll [Get Music Generation](/docs/api-reference/music/get) with this." }, "status": { "type": "string", "enum": [ "pending", "processing", "completed", "failed" ], "description": "Lifecycle phase. `pending` until the workflow picks it up, `processing` while fal is rendering, then `completed` or `failed`." }, "prompt": { "type": "string", "description": "The music description the song was generated from." }, "lyrics": { "type": "string", "description": "The lyrics the song was generated from." }, "model": { "type": "string", "description": "The generating model.", "example": "minimax/music-3" }, "duration_seconds": { "type": [ "number", "null" ], "description": "Actual length of the generated audio. Null until completed; may be shorter than requested." }, "audio_url": { "type": [ "string", "null" ], "description": "Playable audio URL. Null until the generation completes." }, "error_message": { "type": [ "string", "null" ], "description": "Why the generation failed. Null unless `status` is `failed`." }, "created_at": { "type": "string", "format": "date-time", "description": "When the generation was created." }, "updated_at": { "type": "string", "format": "date-time", "description": "When the generation last changed." } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # List Music Generations Source: https://recoupable.dev/docs/api-reference/music/list List music generations ## GET /api/music Full OpenAPI specification: https://recoupable.dev/docs/spec/content.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Content", "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/music": { "get": { "summary": "List music generations", "description": "The calling account's music generations, newest first. Scope follows the standard account override: with no `account_id` you get your own personal and organization generations; pass `account_id` to read another account you can access.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "parameters": [ { "name": "account_id", "in": "query", "required": false, "schema": { "type": "string", "format": "uuid" }, "description": "Optional. Read another account you can access. Defaults to the calling account." }, { "name": "status", "in": "query", "required": false, "schema": { "type": "string", "enum": [ "pending", "processing", "completed", "failed" ] }, "description": "Optional. Return only generations in this state." }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1, "maximum": 50, "default": 20 }, "description": "Optional. Maximum generations to return, newest first." }, { "name": "offset", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 0, "default": 0 }, "description": "Optional. Number of generations to skip, for paging." } ], "responses": { "200": { "description": "Generations in the requested context, newest first.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicGenerationListResponse" } } } }, "400": { "description": "Invalid query parameter.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicGenerationErrorResponse" } } } }, "401": { "description": "Unauthorized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicGenerationErrorResponse" } } } }, "403": { "description": "Access denied to the specified `account_id`.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicGenerationErrorResponse" } } } }, "500": { "description": "Internal server error.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicGenerationErrorResponse" } } } } } } } }, "components": { "schemas": { "MusicGenerationListResponse": { "type": "object", "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the request" }, "generations": { "type": "array", "description": "Generations, newest first. Empty when the context has none.", "items": { "$ref": "#/components/schemas/MusicGeneration" } } } }, "MusicGenerationErrorResponse": { "type": "object", "required": [ "status", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Always `error`" }, "missing_fields": { "type": "array", "description": "JSON path segments to the first field that failed validation, e.g. `[\"lyrics\"]`", "items": { "oneOf": [ { "type": "string" }, { "type": "integer" } ] } }, "error": { "type": "string", "description": "Human-readable error message" } } }, "MusicGeneration": { "type": "object", "description": "One music generation.", "properties": { "id": { "type": "string", "format": "uuid", "description": "The generation's id. Poll [Get Music Generation](/docs/api-reference/music/get) with this." }, "status": { "type": "string", "enum": [ "pending", "processing", "completed", "failed" ], "description": "Lifecycle phase. `pending` until the workflow picks it up, `processing` while fal is rendering, then `completed` or `failed`." }, "prompt": { "type": "string", "description": "The music description the song was generated from." }, "lyrics": { "type": "string", "description": "The lyrics the song was generated from." }, "model": { "type": "string", "description": "The generating model.", "example": "minimax/music-3" }, "duration_seconds": { "type": [ "number", "null" ], "description": "Actual length of the generated audio. Null until completed; may be shorter than requested." }, "audio_url": { "type": [ "string", "null" ], "description": "Playable audio URL. Null until the generation completes." }, "error_message": { "type": [ "string", "null" ], "description": "Why the generation failed. Null unless `status` is `failed`." }, "created_at": { "type": "string", "format": "date-time", "description": "When the generation was created." }, "updated_at": { "type": "string", "format": "date-time", "description": "When the generation last changed." } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Get Music Generation Source: https://recoupable.dev/docs/api-reference/music/get Get music generation ## GET /api/music/{generationId} Full OpenAPI specification: https://recoupable.dev/docs/spec/content.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Content", "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/music/{generationId}": { "get": { "summary": "Get music generation", "description": "One generation, including the `seed` the model used and its `logs` progress, both read live from the provider rather than stored. This is the polling target while a generation is in flight: call it until `status` is `completed` or `failed`. Responses are never cached.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "parameters": [ { "name": "generationId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "The generation's id, from [Generate Music](/docs/api-reference/music/generate)." }, { "name": "account_id", "in": "query", "required": false, "schema": { "type": "string", "format": "uuid" }, "description": "Optional. Read on behalf of another account you can access." } ], "responses": { "200": { "description": "The generation, with the seed it used and its render progress.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicGenerationDetailResponse" } } } }, "400": { "description": "`generationId` is not a valid uuid.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicGenerationErrorResponse" } } } }, "401": { "description": "Unauthorized.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicGenerationErrorResponse" } } } }, "403": { "description": "Access denied to this generation's account.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicGenerationErrorResponse" } } } }, "404": { "description": "No generation with that id.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicGenerationErrorResponse" } } } }, "500": { "description": "Internal server error.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicGenerationErrorResponse" } } } } } } } }, "components": { "schemas": { "MusicGenerationDetailResponse": { "type": "object", "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the request" }, "generation": { "$ref": "#/components/schemas/MusicGenerationDetail" } } }, "MusicGenerationErrorResponse": { "type": "object", "required": [ "status", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Always `error`" }, "missing_fields": { "type": "array", "description": "JSON path segments to the first field that failed validation, e.g. `[\"lyrics\"]`", "items": { "oneOf": [ { "type": "string" }, { "type": "integer" } ] } }, "error": { "type": "string", "description": "Human-readable error message" } } }, "MusicGenerationDetail": { "allOf": [ { "$ref": "#/components/schemas/MusicGeneration" }, { "type": "object", "properties": { "seed": { "type": [ "integer", "null" ], "description": "The seed the model actually used, read live from the provider rather than stored. Null until the generation completes, and null if the provider cannot be reached. Pass it back as `seed` to reproduce a generation. It is the only generation parameter recoverable after the fact: `num_inference_steps` and `guidance_scale` are consumed when the request is submitted and are never reported back." }, "logs": { "type": "array", "description": "The model's own render progress, oldest first, read live from the provider rather than stored. Empty if the provider cannot be reached. Returned only on the single-generation read.", "items": { "$ref": "#/components/schemas/MusicGenerationLogEntry" } } } } ] }, "MusicGeneration": { "type": "object", "description": "One music generation.", "properties": { "id": { "type": "string", "format": "uuid", "description": "The generation's id. Poll [Get Music Generation](/docs/api-reference/music/get) with this." }, "status": { "type": "string", "enum": [ "pending", "processing", "completed", "failed" ], "description": "Lifecycle phase. `pending` until the workflow picks it up, `processing` while fal is rendering, then `completed` or `failed`." }, "prompt": { "type": "string", "description": "The music description the song was generated from." }, "lyrics": { "type": "string", "description": "The lyrics the song was generated from." }, "model": { "type": "string", "description": "The generating model.", "example": "minimax/music-3" }, "duration_seconds": { "type": [ "number", "null" ], "description": "Actual length of the generated audio. Null until completed; may be shorter than requested." }, "audio_url": { "type": [ "string", "null" ], "description": "Playable audio URL. Null until the generation completes." }, "error_message": { "type": [ "string", "null" ], "description": "Why the generation failed. Null unless `status` is `failed`." }, "created_at": { "type": "string", "format": "date-time", "description": "When the generation was created." }, "updated_at": { "type": "string", "format": "date-time", "description": "When the generation last changed." } } }, "MusicGenerationLogEntry": { "type": "object", "description": "One line of the model's render progress.", "properties": { "at": { "type": "string", "format": "date-time", "description": "When the line was emitted." }, "message": { "type": "string", "description": "What the model reported.", "example": "20/180" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Get Artist Posts Source: https://recoupable.dev/docs/api-reference/posts/get Retrieve all social media posts from an artist across all platforms. This endpoint aggregates posts from all connected social media profiles for the specified artist, newest first. Engagement counts (`views`, `likes`, `comments`, `reposts`) are the values captured by the most recent scrape of that profile; they are null on platforms that do not report them and on posts stored before engagement capture. Supports pagination for large post collections. ## GET /api/artists/{id}/posts Full OpenAPI specification: https://recoupable.dev/docs/spec/social.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Social", "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/artists/{id}/posts": { "get": { "description": "Retrieve all social media posts from an artist across all platforms. This endpoint aggregates posts from all connected social media profiles for the specified artist, newest first. Engagement counts (`views`, `likes`, `comments`, `reposts`) are the values captured by the most recent scrape of that profile; they are null on platforms that do not report them and on posts stored before engagement capture. Supports pagination for large post collections.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "parameters": [ { "name": "id", "in": "path", "description": "The unique identifier of the artist account to fetch posts for", "required": true, "schema": { "type": "string", "format": "uuid" } }, { "name": "page", "in": "query", "description": "The page number to retrieve (default: 1)", "required": false, "schema": { "type": "integer", "default": 1 } }, { "name": "limit", "in": "query", "description": "The number of records per page (default: 20, max: 100)", "required": false, "schema": { "type": "integer", "default": 20, "maximum": 100 } } ], "responses": { "200": { "description": "Artist posts retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ArtistPostsResponse" } } } }, "400": { "description": "Bad request - invalid or missing `id` path parameter format", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ArtistPostsErrorResponse" } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ArtistPostsErrorResponse" } } } }, "403": { "description": "Forbidden - caller does not have access to this artist", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ArtistPostsErrorResponse" } } } }, "404": { "description": "Artist not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ArtistPostsErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ArtistPostsErrorResponse" } } } } } } } }, "components": { "schemas": { "ArtistPostsResponse": { "type": "object", "required": [ "status", "posts", "pagination" ], "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the request" }, "posts": { "type": "array", "items": { "$ref": "#/components/schemas/ArtistPost" }, "description": "List of posts from the artist across all social platforms" }, "pagination": { "$ref": "#/components/schemas/ArtistPostsPagination", "description": "Pagination metadata for the response" } } }, "ArtistPostsErrorResponse": { "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" } } }, "ArtistPost": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid", "description": "Unique identifier for the post" }, "post_url": { "type": "string", "description": "Direct URL to the post on the social platform" }, "updated_at": { "type": "string", "format": "date-time", "description": "ISO timestamp of when the post was published on the platform" }, "views": { "type": "integer", "nullable": true, "description": "View or play count at the last scrape. YouTube `viewCount`, TikTok `playCount`, X `viewCount`. Null on Instagram, LinkedIn, Threads and Facebook." }, "likes": { "type": "integer", "nullable": true, "description": "Like count at the last scrape. Instagram `likesCount`, TikTok `diggCount`, YouTube `likes`, X `likeCount`, LinkedIn `likes`. Null on Threads and Facebook." }, "comments": { "type": "integer", "nullable": true, "description": "Comment count at the last scrape. Instagram `commentsCount`, TikTok `commentCount`, YouTube `commentsCount`, X `replyCount`, LinkedIn `comments`. Null on Threads and Facebook." }, "reposts": { "type": "integer", "nullable": true, "description": "Share, repost or retweet count at the last scrape. TikTok `shareCount`, X `retweetCount`, LinkedIn `shares`. Null on Instagram, YouTube, Threads and Facebook." } } }, "ArtistPostsPagination": { "type": "object", "properties": { "total_count": { "type": "integer", "description": "Total number of posts available" }, "page": { "type": "integer", "description": "Current page number" }, "limit": { "type": "integer", "description": "Number of posts per page" }, "total_pages": { "type": "integer", "description": "Total number of pages available" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Get Comments Source: https://recoupable.dev/docs/api-reference/comments/get Retrieve comments associated with an artist or a specific post, with support for pagination. This endpoint returns raw comment data including the comment text, associated post, and commenter's social profile reference. ## GET /api/comments Full OpenAPI specification: https://recoupable.dev/docs/spec/social.json ## Authentication The supplied specification does not declare an OpenAPI security object for this operation. Its declared headers and parameters still apply. Consult the authentication guide and the full specification before calling it. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Social", "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/comments": { "get": { "description": "Retrieve comments associated with an artist or a specific post, with support for pagination. This endpoint returns raw comment data including the comment text, associated post, and commenter's social profile reference.", "parameters": [ { "name": "artist_account_id", "in": "query", "description": "The unique identifier of the artist account to fetch comments for", "required": true, "schema": { "type": "string", "format": "uuid" } }, { "name": "post_id", "in": "query", "description": "Filter comments by specific post", "required": false, "schema": { "type": "string", "format": "uuid" } }, { "name": "page", "in": "query", "description": "Page number for pagination (default: 1)", "required": false, "schema": { "type": "integer", "default": 1 } }, { "name": "limit", "in": "query", "description": "Number of comments per page (default: 10)", "required": false, "schema": { "type": "integer", "default": 10 } } ], "responses": { "200": { "description": "Comments retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CommentsResponse" } } } }, "400": { "description": "Bad request - missing required parameters", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CommentsErrorResponse" } } } } } } } }, "components": { "schemas": { "CommentsResponse": { "type": "object", "required": [ "status", "comments", "pagination" ], "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the request" }, "comments": { "type": "array", "items": { "$ref": "#/components/schemas/Comment" }, "description": "List of comments for the specified artist or post" }, "pagination": { "$ref": "#/components/schemas/CommentsPagination", "description": "Pagination metadata for the response" } } }, "CommentsErrorResponse": { "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" } } }, "Comment": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid", "description": "UUID of the comment" }, "post_id": { "type": "string", "format": "uuid", "description": "UUID of the associated post" }, "social_id": { "type": "string", "format": "uuid", "description": "UUID of the social profile who made the comment" }, "comment": { "type": "string", "description": "Comment text content" }, "commented_at": { "type": "string", "format": "date-time", "description": "Timestamp with timezone of when the comment was made" } } }, "CommentsPagination": { "type": "object", "properties": { "total_count": { "type": "integer", "description": "Total number of comments available" }, "page": { "type": "integer", "description": "Current page number" }, "limit": { "type": "integer", "description": "Number of comments per page" }, "total_pages": { "type": "integer", "description": "Total number of pages available" } } } } } } ``` # Social Scrape Source: https://recoupable.dev/docs/api-reference/social/scrape Trigger a scrape of one social profile. Use [Get Artist Socials](/docs/api-reference/artists/socials) first to get the social `id`. The response is the Apify run metadata; poll it with the [Scraper Results API](/docs/api-reference/apify/scraper). ## POST /api/socials/{id}/scrape Full OpenAPI specification: https://recoupable.dev/docs/spec/social.json ## Authentication The supplied specification does not declare an OpenAPI security object for this operation. Its declared headers and parameters still apply. Consult the authentication guide and the full specification before calling it. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Social", "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/socials/{id}/scrape": { "post": { "description": "Trigger a scrape of one social profile. Use [Get Artist Socials](/docs/api-reference/artists/socials) first to get the social `id`. The response is the Apify run metadata; poll it with the [Scraper Results API](/docs/api-reference/apify/scraper).\n\n**Credits:** `5 + posts` per scrape (5 when `posts` is omitted).\n\n**What `posts` does per platform**\n\n| Platform | `posts` | When `posts` is omitted |\n|---|---|---|\n| Instagram | ignored | the profile's latest posts (up to 12), always |\n| TikTok | `resultsPerPage` | 1 video |\n| YouTube | `maxResults`, `maxResultsShorts`, `maxResultStreams` (per content type) | 1 video, 0 Shorts, 0 streams |\n| X / Twitter | timeline items; retweets and replies are fetched but not stored | 1 item |\n| LinkedIn | `maxPosts` on the posts actor (runs instead of the profile actor) | profile only, no posts |\n| Threads | ignored | profile only |\n| Facebook | ignored | profile only |\n\n**What gets stored.** Once the run succeeds, the webhook handler persists every item in the dataset; nothing is discarded after the response is read:\n\n- The profile: username, avatar, bio, follower and following counts, region. Served by [Get Artist Socials](/docs/api-reference/artists/socials).\n- One follower snapshot per social per day whenever the run reports a follower count (following and post counts too, where the platform reports them). Served by the `history` parameter of Get Artist Socials.\n- Every returned post with its engagement (`views`, `likes`, `comments`, `reposts`, where the platform reports them). Served by [Get Artist Posts](/docs/api-reference/posts/get).\n- Instagram only: the comments on the returned posts (one follow-up comments run), then one batched profile run over the distinct commenters that fills avatar, bio, follower and following counts on every commenter. Served by [Get Artist Fans](/docs/api-reference/fans/get).\n\n**Fan discovery is one hop deep.** The commenter profile run is terminal: it never schedules comments or profile runs of its own. One Instagram scrape therefore produces at most three runs (profile, comments, commenter profiles). Pass `subtitles=true` on a YouTube social to include each video's transcript in the run results.", "parameters": [ { "name": "id", "in": "path", "description": "The unique identifier (UUID) of the social profile to scrape. Obtain this from the [Get Artist Socials](/docs/api-reference/artists/socials) API.", "required": true, "schema": { "type": "string", "format": "uuid" } }, { "name": "posts", "in": "query", "description": "Recent-post depth. Honored on TikTok, YouTube (per content type), X and LinkedIn; ignored on Instagram (always the profile's latest posts, up to 12), Threads and Facebook. Without it TikTok, YouTube and X return a single latest item, so pass it whenever you need post history. Every returned post is stored with its engagement counts. See the per-platform table above.", "required": false, "schema": { "type": "integer", "minimum": 1, "maximum": 100 }, "example": 10 }, { "name": "subtitles", "in": "query", "required": false, "description": "YouTube only. When `true`, the scrape also downloads each returned video's captions (auto-generated captions preferred, English, plain text). The transcript is returned on the run's dataset items as `subtitles[0].plaintext` and is not persisted; read it from [Scraper Results](/docs/api-reference/apify/scraper) while the run's dataset is available. Ignored on every other platform. Adds no credits beyond the `posts` price.", "schema": { "type": "boolean", "default": false } } ], "responses": { "200": { "description": "Scrape job triggered successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApifyRunResult" } } } }, "400": { "description": "Bad request - missing required parameters", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SocialErrorResponse" } } } }, "402": { "description": "Insufficient credits — the body includes a static `billingUrl`. No Stripe object is created.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SocialInsufficientCreditsResponse" } } } } } } } }, "components": { "schemas": { "ApifyRunResult": { "type": "object", "properties": { "runId": { "type": "string", "description": "Unique identifier for the Apify run" }, "datasetId": { "type": "string", "description": "Unique identifier for the dataset containing scraped data" }, "error": { "type": "string", "nullable": true, "description": "Error message if the run failed (null if successful)" } } }, "SocialErrorResponse": { "type": "object", "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Status of the request" }, "message": { "type": "string", "description": "Error message" } } }, "SocialInsufficientCreditsResponse": { "type": "object", "description": "Returned (402) when the account lacks credits for the scrape. Nothing is charged and no Stripe object is created. Credits must be bought explicitly via [`POST /api/credits/sessions`](/docs/api-reference/credits/sessions-create).", "properties": { "error": { "type": "string", "enum": [ "insufficient_credits" ] }, "remaining_credits": { "type": "integer", "example": 0 }, "required_credits": { "type": "integer", "example": 25 }, "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`](/docs/api-reference/credits/sessions-create).", "example": "https://app.recoupable.dev" } }, "required": [ "error", "remaining_credits", "required_credits", "billingUrl" ] } } } } ``` # Scrape Artist Socials Source: https://recoupable.dev/docs/api-reference/artist/socials-scrape Trigger scrape jobs for all social profiles linked to an artist. Returns a runId per social profile that you can poll for status and results via the [Scraper Results API](/docs/api-reference/apify/scraper). ## POST /api/artist/socials/scrape Full OpenAPI specification: https://recoupable.dev/docs/spec/releases.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Releases", "description": "API documentation for the Recoup platform - an AI agent platform for the music industry", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "paths": { "/api/artist/socials/scrape": { "post": { "description": "Trigger scrape jobs for all social profiles linked to an artist. Returns a runId per social profile that you can poll for status and results via the [Scraper Results API](/docs/api-reference/apify/scraper).", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "requestBody": { "description": "Artist to scrape socials for", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ArtistSocialsScrapeRequest" } } } }, "responses": { "200": { "description": "Scrape jobs triggered successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ArtistSocialsScrapeResponse" } } } }, "400": { "description": "Bad request - missing required parameters", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ArtistSocialsErrorResponse" } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ArtistSocialsErrorResponse" } } } }, "402": { "description": "Insufficient credits — the body includes a static `billingUrl`. No Stripe object is created.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ArtistSocialsInsufficientCreditsResponse" } } } }, "403": { "description": "Forbidden - caller does not have access to this artist", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ArtistSocialsErrorResponse" } } } } } } } }, "components": { "schemas": { "ArtistSocialsScrapeRequest": { "type": "object", "required": [ "artist_account_id" ], "properties": { "artist_account_id": { "type": "string", "format": "uuid", "description": "UUID of the artist account to scrape socials for", "example": "1873859c-dd37-4e9a-9bac-80d35a1b2c3d" }, "posts": { "type": "integer", "minimum": 1, "maximum": 100, "example": 10, "description": "Recent-post depth applied to every profile in the batch. Honored on TikTok, YouTube (per content type), X and LinkedIn; ignored on Instagram (always the profile's latest posts, up to 12), Threads and Facebook. Without it TikTok, YouTube and X return a single latest item. Every returned post is stored with its engagement counts; see the per-platform table on [Social Scrape](/docs/api-reference/social/scrape). Credits: `5 + posts` per profile." } } }, "ArtistSocialsScrapeResponse": { "type": "array", "items": { "$ref": "#/components/schemas/ApifyRunResult" }, "description": "Array of Apify run results, one for each social profile scraped" }, "ArtistSocialsErrorResponse": { "type": "object", "required": [ "status", "message" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Status of the request" }, "message": { "type": "string", "description": "Error message describing what went wrong" } } }, "ArtistSocialsInsufficientCreditsResponse": { "type": "object", "description": "Returned (402) when the account lacks credits for the scrapes. Nothing is charged and no Stripe object is created. Credits must be bought explicitly via [`POST /api/credits/sessions`](/docs/api-reference/credits/sessions-create).", "properties": { "error": { "type": "string", "enum": [ "insufficient_credits" ] }, "remaining_credits": { "type": "integer", "example": 0 }, "required_credits": { "type": "integer", "example": 100 }, "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`](/docs/api-reference/credits/sessions-create).", "example": "https://app.recoupable.dev" } }, "required": [ "error", "remaining_credits", "required_credits", "billingUrl" ] }, "ApifyRunResult": { "type": "object", "properties": { "runId": { "type": "string", "description": "Unique identifier for the Apify run" }, "datasetId": { "type": "string", "description": "Unique identifier for the dataset containing scraped data" }, "error": { "type": "string", "nullable": true, "description": "Error message if the run failed (null if successful)" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Search Source: https://recoupable.dev/docs/api-reference/spotify/search Search for artists, albums, tracks, and playlists using the Spotify API. This endpoint is a proxy to the official Spotify Search API. ## GET /api/spotify/search Full OpenAPI specification: https://recoupable.dev/docs/spec/social.json ## Authentication The supplied specification does not declare an OpenAPI security object for this operation. Its declared headers and parameters still apply. Consult the authentication guide and the full specification before calling it. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Social", "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/spotify/search": { "get": { "description": "Search for artists, albums, tracks, and playlists using the Spotify API. This endpoint is a proxy to the official Spotify Search API.", "parameters": [ { "name": "q", "in": "query", "description": "The search query keywords and optional field filters", "required": true, "schema": { "type": "string" } }, { "name": "type", "in": "query", "description": "A comma-separated list of item types to search across: album, artist, playlist, track, show, episode, audiobook", "required": true, "schema": { "type": "string" } }, { "name": "market", "in": "query", "description": "An ISO 3166-1 alpha-2 country code or 'from_token'", "required": false, "schema": { "type": "string" } }, { "name": "limit", "in": "query", "description": "Maximum number of results to return (default: 20, min: 1, max: 50)", "required": false, "schema": { "type": "integer", "default": 20, "minimum": 1, "maximum": 50 } }, { "name": "offset", "in": "query", "description": "The index of the first result to return (default: 0, max: 10000)", "required": false, "schema": { "type": "integer", "default": 0, "maximum": 10000 } } ], "responses": { "200": { "description": "Search results retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SpotifySearchResponse" } } } }, "400": { "description": "Bad request - missing required parameters", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SpotifyErrorResponse" } } } } } } } }, "components": { "schemas": { "SpotifySearchResponse": { "type": "object", "properties": { "artists": { "$ref": "#/components/schemas/SpotifyArtistsPaginated", "description": "Search results for artists (if type includes artist)" }, "albums": { "type": "object", "description": "Search results for albums (if type includes album)" }, "tracks": { "type": "object", "description": "Search results for tracks (if type includes track)" }, "playlists": { "type": "object", "description": "Search results for playlists (if type includes playlist)" } } }, "SpotifyErrorResponse": { "type": "object", "properties": { "error": { "type": "object", "properties": { "status": { "type": "integer", "description": "HTTP status code" }, "message": { "type": "string", "description": "Error message" } }, "description": "Error details" } } }, "SpotifyArtistsPaginated": { "type": "object", "properties": { "href": { "type": "string", "description": "A link to the Web API endpoint returning the full result" }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/SpotifyArtistObject" }, "description": "List of artist objects" }, "limit": { "type": "integer", "description": "The maximum number of items in the response" }, "next": { "type": "string", "nullable": true, "description": "URL to the next page of items" }, "offset": { "type": "integer", "description": "The offset of the items returned" }, "previous": { "type": "string", "nullable": true, "description": "URL to the previous page of items" }, "total": { "type": "integer", "description": "The total number of items available" } } }, "SpotifyArtistObject": { "type": "object", "properties": { "external_urls": { "$ref": "#/components/schemas/SpotifyExternalUrls" }, "followers": { "$ref": "#/components/schemas/SpotifyFollowers" }, "genres": { "type": "array", "items": { "type": "string" }, "description": "A list of the genres the artist is associated with" }, "href": { "type": "string", "description": "A link to the Web API endpoint providing full details" }, "id": { "type": "string", "description": "The Spotify ID for the artist" }, "images": { "type": "array", "items": { "$ref": "#/components/schemas/SpotifyImage" }, "description": "Images of the artist in various sizes" }, "name": { "type": "string", "description": "The name of the artist" }, "popularity": { "type": "integer", "description": "The popularity of the artist (0-100)" }, "type": { "type": "string", "enum": [ "artist" ], "description": "The object type, always 'artist'" }, "uri": { "type": "string", "description": "The Spotify URI for the artist" } } }, "SpotifyExternalUrls": { "type": "object", "properties": { "spotify": { "type": "string", "description": "The Spotify URL for the object" } } }, "SpotifyFollowers": { "type": "object", "properties": { "href": { "type": "string", "nullable": true, "description": "This will always be set to null" }, "total": { "type": "integer", "description": "The total number of followers" } } }, "SpotifyImage": { "type": "object", "properties": { "url": { "type": "string", "description": "The source URL of the image" }, "height": { "type": "integer", "nullable": true, "description": "The image height in pixels" }, "width": { "type": "integer", "nullable": true, "description": "The image width in pixels" } } } } } } ``` # Get Artist Source: https://recoupable.dev/docs/api-reference/spotify/artist Get Spotify catalog information for a single artist identified by their unique Spotify ID. ## GET /api/spotify/artist/ Full OpenAPI specification: https://recoupable.dev/docs/spec/social.json ## Authentication The supplied specification does not declare an OpenAPI security object for this operation. Its declared headers and parameters still apply. Consult the authentication guide and the full specification before calling it. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Social", "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/spotify/artist/": { "get": { "description": "Get Spotify catalog information for a single artist identified by their unique Spotify ID.", "parameters": [ { "name": "id", "in": "query", "description": "The Spotify ID of the artist", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Artist retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SpotifyGetArtistResponse" } } } }, "400": { "description": "Bad request - missing required parameters", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SpotifyErrorResponse" } } } } } } } }, "components": { "schemas": { "SpotifyGetArtistResponse": { "type": "object", "properties": { "artist": { "$ref": "#/components/schemas/SpotifyArtistObject", "nullable": true, "description": "The Spotify artist object (null if error)" }, "error": { "type": "object", "nullable": true, "description": "Error object if request failed (null if successful)" } } }, "SpotifyErrorResponse": { "type": "object", "properties": { "error": { "type": "object", "properties": { "status": { "type": "integer", "description": "HTTP status code" }, "message": { "type": "string", "description": "Error message" } }, "description": "Error details" } } }, "SpotifyArtistObject": { "type": "object", "properties": { "external_urls": { "$ref": "#/components/schemas/SpotifyExternalUrls" }, "followers": { "$ref": "#/components/schemas/SpotifyFollowers" }, "genres": { "type": "array", "items": { "type": "string" }, "description": "A list of the genres the artist is associated with" }, "href": { "type": "string", "description": "A link to the Web API endpoint providing full details" }, "id": { "type": "string", "description": "The Spotify ID for the artist" }, "images": { "type": "array", "items": { "$ref": "#/components/schemas/SpotifyImage" }, "description": "Images of the artist in various sizes" }, "name": { "type": "string", "description": "The name of the artist" }, "popularity": { "type": "integer", "description": "The popularity of the artist (0-100)" }, "type": { "type": "string", "enum": [ "artist" ], "description": "The object type, always 'artist'" }, "uri": { "type": "string", "description": "The Spotify URI for the artist" } } }, "SpotifyExternalUrls": { "type": "object", "properties": { "spotify": { "type": "string", "description": "The Spotify URL for the object" } } }, "SpotifyFollowers": { "type": "object", "properties": { "href": { "type": "string", "nullable": true, "description": "This will always be set to null" }, "total": { "type": "integer", "description": "The total number of followers" } } }, "SpotifyImage": { "type": "object", "properties": { "url": { "type": "string", "description": "The source URL of the image" }, "height": { "type": "integer", "nullable": true, "description": "The image height in pixels" }, "width": { "type": "integer", "nullable": true, "description": "The image width in pixels" } } } } } } ``` # Get Artist Albums Source: https://recoupable.dev/docs/api-reference/spotify/artist-albums Get Spotify catalog information about an artist's albums. ## GET /api/spotify/artist/albums Full OpenAPI specification: https://recoupable.dev/docs/spec/social.json ## Authentication The supplied specification does not declare an OpenAPI security object for this operation. Its declared headers and parameters still apply. Consult the authentication guide and the full specification before calling it. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Social", "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/spotify/artist/albums": { "get": { "description": "Get Spotify catalog information about an artist's albums.", "parameters": [ { "name": "id", "in": "query", "description": "The Spotify ID of the artist", "required": true, "schema": { "type": "string" } }, { "name": "include_groups", "in": "query", "description": "A comma-separated list of keywords to filter the response. Valid values are: album, single, appears_on, compilation", "required": false, "schema": { "type": "string" } }, { "name": "market", "in": "query", "description": "An ISO 3166-1 alpha-2 country code. If specified, only content available in that market will be returned", "required": false, "schema": { "type": "string" } }, { "name": "limit", "in": "query", "description": "The maximum number of items to return (default: 20, min: 1, max: 50)", "required": false, "schema": { "type": "integer", "default": 20, "minimum": 1, "maximum": 50 } }, { "name": "offset", "in": "query", "description": "The index of the first item to return (default: 0)", "required": false, "schema": { "type": "integer", "default": 0 } } ], "responses": { "200": { "description": "Artist albums retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SpotifyArtistAlbumsResponse" } } } }, "400": { "description": "Bad request - missing required parameters", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SpotifyErrorResponse" } } } } } } } }, "components": { "schemas": { "SpotifyArtistAlbumsResponse": { "type": "object", "properties": { "href": { "type": "string", "description": "A link to the Web API endpoint returning the full result" }, "limit": { "type": "integer", "description": "The maximum number of items in the response" }, "next": { "type": "string", "nullable": true, "description": "URL to the next page of items" }, "offset": { "type": "integer", "description": "The offset of the items returned" }, "previous": { "type": "string", "nullable": true, "description": "URL to the previous page of items" }, "total": { "type": "integer", "description": "The total number of items available" }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/SpotifySimplifiedAlbum" }, "description": "Array of simplified album objects" } } }, "SpotifyErrorResponse": { "type": "object", "properties": { "error": { "type": "object", "properties": { "status": { "type": "integer", "description": "HTTP status code" }, "message": { "type": "string", "description": "Error message" } }, "description": "Error details" } } }, "SpotifySimplifiedAlbum": { "type": "object", "properties": { "album_type": { "type": "string", "enum": [ "album", "single", "compilation" ], "description": "The type of the album" }, "total_tracks": { "type": "integer", "description": "The number of tracks in the album" }, "available_markets": { "type": "array", "items": { "type": "string" }, "description": "Markets in which the album is available" }, "external_urls": { "$ref": "#/components/schemas/SpotifyExternalUrls" }, "href": { "type": "string", "description": "A link to the Web API endpoint providing full details" }, "id": { "type": "string", "description": "The Spotify ID for the album" }, "images": { "type": "array", "items": { "$ref": "#/components/schemas/SpotifyImage" }, "description": "The cover art for the album in various sizes" }, "name": { "type": "string", "description": "The name of the album" }, "release_date": { "type": "string", "description": "The date the album was first released" }, "release_date_precision": { "type": "string", "enum": [ "year", "month", "day" ], "description": "The precision with which release_date value is known" }, "restrictions": { "type": "object", "properties": { "reason": { "type": "string", "description": "The reason for the restriction" } }, "description": "Included when a content restriction is applied" }, "type": { "type": "string", "enum": [ "album" ], "description": "The object type, always 'album'" }, "uri": { "type": "string", "description": "The Spotify URI for the album" }, "artists": { "type": "array", "items": { "$ref": "#/components/schemas/SpotifySimplifiedArtist" }, "description": "The artists of the album" }, "album_group": { "type": "string", "enum": [ "album", "single", "compilation", "appears_on" ], "description": "The field to distinguish albums by various groups" } } }, "SpotifyExternalUrls": { "type": "object", "properties": { "spotify": { "type": "string", "description": "The Spotify URL for the object" } } }, "SpotifyImage": { "type": "object", "properties": { "url": { "type": "string", "description": "The source URL of the image" }, "height": { "type": "integer", "nullable": true, "description": "The image height in pixels" }, "width": { "type": "integer", "nullable": true, "description": "The image width in pixels" } } }, "SpotifySimplifiedArtist": { "type": "object", "properties": { "external_urls": { "$ref": "#/components/schemas/SpotifyExternalUrls" }, "href": { "type": "string", "description": "A link to the Web API endpoint providing full details" }, "id": { "type": "string", "description": "The Spotify ID for the artist" }, "name": { "type": "string", "description": "The name of the artist" }, "type": { "type": "string", "enum": [ "artist" ], "description": "The object type, always 'artist'" }, "uri": { "type": "string", "description": "The Spotify URI for the artist" } } } } } } ``` # Get Artist Top Tracks Source: https://recoupable.dev/docs/api-reference/spotify/artist-top-tracks Get an artist's top tracks by country. ## GET /api/spotify/artist/topTracks Full OpenAPI specification: https://recoupable.dev/docs/spec/social.json ## Authentication The supplied specification does not declare an OpenAPI security object for this operation. Its declared headers and parameters still apply. Consult the authentication guide and the full specification before calling it. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Social", "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/spotify/artist/topTracks": { "get": { "description": "Get an artist's top tracks by country.", "parameters": [ { "name": "id", "in": "query", "description": "The Spotify ID of the artist", "required": true, "schema": { "type": "string" } }, { "name": "market", "in": "query", "description": "An ISO 3166-1 alpha-2 country code. If provided, only tracks available in that market are returned", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Artist top tracks retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SpotifyArtistTopTracksResponse" } } } }, "400": { "description": "Bad request - missing required parameters", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SpotifyErrorResponse" } } } } } } } }, "components": { "schemas": { "SpotifyArtistTopTracksResponse": { "type": "object", "properties": { "tracks": { "type": "array", "items": { "$ref": "#/components/schemas/SpotifyTrack" }, "description": "Array of track objects" } } }, "SpotifyErrorResponse": { "type": "object", "properties": { "error": { "type": "object", "properties": { "status": { "type": "integer", "description": "HTTP status code" }, "message": { "type": "string", "description": "Error message" } }, "description": "Error details" } } }, "SpotifyTrack": { "type": "object", "properties": { "album": { "$ref": "#/components/schemas/SpotifySimplifiedAlbum", "description": "The album the track appears on" }, "artists": { "type": "array", "items": { "$ref": "#/components/schemas/SpotifySimplifiedArtist" }, "description": "Artists who performed the track" }, "available_markets": { "type": "array", "items": { "type": "string" }, "description": "Markets in which the track is available" }, "disc_number": { "type": "integer", "description": "Disc number the track is on" }, "duration_ms": { "type": "integer", "description": "Track length in milliseconds" }, "explicit": { "type": "boolean", "description": "Whether the track has explicit lyrics" }, "external_ids": { "type": "object", "properties": { "isrc": { "type": "string", "description": "International Standard Recording Code" }, "ean": { "type": "string", "description": "International Article Number" }, "upc": { "type": "string", "description": "Universal Product Code" } }, "description": "Known external IDs for the track" }, "external_urls": { "$ref": "#/components/schemas/SpotifyExternalUrls" }, "href": { "type": "string", "description": "Link to the Web API endpoint with full details" }, "id": { "type": "string", "description": "Spotify ID for the track" }, "is_playable": { "type": "boolean", "description": "If true, the track is playable in the given market" }, "linked_from": { "type": "object", "description": "Information about the originally requested track when track relinking is applied" }, "restrictions": { "type": "object", "properties": { "reason": { "type": "string", "description": "The reason for the restriction" } }, "description": "Content restriction information" }, "name": { "type": "string", "description": "Track name" }, "popularity": { "type": "integer", "description": "Popularity score (0-100)" }, "preview_url": { "type": "string", "nullable": true, "description": "URL to a 30 second preview, if available" }, "track_number": { "type": "integer", "description": "Track number on the album" }, "type": { "type": "string", "enum": [ "track" ], "description": "The object type, always 'track'" }, "uri": { "type": "string", "description": "The Spotify URI for the track" }, "is_local": { "type": "boolean", "description": "Whether the track is from a local file" } } }, "SpotifySimplifiedAlbum": { "type": "object", "properties": { "album_type": { "type": "string", "enum": [ "album", "single", "compilation" ], "description": "The type of the album" }, "total_tracks": { "type": "integer", "description": "The number of tracks in the album" }, "available_markets": { "type": "array", "items": { "type": "string" }, "description": "Markets in which the album is available" }, "external_urls": { "$ref": "#/components/schemas/SpotifyExternalUrls" }, "href": { "type": "string", "description": "A link to the Web API endpoint providing full details" }, "id": { "type": "string", "description": "The Spotify ID for the album" }, "images": { "type": "array", "items": { "$ref": "#/components/schemas/SpotifyImage" }, "description": "The cover art for the album in various sizes" }, "name": { "type": "string", "description": "The name of the album" }, "release_date": { "type": "string", "description": "The date the album was first released" }, "release_date_precision": { "type": "string", "enum": [ "year", "month", "day" ], "description": "The precision with which release_date value is known" }, "restrictions": { "type": "object", "properties": { "reason": { "type": "string", "description": "The reason for the restriction" } }, "description": "Included when a content restriction is applied" }, "type": { "type": "string", "enum": [ "album" ], "description": "The object type, always 'album'" }, "uri": { "type": "string", "description": "The Spotify URI for the album" }, "artists": { "type": "array", "items": { "$ref": "#/components/schemas/SpotifySimplifiedArtist" }, "description": "The artists of the album" }, "album_group": { "type": "string", "enum": [ "album", "single", "compilation", "appears_on" ], "description": "The field to distinguish albums by various groups" } } }, "SpotifySimplifiedArtist": { "type": "object", "properties": { "external_urls": { "$ref": "#/components/schemas/SpotifyExternalUrls" }, "href": { "type": "string", "description": "A link to the Web API endpoint providing full details" }, "id": { "type": "string", "description": "The Spotify ID for the artist" }, "name": { "type": "string", "description": "The name of the artist" }, "type": { "type": "string", "enum": [ "artist" ], "description": "The object type, always 'artist'" }, "uri": { "type": "string", "description": "The Spotify URI for the artist" } } }, "SpotifyExternalUrls": { "type": "object", "properties": { "spotify": { "type": "string", "description": "The Spotify URL for the object" } } }, "SpotifyImage": { "type": "object", "properties": { "url": { "type": "string", "description": "The source URL of the image" }, "height": { "type": "integer", "nullable": true, "description": "The image height in pixels" }, "width": { "type": "integer", "nullable": true, "description": "The image width in pixels" } } } } } } ``` # Get Album Source: https://recoupable.dev/docs/api-reference/spotify/album Get Spotify catalog information for a single album. ## GET /api/spotify/album Full OpenAPI specification: https://recoupable.dev/docs/spec/social.json ## Authentication The supplied specification does not declare an OpenAPI security object for this operation. Its declared headers and parameters still apply. Consult the authentication guide and the full specification before calling it. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Social", "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/spotify/album": { "get": { "description": "Get Spotify catalog information for a single album.", "parameters": [ { "name": "id", "in": "query", "description": "The Spotify ID of the album", "required": true, "schema": { "type": "string" } }, { "name": "market", "in": "query", "description": "An ISO 3166-1 alpha-2 country code. If provided, only content available in that market is returned", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Album retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SpotifyAlbum" } } } }, "400": { "description": "Bad request - missing required parameters", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SpotifyErrorResponse" } } } } } } } }, "components": { "schemas": { "SpotifyAlbum": { "type": "object", "properties": { "album_type": { "type": "string", "enum": [ "album", "single", "compilation" ], "description": "The type of the album" }, "total_tracks": { "type": "integer", "description": "The number of tracks in the album" }, "available_markets": { "type": "array", "items": { "type": "string" }, "description": "Markets in which the album is available" }, "external_urls": { "$ref": "#/components/schemas/SpotifyExternalUrls" }, "href": { "type": "string", "description": "A link to the Web API endpoint providing full details" }, "id": { "type": "string", "description": "The Spotify ID for the album" }, "images": { "type": "array", "items": { "$ref": "#/components/schemas/SpotifyImage" }, "description": "The cover art for the album in various sizes" }, "name": { "type": "string", "description": "The name of the album" }, "release_date": { "type": "string", "description": "The date the album was first released" }, "release_date_precision": { "type": "string", "enum": [ "year", "month", "day" ], "description": "The precision with which release_date value is known" }, "restrictions": { "type": "object", "properties": { "reason": { "type": "string" } }, "description": "Included when a content restriction is applied" }, "type": { "type": "string", "enum": [ "album" ], "description": "The object type, always 'album'" }, "uri": { "type": "string", "description": "The Spotify URI for the album" }, "artists": { "type": "array", "items": { "$ref": "#/components/schemas/SpotifySimplifiedArtist" }, "description": "The artists of the album" }, "tracks": { "$ref": "#/components/schemas/SpotifyAlbumTracks", "description": "The tracks of the album" }, "copyrights": { "type": "array", "items": { "$ref": "#/components/schemas/SpotifyCopyright" }, "description": "Copyright statements of the album" }, "external_ids": { "type": "object", "properties": { "isrc": { "type": "string" }, "ean": { "type": "string" }, "upc": { "type": "string" } }, "description": "Known external IDs for the album" }, "genres": { "type": "array", "items": { "type": "string" }, "description": "Deprecated. Always empty." }, "label": { "type": "string", "description": "The label associated with the album" }, "popularity": { "type": "integer", "description": "Popularity of the album (0-100)" } } }, "SpotifyErrorResponse": { "type": "object", "properties": { "error": { "type": "object", "properties": { "status": { "type": "integer", "description": "HTTP status code" }, "message": { "type": "string", "description": "Error message" } }, "description": "Error details" } } }, "SpotifyExternalUrls": { "type": "object", "properties": { "spotify": { "type": "string", "description": "The Spotify URL for the object" } } }, "SpotifyImage": { "type": "object", "properties": { "url": { "type": "string", "description": "The source URL of the image" }, "height": { "type": "integer", "nullable": true, "description": "The image height in pixels" }, "width": { "type": "integer", "nullable": true, "description": "The image width in pixels" } } }, "SpotifySimplifiedArtist": { "type": "object", "properties": { "external_urls": { "$ref": "#/components/schemas/SpotifyExternalUrls" }, "href": { "type": "string", "description": "A link to the Web API endpoint providing full details" }, "id": { "type": "string", "description": "The Spotify ID for the artist" }, "name": { "type": "string", "description": "The name of the artist" }, "type": { "type": "string", "enum": [ "artist" ], "description": "The object type, always 'artist'" }, "uri": { "type": "string", "description": "The Spotify URI for the artist" } } }, "SpotifyAlbumTracks": { "type": "object", "properties": { "href": { "type": "string", "description": "A link to the Web API endpoint" }, "limit": { "type": "integer", "description": "The maximum number of items in the response" }, "next": { "type": "string", "nullable": true, "description": "URL to the next page of items" }, "offset": { "type": "integer", "description": "The offset of the items returned" }, "previous": { "type": "string", "nullable": true, "description": "URL to the previous page" }, "total": { "type": "integer", "description": "Total number of items available" }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/SpotifySimplifiedTrack" }, "description": "Array of simplified track objects" } } }, "SpotifyCopyright": { "type": "object", "properties": { "text": { "type": "string", "description": "The copyright text" }, "type": { "type": "string", "description": "The type of copyright" } } }, "SpotifySimplifiedTrack": { "type": "object", "properties": { "artists": { "type": "array", "items": { "$ref": "#/components/schemas/SpotifySimplifiedArtist" }, "description": "The artists who performed the track" }, "available_markets": { "type": "array", "items": { "type": "string" }, "description": "Markets in which the track is available" }, "disc_number": { "type": "integer", "description": "Disc number the track is on" }, "duration_ms": { "type": "integer", "description": "Track length in milliseconds" }, "explicit": { "type": "boolean", "description": "Whether the track has explicit lyrics" }, "external_urls": { "$ref": "#/components/schemas/SpotifyExternalUrls" }, "href": { "type": "string", "description": "Link to the Web API endpoint" }, "id": { "type": "string", "description": "Spotify ID for the track" }, "is_playable": { "type": "boolean", "description": "If true, the track is playable" }, "linked_from": { "type": "object", "description": "Track relinking info" }, "restrictions": { "type": "object", "properties": { "reason": { "type": "string" } } }, "name": { "type": "string", "description": "Track name" }, "preview_url": { "type": "string", "nullable": true, "description": "URL to a 30 second preview" }, "track_number": { "type": "integer", "description": "Track number on the album" }, "type": { "type": "string", "enum": [ "track" ] }, "uri": { "type": "string", "description": "Spotify URI for the track" }, "is_local": { "type": "boolean", "description": "Whether from a local file" } } } } } } ``` # Songs by ISRC Source: https://recoupable.dev/docs/api-reference/apple/songs Look up recordings in the Apple Music catalog by ISRC. Accepts up to 25 comma-separated ISRCs per request and returns one row per **requested** ISRC, so a recording that Apple does not carry comes back explicitly as `found: false` rather than being omitted. Complements `GET /api/spotify/search`, which reaches ISRCs only through a fuzzy `isrc:` search query; Apple matches the identifier exactly and additionally returns release-level rights metadata (`upc`, `record_label`, `copyright`) that Spotify does not expose. ## GET /api/apple/songs Full OpenAPI specification: https://recoupable.dev/docs/spec/social.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Social", "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/apple/songs": { "get": { "description": "Look up recordings in the Apple Music catalog by ISRC. Accepts up to 25 comma-separated ISRCs per request and returns one row per **requested** ISRC, so a recording that Apple does not carry comes back explicitly as `found: false` rather than being omitted. Complements `GET /api/spotify/search`, which reaches ISRCs only through a fuzzy `isrc:` search query; Apple matches the identifier exactly and additionally returns release-level rights metadata (`upc`, `record_label`, `copyright`) that Spotify does not expose.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "parameters": [ { "name": "isrc", "in": "query", "description": "One or more ISRCs, comma-separated. Case-insensitive; each must match the 12-character ISRC format (two-letter country code, three-character registrant code, two-digit year, five-digit designation). Maximum 25 per request.", "required": true, "schema": { "type": "string", "example": "DEH742611917,TCAEC1931080" } }, { "name": "storefront", "in": "query", "description": "Apple Music storefront to search, as a two-letter country code. Availability is territory-specific, so a recording present in one storefront may be absent from another.", "required": false, "schema": { "type": "string", "default": "us", "example": "gb" } } ], "responses": { "200": { "description": "Lookup completed. One entry per requested ISRC, in the order requested.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AppleSongsResponse" } } } }, "400": { "description": "Bad request - `isrc` missing, malformed, or over the 25-ISRC limit, or an unknown `storefront`", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AppleErrorResponse" }, "example": { "status": "error", "error": "isrc must be a valid ISRC: NOTANISRC" } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AppleErrorResponse" }, "example": { "status": "error", "error": "Exactly one of x-api-key or Authorization must be provided" } } } }, "500": { "description": "Internal server error, including a failure to reach the Apple Music API", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AppleErrorResponse" }, "example": { "status": "error", "error": "Failed to reach the Apple Music API" } } } } } } } }, "components": { "schemas": { "AppleSongsResponse": { "type": "object", "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the request" }, "storefront": { "type": "string", "description": "The storefront the lookup ran against", "example": "us" }, "results": { "type": "array", "description": "One entry per requested ISRC, in the order requested", "items": { "$ref": "#/components/schemas/AppleIsrcResult" } } } }, "AppleErrorResponse": { "type": "object", "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Status of the request" }, "error": { "type": "string", "description": "Human-readable error message" } } }, "AppleIsrcResult": { "type": "object", "properties": { "isrc": { "type": "string", "description": "The requested ISRC, uppercased", "example": "DEH742611917" }, "found": { "type": "boolean", "description": "Whether Apple Music carries at least one recording with this ISRC in the requested storefront", "example": true }, "songs": { "type": "array", "description": "Every recording Apple returns for this ISRC. Usually one, but the same recording can appear on several releases (compilations, anniversary editions), each with its own song id.", "items": { "$ref": "#/components/schemas/AppleSong" } } } }, "AppleSong": { "type": "object", "properties": { "id": { "type": "string", "description": "Apple Music catalog song id", "example": "1894880802" }, "isrc": { "type": "string", "example": "DEH742611917" }, "name": { "type": "string", "example": "Steady Box Fan White Noise (Extended Mix)" }, "artist_name": { "type": "string", "description": "Display credit as Apple renders it, which may combine several artists", "example": "Sleep Sounds & Sleepy Buddy" }, "composer_name": { "type": "string", "nullable": true, "example": "Zachary Kubilus" }, "album_name": { "type": "string", "example": "Box Fan All Night" }, "release_date": { "type": "string", "format": "date", "nullable": true, "example": "2026-05-01" }, "duration_ms": { "type": "integer", "example": 124001 }, "track_number": { "type": "integer", "nullable": true, "example": 6 }, "disc_number": { "type": "integer", "nullable": true, "example": 1 }, "genre_names": { "type": "array", "items": { "type": "string" }, "example": [ "New Age", "Music", "Worldwide" ] }, "has_lyrics": { "type": "boolean", "example": false }, "is_apple_digital_master": { "type": "boolean", "example": false }, "audio_variants": { "type": "array", "description": "Delivery formats available, for example `lossless`, `lossy-stereo`, `dolby-atmos`", "items": { "type": "string" }, "example": [ "lossless", "lossy-stereo" ] }, "url": { "type": "string", "description": "Public Apple Music page for the recording", "example": "https://music.apple.com/us/album/steady-box-fan-white-noise-extended-mix/1894880796?i=1894880802" }, "artwork_url": { "type": "string", "nullable": true, "description": "Artwork template URL. Substitute `{w}` and `{h}` with the pixel dimensions you want.", "example": "https://is1-ssl.mzstatic.com/image/thumb/Music221/v4/d5/d1/12/d5d112cd-19f8-6cf7-b048-b2f94c75fefd/4065328882161.png/{w}x{h}bb.jpg" }, "preview_url": { "type": "string", "nullable": true, "description": "30-second AAC preview", "example": "https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview211/v4/56/64/02/56640222-1893-38a9-e25f-9869aa117282/mzaf_15389777242743919344.plus.aac.p.m4a" }, "album": { "$ref": "#/components/schemas/AppleSongAlbum" } } }, "AppleSongAlbum": { "type": "object", "nullable": true, "description": "The release this recording appears on. Carries the rights metadata Apple exposes and Spotify does not.", "properties": { "id": { "type": "string", "example": "1894880796" }, "name": { "type": "string", "example": "Box Fan All Night" }, "upc": { "type": "string", "nullable": true, "example": "4065328882161" }, "record_label": { "type": "string", "nullable": true, "example": "Sleep Sounds" }, "copyright": { "type": "string", "nullable": true, "description": "The phonographic copyright line", "example": "℗ 2026 Sleep Sounds" }, "release_date": { "type": "string", "format": "date", "nullable": true, "example": "2026-05-01" }, "track_count": { "type": "integer", "nullable": true, "example": 20 }, "is_single": { "type": "boolean", "example": false }, "is_compilation": { "type": "boolean", "example": false }, "is_complete": { "type": "boolean", "description": "Whether every track on the release is available in this storefront", "example": true }, "url": { "type": "string", "example": "https://music.apple.com/us/album/box-fan-all-night/1894880796" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Scraper Results Source: https://recoupable.dev/docs/api-reference/apify/scraper Check the status and retrieve results from Apify scraper runs. This endpoint uses the Apify API Client to fetch the current status of a scraper run and its results if available. Use the runId returned from endpoints like Instagram Comments, Instagram Profiles, Social Scrape, or Artist Socials Scrape to poll for results. ## GET /api/apify/runs/{runId} Full OpenAPI specification: https://recoupable.dev/docs/spec/social.json ## Authentication The supplied specification does not declare an OpenAPI security object for this operation. Its declared headers and parameters still apply. Consult the authentication guide and the full specification before calling it. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Social", "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/apify/runs/{runId}": { "get": { "description": "Check the status and retrieve results from Apify scraper runs. This endpoint uses the Apify API Client to fetch the current status of a scraper run and its results if available. Use the runId returned from endpoints like Instagram Comments, Instagram Profiles, Social Scrape, or Artist Socials Scrape to poll for results.", "parameters": [ { "name": "runId", "in": "path", "description": "The ID of the Apify run to check status for. This is returned when starting a scrape via Instagram Comments, Instagram Profiles, Social Scrape, or Artist Socials Scrape endpoints.", "required": true, "schema": { "type": "string" }, "example": "abc123xyz" } ], "responses": { "200": { "description": "Scraper run status retrieved successfully. Returns status info for in-progress runs, or status info plus data for completed runs.", "content": { "application/json": { "schema": { "oneOf": [ { "$ref": "#/components/schemas/ApifyScraperInProgressResponse" }, { "$ref": "#/components/schemas/ApifyScraperCompletedResponse" } ] } } } }, "400": { "description": "Bad request - missing required runId parameter", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApifyScraperErrorResponse" } } } }, "500": { "description": "Server error - failed to fetch run status from Apify", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApifyScraperErrorResponse" } } } } } } } }, "components": { "schemas": { "ApifyScraperInProgressResponse": { "type": "object", "required": [ "status", "dataset_id" ], "description": "Response when the Apify run is still in progress", "properties": { "status": { "type": "string", "description": "Current status of the Apify run (e.g., 'RUNNING', 'READY')", "example": "RUNNING" }, "dataset_id": { "type": "string", "description": "ID of the dataset that will contain the results when the run completes", "example": "def456uvw", "nullable": true } } }, "ApifyScraperCompletedResponse": { "type": "object", "required": [ "status", "dataset_id", "data" ], "description": "Response when the Apify run has completed successfully", "properties": { "status": { "type": "string", "description": "Final status of the Apify run (typically 'SUCCEEDED')", "example": "SUCCEEDED" }, "dataset_id": { "type": "string", "description": "ID of the dataset containing the results", "example": "def456uvw" }, "data": { "type": "array", "items": { "type": "object", "additionalProperties": true }, "description": "Array of dataset items returned by the scraper. The structure of each item varies depending on the scraper type.", "example": [ { "id": "123456789", "username": "example_user", "fullName": "Example User", "biography": "This is a sample biography", "followersCount": 1000, "followsCount": 500, "profilePicUrl": "https://example.com/profile.jpg", "latestPosts": [ { "shortCode": "AbCdEfGhIjK", "type": "Video", "caption": "Example reel caption", "likesCount": 16, "commentsCount": 4, "videoViewCount": 56, "timestamp": "2026-06-30T00:26:41.000Z" } ] }, { "type": "tweet", "id": "1234567890123456789", "url": "https://x.com/example_user/status/1234567890123456789", "text": "Example tweet text", "createdAt": "Wed Jul 01 17:38:08 +0000 2026", "likeCount": 7, "retweetCount": 2, "replyCount": 1, "quoteCount": 0, "bookmarkCount": 1, "viewCount": 258 }, { "type": "video", "id": "aBcDeFgHiJk", "title": "Example Short", "url": "https://www.youtube.com/watch?v=aBcDeFgHiJk", "date": "2026-07-01T17:55:00.000Z", "viewCount": 26, "likes": 1, "commentsCount": 0, "duration": "00:00:45", "channelUsername": "examplechannel" } ] } } }, "ApifyScraperErrorResponse": { "type": "object", "required": [ "status", "message" ], "description": "Error response from the Apify scraper results endpoint", "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Status indicating an error occurred" }, "message": { "type": "string", "description": "Error message describing what went wrong", "example": "runId is required" } } } } } } ``` # Agent Signup Source: https://recoupable.dev/docs/api-reference/agents/signup Register an agent. For new agent+ emails, returns an API key immediately. For all other cases, sends a verification code to the email — call [POST /api/agents/verify](/docs/api-reference/agents/verify) with the code to get your API key. ## POST /api/agents/signup Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation's specification permits a request without authentication. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/agents/signup": { "post": { "description": "Register an agent. For new agent+ emails, returns an API key immediately. For all other cases, sends a verification code to the email — call [POST /api/agents/verify](/docs/api-reference/agents/verify) with the code to get your API key.\n\n**Tip:** If you're unsure what email to register, generate a unique `agent+{suffix}@recoupable.com` address (e.g. `agent+1744410896-28439@recoupable.com`, combining a Unix timestamp with a random integer). This guarantees a fresh `agent+` address on every call — including multiple signups within the same second — and returns an API key instantly with no email verification required.", "security": [], "requestBody": { "description": "Agent email to register", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AgentSignupRequest" } } } }, "responses": { "200": { "description": "Signup processed successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AgentSignupResponse" } } } } } } } }, "components": { "schemas": { "AgentSignupRequest": { "type": "object", "required": [ "email" ], "properties": { "email": { "type": "string", "format": "email", "description": "The agent email address. Emails with the agent+ prefix (e.g. agent+mybot@example.com) get an API key immediately on first signup.", "example": "agent+mybot@example.com" } } }, "AgentSignupResponse": { "type": "object", "required": [ "account_id", "message" ], "properties": { "account_id": { "type": "string", "format": "uuid", "description": "The account ID for the registered agent.", "example": "123e4567-e89b-12d3-a456-426614174000" }, "api_key": { "type": [ "string", "null" ], "description": "API key returned immediately for new agent+ prefix emails. Null for all other cases — check your email for a verification code.", "example": "recoup_sk_abc123..." }, "message": { "type": "string", "description": "Human-readable status message.", "example": "If this is a new agent+ email, your API key is included. Otherwise, check your email for a verification code." } } } } } } ``` # Verify Agent Email Source: https://recoupable.dev/docs/api-reference/agents/verify Verify an agent's email with the code sent during signup. Returns an API key on success. ## POST /api/agents/verify Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation's specification permits a request without authentication. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/agents/verify": { "post": { "description": "Verify an agent's email with the code sent during signup. Returns an API key on success.", "security": [], "requestBody": { "description": "Email and verification code", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AgentVerifyRequest" } } } }, "responses": { "200": { "description": "Verification successful", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AgentVerifyResponse" } } } }, "400": { "description": "Invalid or expired verification code", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "429": { "description": "Too many failed verification attempts", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "AgentVerifyRequest": { "type": "object", "required": [ "email", "code" ], "properties": { "email": { "type": "string", "format": "email", "description": "The email address used during signup.", "example": "you@example.com" }, "code": { "type": "string", "minLength": 6, "maxLength": 6, "pattern": "^[0-9]{6}$", "description": "The 6-digit verification code sent to the email.", "example": "123456" } } }, "AgentVerifyResponse": { "type": "object", "required": [ "account_id", "api_key", "message" ], "properties": { "account_id": { "type": "string", "format": "uuid", "description": "The account ID for the verified agent.", "example": "123e4567-e89b-12d3-a456-426614174000" }, "api_key": { "type": "string", "description": "API key for the verified account.", "example": "recoup_sk_abc123..." }, "message": { "type": "string", "description": "Human-readable status message.", "example": "Verified" } } }, "Error": { "required": [ "error", "message" ], "type": "object", "properties": { "error": { "type": "integer", "format": "int32" }, "message": { "type": "string" } } } } } } ``` # Agents Source: https://recoupable.dev/docs/agents Programmatic agent onboarding — sign up and obtain API keys in one call, no dashboard, no human in the loop. ## Quickest start Get a working API key in a single unauthenticated request: ```bash curl -X POST "https://api.recoupable.dev/api/agents/signup" \ -H "Content-Type: application/json" \ -d '{"email": "agent+'$(date +%s)-$RANDOM'@recoupable.com"}' ``` Response: ```json { "account_id": "123e4567-e89b-12d3-a456-426614174000", "api_key": "recoup_sk_abc123...", "message": "If this is a new agent+ email, your API key is included. Otherwise, check your email for a verification code." } ``` That's it. Store `api_key`, pass it in the `x-api-key` header on every subsequent request, and you're done. > **Tip** > **One-liner — sign up and export the key in one shot.** Drop this into your shell and you'll have `$RECOUP_API_KEY` ready to use on the next line: > > ```bash > export RECOUP_API_KEY=$(curl -s -X POST "https://api.recoupable.dev/api/agents/signup" \ > -H "Content-Type: application/json" \ > -d '{"email": "agent+'$(date +%s)-$RANDOM'@recoupable.com"}' | jq -r .api_key) > ``` > > Verify it worked: > > ```bash > curl -H "x-api-key: $RECOUP_API_KEY" https://api.recoupable.dev/api/accounts/id > ``` > **Tip** > The `agent+{unique-suffix}@recoupable.com` shape is the recommended path for agents — it always returns an API key instantly, with no email verification required. Combining `$(date +%s)` with `$RANDOM` guarantees a fresh, collision-free address on every call (including multiple signups within the same second) and is portable across macOS and Linux shells. ## How it works Two unauthenticated endpoints power agent onboarding: - **[`POST /api/agents/signup`](https://recoupable.dev/docs/api-reference/agents/signup)** — Register with an email address. Emails with the `agent+` prefix that have never been seen before receive an API key immediately. Any other email (or a previously-used `agent+` address) receives a 6-digit verification code via email. - **[`POST /api/agents/verify`](https://recoupable.dev/docs/api-reference/agents/verify)** — Submit the verification code to receive an API key. Multiple API keys per account are supported — each signup or verification generates a new key without revoking existing ones. ## Standard signup (email verification) If you're building a human-facing integration and want the user to verify their real email, use any non-`agent+` address: Step 1 — request a verification code: ```bash curl -X POST "https://api.recoupable.dev/api/agents/signup" \ -H "Content-Type: application/json" \ -d '{"email": "you@example.com"}' ``` Step 2 — submit the 6-digit code from the verification email: ```bash curl -X POST "https://api.recoupable.dev/api/agents/verify" \ -H "Content-Type: application/json" \ -d '{"email": "you@example.com", "code": "123456"}' ``` Response: ```json { "account_id": "123e4567-e89b-12d3-a456-426614174000", "api_key": "recoup_sk_abc123...", "message": "Verified" } ``` ## Using your API key Pass the returned `api_key` in the `x-api-key` header on every authenticated request: ```bash curl -X GET "https://api.recoupable.dev/api/tasks" \ -H "x-api-key: YOUR_API_KEY" ``` See [Authentication](https://recoupable.dev/docs/authentication) for the full authentication model, including organization access and Bearer token support, and [Quickstart](https://recoupable.dev/docs/quickstart) for your first end-to-end request. # Content Source: https://recoupable.dev/docs/content-agent Generate images, videos, captions, and social-ready content using AI-powered primitives ## Overview Recoup's content API gives you seven independent primitives for generating and editing visual content. Each primitive does one thing well. You orchestrate them. **Every primitive works without a template.** Pass your own prompt, reference images, and parameters directly. Templates are optional shortcuts — opinionated creative recipes that pre-fill parameters for a specific look. ## Primitives | Primitive | Endpoint | What it does | | --- | --- | --- | | Generate Image | [POST /api/content/image](https://recoupable.dev/docs/api-reference/content/generate-image) | Create an image from a text prompt, optionally with a reference image for face/style | | Generate Video | [POST /api/content/video](https://recoupable.dev/docs/api-reference/content/generate-video) | Create a video — 6 modes: prompt, animate, reference, extend, first-last, lipsync | | Generate Caption | [POST /api/content/caption](https://recoupable.dev/docs/api-reference/content/generate-caption) | Generate on-screen text for social media videos | | Transcribe Audio | [POST /api/content/transcribe](https://recoupable.dev/docs/api-reference/content/transcribe-audio) | Transcribe audio to timestamped lyrics/text | | Edit Content | [PATCH /api/content](https://recoupable.dev/docs/api-reference/content/edit) | Trim, crop, resize, overlay text, or add audio — one processing pass | | Upscale | [POST /api/content/upscale](https://recoupable.dev/docs/api-reference/content/upscale) | Upscale image or video resolution (up to 4x) | | Analyze Video | [POST /api/content/analyze](https://recoupable.dev/docs/api-reference/content/analyze-video) | AI video analysis — describe scenes, check quality, evaluate content | There is also [POST /api/content/create](https://recoupable.dev/docs/api-reference/content/create) which runs the full pipeline in one call — use it when you want a video without creative control over each step. ## How It Works ### Without a template (malleable mode) Pass your own parameters directly to any primitive. Maximum creative control. ```bash # Generate an image with your own prompt curl -X POST https://api.recoupable.dev/api/content/image \ -H "x-api-key: YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{"prompt": "A moody portrait in a dimly lit room, front-facing phone camera"}' # Generate a video from that image curl -X POST https://api.recoupable.dev/api/content/video \ -H "x-api-key: YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{"image_url": "IMAGE_URL_FROM_ABOVE", "prompt": "subtle breathing motion, nearly still"}' ``` ### With a template (shortcut mode) Pass a template ID and the primitive fills in prompts, reference images, and style rules automatically. You can still override any parameter. ```bash # Same image, but the template provides the prompt and reference images curl -X POST https://api.recoupable.dev/api/content/image \ -H "x-api-key: YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{"template": "artist-caption-bedroom", "reference_image_url": "YOUR_FACE_IMAGE"}' ``` Use [GET /api/content/templates](https://recoupable.dev/docs/api-reference/content/templates) to see available templates with descriptions. ## Templates A template is a complete creative recipe — it defines what a piece of content looks like across every primitive: - **Image config**: prompt, reference images, style rules (camera, lighting, composition) - **Video config**: mood variations, movement descriptions - **Caption config**: tone, formatting rules, example captions - **Edit config**: crop ratio, text overlay style, audio mixing Templates are optional. They save time by pre-filling parameters with curated defaults. When you see customers repeatedly creating the same kind of content, that pattern becomes a template. ### Override priority When using a template, your explicit parameters always win: 1. **Your params** — highest priority. What you pass overrides everything. 2. **Artist context** — if the artist has a style guide, it personalizes the template. 3. **Template defaults** — lowest priority. The recipe's built-in values. ## Video Modes The video primitive supports 6 generation modes: | Mode | What it does | Required inputs | | --- | --- | --- | | `prompt` | Create from text description | `prompt` | | `animate` | Animate a still image | `image_url`, `prompt` | | `reference` | Use image as style reference (not first frame) | `image_url`, `prompt` | | `extend` | Continue an existing video | `video_url`, `prompt` | | `first-last` | Transition between two images | `image_url`, `end_image_url`, `prompt` | | `lipsync` | Sync face to audio | `image_url`, `audio_url` | Set `mode` explicitly, or omit it and the API infers the mode from the inputs you provide. ## Iteration Each primitive is independent. Redo any step without rerunning the whole pipeline: - Bad image? Regenerate with a different prompt or reference - Caption too long? Regenerate with `length: "short"` - Video glitchy? Analyze it, then regenerate with adjusted params - Clip too short? Use `extend` mode to continue it - Low quality? Upscale the image or video - Everything good but wrong caption? Just re-run the edit step ## Content Agent (Slack Bot) The **Recoup Content Agent** is a Slack bot that generates social-ready artist videos on @mention. It plugs into the content creation pipeline and delivers results directly in your Slack thread. ### @Mention Syntax ``` @RecoupContentAgent [template] [batch=N] [lipsync] ``` | Parameter | Required | Description | | --- | --- | --- | | `artist_account_id` | Yes | UUID of the artist account | | `template` | No | Content template name. Optional — when omitted, the pipeline runs with default settings. See [GET /api/content/templates](https://recoupable.dev/docs/api-reference/content/templates) for options. | | `batch=N` | No | Number of videos to generate (1-30, default 1) | | `lipsync` | No | Enable lipsync mode (audio baked into video) | ### Examples **Basic — single video with default template:** ``` @RecoupContentAgent abc-123-uuid ``` **Custom template:** ``` @RecoupContentAgent abc-123-uuid artist-caption-bedroom ``` **Batch with lipsync:** ``` @RecoupContentAgent abc-123-uuid batch=3 lipsync ``` ### Architecture | Component | Location | Purpose | | --- | --- | --- | | Slack webhook | `POST /api/content-agent/slack` | Receives @mention events | | Callback endpoint | `POST /api/content-agent/callback` | Receives polling results | | Bot singleton | `lib/content-agent/bot.ts` | Chat SDK with Slack adapter + Redis state | | Mention handler | `lib/content-agent/handlers/` | Parses args, validates artist, triggers pipeline | | Poll task | `poll-content-run` (Trigger.dev) | Monitors content runs, posts results via callback | ### Data Flow 1. **Slack event** → `POST /api/content-agent/slack` handles the webhook 2. **Mention handler** parses the command, calls [`GET /api/content/validate`](https://recoupable.dev/docs/api-reference/content/validate) to check artist readiness 3. **Content creation** triggered via [`POST /api/content/create`](https://recoupable.dev/docs/api-reference/content/create) — returns `runIds` 4. **Poll task** (`poll-content-run`) monitors the Trigger.dev runs every 30 seconds (up to 30 minutes) 5. **Callback** → [`POST /api/content-agent/callback`](https://recoupable.dev/docs/api-reference/content-agent/callback) receives results and posts video URLs back to the Slack thread ### Setup #### 1. Create a Slack App 1. Go to [api.slack.com/apps](https://api.slack.com/apps) and create a new app 2. Under **OAuth & Permissions**, add bot scopes: - `chat:write` — post messages - `app_mentions:read` — receive @mention events 3. Under **Event Subscriptions**: - Enable events - Set the request URL to `https://api.recoupable.dev/api/content-agent/slack` - Subscribe to `app_mention` bot event 4. Install the app to your workspace #### 2. Configure Environment Variables | Variable | Where | Description | | --- | --- | --- | | `SLACK_CONTENT_BOT_TOKEN` | API (Vercel) | Bot OAuth token (`xoxb-...`) from Slack app | | `SLACK_CONTENT_SIGNING_SECRET` | API (Vercel) | Signing secret from Slack app **Basic Information** | | `CONTENT_AGENT_CALLBACK_SECRET` | API + Tasks | Shared secret for callback authentication (generate a random string) | | `RECOUP_API_KEY` | API + Tasks | Recoup API key for authenticating pipeline requests | | `RECOUP_API_BASE_URL` | Tasks (Trigger.dev) | API base URL (e.g., `https://api.recoupable.dev`) | #### 3. Verify Mention the bot in any Slack channel where it's been added: ``` @RecoupContentAgent ``` You should see: 1. An immediate acknowledgment message 2. A video URL reply in the thread after ~5-10 minutes ### Troubleshooting | Issue | Cause | Fix | | --- | --- | --- | | No response from bot | Event subscription URL not configured | Check Slack app Event Subscriptions | | "Artist not found" | Invalid `artist_account_id` | Verify the UUID exists in the platform | | "No GitHub repository found" | Artist missing repo config | Ensure the artist account has a linked GitHub repo | | Timeout after 30 min | Pipeline took too long | Check Trigger.dev dashboard for the failed run | | "Unsupported template" | Invalid template name | Use [`GET /api/content/templates`](https://recoupable.dev/docs/api-reference/content/templates) to list available templates | # List Templates Source: https://recoupable.dev/docs/api-reference/templates/list List templates ## GET /api/agents/templates Full OpenAPI specification: https://recoupable.dev/docs/spec/templates.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Templates", "description": "API documentation for managing reusable templates on the Recoup platform. Templates capture a prompt, description, and tags that can be shared with other accounts or kept private.", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "paths": { "/api/agents/templates": { "get": { "summary": "List templates", "description": "Retrieve every template visible to the authenticated account. The response combines templates the account owns, public templates created by other accounts, and private templates that have been shared with the authenticated account's email.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "responses": { "200": { "description": "Templates retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ListTemplatesResponse" } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } } }, "components": { "schemas": { "ListTemplatesResponse": { "type": "object", "required": [ "status", "templates" ], "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the request" }, "templates": { "type": "array", "items": { "$ref": "#/components/schemas/Template" }, "description": "Templates accessible to the authenticated account (own, public, and shared)" } } }, "ErrorResponse": { "type": "object", "required": [ "status", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Status of the request" }, "error": { "type": "string", "description": "Human-readable error message" }, "missing_fields": { "type": "array", "items": { "type": "string" }, "description": "Names of required fields that were missing from the request, when applicable" } } }, "Template": { "type": "object", "required": [ "id", "title", "description", "prompt", "tags", "creator", "is_private", "is_favourite", "favorites_count", "shared_emails", "created_at", "updated_at" ], "properties": { "id": { "type": "string", "format": "uuid", "description": "Unique identifier of the template" }, "title": { "type": "string", "description": "Short human-readable title for the template (3-50 characters)" }, "description": { "type": "string", "description": "Description of what the template does (10-200 characters)" }, "prompt": { "type": "string", "description": "The agent prompt body (20-10000 characters)" }, "tags": { "type": "array", "items": { "type": "string" }, "description": "Free-form tags used to categorize the template. Empty array when there are no tags." }, "creator": { "$ref": "#/components/schemas/TemplateCreator" }, "is_private": { "type": "boolean", "description": "Whether the template is restricted to the creator and explicitly shared accounts" }, "is_favourite": { "type": "boolean", "description": "Whether the authenticated account has favorited this template" }, "favorites_count": { "type": "integer", "description": "Total number of accounts that have favorited this template" }, "shared_emails": { "type": "array", "items": { "type": "string", "format": "email" }, "description": "Emails the template has been shared with. Only populated for private templates the authenticated account owns; empty for public templates and for private templates shared with the caller." }, "created_at": { "type": "string", "format": "date-time", "description": "ISO 8601 timestamp of when the template was created" }, "updated_at": { "type": [ "string", "null" ], "format": "date-time", "description": "ISO 8601 timestamp of when the template was last updated" } } }, "TemplateCreator": { "type": [ "object", "null" ], "description": "The account that created the template. May be null when the creator account is unavailable.", "required": [ "id", "name", "image", "is_admin" ], "properties": { "id": { "type": "string", "format": "uuid", "description": "Unique identifier of the creator account" }, "name": { "type": [ "string", "null" ], "description": "Display name of the creator account" }, "image": { "type": [ "string", "null" ], "description": "Profile image URL of the creator account" }, "is_admin": { "type": "boolean", "description": "Whether the creator is a Recoup admin (their account email is on the platform admin allow-list)" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Create Template Source: https://recoupable.dev/docs/api-reference/templates/create Create a template ## POST /api/agents/templates Full OpenAPI specification: https://recoupable.dev/docs/spec/templates.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Templates", "description": "API documentation for managing reusable templates on the Recoup platform. Templates capture a prompt, description, and tags that can be shared with other accounts or kept private.", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "paths": { "/api/agents/templates": { "post": { "summary": "Create a template", "description": "Create a new template owned by the authenticated account. When `is_private` is true, the optional `share_emails` array grants explicit read access to the listed accounts; for public templates the field is ignored.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "requestBody": { "description": "Template fields", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateTemplateRequest" } } } }, "responses": { "201": { "description": "Template created successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TemplateResponse" } } } }, "400": { "description": "Bad request - validation failed or required fields missing", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } } }, "components": { "schemas": { "CreateTemplateRequest": { "type": "object", "required": [ "title", "description", "prompt", "tags", "is_private" ], "properties": { "title": { "type": "string", "minLength": 3, "maxLength": 50, "description": "Short human-readable title for the template" }, "description": { "type": "string", "minLength": 10, "maxLength": 200, "description": "Description of what the template does" }, "prompt": { "type": "string", "minLength": 20, "maxLength": 10000, "description": "The agent prompt body" }, "tags": { "type": "array", "items": { "type": "string" }, "description": "Free-form tags used to categorize the template. Pass an empty array if there are no tags." }, "is_private": { "type": "boolean", "description": "When true, the template is restricted to the creator and the accounts listed in `share_emails`" }, "share_emails": { "type": "array", "items": { "type": "string", "format": "email" }, "description": "Emails to grant access to. Only applied when `is_private` is true. Defaults to an empty array when omitted." } } }, "TemplateResponse": { "type": "object", "required": [ "status", "template" ], "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the request" }, "template": { "$ref": "#/components/schemas/Template" } } }, "ErrorResponse": { "type": "object", "required": [ "status", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Status of the request" }, "error": { "type": "string", "description": "Human-readable error message" }, "missing_fields": { "type": "array", "items": { "type": "string" }, "description": "Names of required fields that were missing from the request, when applicable" } } }, "Template": { "type": "object", "required": [ "id", "title", "description", "prompt", "tags", "creator", "is_private", "is_favourite", "favorites_count", "shared_emails", "created_at", "updated_at" ], "properties": { "id": { "type": "string", "format": "uuid", "description": "Unique identifier of the template" }, "title": { "type": "string", "description": "Short human-readable title for the template (3-50 characters)" }, "description": { "type": "string", "description": "Description of what the template does (10-200 characters)" }, "prompt": { "type": "string", "description": "The agent prompt body (20-10000 characters)" }, "tags": { "type": "array", "items": { "type": "string" }, "description": "Free-form tags used to categorize the template. Empty array when there are no tags." }, "creator": { "$ref": "#/components/schemas/TemplateCreator" }, "is_private": { "type": "boolean", "description": "Whether the template is restricted to the creator and explicitly shared accounts" }, "is_favourite": { "type": "boolean", "description": "Whether the authenticated account has favorited this template" }, "favorites_count": { "type": "integer", "description": "Total number of accounts that have favorited this template" }, "shared_emails": { "type": "array", "items": { "type": "string", "format": "email" }, "description": "Emails the template has been shared with. Only populated for private templates the authenticated account owns; empty for public templates and for private templates shared with the caller." }, "created_at": { "type": "string", "format": "date-time", "description": "ISO 8601 timestamp of when the template was created" }, "updated_at": { "type": [ "string", "null" ], "format": "date-time", "description": "ISO 8601 timestamp of when the template was last updated" } } }, "TemplateCreator": { "type": [ "object", "null" ], "description": "The account that created the template. May be null when the creator account is unavailable.", "required": [ "id", "name", "image", "is_admin" ], "properties": { "id": { "type": "string", "format": "uuid", "description": "Unique identifier of the creator account" }, "name": { "type": [ "string", "null" ], "description": "Display name of the creator account" }, "image": { "type": [ "string", "null" ], "description": "Profile image URL of the creator account" }, "is_admin": { "type": "boolean", "description": "Whether the creator is a Recoup admin (their account email is on the platform admin allow-list)" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Update Template Source: https://recoupable.dev/docs/api-reference/templates/update Update a template ## PATCH /api/agents/templates/{id} Full OpenAPI specification: https://recoupable.dev/docs/spec/templates.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Templates", "description": "API documentation for managing reusable templates on the Recoup platform. Templates capture a prompt, description, and tags that can be shared with other accounts or kept private.", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "paths": { "/api/agents/templates/{id}": { "patch": { "summary": "Update a template", "description": "Update fields on an existing template owned by the authenticated account. All body fields are optional and only the supplied fields are modified. Providing `share_emails` replaces the existing share list for the template.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "parameters": [ { "name": "id", "in": "path", "description": "The unique identifier (UUID) of the template to update", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "description": "Fields to update on the template", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdateTemplateRequest" } } } }, "responses": { "200": { "description": "Template updated successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TemplateResponse" } } } }, "400": { "description": "Bad request - validation failed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "403": { "description": "Forbidden - the authenticated account does not own this template", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "404": { "description": "Template not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } } }, "components": { "schemas": { "UpdateTemplateRequest": { "type": "object", "description": "All fields are optional. Only supplied fields are updated.", "properties": { "title": { "type": "string", "minLength": 3, "maxLength": 50, "description": "Short human-readable title for the template" }, "description": { "type": "string", "minLength": 10, "maxLength": 200, "description": "Description of what the template does" }, "prompt": { "type": "string", "minLength": 20, "maxLength": 10000, "description": "The agent prompt body" }, "tags": { "type": "array", "items": { "type": "string" }, "description": "Free-form tags used to categorize the template. Pass an empty array to clear tags." }, "is_private": { "type": "boolean", "description": "When true, the template is restricted to the creator and the accounts listed in `share_emails`" }, "share_emails": { "type": "array", "items": { "type": "string", "format": "email" }, "description": "Replaces the existing share list for the template. Only applied when the template is (or becomes) private." } } }, "TemplateResponse": { "type": "object", "required": [ "status", "template" ], "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the request" }, "template": { "$ref": "#/components/schemas/Template" } } }, "ErrorResponse": { "type": "object", "required": [ "status", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Status of the request" }, "error": { "type": "string", "description": "Human-readable error message" }, "missing_fields": { "type": "array", "items": { "type": "string" }, "description": "Names of required fields that were missing from the request, when applicable" } } }, "Template": { "type": "object", "required": [ "id", "title", "description", "prompt", "tags", "creator", "is_private", "is_favourite", "favorites_count", "shared_emails", "created_at", "updated_at" ], "properties": { "id": { "type": "string", "format": "uuid", "description": "Unique identifier of the template" }, "title": { "type": "string", "description": "Short human-readable title for the template (3-50 characters)" }, "description": { "type": "string", "description": "Description of what the template does (10-200 characters)" }, "prompt": { "type": "string", "description": "The agent prompt body (20-10000 characters)" }, "tags": { "type": "array", "items": { "type": "string" }, "description": "Free-form tags used to categorize the template. Empty array when there are no tags." }, "creator": { "$ref": "#/components/schemas/TemplateCreator" }, "is_private": { "type": "boolean", "description": "Whether the template is restricted to the creator and explicitly shared accounts" }, "is_favourite": { "type": "boolean", "description": "Whether the authenticated account has favorited this template" }, "favorites_count": { "type": "integer", "description": "Total number of accounts that have favorited this template" }, "shared_emails": { "type": "array", "items": { "type": "string", "format": "email" }, "description": "Emails the template has been shared with. Only populated for private templates the authenticated account owns; empty for public templates and for private templates shared with the caller." }, "created_at": { "type": "string", "format": "date-time", "description": "ISO 8601 timestamp of when the template was created" }, "updated_at": { "type": [ "string", "null" ], "format": "date-time", "description": "ISO 8601 timestamp of when the template was last updated" } } }, "TemplateCreator": { "type": [ "object", "null" ], "description": "The account that created the template. May be null when the creator account is unavailable.", "required": [ "id", "name", "image", "is_admin" ], "properties": { "id": { "type": "string", "format": "uuid", "description": "Unique identifier of the creator account" }, "name": { "type": [ "string", "null" ], "description": "Display name of the creator account" }, "image": { "type": [ "string", "null" ], "description": "Profile image URL of the creator account" }, "is_admin": { "type": "boolean", "description": "Whether the creator is a Recoup admin (their account email is on the platform admin allow-list)" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Delete Template Source: https://recoupable.dev/docs/api-reference/templates/delete Delete a template ## DELETE /api/agents/templates/{id} Full OpenAPI specification: https://recoupable.dev/docs/spec/templates.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Templates", "description": "API documentation for managing reusable templates on the Recoup platform. Templates capture a prompt, description, and tags that can be shared with other accounts or kept private.", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "paths": { "/api/agents/templates/{id}": { "delete": { "summary": "Delete a template", "description": "Permanently delete an template. Only the owner of the template may perform this action. Associated share records and favorite entries are removed as part of the deletion.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "parameters": [ { "name": "id", "in": "path", "description": "The unique identifier (UUID) of the template to delete", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Template deleted successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SuccessResponse" } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "403": { "description": "Forbidden - the authenticated account does not own this template", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "404": { "description": "Template not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } } }, "components": { "schemas": { "SuccessResponse": { "type": "object", "required": [ "status" ], "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the request" } } }, "ErrorResponse": { "type": "object", "required": [ "status", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Status of the request" }, "error": { "type": "string", "description": "Human-readable error message" }, "missing_fields": { "type": "array", "items": { "type": "string" }, "description": "Names of required fields that were missing from the request, when applicable" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Favorite Template Source: https://recoupable.dev/docs/api-reference/templates/favorite Toggle favorite on a template ## PUT /api/agents/templates/{id}/favorite Full OpenAPI specification: https://recoupable.dev/docs/spec/templates.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Templates", "description": "API documentation for managing reusable templates on the Recoup platform. Templates capture a prompt, description, and tags that can be shared with other accounts or kept private.", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "paths": { "/api/agents/templates/{id}/favorite": { "put": { "summary": "Toggle favorite on a template", "description": "Mark or unmark an template as a favorite for the authenticated account. The endpoint is idempotent - calling it repeatedly with the same `is_favourite` value has no additional effect. The caller must be able to see the template (own it, the template is public, or it has been shared with them).", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "parameters": [ { "name": "id", "in": "path", "description": "The unique identifier (UUID) of the template to favorite or unfavorite", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "description": "Desired favorite state", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ToggleFavoriteRequest" } } } }, "responses": { "200": { "description": "Favorite state updated successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SuccessResponse" } } } }, "400": { "description": "Bad request - validation failed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "403": { "description": "Forbidden - the template is private and has not been shared with the authenticated account", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "404": { "description": "Template not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } } }, "components": { "schemas": { "ToggleFavoriteRequest": { "type": "object", "required": [ "is_favourite" ], "properties": { "is_favourite": { "type": "boolean", "description": "Desired favorite state for the authenticated account" } } }, "SuccessResponse": { "type": "object", "required": [ "status" ], "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the request" } } }, "ErrorResponse": { "type": "object", "required": [ "status", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Status of the request" }, "error": { "type": "string", "description": "Human-readable error message" }, "missing_fields": { "type": "array", "items": { "type": "string" }, "description": "Names of required fields that were missing from the request, when applicable" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Get Tasks Source: https://recoupable.dev/docs/api-reference/tasks/get Retrieve scheduled tasks. Each task includes `recent_runs` (last 5 runs), `upcoming` (next scheduled run times) sourced directly from the Trigger.dev API, and `owner_email` when an account email exists for the task owner. Supports filtering by id, account_id, or artist_account_id. ## GET /api/tasks Full OpenAPI specification: https://recoupable.dev/docs/spec/releases.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Releases", "description": "API documentation for the Recoup platform - an AI agent platform for the music industry", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "paths": { "/api/tasks": { "get": { "description": "Retrieve scheduled tasks. Each task includes `recent_runs` (last 5 runs), `upcoming` (next scheduled run times) sourced directly from the Trigger.dev API, and `owner_email` when an account email exists for the task owner. Supports filtering by id, account_id, or artist_account_id.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "parameters": [ { "name": "id", "in": "query", "description": "Filter by task ID (UUID). Returns a single task matching the provided ID. Admin callers may retrieve any task by ID regardless of owner; non-admin callers only receive the task if it belongs to their authenticated account.", "required": false, "schema": { "type": "string", "format": "uuid" } }, { "name": "account_id", "in": "query", "description": "Filter tasks to only include those for the specified account.", "required": false, "schema": { "type": "string", "format": "uuid" } }, { "name": "artist_account_id", "in": "query", "description": "Filter tasks to only include those for the specified artist account.", "required": false, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Tasks retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TasksResponse" } } } }, "400": { "description": "Bad request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "Forbidden - account_id is outside caller authorization scope", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "description": "Internal server error while retrieving tasks.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "TasksResponse": { "type": "object", "required": [ "status", "tasks" ], "properties": { "status": { "type": "string", "enum": [ "success", "error" ], "description": "Status of the request" }, "tasks": { "type": "array", "items": { "$ref": "#/components/schemas/Task" }, "description": "Array of task objects" }, "error": { "type": "string", "description": "Error message (only present if status is error)" } } }, "Error": { "required": [ "error", "message" ], "type": "object", "properties": { "error": { "type": "integer", "format": "int32" }, "message": { "type": "string" } } }, "Task": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid", "description": "Unique identifier for the task" }, "title": { "type": "string", "description": "Descriptive title or name of the task" }, "prompt": { "type": "string", "description": "Detailed instruction or prompt for task execution" }, "schedule": { "type": "string", "description": "Cron expression defining when the task should execute (e.g., '0 10 * * *')" }, "timezone": { "type": "string", "nullable": true, "description": "IANA time zone the task's cron is interpreted in, read from the Trigger.dev schedule (the source of truth). Null when the task has no schedule or the timezone can't be read.", "example": "America/New_York" }, "account_id": { "type": "string", "format": "uuid", "description": "Unique identifier for the associated account" }, "artist_account_id": { "type": "string", "format": "uuid", "description": "Unique identifier for the associated artist account" }, "enabled": { "type": "boolean", "nullable": true, "description": "Whether the task is enabled. Defaults to true." }, "trigger_schedule_id": { "type": "string", "nullable": true, "description": "Identifier for the trigger schedule associated with this task" }, "recent_runs": { "type": "array", "items": { "$ref": "#/components/schemas/TaskRunResponse" }, "description": "Last 5 runs for this task, sourced from the Trigger.dev API." }, "upcoming": { "type": "array", "items": { "type": "string", "format": "date-time" }, "description": "Next scheduled run times." }, "owner_email": { "type": "string", "nullable": true, "description": "Primary email address for the task owner account when one exists." }, "artist_name": { "type": "string", "nullable": true, "description": "Display name of the artist account the task runs for (`artist_account_id`), so a task list can label rows without a roster lookup. Null when the artist account no longer exists.", "example": "Braden Bales" }, "model": { "type": "string", "nullable": true, "minLength": 1, "description": "AI model identifier used when the task runs. When present as a string, must be non-empty; otherwise `null` if unset." } } }, "TaskRunResponse": { "type": "object", "description": "Raw Trigger.dev SDK run object. The API passes through the SDK response without field mapping. See https://trigger.dev/docs/management/runs/retrieve for the full reference. When listing runs, `output`, `error`, `payload`, and `attempts` are not included.", "required": [ "id", "status", "taskIdentifier", "createdAt", "updatedAt" ], "properties": { "id": { "type": "string", "description": "The unique run identifier, prefixed with `run_`" }, "status": { "type": "string", "enum": [ "PENDING_VERSION", "DELAYED", "QUEUED", "EXECUTING", "REATTEMPTING", "FROZEN", "COMPLETED", "CANCELED", "FAILED", "CRASHED", "INTERRUPTED", "SYSTEM_FAILURE" ], "description": "Current run status" }, "taskIdentifier": { "type": "string", "description": "The task type identifier (e.g. 'setup-sandbox', 'run-sandbox-command')" }, "idempotencyKey": { "type": "string", "nullable": true, "description": "Idempotency key used to deduplicate trigger requests" }, "version": { "type": "string", "description": "The worker version that executed the run" }, "isTest": { "type": "boolean", "description": "Whether this is a test run" }, "createdAt": { "type": "string", "format": "date-time", "description": "When the run was created (ISO 8601)" }, "updatedAt": { "type": "string", "format": "date-time", "description": "When the run was last updated (ISO 8601)" }, "startedAt": { "type": "string", "format": "date-time", "nullable": true, "description": "When execution started (null if not yet started)" }, "finishedAt": { "type": "string", "format": "date-time", "nullable": true, "description": "When the run finished (null if still running)" }, "delayedUntil": { "type": "string", "format": "date-time", "nullable": true, "description": "If delayed, when the run becomes eligible to execute" }, "ttl": { "description": "Time-to-live. If the run is not started within this duration, it expires.", "nullable": true }, "expiredAt": { "type": "string", "format": "date-time", "nullable": true, "description": "When the run expired (null if not expired)" }, "tags": { "type": "array", "items": { "type": "string" }, "description": "Tags associated with this run (max 10)" }, "metadata": { "type": "object", "nullable": true, "description": "JSON metadata attached to the run" }, "costInCents": { "type": "number", "description": "Compute cost of the run in cents" }, "baseCostInCents": { "type": "number", "description": "Base invocation cost in cents" }, "durationMs": { "type": "number", "description": "Compute duration in milliseconds" }, "env": { "type": "object", "description": "Environment the run executed in", "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "user": { "type": "string", "nullable": true } } }, "depth": { "type": "integer", "description": "Nesting depth for child runs" }, "batchId": { "type": "string", "nullable": true, "description": "Batch ID if triggered as part of a batch" }, "triggerFunction": { "type": "string", "enum": [ "trigger", "triggerAndWait", "batchTrigger", "batchTriggerAndWait" ], "description": "The function used to trigger this run" }, "payload": { "description": "Input payload for the task. Only present when retrieving by runId.", "nullable": true }, "output": { "description": "Task output data. Only present when retrieving by runId, and only populated when `status` is `COMPLETED`. Shape varies by `taskIdentifier`. The `create-content` task returns the schema below; other tasks return their own.", "nullable": true, "oneOf": [ { "$ref": "#/components/schemas/CreateContentRunOutput" }, { "type": "object", "description": "Generic task output (for tasks not yet schema'd)" } ] }, "error": { "type": "object", "nullable": true, "description": "Error details if the run failed. Only present when retrieving by runId.", "properties": { "message": { "type": "string", "description": "Human-readable error message" }, "name": { "type": "string", "description": "Error name or type" }, "stackTrace": { "type": "string", "description": "Stack trace" } } }, "attempts": { "type": "array", "description": "Attempt history. Only present when retrieving by runId.", "items": { "type": "object", "properties": { "id": { "type": "string", "description": "Attempt ID, prefixed with `attempt_`" }, "status": { "type": "string", "enum": [ "PENDING", "EXECUTING", "PAUSED", "COMPLETED", "FAILED", "CANCELED" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "startedAt": { "type": "string", "format": "date-time", "nullable": true }, "completedAt": { "type": "string", "format": "date-time", "nullable": true }, "error": { "type": "object", "nullable": true, "properties": { "message": { "type": "string" }, "name": { "type": "string" }, "stackTrace": { "type": "string" } } } } } }, "schedule": { "type": "object", "nullable": true, "description": "Schedule information if triggered by a schedule. Only present when retrieving by runId." }, "relatedRuns": { "type": "object", "nullable": true, "description": "Related run references (root, parent, children). Only present when retrieving by runId." } } }, "CreateContentRunOutput": { "type": "object", "description": "Output payload for a `create-content` task run. Returned in `output` when `status` is `COMPLETED`. Agents should poll `/api/tasks/runs?runId=…` and read these fields once the run is done.", "required": [ "videoSourceUrl", "imageUrl", "captionText", "template" ], "properties": { "videoSourceUrl": { "type": "string", "format": "uri", "description": "URL to the rendered final 9:16 video (image + motion + audio + caption already composed)." }, "imageUrl": { "type": "string", "format": "uri", "description": "URL to the base image used for video generation." }, "captionText": { "type": "string", "description": "The caption text burned into the video." }, "template": { "type": "string", "description": "Template id used for this run (e.g. `album-record-store`, `artist-caption-bedroom`).", "example": "artist-caption-bedroom" }, "lipsync": { "type": "boolean", "description": "Whether lipsync was applied (audio-driven mouth animation)." }, "audio": { "type": "object", "description": "Metadata about the song clip used in the final composition.", "properties": { "songTitle": { "type": "string" }, "songFilename": { "type": "string" }, "startSeconds": { "type": "number", "description": "Offset into the source song where the clip starts." }, "durationSeconds": { "type": "number", "description": "Length of the audio clip in seconds." }, "clipLyrics": { "type": "string", "nullable": true, "description": "Lyrics for the chosen clip, when available." }, "clipMood": { "type": "string", "nullable": true, "description": "Inferred mood for the chosen clip, when available." } } } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Create Task Source: https://recoupable.dev/docs/api-reference/tasks/create Create a new scheduled task that runs a prompt against an artist on a recurring cron schedule. The response matches the [GET endpoint](/docs/api-reference/tasks/get) (a `TasksResponse` with the created task in the `tasks` array). ## POST /api/tasks Full OpenAPI specification: https://recoupable.dev/docs/spec/releases.json ## Authentication The supplied specification does not declare an OpenAPI security object for this operation. Its declared headers and parameters still apply. Consult the authentication guide and the full specification before calling it. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Releases", "description": "API documentation for the Recoup platform - an AI agent platform for the music industry", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "paths": { "/api/tasks": { "post": { "description": "Create a new scheduled task that runs a prompt against an artist on a recurring cron schedule. The response matches the [GET endpoint](/docs/api-reference/tasks/get) (a `TasksResponse` with the created task in the `tasks` array).", "requestBody": { "description": "Task to create", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateTaskRequest" } } } }, "responses": { "200": { "description": "Task created successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TasksResponse" } } } }, "400": { "description": "Bad request - missing required fields or invalid body", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "402": { "description": "Payment required - the account's plan does not allow this task. `limit` is `task_count` when the account already has `task_limit` enabled tasks, or `min_cadence` when the cron schedule fires more often than `min_cadence_minutes` allows. Nothing is written; upgrade at `billingUrl`.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PlanLimitError" }, "examples": { "taskCount": { "summary": "Free account creating a second task", "value": { "status": "error", "error": "plan_limit", "limit": "task_count", "message": "Free includes 1 task. Starter includes 3, Pro is unlimited.", "plan": "free", "task_limit": 1, "min_cadence_minutes": 10080, "current_task_count": 1, "billingUrl": "https://app.recoupable.dev/plan" } }, "minCadence": { "summary": "Free account scheduling a daily task", "value": { "status": "error", "error": "plan_limit", "limit": "min_cadence", "message": "Free runs tasks weekly at most. Starter runs daily, Pro runs hourly.", "plan": "free", "task_limit": 1, "min_cadence_minutes": 10080, "current_task_count": 0, "billingUrl": "https://app.recoupable.dev/plan" } } } } } }, "403": { "description": "Forbidden - account_id is outside caller authorization scope", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "CreateTaskRequest": { "type": "object", "required": [ "title", "prompt", "schedule", "artist_account_id" ], "properties": { "title": { "type": "string", "description": "Descriptive title of the task", "example": "Weekly Genre Pulse Check" }, "prompt": { "type": "string", "description": "Instruction/prompt executed by the task", "example": "Execute this weekly genre analysis workflow and email a summary to the team." }, "schedule": { "type": "string", "description": "Cron expression defining when the task runs (e.g., '0 9 * * 4' for Thursdays at 9 AM)", "example": "0 9 * * 4" }, "timezone": { "type": "string", "default": "UTC", "description": "Optional IANA time zone (e.g. America/New_York) the cron schedule is interpreted in, DST-aware. Defaults to UTC. Applied to the Trigger.dev schedule (the source of truth); not stored separately.", "example": "America/New_York" }, "account_id": { "type": "string", "format": "uuid", "description": "UUID of the account to create the task for. Only applicable when the authenticated account has access to multiple accounts via organization membership. If not provided, the task is created for the API key's own account.", "example": "848cd58d-700f-4b38-ab4c-d9f52a1b2c3d" }, "artist_account_id": { "type": "string", "format": "uuid", "description": "UUID of the associated artist account", "example": "1873859c-dd37-4e9a-9bac-80d35a1b2c3d" } } }, "TasksResponse": { "type": "object", "required": [ "status", "tasks" ], "properties": { "status": { "type": "string", "enum": [ "success", "error" ], "description": "Status of the request" }, "tasks": { "type": "array", "items": { "$ref": "#/components/schemas/Task" }, "description": "Array of task objects" }, "error": { "type": "string", "description": "Error message (only present if status is error)" } } }, "Error": { "required": [ "error", "message" ], "type": "object", "properties": { "error": { "type": "integer", "format": "int32" }, "message": { "type": "string" } } }, "PlanLimitError": { "type": "object", "required": [ "status", "error", "limit", "message", "plan", "task_limit", "min_cadence_minutes", "current_task_count", "billingUrl" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Always `error` for this response shape" }, "error": { "type": "string", "enum": [ "plan_limit" ], "description": "Machine-readable code. Literally `plan_limit`." }, "limit": { "type": "string", "enum": [ "task_count", "min_cadence" ], "description": "Which entitlement blocked the request: `task_count` (the plan's enabled-task cap is reached) or `min_cadence` (the cron fires more often than the plan allows)." }, "message": { "type": "string", "description": "Human-readable explanation naming the plan and the limit, suitable to show to a user.", "example": "Free includes 1 task. Starter includes 3, Pro is unlimited." }, "plan": { "type": "string", "enum": [ "free", "starter", "pro" ], "description": "The account's current plan, resolved from its subscription." }, "task_limit": { "type": "integer", "nullable": true, "minimum": 1, "description": "Maximum enabled tasks on the current plan. Null means uncapped.", "example": 1 }, "min_cadence_minutes": { "type": "integer", "minimum": 1, "description": "Shortest allowed gap between two consecutive runs of a task on the current plan, in minutes (Free 10080, Starter 1440, Pro 60).", "example": 10080 }, "current_task_count": { "type": "integer", "minimum": 0, "description": "Enabled tasks the account has right now, excluding the one being created or updated.", "example": 1 }, "billingUrl": { "type": "string", "format": "uri", "description": "Static link to `/plan` in the Recoup app, where a human can upgrade. Constant; retrying creates nothing.", "example": "https://app.recoupable.dev/plan" } } }, "Task": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid", "description": "Unique identifier for the task" }, "title": { "type": "string", "description": "Descriptive title or name of the task" }, "prompt": { "type": "string", "description": "Detailed instruction or prompt for task execution" }, "schedule": { "type": "string", "description": "Cron expression defining when the task should execute (e.g., '0 10 * * *')" }, "timezone": { "type": "string", "nullable": true, "description": "IANA time zone the task's cron is interpreted in, read from the Trigger.dev schedule (the source of truth). Null when the task has no schedule or the timezone can't be read.", "example": "America/New_York" }, "account_id": { "type": "string", "format": "uuid", "description": "Unique identifier for the associated account" }, "artist_account_id": { "type": "string", "format": "uuid", "description": "Unique identifier for the associated artist account" }, "enabled": { "type": "boolean", "nullable": true, "description": "Whether the task is enabled. Defaults to true." }, "trigger_schedule_id": { "type": "string", "nullable": true, "description": "Identifier for the trigger schedule associated with this task" }, "recent_runs": { "type": "array", "items": { "$ref": "#/components/schemas/TaskRunResponse" }, "description": "Last 5 runs for this task, sourced from the Trigger.dev API." }, "upcoming": { "type": "array", "items": { "type": "string", "format": "date-time" }, "description": "Next scheduled run times." }, "owner_email": { "type": "string", "nullable": true, "description": "Primary email address for the task owner account when one exists." }, "artist_name": { "type": "string", "nullable": true, "description": "Display name of the artist account the task runs for (`artist_account_id`), so a task list can label rows without a roster lookup. Null when the artist account no longer exists.", "example": "Braden Bales" }, "model": { "type": "string", "nullable": true, "minLength": 1, "description": "AI model identifier used when the task runs. When present as a string, must be non-empty; otherwise `null` if unset." } } }, "TaskRunResponse": { "type": "object", "description": "Raw Trigger.dev SDK run object. The API passes through the SDK response without field mapping. See https://trigger.dev/docs/management/runs/retrieve for the full reference. When listing runs, `output`, `error`, `payload`, and `attempts` are not included.", "required": [ "id", "status", "taskIdentifier", "createdAt", "updatedAt" ], "properties": { "id": { "type": "string", "description": "The unique run identifier, prefixed with `run_`" }, "status": { "type": "string", "enum": [ "PENDING_VERSION", "DELAYED", "QUEUED", "EXECUTING", "REATTEMPTING", "FROZEN", "COMPLETED", "CANCELED", "FAILED", "CRASHED", "INTERRUPTED", "SYSTEM_FAILURE" ], "description": "Current run status" }, "taskIdentifier": { "type": "string", "description": "The task type identifier (e.g. 'setup-sandbox', 'run-sandbox-command')" }, "idempotencyKey": { "type": "string", "nullable": true, "description": "Idempotency key used to deduplicate trigger requests" }, "version": { "type": "string", "description": "The worker version that executed the run" }, "isTest": { "type": "boolean", "description": "Whether this is a test run" }, "createdAt": { "type": "string", "format": "date-time", "description": "When the run was created (ISO 8601)" }, "updatedAt": { "type": "string", "format": "date-time", "description": "When the run was last updated (ISO 8601)" }, "startedAt": { "type": "string", "format": "date-time", "nullable": true, "description": "When execution started (null if not yet started)" }, "finishedAt": { "type": "string", "format": "date-time", "nullable": true, "description": "When the run finished (null if still running)" }, "delayedUntil": { "type": "string", "format": "date-time", "nullable": true, "description": "If delayed, when the run becomes eligible to execute" }, "ttl": { "description": "Time-to-live. If the run is not started within this duration, it expires.", "nullable": true }, "expiredAt": { "type": "string", "format": "date-time", "nullable": true, "description": "When the run expired (null if not expired)" }, "tags": { "type": "array", "items": { "type": "string" }, "description": "Tags associated with this run (max 10)" }, "metadata": { "type": "object", "nullable": true, "description": "JSON metadata attached to the run" }, "costInCents": { "type": "number", "description": "Compute cost of the run in cents" }, "baseCostInCents": { "type": "number", "description": "Base invocation cost in cents" }, "durationMs": { "type": "number", "description": "Compute duration in milliseconds" }, "env": { "type": "object", "description": "Environment the run executed in", "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "user": { "type": "string", "nullable": true } } }, "depth": { "type": "integer", "description": "Nesting depth for child runs" }, "batchId": { "type": "string", "nullable": true, "description": "Batch ID if triggered as part of a batch" }, "triggerFunction": { "type": "string", "enum": [ "trigger", "triggerAndWait", "batchTrigger", "batchTriggerAndWait" ], "description": "The function used to trigger this run" }, "payload": { "description": "Input payload for the task. Only present when retrieving by runId.", "nullable": true }, "output": { "description": "Task output data. Only present when retrieving by runId, and only populated when `status` is `COMPLETED`. Shape varies by `taskIdentifier`. The `create-content` task returns the schema below; other tasks return their own.", "nullable": true, "oneOf": [ { "$ref": "#/components/schemas/CreateContentRunOutput" }, { "type": "object", "description": "Generic task output (for tasks not yet schema'd)" } ] }, "error": { "type": "object", "nullable": true, "description": "Error details if the run failed. Only present when retrieving by runId.", "properties": { "message": { "type": "string", "description": "Human-readable error message" }, "name": { "type": "string", "description": "Error name or type" }, "stackTrace": { "type": "string", "description": "Stack trace" } } }, "attempts": { "type": "array", "description": "Attempt history. Only present when retrieving by runId.", "items": { "type": "object", "properties": { "id": { "type": "string", "description": "Attempt ID, prefixed with `attempt_`" }, "status": { "type": "string", "enum": [ "PENDING", "EXECUTING", "PAUSED", "COMPLETED", "FAILED", "CANCELED" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "startedAt": { "type": "string", "format": "date-time", "nullable": true }, "completedAt": { "type": "string", "format": "date-time", "nullable": true }, "error": { "type": "object", "nullable": true, "properties": { "message": { "type": "string" }, "name": { "type": "string" }, "stackTrace": { "type": "string" } } } } } }, "schedule": { "type": "object", "nullable": true, "description": "Schedule information if triggered by a schedule. Only present when retrieving by runId." }, "relatedRuns": { "type": "object", "nullable": true, "description": "Related run references (root, parent, children). Only present when retrieving by runId." } } }, "CreateContentRunOutput": { "type": "object", "description": "Output payload for a `create-content` task run. Returned in `output` when `status` is `COMPLETED`. Agents should poll `/api/tasks/runs?runId=…` and read these fields once the run is done.", "required": [ "videoSourceUrl", "imageUrl", "captionText", "template" ], "properties": { "videoSourceUrl": { "type": "string", "format": "uri", "description": "URL to the rendered final 9:16 video (image + motion + audio + caption already composed)." }, "imageUrl": { "type": "string", "format": "uri", "description": "URL to the base image used for video generation." }, "captionText": { "type": "string", "description": "The caption text burned into the video." }, "template": { "type": "string", "description": "Template id used for this run (e.g. `album-record-store`, `artist-caption-bedroom`).", "example": "artist-caption-bedroom" }, "lipsync": { "type": "boolean", "description": "Whether lipsync was applied (audio-driven mouth animation)." }, "audio": { "type": "object", "description": "Metadata about the song clip used in the final composition.", "properties": { "songTitle": { "type": "string" }, "songFilename": { "type": "string" }, "startSeconds": { "type": "number", "description": "Offset into the source song where the clip starts." }, "durationSeconds": { "type": "number", "description": "Length of the audio clip in seconds." }, "clipLyrics": { "type": "string", "nullable": true, "description": "Lyrics for the chosen clip, when available." }, "clipMood": { "type": "string", "nullable": true, "description": "Inferred mood for the chosen clip, when available." } } } } } } } } ``` # Update Task Source: https://recoupable.dev/docs/api-reference/tasks/update Update scheduled task ## PATCH /api/tasks Full OpenAPI specification: https://recoupable.dev/docs/spec/releases.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Releases", "description": "API documentation for the Recoup platform - an AI agent platform for the music industry", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "paths": { "/api/tasks": { "patch": { "summary": "Update scheduled task", "description": "Update an existing scheduled task. Only the id field is required; any additional fields you include will be updated on the task. The response shape matches the GET endpoint (an array containing the updated task).", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "requestBody": { "description": "JSON object with `id` and optional fields to merge onto the task.", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdateTaskRequest" }, "examples": { "updateTitleAndSchedule": { "summary": "Change title and cron", "value": { "id": "aade2bce-55c7-468e-a606-c4e76fb2ea2a", "title": "Weekly Genre Pulse Check (rev)", "schedule": "0 10 * * 4" } }, "toggleEnabled": { "summary": "Pause or resume a task", "value": { "id": "aade2bce-55c7-468e-a606-c4e76fb2ea2a", "enabled": false } }, "changeModel": { "summary": "Switch model only", "value": { "id": "aade2bce-55c7-468e-a606-c4e76fb2ea2a", "model": "claude-sonnet-4-20250514" } } } } } }, "responses": { "200": { "description": "Task updated successfully. Body is a `TasksResponse` with the updated task in `tasks` (typically one task).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TasksResponse" } } } }, "400": { "description": "Bad request — missing `id`, empty strings where a field is provided, or other validation failure from the request body. The body reports the first Zod issue via `missing_fields` and `error`.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdateTaskValidationErrorResponse" } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "402": { "description": "Payment required - the account's plan does not allow this task. `limit` is `task_count` when the account already has `task_limit` enabled tasks, or `min_cadence` when the cron schedule fires more often than `min_cadence_minutes` allows. Nothing is written; upgrade at `billingUrl`.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PlanLimitError" }, "examples": { "taskCount": { "summary": "Free account re-enabling a disabled task while another is enabled", "value": { "status": "error", "error": "plan_limit", "limit": "task_count", "message": "Free includes 1 task. Starter includes 3, Pro is unlimited.", "plan": "free", "task_limit": 1, "min_cadence_minutes": 10080, "current_task_count": 1, "billingUrl": "https://app.recoupable.dev/plan" } }, "minCadence": { "summary": "Free account changing a task to a daily schedule", "value": { "status": "error", "error": "plan_limit", "limit": "min_cadence", "message": "Free runs tasks weekly at most. Starter runs daily, Pro runs hourly.", "plan": "free", "task_limit": 1, "min_cadence_minutes": 10080, "current_task_count": 0, "billingUrl": "https://app.recoupable.dev/plan" } } } } } }, "403": { "description": "Forbidden - account_id is outside caller authorization scope", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "No task exists with the given `id`.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdateTaskFailureResponse" }, "examples": { "notFound": { "summary": "Unknown task id", "value": { "status": "error", "error": "Task not found" } } } } } }, "500": { "description": "Internal server error while updating the task or syncing the Trigger.dev schedule.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdateTaskFailureResponse" } } } } } } } }, "components": { "schemas": { "UpdateTaskRequest": { "type": "object", "required": [ "id" ], "properties": { "id": { "type": "string", "format": "uuid", "description": "UUID of the task to update", "example": "aade2bce-55c7-468e-a606-c4e76fb2ea2a" }, "title": { "type": "string", "minLength": 1, "description": "New descriptive title. If sent, must be a non-empty string.", "example": "Weekly Genre Pulse Check (Updated)" }, "prompt": { "type": "string", "minLength": 1, "description": "New instruction/prompt executed by the task. If sent, must be a non-empty string.", "example": "Execute this weekly genre analysis workflow and email a summary to the team." }, "schedule": { "type": "string", "minLength": 1, "description": "New cron expression. If sent, must be non-empty and valid for your environment.", "example": "0 10 * * 4" }, "timezone": { "type": "string", "description": "Optional IANA time zone the cron is interpreted in (DST-aware). If sent, updates the Trigger.dev schedule; a timezone-only change re-syncs the schedule. A cron-only update preserves the existing time zone.", "example": "America/New_York" }, "account_id": { "type": "string", "format": "uuid", "description": "UUID of the account to update the task for. Only applicable when the authenticated account has access to multiple accounts via organization membership. If not provided, the task is updated for the API key's own account.", "example": "848cd58d-700f-4b38-ab4c-d9f52a1b2c3d" }, "artist_account_id": { "type": "string", "format": "uuid", "description": "UUID of the artist account to associate with the task after update", "example": "1873859c-dd37-4e9a-9bac-80d35a1b2c3d" }, "enabled": { "type": "boolean", "nullable": true, "description": "Whether the task is enabled (`true` / `false`), or `null` to clear an explicit override depending on server rules.", "example": true }, "model": { "type": "string", "minLength": 1, "description": "AI model identifier to use when the task runs. If sent, must be a non-empty string.", "example": "claude-sonnet-4-20250514" } } }, "TasksResponse": { "type": "object", "required": [ "status", "tasks" ], "properties": { "status": { "type": "string", "enum": [ "success", "error" ], "description": "Status of the request" }, "tasks": { "type": "array", "items": { "$ref": "#/components/schemas/Task" }, "description": "Array of task objects" }, "error": { "type": "string", "description": "Error message (only present if status is error)" } } }, "UpdateTaskValidationErrorResponse": { "type": "object", "required": [ "status", "missing_fields", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Always `error` when validation fails" }, "missing_fields": { "type": "array", "description": "JSON path segments to the first field that failed validation (from Zod), e.g. `[\"id\"]` or `[\"schedule\"]`", "items": { "oneOf": [ { "type": "string" }, { "type": "integer" } ] } }, "error": { "type": "string", "description": "Validation message for the first failing field" } } }, "Error": { "required": [ "error", "message" ], "type": "object", "properties": { "error": { "type": "integer", "format": "int32" }, "message": { "type": "string" } } }, "PlanLimitError": { "type": "object", "required": [ "status", "error", "limit", "message", "plan", "task_limit", "min_cadence_minutes", "current_task_count", "billingUrl" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Always `error` for this response shape" }, "error": { "type": "string", "enum": [ "plan_limit" ], "description": "Machine-readable code. Literally `plan_limit`." }, "limit": { "type": "string", "enum": [ "task_count", "min_cadence" ], "description": "Which entitlement blocked the request: `task_count` (the plan's enabled-task cap is reached) or `min_cadence` (the cron fires more often than the plan allows)." }, "message": { "type": "string", "description": "Human-readable explanation naming the plan and the limit, suitable to show to a user.", "example": "Free includes 1 task. Starter includes 3, Pro is unlimited." }, "plan": { "type": "string", "enum": [ "free", "starter", "pro" ], "description": "The account's current plan, resolved from its subscription." }, "task_limit": { "type": "integer", "nullable": true, "minimum": 1, "description": "Maximum enabled tasks on the current plan. Null means uncapped.", "example": 1 }, "min_cadence_minutes": { "type": "integer", "minimum": 1, "description": "Shortest allowed gap between two consecutive runs of a task on the current plan, in minutes (Free 10080, Starter 1440, Pro 60).", "example": 10080 }, "current_task_count": { "type": "integer", "minimum": 0, "description": "Enabled tasks the account has right now, excluding the one being created or updated.", "example": 1 }, "billingUrl": { "type": "string", "format": "uri", "description": "Static link to `/plan` in the Recoup app, where a human can upgrade. Constant; retrying creates nothing.", "example": "https://app.recoupable.dev/plan" } } }, "UpdateTaskFailureResponse": { "type": "object", "required": [ "status", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Always `error` for this response shape" }, "error": { "type": "string", "description": "Human-readable error message (for example `Task not found` on 404)" } } }, "Task": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid", "description": "Unique identifier for the task" }, "title": { "type": "string", "description": "Descriptive title or name of the task" }, "prompt": { "type": "string", "description": "Detailed instruction or prompt for task execution" }, "schedule": { "type": "string", "description": "Cron expression defining when the task should execute (e.g., '0 10 * * *')" }, "timezone": { "type": "string", "nullable": true, "description": "IANA time zone the task's cron is interpreted in, read from the Trigger.dev schedule (the source of truth). Null when the task has no schedule or the timezone can't be read.", "example": "America/New_York" }, "account_id": { "type": "string", "format": "uuid", "description": "Unique identifier for the associated account" }, "artist_account_id": { "type": "string", "format": "uuid", "description": "Unique identifier for the associated artist account" }, "enabled": { "type": "boolean", "nullable": true, "description": "Whether the task is enabled. Defaults to true." }, "trigger_schedule_id": { "type": "string", "nullable": true, "description": "Identifier for the trigger schedule associated with this task" }, "recent_runs": { "type": "array", "items": { "$ref": "#/components/schemas/TaskRunResponse" }, "description": "Last 5 runs for this task, sourced from the Trigger.dev API." }, "upcoming": { "type": "array", "items": { "type": "string", "format": "date-time" }, "description": "Next scheduled run times." }, "owner_email": { "type": "string", "nullable": true, "description": "Primary email address for the task owner account when one exists." }, "artist_name": { "type": "string", "nullable": true, "description": "Display name of the artist account the task runs for (`artist_account_id`), so a task list can label rows without a roster lookup. Null when the artist account no longer exists.", "example": "Braden Bales" }, "model": { "type": "string", "nullable": true, "minLength": 1, "description": "AI model identifier used when the task runs. When present as a string, must be non-empty; otherwise `null` if unset." } } }, "TaskRunResponse": { "type": "object", "description": "Raw Trigger.dev SDK run object. The API passes through the SDK response without field mapping. See https://trigger.dev/docs/management/runs/retrieve for the full reference. When listing runs, `output`, `error`, `payload`, and `attempts` are not included.", "required": [ "id", "status", "taskIdentifier", "createdAt", "updatedAt" ], "properties": { "id": { "type": "string", "description": "The unique run identifier, prefixed with `run_`" }, "status": { "type": "string", "enum": [ "PENDING_VERSION", "DELAYED", "QUEUED", "EXECUTING", "REATTEMPTING", "FROZEN", "COMPLETED", "CANCELED", "FAILED", "CRASHED", "INTERRUPTED", "SYSTEM_FAILURE" ], "description": "Current run status" }, "taskIdentifier": { "type": "string", "description": "The task type identifier (e.g. 'setup-sandbox', 'run-sandbox-command')" }, "idempotencyKey": { "type": "string", "nullable": true, "description": "Idempotency key used to deduplicate trigger requests" }, "version": { "type": "string", "description": "The worker version that executed the run" }, "isTest": { "type": "boolean", "description": "Whether this is a test run" }, "createdAt": { "type": "string", "format": "date-time", "description": "When the run was created (ISO 8601)" }, "updatedAt": { "type": "string", "format": "date-time", "description": "When the run was last updated (ISO 8601)" }, "startedAt": { "type": "string", "format": "date-time", "nullable": true, "description": "When execution started (null if not yet started)" }, "finishedAt": { "type": "string", "format": "date-time", "nullable": true, "description": "When the run finished (null if still running)" }, "delayedUntil": { "type": "string", "format": "date-time", "nullable": true, "description": "If delayed, when the run becomes eligible to execute" }, "ttl": { "description": "Time-to-live. If the run is not started within this duration, it expires.", "nullable": true }, "expiredAt": { "type": "string", "format": "date-time", "nullable": true, "description": "When the run expired (null if not expired)" }, "tags": { "type": "array", "items": { "type": "string" }, "description": "Tags associated with this run (max 10)" }, "metadata": { "type": "object", "nullable": true, "description": "JSON metadata attached to the run" }, "costInCents": { "type": "number", "description": "Compute cost of the run in cents" }, "baseCostInCents": { "type": "number", "description": "Base invocation cost in cents" }, "durationMs": { "type": "number", "description": "Compute duration in milliseconds" }, "env": { "type": "object", "description": "Environment the run executed in", "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "user": { "type": "string", "nullable": true } } }, "depth": { "type": "integer", "description": "Nesting depth for child runs" }, "batchId": { "type": "string", "nullable": true, "description": "Batch ID if triggered as part of a batch" }, "triggerFunction": { "type": "string", "enum": [ "trigger", "triggerAndWait", "batchTrigger", "batchTriggerAndWait" ], "description": "The function used to trigger this run" }, "payload": { "description": "Input payload for the task. Only present when retrieving by runId.", "nullable": true }, "output": { "description": "Task output data. Only present when retrieving by runId, and only populated when `status` is `COMPLETED`. Shape varies by `taskIdentifier`. The `create-content` task returns the schema below; other tasks return their own.", "nullable": true, "oneOf": [ { "$ref": "#/components/schemas/CreateContentRunOutput" }, { "type": "object", "description": "Generic task output (for tasks not yet schema'd)" } ] }, "error": { "type": "object", "nullable": true, "description": "Error details if the run failed. Only present when retrieving by runId.", "properties": { "message": { "type": "string", "description": "Human-readable error message" }, "name": { "type": "string", "description": "Error name or type" }, "stackTrace": { "type": "string", "description": "Stack trace" } } }, "attempts": { "type": "array", "description": "Attempt history. Only present when retrieving by runId.", "items": { "type": "object", "properties": { "id": { "type": "string", "description": "Attempt ID, prefixed with `attempt_`" }, "status": { "type": "string", "enum": [ "PENDING", "EXECUTING", "PAUSED", "COMPLETED", "FAILED", "CANCELED" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "startedAt": { "type": "string", "format": "date-time", "nullable": true }, "completedAt": { "type": "string", "format": "date-time", "nullable": true }, "error": { "type": "object", "nullable": true, "properties": { "message": { "type": "string" }, "name": { "type": "string" }, "stackTrace": { "type": "string" } } } } } }, "schedule": { "type": "object", "nullable": true, "description": "Schedule information if triggered by a schedule. Only present when retrieving by runId." }, "relatedRuns": { "type": "object", "nullable": true, "description": "Related run references (root, parent, children). Only present when retrieving by runId." } } }, "CreateContentRunOutput": { "type": "object", "description": "Output payload for a `create-content` task run. Returned in `output` when `status` is `COMPLETED`. Agents should poll `/api/tasks/runs?runId=…` and read these fields once the run is done.", "required": [ "videoSourceUrl", "imageUrl", "captionText", "template" ], "properties": { "videoSourceUrl": { "type": "string", "format": "uri", "description": "URL to the rendered final 9:16 video (image + motion + audio + caption already composed)." }, "imageUrl": { "type": "string", "format": "uri", "description": "URL to the base image used for video generation." }, "captionText": { "type": "string", "description": "The caption text burned into the video." }, "template": { "type": "string", "description": "Template id used for this run (e.g. `album-record-store`, `artist-caption-bedroom`).", "example": "artist-caption-bedroom" }, "lipsync": { "type": "boolean", "description": "Whether lipsync was applied (audio-driven mouth animation)." }, "audio": { "type": "object", "description": "Metadata about the song clip used in the final composition.", "properties": { "songTitle": { "type": "string" }, "songFilename": { "type": "string" }, "startSeconds": { "type": "number", "description": "Offset into the source song where the clip starts." }, "durationSeconds": { "type": "number", "description": "Length of the audio clip in seconds." }, "clipLyrics": { "type": "string", "nullable": true, "description": "Lyrics for the chosen clip, when available." }, "clipMood": { "type": "string", "nullable": true, "description": "Inferred mood for the chosen clip, when available." } } } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Delete Task Source: https://recoupable.dev/docs/api-reference/tasks/delete Delete scheduled task ## DELETE /api/tasks Full OpenAPI specification: https://recoupable.dev/docs/spec/releases.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Releases", "description": "API documentation for the Recoup platform - an AI agent platform for the music industry", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "paths": { "/api/tasks": { "delete": { "summary": "Delete scheduled task", "description": "Delete an existing scheduled task by `id`. Returns only the delete operation status.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "requestBody": { "description": "JSON object containing the task `id` to delete.", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeleteTaskRequest" }, "examples": { "deleteTask": { "summary": "Delete one task", "value": { "id": "aade2bce-55c7-468e-a606-c4e76fb2ea2a" } } } } } }, "responses": { "200": { "description": "Task deleted successfully.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeleteTaskResponse" } } } }, "400": { "description": "Bad request — missing `id` or invalid request body.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeleteTaskValidationErrorResponse" } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "Forbidden - account_id is outside caller authorization scope", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "No task exists with the given `id`.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeleteTaskFailureResponse" }, "examples": { "notFound": { "summary": "Unknown task id", "value": { "status": "error", "error": "Task not found" } } } } } }, "500": { "description": "Internal server error while deleting the task or deleting its Trigger.dev schedule.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeleteTaskFailureResponse" } } } } } } } }, "components": { "schemas": { "DeleteTaskRequest": { "type": "object", "required": [ "id" ], "properties": { "id": { "type": "string", "format": "uuid", "description": "UUID of the task to delete", "example": "aade2bce-55c7-468e-a606-c4e76fb2ea2a" } } }, "DeleteTaskResponse": { "type": "object", "required": [ "status" ], "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the delete operation" } } }, "DeleteTaskValidationErrorResponse": { "type": "object", "required": [ "status", "missing_fields", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Always `error` when validation fails" }, "missing_fields": { "type": "array", "description": "JSON path segments to the first field that failed validation (from Zod), e.g. `[\"id\"]`", "items": { "oneOf": [ { "type": "string" }, { "type": "integer" } ] } }, "error": { "type": "string", "description": "Validation message for the first failing field" } } }, "Error": { "required": [ "error", "message" ], "type": "object", "properties": { "error": { "type": "integer", "format": "int32" }, "message": { "type": "string" } } }, "DeleteTaskFailureResponse": { "type": "object", "required": [ "status", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Always `error` for this response shape" }, "error": { "type": "string", "description": "Human-readable error message (for example `Task not found` on 404)" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Get Task Runs Source: https://recoupable.dev/docs/api-reference/tasks/runs Get task runs ## GET /api/tasks/runs Full OpenAPI specification: https://recoupable.dev/docs/spec/releases.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Releases", "description": "API documentation for the Recoup platform - an AI agent platform for the music industry", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "paths": { "/api/tasks/runs": { "get": { "summary": "Get task runs", "description": "Returns task runs for the authenticated account. When `runId` is provided, the response contains that single run (`runs` length 1) or 404 if not found. When `runId` is omitted, returns recent runs filtered by account context (default authenticated account, or `account_id` override when authorized).", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "parameters": [ { "name": "runId", "in": "query", "description": "The unique identifier of a specific task run to retrieve. If omitted, returns a list of recent runs for the authenticated account.", "required": false, "schema": { "type": "string" } }, { "name": "limit", "in": "query", "description": "Maximum number of runs to return when listing (ignored when runId is provided). Default 20, max 100.", "required": false, "schema": { "type": "integer", "default": 20, "minimum": 1, "maximum": 100 } }, { "name": "account_id", "in": "query", "description": "Filter runs by account ID. When provided, returns runs tagged with account:. Only applicable when the authenticated account has access to multiple accounts via organization membership.", "required": false, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Task runs retrieved successfully.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TaskRunListResponse" } } } }, "400": { "description": "Bad request - invalid query parameters (for example non-numeric `limit`, out-of-range `limit`, or invalid empty values).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TaskRunsErrorResponse" } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TaskRunsErrorResponse" } } } }, "403": { "description": "Forbidden - account_id is not accessible with the provided credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TaskRunsErrorResponse" } } } }, "404": { "description": "Task run not found (only when runId is provided)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TaskRunsErrorResponse" }, "examples": { "notFound": { "summary": "Unknown run id", "value": { "status": "error", "error": "Task run not found" } } } } } }, "500": { "description": "Internal server error while fetching runs from Trigger.dev.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TaskRunsErrorResponse" } } } } } } } }, "components": { "schemas": { "TaskRunListResponse": { "type": "object", "required": [ "status", "runs" ], "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Indicates the request was successful" }, "runs": { "type": "array", "items": { "$ref": "#/components/schemas/TaskRunResponse" }, "description": "List of recent task runs for the authenticated account. Each item is a raw Trigger.dev SDK run object (same shape as TaskRunResponse, but without `output` and `error` fields)." } } }, "TaskRunsErrorResponse": { "type": "object", "required": [ "status", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Always `error` for this response shape" }, "error": { "type": "string", "description": "Human-readable error message" } } }, "TaskRunResponse": { "type": "object", "description": "Raw Trigger.dev SDK run object. The API passes through the SDK response without field mapping. See https://trigger.dev/docs/management/runs/retrieve for the full reference. When listing runs, `output`, `error`, `payload`, and `attempts` are not included.", "required": [ "id", "status", "taskIdentifier", "createdAt", "updatedAt" ], "properties": { "id": { "type": "string", "description": "The unique run identifier, prefixed with `run_`" }, "status": { "type": "string", "enum": [ "PENDING_VERSION", "DELAYED", "QUEUED", "EXECUTING", "REATTEMPTING", "FROZEN", "COMPLETED", "CANCELED", "FAILED", "CRASHED", "INTERRUPTED", "SYSTEM_FAILURE" ], "description": "Current run status" }, "taskIdentifier": { "type": "string", "description": "The task type identifier (e.g. 'setup-sandbox', 'run-sandbox-command')" }, "idempotencyKey": { "type": "string", "nullable": true, "description": "Idempotency key used to deduplicate trigger requests" }, "version": { "type": "string", "description": "The worker version that executed the run" }, "isTest": { "type": "boolean", "description": "Whether this is a test run" }, "createdAt": { "type": "string", "format": "date-time", "description": "When the run was created (ISO 8601)" }, "updatedAt": { "type": "string", "format": "date-time", "description": "When the run was last updated (ISO 8601)" }, "startedAt": { "type": "string", "format": "date-time", "nullable": true, "description": "When execution started (null if not yet started)" }, "finishedAt": { "type": "string", "format": "date-time", "nullable": true, "description": "When the run finished (null if still running)" }, "delayedUntil": { "type": "string", "format": "date-time", "nullable": true, "description": "If delayed, when the run becomes eligible to execute" }, "ttl": { "description": "Time-to-live. If the run is not started within this duration, it expires.", "nullable": true }, "expiredAt": { "type": "string", "format": "date-time", "nullable": true, "description": "When the run expired (null if not expired)" }, "tags": { "type": "array", "items": { "type": "string" }, "description": "Tags associated with this run (max 10)" }, "metadata": { "type": "object", "nullable": true, "description": "JSON metadata attached to the run" }, "costInCents": { "type": "number", "description": "Compute cost of the run in cents" }, "baseCostInCents": { "type": "number", "description": "Base invocation cost in cents" }, "durationMs": { "type": "number", "description": "Compute duration in milliseconds" }, "env": { "type": "object", "description": "Environment the run executed in", "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "user": { "type": "string", "nullable": true } } }, "depth": { "type": "integer", "description": "Nesting depth for child runs" }, "batchId": { "type": "string", "nullable": true, "description": "Batch ID if triggered as part of a batch" }, "triggerFunction": { "type": "string", "enum": [ "trigger", "triggerAndWait", "batchTrigger", "batchTriggerAndWait" ], "description": "The function used to trigger this run" }, "payload": { "description": "Input payload for the task. Only present when retrieving by runId.", "nullable": true }, "output": { "description": "Task output data. Only present when retrieving by runId, and only populated when `status` is `COMPLETED`. Shape varies by `taskIdentifier`. The `create-content` task returns the schema below; other tasks return their own.", "nullable": true, "oneOf": [ { "$ref": "#/components/schemas/CreateContentRunOutput" }, { "type": "object", "description": "Generic task output (for tasks not yet schema'd)" } ] }, "error": { "type": "object", "nullable": true, "description": "Error details if the run failed. Only present when retrieving by runId.", "properties": { "message": { "type": "string", "description": "Human-readable error message" }, "name": { "type": "string", "description": "Error name or type" }, "stackTrace": { "type": "string", "description": "Stack trace" } } }, "attempts": { "type": "array", "description": "Attempt history. Only present when retrieving by runId.", "items": { "type": "object", "properties": { "id": { "type": "string", "description": "Attempt ID, prefixed with `attempt_`" }, "status": { "type": "string", "enum": [ "PENDING", "EXECUTING", "PAUSED", "COMPLETED", "FAILED", "CANCELED" ] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "startedAt": { "type": "string", "format": "date-time", "nullable": true }, "completedAt": { "type": "string", "format": "date-time", "nullable": true }, "error": { "type": "object", "nullable": true, "properties": { "message": { "type": "string" }, "name": { "type": "string" }, "stackTrace": { "type": "string" } } } } } }, "schedule": { "type": "object", "nullable": true, "description": "Schedule information if triggered by a schedule. Only present when retrieving by runId." }, "relatedRuns": { "type": "object", "nullable": true, "description": "Related run references (root, parent, children). Only present when retrieving by runId." } } }, "CreateContentRunOutput": { "type": "object", "description": "Output payload for a `create-content` task run. Returned in `output` when `status` is `COMPLETED`. Agents should poll `/api/tasks/runs?runId=…` and read these fields once the run is done.", "required": [ "videoSourceUrl", "imageUrl", "captionText", "template" ], "properties": { "videoSourceUrl": { "type": "string", "format": "uri", "description": "URL to the rendered final 9:16 video (image + motion + audio + caption already composed)." }, "imageUrl": { "type": "string", "format": "uri", "description": "URL to the base image used for video generation." }, "captionText": { "type": "string", "description": "The caption text burned into the video." }, "template": { "type": "string", "description": "Template id used for this run (e.g. `album-record-store`, `artist-caption-bedroom`).", "example": "artist-caption-bedroom" }, "lipsync": { "type": "boolean", "description": "Whether lipsync was applied (audio-driven mouth animation)." }, "audio": { "type": "object", "description": "Metadata about the song clip used in the final composition.", "properties": { "songTitle": { "type": "string" }, "songFilename": { "type": "string" }, "startSeconds": { "type": "number", "description": "Offset into the source song where the clip starts." }, "durationSeconds": { "type": "number", "description": "Length of the audio clip in seconds." }, "clipLyrics": { "type": "string", "nullable": true, "description": "Lyrics for the chosen clip, when available." }, "clipMood": { "type": "string", "nullable": true, "description": "Inferred mood for the chosen clip, when available." } } } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Slack Webhook Source: https://recoupable.dev/docs/api-reference/content-agent/webhook Webhook endpoint for the Recoup Content Agent Slack bot. Receives @mention events from Slack and triggers content generation for the mentioned artist. The bot parses the mention text for ` [template] [batch=N] [lipsync]`, validates the artist, calls POST /api/content/create, and starts a background polling task that reports results back to the Slack thread. ## POST /api/content-agent/{platform} Full OpenAPI specification: https://recoupable.dev/docs/spec/content.json ## Authentication The supplied specification does not declare an OpenAPI security object for this operation. Its declared headers and parameters still apply. Consult the authentication guide and the full specification before calling it. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Content", "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/content-agent/{platform}": { "post": { "description": "Webhook endpoint for the Recoup Content Agent Slack bot. Receives @mention events from Slack and triggers content generation for the mentioned artist. The bot parses the mention text for ` [template] [batch=N] [lipsync]`, validates the artist, calls POST /api/content/create, and starts a background polling task that reports results back to the Slack thread.\n\nFor Slack, also handles `url_verification` challenges during app setup.", "parameters": [ { "name": "platform", "in": "path", "description": "Chat platform identifier. Currently supports `slack`.", "required": true, "schema": { "type": "string", "enum": [ "slack" ] } } ], "requestBody": { "description": "Slack Events API payload (app_mention event or url_verification challenge)", "required": true, "content": { "application/json": { "schema": { "type": "object", "description": "Slack Events API envelope — the shape depends on the event type" } } } }, "responses": { "200": { "description": "Event processed successfully", "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" } } } } } }, "404": { "description": "Unknown platform" } } } } } } ``` # Task Callback Source: https://recoupable.dev/docs/api-reference/content-agent/callback Internal callback endpoint for the `poll-content-run` Trigger.dev task. Receives content generation results and posts them back to the originating Slack thread. Authenticated via the `x-callback-secret` header. ## POST /api/content-agent/callback Full OpenAPI specification: https://recoupable.dev/docs/spec/content.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) Documentation gap: the published specification names `callbackSecret` but does not define that security scheme. Check the full specification and authentication guide before calling this operation. ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Content", "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/content-agent/callback": { "post": { "description": "Internal callback endpoint for the `poll-content-run` Trigger.dev task. Receives content generation results and posts them back to the originating Slack thread. Authenticated via the `x-callback-secret` header.\n\nThis endpoint is not intended for external use — it is called automatically by the polling task when content runs complete, fail, or time out.", "requestBody": { "description": "Content generation results from the polling task", "required": true, "content": { "application/json": { "schema": { "type": "object", "required": [ "threadId", "status" ], "properties": { "threadId": { "type": "string", "description": "Chat SDK thread identifier for the originating Slack thread" }, "status": { "type": "string", "enum": [ "completed", "failed", "timeout" ], "description": "Overall status of the content generation batch" }, "results": { "type": "array", "description": "Per-run results", "items": { "type": "object", "required": [ "runId", "status" ], "properties": { "runId": { "type": "string", "description": "Trigger.dev run ID" }, "status": { "type": "string", "enum": [ "completed", "failed", "timeout" ] }, "videoUrl": { "type": "string", "description": "URL of the generated video (when completed)" }, "captionText": { "type": "string", "description": "Generated caption text (when completed)" }, "error": { "type": "string", "description": "Error message (when failed)" } } } }, "message": { "type": "string", "description": "Optional human-readable message" } } } } } }, "responses": { "200": { "description": "Callback processed and results posted to Slack", "content": { "application/json": { "schema": { "type": "object", "properties": { "status": { "type": "string", "example": "ok" } } } } } }, "400": { "description": "Invalid request body" }, "401": { "description": "Missing or invalid callback secret" } }, "security": [ { "callbackSecret": [] } ] } } } } ``` # List Sandboxes Source: https://recoupable.dev/docs/api-reference/sandboxes/list List all sandboxes associated with the authenticated account and their current statuses. Returns sandbox details including lifecycle state, timeout remaining, and creation timestamp. Pass account_id to retrieve sandboxes for a specific account the API key has access to. Authentication is handled via the x-api-key header or Authorization Bearer token. ## GET /api/sandboxes Full OpenAPI specification: https://recoupable.dev/docs/spec/content.json ## Authentication The supplied specification does not declare an OpenAPI security object for this operation. Its declared headers and parameters still apply. Consult the authentication guide and the full specification before calling it. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Content", "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/sandboxes": { "get": { "description": "List all sandboxes associated with the authenticated account and their current statuses. Returns sandbox details including lifecycle state, timeout remaining, and creation timestamp. Pass account_id to retrieve sandboxes for a specific account the API key has access to. Authentication is handled via the x-api-key header or Authorization Bearer token.", "parameters": [ { "name": "sandbox_id", "in": "query", "required": false, "description": "Filter by a specific sandbox ID. When provided, returns only the sandbox matching this ID. Must be a sandbox that your account or organization is an admin of.", "schema": { "type": "string", "example": "sbx_abc123def456" } }, { "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": "Sandboxes retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxesResponse" } } } }, "401": { "description": "Unauthorized - invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxErrorResponse" } } } }, "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/SandboxErrorResponse" } } } } } } } }, "components": { "schemas": { "SandboxesResponse": { "type": "object", "required": [ "status", "sandboxes" ], "description": "Response containing sandbox information", "properties": { "status": { "type": "string", "enum": [ "success", "error" ], "description": "Status of the request" }, "sandboxes": { "type": "array", "items": { "$ref": "#/components/schemas/Sandbox" }, "description": "Array of sandbox objects" }, "snapshot_id": { "type": "string", "description": "The account's saved snapshot ID used for creating new sandboxes. Null if no snapshot has been saved.", "example": "snap_abc123def456", "nullable": true }, "github_repo": { "type": "string", "description": "The GitHub repository URL associated with the account's sandbox environment. Used as the filesystem source when restoring sandboxes.", "example": "https://github.com/username/repo", "nullable": true }, "filetree": { "type": "array", "nullable": true, "description": "The recursive file tree of the account's GitHub repository. Null if no github_repo is set or if the fetch fails.", "items": { "$ref": "#/components/schemas/FileTreeEntry" } }, "error": { "type": "string", "description": "Error message (only present if status is error)" } } }, "SandboxErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "description": "Error message describing what went wrong", "example": "Failed to create sandbox" } } }, "Sandbox": { "type": "object", "required": [ "sandboxId", "sandboxStatus", "timeout", "createdAt" ], "description": "A sandbox environment instance", "properties": { "sandboxId": { "type": "string", "description": "Unique identifier for the sandbox", "example": "sbx_abc123def456" }, "sandboxStatus": { "type": "string", "enum": [ "pending", "running", "stopping", "stopped", "failed" ], "description": "Current lifecycle state of the sandbox", "example": "running" }, "timeout": { "type": "integer", "description": "Milliseconds remaining before the sandbox stops automatically", "example": 300000 }, "createdAt": { "type": "string", "format": "date-time", "description": "ISO 8601 timestamp when the sandbox was created", "example": "2024-01-15T10:30:00.000Z" }, "runId": { "type": "string", "description": "Unique identifier for the command execution run. Only present if a command was provided when creating the sandbox. Use this with [GET /api/tasks/runs](/docs/api-reference/tasks/runs) to check the status and retrieve results.", "example": "run_xyz789abc123" } } }, "FileTreeEntry": { "type": "object", "required": [ "path", "type", "sha" ], "description": "A single entry in a GitHub repository file tree", "properties": { "path": { "type": "string", "description": "The file or directory path relative to the repository root", "example": "src/index.ts" }, "type": { "type": "string", "enum": [ "blob", "tree" ], "description": "The type of entry: blob for files, tree for directories" }, "sha": { "type": "string", "description": "The SHA hash of the entry", "example": "abc123def456" }, "size": { "type": "integer", "description": "The size of the file in bytes. Only present for blob entries.", "example": 1024 } } } } } } ``` # Create Sandbox Source: https://recoupable.dev/docs/api-reference/sandboxes/create Create a new ephemeral sandbox environment. Optionally executes a command or an OpenCode prompt if provided. Sandboxes are isolated Linux microVMs that can be used to evaluate account-generated code, run AI agent output safely, or execute reproducible tasks. The sandbox will automatically stop after the timeout period. If no command or prompt is provided, the sandbox is created without triggering any background task. Use the prompt parameter as a shortcut to run `opencode run ""` in the sandbox. Pass account_id to create a sandbox for a specific account the API key has access to. Authentication is handled via the x-api-key header or Authorization Bearer token. ## POST /api/sandboxes Full OpenAPI specification: https://recoupable.dev/docs/spec/content.json ## Authentication The supplied specification does not declare an OpenAPI security object for this operation. Its declared headers and parameters still apply. Consult the authentication guide and the full specification before calling it. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Content", "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/sandboxes": { "post": { "description": "Create a new ephemeral sandbox environment. Optionally executes a command or an OpenCode prompt if provided. Sandboxes are isolated Linux microVMs that can be used to evaluate account-generated code, run AI agent output safely, or execute reproducible tasks. The sandbox will automatically stop after the timeout period. If no command or prompt is provided, the sandbox is created without triggering any background task. Use the prompt parameter as a shortcut to run `opencode run \"\"` in the sandbox. Pass account_id to create a sandbox for a specific account the API key has access to. Authentication is handled via the x-api-key header or Authorization Bearer token.", "requestBody": { "description": "Optional command execution parameters. If command and prompt are both omitted, sandbox is created without running any command. Use prompt as a shortcut for running OpenCode.", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateSandboxRequest" } } } }, "responses": { "200": { "description": "Sandbox created successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxesResponse" } } } }, "400": { "description": "Bad request - failed to create sandbox", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxErrorResponse" } } } }, "401": { "description": "Unauthorized - invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxErrorResponse" } } } }, "403": { "description": "Forbidden - account_id is not a member of the organization or account tried to use an account_id they don't have access to", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxErrorResponse" } } } } } } } }, "components": { "schemas": { "CreateSandboxRequest": { "type": "object", "description": "Request body for creating a new sandbox. All fields are optional - if no command or prompt is provided, sandbox is created without triggering a command execution task. Use prompt as a shortcut to run OpenCode with a given prompt instead of specifying command/args manually.", "properties": { "command": { "type": "string", "minLength": 1, "description": "The command to execute in the sandbox environment. If omitted, the sandbox is created without running any command.", "example": "ls" }, "args": { "type": "array", "items": { "type": "string" }, "description": "Optional arguments to pass to the command.", "example": [ "-la", "/home" ] }, "cwd": { "type": "string", "description": "Optional working directory for command execution.", "example": "/home/user" }, "prompt": { "type": "string", "minLength": 1, "description": "A prompt to pass to OpenCode in the sandbox. When provided, the sandbox will execute `opencode run \"\"`. Cannot be used together with command.", "example": "create a hello world index.html" }, "account_id": { "type": "string", "format": "uuid", "description": "UUID of the account to create the sandbox for. Only applicable when the authenticated account has access to multiple accounts via organization membership. If not provided, creates the sandbox for the API key's own account." } } }, "SandboxesResponse": { "type": "object", "required": [ "status", "sandboxes" ], "description": "Response containing sandbox information", "properties": { "status": { "type": "string", "enum": [ "success", "error" ], "description": "Status of the request" }, "sandboxes": { "type": "array", "items": { "$ref": "#/components/schemas/Sandbox" }, "description": "Array of sandbox objects" }, "snapshot_id": { "type": "string", "description": "The account's saved snapshot ID used for creating new sandboxes. Null if no snapshot has been saved.", "example": "snap_abc123def456", "nullable": true }, "github_repo": { "type": "string", "description": "The GitHub repository URL associated with the account's sandbox environment. Used as the filesystem source when restoring sandboxes.", "example": "https://github.com/username/repo", "nullable": true }, "filetree": { "type": "array", "nullable": true, "description": "The recursive file tree of the account's GitHub repository. Null if no github_repo is set or if the fetch fails.", "items": { "$ref": "#/components/schemas/FileTreeEntry" } }, "error": { "type": "string", "description": "Error message (only present if status is error)" } } }, "SandboxErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "description": "Error message describing what went wrong", "example": "Failed to create sandbox" } } }, "Sandbox": { "type": "object", "required": [ "sandboxId", "sandboxStatus", "timeout", "createdAt" ], "description": "A sandbox environment instance", "properties": { "sandboxId": { "type": "string", "description": "Unique identifier for the sandbox", "example": "sbx_abc123def456" }, "sandboxStatus": { "type": "string", "enum": [ "pending", "running", "stopping", "stopped", "failed" ], "description": "Current lifecycle state of the sandbox", "example": "running" }, "timeout": { "type": "integer", "description": "Milliseconds remaining before the sandbox stops automatically", "example": 300000 }, "createdAt": { "type": "string", "format": "date-time", "description": "ISO 8601 timestamp when the sandbox was created", "example": "2024-01-15T10:30:00.000Z" }, "runId": { "type": "string", "description": "Unique identifier for the command execution run. Only present if a command was provided when creating the sandbox. Use this with [GET /api/tasks/runs](/docs/api-reference/tasks/runs) to check the status and retrieve results.", "example": "run_xyz789abc123" } } }, "FileTreeEntry": { "type": "object", "required": [ "path", "type", "sha" ], "description": "A single entry in a GitHub repository file tree", "properties": { "path": { "type": "string", "description": "The file or directory path relative to the repository root", "example": "src/index.ts" }, "type": { "type": "string", "enum": [ "blob", "tree" ], "description": "The type of entry: blob for files, tree for directories" }, "sha": { "type": "string", "description": "The SHA hash of the entry", "example": "abc123def456" }, "size": { "type": "integer", "description": "The size of the file in bytes. Only present for blob entries.", "example": 1024 } } } } } } ``` # Update Snapshot Source: https://recoupable.dev/docs/api-reference/sandboxes/snapshot Set a custom snapshot ID for an account. By default, updates the key owner's account. Pass account_id to target a specific account the API key has access to. This allows accounts to use a specific sandbox snapshot when creating new sandboxes, enabling reproducible environments with pre-configured tools, dependencies, and files. ## PATCH /api/sandboxes Full OpenAPI specification: https://recoupable.dev/docs/spec/content.json ## Authentication The supplied specification does not declare an OpenAPI security object for this operation. Its declared headers and parameters still apply. Consult the authentication guide and the full specification before calling it. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Content", "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/sandboxes": { "patch": { "description": "Set a custom snapshot ID for an account. By default, updates the key owner's account. Pass account_id to target a specific account the API key has access to. This allows accounts to use a specific sandbox snapshot when creating new sandboxes, enabling reproducible environments with pre-configured tools, dependencies, and files.", "requestBody": { "description": "Snapshot configuration parameters", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdateSnapshotRequest" } } } }, "responses": { "200": { "description": "Snapshot ID updated successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdateSnapshotResponse" } } } }, "400": { "description": "Bad request - invalid snapshot ID", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxErrorResponse" } } } }, "401": { "description": "Unauthorized - invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxErrorResponse" } } } }, "403": { "description": "Forbidden - account_id is not a member of the organization or account tried to use an account_id they don't have access to", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxErrorResponse" } } } } } } } }, "components": { "schemas": { "UpdateSnapshotRequest": { "type": "object", "required": [], "properties": { "snapshotId": { "type": "string", "description": "The snapshot ID to set for the account. This snapshot will be used as the base environment when creating new sandboxes.", "example": "snap_abc123def456" }, "github_repo": { "type": "string", "format": "uri", "description": "The GitHub repository URL to associate with the account's sandbox environment. Must be a valid URL.", "example": "https://github.com/org/repo" }, "account_id": { "type": "string", "format": "uuid", "description": "UUID of the account to update the snapshot for. Only applicable when the authenticated account has access to multiple accounts via organization membership. If not provided, updates the snapshot for the API key's own account." } } }, "UpdateSnapshotResponse": { "type": "object", "required": [ "account_id", "snapshot_id", "expires_at", "github_repo", "created_at" ], "properties": { "account_id": { "type": "string", "format": "uuid", "description": "The account ID this snapshot belongs to", "example": "550e8400-e29b-41d4-a716-446655440000" }, "snapshot_id": { "type": "string", "description": "The snapshot ID that was set for the account", "example": "snap_abc123def456" }, "expires_at": { "type": "string", "format": "date-time", "description": "When the snapshot expires", "example": "2027-01-01T00:00:00.000Z" }, "github_repo": { "type": "string", "nullable": true, "description": "The GitHub repository URL associated with the sandbox", "example": "https://github.com/org/repo" }, "created_at": { "type": "string", "format": "date-time", "nullable": true, "description": "When the snapshot record was created", "example": "2025-01-01T00:00:00.000Z" } } }, "SandboxErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "description": "Error message describing what went wrong", "example": "Failed to create sandbox" } } } } } } ``` # Delete Sandbox Source: https://recoupable.dev/docs/api-reference/sandboxes/delete Delete a sandbox environment for the authenticated account. This permanently deletes the associated GitHub repository and removes the account's snapshot record from the database. By default, deletes the sandbox for the key owner's account. Pass account_id to target a specific account the API key has access to. Authentication is handled via the x-api-key header or Authorization Bearer token. ## DELETE /api/sandboxes Full OpenAPI specification: https://recoupable.dev/docs/spec/content.json ## Authentication The supplied specification does not declare an OpenAPI security object for this operation. Its declared headers and parameters still apply. Consult the authentication guide and the full specification before calling it. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Content", "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/sandboxes": { "delete": { "description": "Delete a sandbox environment for the authenticated account. This permanently deletes the associated GitHub repository and removes the account's snapshot record from the database. By default, deletes the sandbox for the key owner's account. Pass account_id to target a specific account the API key has access to. Authentication is handled via the x-api-key header or Authorization Bearer token.", "requestBody": { "description": "Optional account targeting parameters", "required": false, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeleteSandboxRequest" } } } }, "responses": { "200": { "description": "Sandbox deleted successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeleteSandboxResponse" } } } }, "400": { "description": "Bad request - failed to delete sandbox", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxErrorResponse" } } } }, "401": { "description": "Unauthorized - invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxErrorResponse" } } } }, "403": { "description": "Forbidden - account_id is not a member of the organization or account tried to use an account_id they don't have access to", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxErrorResponse" } } } } } } } }, "components": { "schemas": { "DeleteSandboxRequest": { "type": "object", "properties": { "account_id": { "type": "string", "format": "uuid", "description": "UUID of the account to delete the sandbox for. Only applicable when the authenticated account has access to multiple accounts via organization membership. If not provided, deletes the sandbox for the API key's own account." } } }, "DeleteSandboxResponse": { "type": "object", "required": [ "status" ], "properties": { "status": { "type": "string", "enum": [ "success", "error" ], "description": "Status of the delete operation" }, "deleted_snapshot": { "nullable": true, "description": "The snapshot record that was deleted. Null if no snapshot existed for the account.", "$ref": "#/components/schemas/UpdateSnapshotResponse" }, "error": { "type": "string", "description": "Error message (only present if status is error)" } } }, "SandboxErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "description": "Error message describing what went wrong", "example": "Failed to create sandbox" } } }, "UpdateSnapshotResponse": { "type": "object", "required": [ "account_id", "snapshot_id", "expires_at", "github_repo", "created_at" ], "properties": { "account_id": { "type": "string", "format": "uuid", "description": "The account ID this snapshot belongs to", "example": "550e8400-e29b-41d4-a716-446655440000" }, "snapshot_id": { "type": "string", "description": "The snapshot ID that was set for the account", "example": "snap_abc123def456" }, "expires_at": { "type": "string", "format": "date-time", "description": "When the snapshot expires", "example": "2027-01-01T00:00:00.000Z" }, "github_repo": { "type": "string", "nullable": true, "description": "The GitHub repository URL associated with the sandbox", "example": "https://github.com/org/repo" }, "created_at": { "type": "string", "format": "date-time", "nullable": true, "description": "When the snapshot record was created", "example": "2025-01-01T00:00:00.000Z" } } } } } } ``` # Setup Sandbox Source: https://recoupable.dev/docs/api-reference/sandboxes/setup Triggers the setup-sandbox background task to create a personal sandbox, provision a GitHub repo, take a snapshot, and shut down. By default, sets up the sandbox for the key owner's account. Pass account_id to target a specific account the API key has access to. Authentication is handled via the x-api-key header or Authorization Bearer token. ## POST /api/sandboxes/setup Full OpenAPI specification: https://recoupable.dev/docs/spec/content.json ## Authentication The supplied specification does not declare an OpenAPI security object for this operation. Its declared headers and parameters still apply. Consult the authentication guide and the full specification before calling it. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Content", "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/sandboxes/setup": { "post": { "description": "Triggers the setup-sandbox background task to create a personal sandbox, provision a GitHub repo, take a snapshot, and shut down. By default, sets up the sandbox for the key owner's account. Pass account_id to target a specific account the API key has access to. Authentication is handled via the x-api-key header or Authorization Bearer token.", "requestBody": { "description": "Optional account targeting parameters", "required": false, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SetupSandboxRequest" } } } }, "responses": { "200": { "description": "Setup task triggered successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SetupSandboxResponse" } } } }, "400": { "description": "Bad request - invalid account_id format", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxErrorResponse" } } } }, "401": { "description": "Unauthorized - invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxErrorResponse" } } } } } } } }, "components": { "schemas": { "SetupSandboxRequest": { "type": "object", "properties": { "account_id": { "type": "string", "format": "uuid", "description": "UUID of the account to set up the sandbox for. Only applicable when the authenticated account has access to multiple accounts via organization membership. If not provided, sets up the sandbox for the API key's own account." } } }, "SetupSandboxResponse": { "type": "object", "required": [ "status", "runId" ], "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the setup operation" }, "runId": { "type": "string", "description": "The Trigger.dev run ID for the setup-sandbox background task. Use this with [GET /api/tasks/runs](/docs/api-reference/tasks/runs) to check the status and retrieve results.", "example": "run_abc123def456" } } }, "SandboxErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "description": "Error message describing what went wrong", "example": "Failed to create sandbox" } } } } } } ``` # Get File Contents Source: https://recoupable.dev/docs/api-reference/sandboxes/get-file Retrieve the contents of a file from the authenticated account's sandbox GitHub repository. Resolves the github_repo from the [account's snapshot](/docs/api-reference/sandboxes/list), then fetches the file at the specified path from the repository's main branch. **Text files are returned verbatim. Binary files (`.mp3`, `.png`, `.jpg`, `.mp4`, `.pdf`, etc.) are returned base64-encoded — decode before writing to disk.** Authentication is handled via the x-api-key header or Authorization Bearer token. ## GET /api/sandboxes/file Full OpenAPI specification: https://recoupable.dev/docs/spec/content.json ## Authentication The supplied specification does not declare an OpenAPI security object for this operation. Its declared headers and parameters still apply. Consult the authentication guide and the full specification before calling it. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Content", "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/sandboxes/file": { "get": { "description": "Retrieve the contents of a file from the authenticated account's sandbox GitHub repository. Resolves the github_repo from the [account's snapshot](/docs/api-reference/sandboxes/list), then fetches the file at the specified path from the repository's main branch. **Text files are returned verbatim. Binary files (`.mp3`, `.png`, `.jpg`, `.mp4`, `.pdf`, etc.) are returned base64-encoded — decode before writing to disk.** Authentication is handled via the x-api-key header or Authorization Bearer token.", "parameters": [ { "name": "path", "in": "query", "required": true, "description": "The file path within the repository (e.g. \"src/index.ts\" or \"README.md\").", "schema": { "type": "string", "example": "src/index.ts" } } ], "responses": { "200": { "description": "File contents retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxFileResponse" } } } }, "400": { "description": "Bad request - missing or invalid path parameter", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxErrorResponse" } } } }, "401": { "description": "Unauthorized - invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxErrorResponse" } } } }, "403": { "description": "Forbidden - account does not have access", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxErrorResponse" } } } }, "404": { "description": "Not found - no snapshot, no github_repo, or file not found in repository", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxErrorResponse" } } } } } } } }, "components": { "schemas": { "SandboxFileResponse": { "type": "object", "required": [ "status", "content" ], "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the operation" }, "content": { "type": "string", "description": "The file content. Text files (`.md`, `.json`, `.txt`, source code, etc.) are returned verbatim. Binary files (`.mp3`, `.png`, `.jpg`, `.mp4`, `.pdf`, etc.) are returned base64-encoded — decode before writing to disk. The encoding is inferred from the file extension server-side; `encoding` field is not yet returned but planned." } } }, "SandboxErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "description": "Error message describing what went wrong", "example": "Failed to create sandbox" } } } } } } ``` # Stage File Source: https://recoupable.dev/docs/api-reference/sandboxes/stage-file Issue a presigned upload token so the browser can upload a file directly to Vercel Blob, then call `POST /api/sandboxes/files` with the resulting blob URL to commit it to the sandbox repo. Use `upload()` from `@vercel/blob/client` rather than calling this route by hand. Max file size 100MB. ## POST /api/sandboxes/staged-file Full OpenAPI specification: https://recoupable.dev/docs/spec/content.json ## Authentication The supplied specification does not declare an OpenAPI security object for this operation. Its declared headers and parameters still apply. Consult the authentication guide and the full specification before calling it. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Content", "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/sandboxes/staged-file": { "post": { "description": "Issue a presigned upload token so the browser can upload a file directly to Vercel Blob, then call `POST /api/sandboxes/files` with the resulting blob URL to commit it to the sandbox repo. Use `upload()` from `@vercel/blob/client` rather than calling this route by hand. Max file size 100MB.", "requestBody": { "description": "`HandleUploadBody` envelope from `@vercel/blob/client`.", "required": true, "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } } }, "responses": { "200": { "description": "Handshake response from `@vercel/blob/client` — return verbatim to the library.", "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } } }, "401": { "description": "Missing or invalid auth on the handshake POST.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxErrorResponse" } } } }, "500": { "description": "Invalid body or upstream Vercel Blob failure.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxErrorResponse" } } } } } } } }, "components": { "schemas": { "SandboxErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "description": "Error message describing what went wrong", "example": "Failed to create sandbox" } } } } } } ``` # Upload Files Source: https://recoupable.dev/docs/api-reference/sandboxes/upload-files Upload one or more files to the authenticated account's sandbox GitHub repository. Accepts an array of file URLs and commits each file to the specified directory path within the repository. Supports submodule resolution — if the target path falls within a git submodule, the file is committed to the submodule's repository. Authentication is handled via the x-api-key header or Authorization Bearer token. ## POST /api/sandboxes/files Full OpenAPI specification: https://recoupable.dev/docs/spec/content.json ## Authentication The supplied specification does not declare an OpenAPI security object for this operation. Its declared headers and parameters still apply. Consult the authentication guide and the full specification before calling it. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Content", "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/sandboxes/files": { "post": { "description": "Upload one or more files to the authenticated account's sandbox GitHub repository. Accepts an array of file URLs and commits each file to the specified directory path within the repository. Supports submodule resolution — if the target path falls within a git submodule, the file is committed to the submodule's repository. Authentication is handled via the x-api-key header or Authorization Bearer token.", "requestBody": { "description": "JSON body containing file URLs and target path", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UploadSandboxFilesRequest" } } } }, "responses": { "200": { "description": "Files uploaded successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UploadSandboxFilesResponse" } } } }, "400": { "description": "Bad request - missing files or invalid path", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxErrorResponse" } } } }, "401": { "description": "Unauthorized - invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxErrorResponse" } } } }, "403": { "description": "Forbidden - account does not have access", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxErrorResponse" } } } }, "404": { "description": "Not found - no snapshot or no github_repo configured", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxErrorResponse" } } } } } } } }, "components": { "schemas": { "UploadSandboxFilesRequest": { "type": "object", "required": [ "files" ], "properties": { "path": { "type": "string", "description": "The target directory path within the repository to upload files to. Defaults to the repository root if omitted.", "example": "assets/images" }, "files": { "type": "array", "items": { "type": "object", "required": [ "url", "name" ], "properties": { "url": { "type": "string", "format": "uri", "description": "The URL of the file to upload", "example": "https://example.com/files/album-cover.png" }, "name": { "type": "string", "description": "The filename to use when committing to the repository", "example": "album-cover.png" } } }, "description": "Array of files to upload, each with a URL and target filename" }, "message": { "type": "string", "description": "Optional commit message. Defaults to 'Upload files via API'.", "example": "Add new album artwork" } } }, "UploadSandboxFilesResponse": { "type": "object", "required": [ "status", "uploaded" ], "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the operation" }, "uploaded": { "type": "array", "items": { "type": "object", "properties": { "path": { "type": "string", "description": "The full path of the uploaded file in the repository" }, "sha": { "type": "string", "description": "The git SHA of the created/updated file" } } }, "description": "Array of uploaded file details" } } }, "SandboxErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "description": "Error message describing what went wrong", "example": "Failed to create sandbox" } } } } } } ``` # Create Session Source: https://recoupable.dev/docs/api-reference/sessions/create Create session ## POST /api/sessions Full OpenAPI specification: https://recoupable.dev/docs/spec/sessions.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Sessions", "description": "Sessions — sandboxed runs of an LLM-driven agent with tool use, lifecycle hooks, and persistence. Each session pairs one Vercel Sandbox with one or more chat threads.", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "security": [ { "ApiKeyAuth": [] }, { "BearerAuth": [] } ], "paths": { "/api/sessions": { "post": { "summary": "Create session", "description": "Creates a new agent session and an initial empty chat in a single transaction. The session is created in the `provisioning` lifecycle state — a separate orchestration step claims it and starts the sandbox.", "requestBody": { "required": false, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateSessionRequest" } } } }, "responses": { "200": { "description": "Session and initial chat created successfully.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateSessionResponse" } } } }, "400": { "description": "Invalid request body — either malformed JSON or a field failed validation.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "Unauthorized — invalid or missing API key / Bearer token.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "description": "Server error — the session could not be persisted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "CreateSessionRequest": { "type": "object", "description": "All fields are optional. An empty body is valid and creates a personal session — the server provisions (or reuses) a workspace repo at `recoupable/` for the authenticated user. Include `organizationId` to create an org session instead — the server provisions (or reuses) `recoupable/`. The caller never constructs repo URLs.", "properties": { "title": { "type": "string", "description": "Display title for the session. When omitted, the server generates one." }, "organizationId": { "type": "string", "format": "uuid", "description": "Recoupable organization id. When provided, the session is created against the org's workspace repo (`recoupable/`) and the caller must have access to that organization. When omitted, the session is personal and uses the caller's own workspace repo (`recoupable/`)." }, "artistId": { "type": "string", "format": "uuid", "description": "Artist account id to associate the session with. When provided, the session is created in the context of that artist — used by the chat sidebar to filter chats by artist. Optional; omit for a session with no artist context." } } }, "CreateSessionResponse": { "type": "object", "required": [ "session", "chat" ], "properties": { "session": { "$ref": "#/components/schemas/Session" }, "chat": { "$ref": "#/components/schemas/Chat" } } }, "Error": { "type": "object", "required": [ "status", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Always `\"error\"` for error responses." }, "error": { "type": "string", "description": "Human-readable error message." } } }, "Session": { "type": "object", "description": "Agent session returned by [`POST /api/sessions`](/docs/api-reference/sessions/create), [`GET /api/sessions/{sessionId}`](/docs/api-reference/sessions/get), and [`PATCH /api/sessions/{sessionId}`](/docs/api-reference/sessions/patch). The api serializes every field listed in `required` on each response, including `isNewBranch` (boolean, from the non-null `sessions.is_new_branch` column) and `artistId` (UUID or null).", "required": [ "id", "userId", "artistId", "title", "status", "isNewBranch", "globalSkillRefs", "lifecycleVersion", "createdAt", "updatedAt" ], "properties": { "id": { "type": "string", "description": "Session id (nanoid)." }, "userId": { "type": "string", "format": "uuid", "description": "Owning account id. Named `userId` here for compatibility with existing clients; on the server side this is the account_id foreign key into the accounts table." }, "title": { "type": "string" }, "status": { "type": "string", "enum": [ "running", "completed", "failed", "archived" ] }, "repoOwner": { "type": "string", "nullable": true, "description": "GitHub repo owner if the session is bound to a repository." }, "repoName": { "type": "string", "nullable": true }, "branch": { "type": "string", "nullable": true }, "cloneUrl": { "type": "string", "nullable": true, "description": "Clone URL the sandbox should fetch from." }, "isNewBranch": { "type": "boolean", "description": "Always present on session responses. True when this session created and pushed a new branch (not committing back to the original); false otherwise." }, "globalSkillRefs": { "type": "array", "description": "Skills attached to the agent at provision time.", "items": { "type": "object", "additionalProperties": true } }, "sandboxState": { "type": "object", "nullable": true, "additionalProperties": true, "description": "Sandbox runtime state (Vercel Sandbox)." }, "lifecycleState": { "type": "string", "nullable": true, "enum": [ "provisioning", "active", "hibernating", "hibernated", "restoring", "archived", "failed" ], "description": "Lifecycle orchestration state for the sandbox." }, "lifecycleVersion": { "type": "integer", "description": "Optimistic concurrency token for lifecycle transitions." }, "lastActivityAt": { "type": "string", "format": "date-time", "nullable": true }, "sandboxExpiresAt": { "type": "string", "format": "date-time", "nullable": true }, "hibernateAfter": { "type": "string", "format": "date-time", "nullable": true }, "lifecycleRunId": { "type": "string", "nullable": true }, "lifecycleError": { "type": "string", "nullable": true }, "linesAdded": { "type": "integer", "nullable": true, "description": "Lines added across the session's diff. Defaults to 0; null when stats have not been computed." }, "linesRemoved": { "type": "integer", "nullable": true, "description": "Lines removed across the session's diff. Defaults to 0; null when stats have not been computed." }, "snapshotUrl": { "type": "string", "nullable": true }, "snapshotCreatedAt": { "type": "string", "format": "date-time", "nullable": true }, "snapshotSizeBytes": { "type": "integer", "nullable": true }, "cachedDiff": { "type": "object", "nullable": true, "additionalProperties": true }, "cachedDiffUpdatedAt": { "type": "string", "format": "date-time", "nullable": true }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "artistId": { "type": [ "string", "null" ], "format": "uuid", "description": "Artist account id this session was created in the context of, or `null` when no artist was associated. Set via `POST /api/sessions { artistId }`; surfaces here for clients (e.g. the chat sidebar) that filter sessions/chats by artist." } } }, "Chat": { "type": "object", "required": [ "id", "sessionId", "title", "modelId", "createdAt", "updatedAt" ], "properties": { "id": { "type": "string", "description": "Chat id (nanoid)." }, "sessionId": { "type": "string", "description": "Owning session id." }, "title": { "type": "string", "description": "Display title for the chat. The initial chat created with a session is titled `New chat`." }, "modelId": { "type": "string", "description": "AI Gateway model identifier the chat is configured to use (e.g. `openai/gpt-5.4`)." }, "activeStreamId": { "type": "string", "nullable": true, "description": "Id of an in-flight assistant stream, if one is active." }, "lastAssistantMessageAt": { "type": "string", "format": "date-time", "nullable": true, "description": "Timestamp of the most recent assistant message in this chat." }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } } } }, "securitySchemes": { "ApiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key" }, "BearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Get Session Source: https://recoupable.dev/docs/api-reference/sessions/get Get session by id ## GET /api/sessions/{sessionId} Full OpenAPI specification: https://recoupable.dev/docs/spec/sessions.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Sessions", "description": "Sessions — sandboxed runs of an LLM-driven agent with tool use, lifecycle hooks, and persistence. Each session pairs one Vercel Sandbox with one or more chat threads.", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "security": [ { "ApiKeyAuth": [] }, { "BearerAuth": [] } ], "paths": { "/api/sessions/{sessionId}": { "get": { "summary": "Get session by id", "description": "Returns a single agent session by its id.", "parameters": [ { "name": "sessionId", "in": "path", "required": true, "description": "The id of the session to fetch.", "schema": { "type": "string" } } ], "responses": { "200": { "description": "Session retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GetSessionResponse" } } } }, "401": { "description": "Unauthorized - invalid or missing API key / Bearer token", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "Forbidden - the authenticated account does not own this session", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "Not found - no session exists with the given id", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "GetSessionResponse": { "type": "object", "required": [ "session" ], "properties": { "session": { "$ref": "#/components/schemas/Session" } } }, "Error": { "type": "object", "required": [ "status", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Always `\"error\"` for error responses." }, "error": { "type": "string", "description": "Human-readable error message." } } }, "Session": { "type": "object", "description": "Agent session returned by [`POST /api/sessions`](/docs/api-reference/sessions/create), [`GET /api/sessions/{sessionId}`](/docs/api-reference/sessions/get), and [`PATCH /api/sessions/{sessionId}`](/docs/api-reference/sessions/patch). The api serializes every field listed in `required` on each response, including `isNewBranch` (boolean, from the non-null `sessions.is_new_branch` column) and `artistId` (UUID or null).", "required": [ "id", "userId", "artistId", "title", "status", "isNewBranch", "globalSkillRefs", "lifecycleVersion", "createdAt", "updatedAt" ], "properties": { "id": { "type": "string", "description": "Session id (nanoid)." }, "userId": { "type": "string", "format": "uuid", "description": "Owning account id. Named `userId` here for compatibility with existing clients; on the server side this is the account_id foreign key into the accounts table." }, "title": { "type": "string" }, "status": { "type": "string", "enum": [ "running", "completed", "failed", "archived" ] }, "repoOwner": { "type": "string", "nullable": true, "description": "GitHub repo owner if the session is bound to a repository." }, "repoName": { "type": "string", "nullable": true }, "branch": { "type": "string", "nullable": true }, "cloneUrl": { "type": "string", "nullable": true, "description": "Clone URL the sandbox should fetch from." }, "isNewBranch": { "type": "boolean", "description": "Always present on session responses. True when this session created and pushed a new branch (not committing back to the original); false otherwise." }, "globalSkillRefs": { "type": "array", "description": "Skills attached to the agent at provision time.", "items": { "type": "object", "additionalProperties": true } }, "sandboxState": { "type": "object", "nullable": true, "additionalProperties": true, "description": "Sandbox runtime state (Vercel Sandbox)." }, "lifecycleState": { "type": "string", "nullable": true, "enum": [ "provisioning", "active", "hibernating", "hibernated", "restoring", "archived", "failed" ], "description": "Lifecycle orchestration state for the sandbox." }, "lifecycleVersion": { "type": "integer", "description": "Optimistic concurrency token for lifecycle transitions." }, "lastActivityAt": { "type": "string", "format": "date-time", "nullable": true }, "sandboxExpiresAt": { "type": "string", "format": "date-time", "nullable": true }, "hibernateAfter": { "type": "string", "format": "date-time", "nullable": true }, "lifecycleRunId": { "type": "string", "nullable": true }, "lifecycleError": { "type": "string", "nullable": true }, "linesAdded": { "type": "integer", "nullable": true, "description": "Lines added across the session's diff. Defaults to 0; null when stats have not been computed." }, "linesRemoved": { "type": "integer", "nullable": true, "description": "Lines removed across the session's diff. Defaults to 0; null when stats have not been computed." }, "snapshotUrl": { "type": "string", "nullable": true }, "snapshotCreatedAt": { "type": "string", "format": "date-time", "nullable": true }, "snapshotSizeBytes": { "type": "integer", "nullable": true }, "cachedDiff": { "type": "object", "nullable": true, "additionalProperties": true }, "cachedDiffUpdatedAt": { "type": "string", "format": "date-time", "nullable": true }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "artistId": { "type": [ "string", "null" ], "format": "uuid", "description": "Artist account id this session was created in the context of, or `null` when no artist was associated. Set via `POST /api/sessions { artistId }`; surfaces here for clients (e.g. the chat sidebar) that filter sessions/chats by artist." } } } }, "securitySchemes": { "ApiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key" }, "BearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Update Session Source: https://recoupable.dev/docs/api-reference/sessions/patch Rename a session, change lifecycle status, or update line counters. ## PATCH /api/sessions/{sessionId} Full OpenAPI specification: https://recoupable.dev/docs/spec/sessions.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Sessions", "description": "Sessions — sandboxed runs of an LLM-driven agent with tool use, lifecycle hooks, and persistence. Each session pairs one Vercel Sandbox with one or more chat threads.", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "security": [ { "ApiKeyAuth": [] }, { "BearerAuth": [] } ], "paths": { "/api/sessions/{sessionId}": { "patch": { "operationId": "patchSessionById", "summary": "Update session", "description": "Renames a session or changes its status (e.g. archive / unarchive). All body fields are optional; omitted fields are left unchanged.", "parameters": [ { "name": "sessionId", "in": "path", "required": true, "description": "The id of the session to update.", "schema": { "type": "string" } } ], "requestBody": { "required": false, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PatchSessionBody" } } } }, "responses": { "200": { "description": "Session updated successfully.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GetSessionResponse" } } } }, "400": { "description": "Bad request — malformed JSON body.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "Unauthorized — invalid or missing API key / Bearer token.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "Forbidden — the authenticated account does not own this session.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "Not found — no session exists with the given id.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "description": "Internal server error — the session could not be updated.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "PatchSessionBody": { "type": "object", "description": "All fields are optional; an empty object `{}` is valid and leaves the session unchanged.", "properties": { "title": { "type": "string", "description": "New display title for the session (rename)." }, "status": { "type": "string", "enum": [ "running", "completed", "failed", "archived" ], "description": "Lifecycle status (matches the DB CHECK). `running` is active / unarchived; `completed` and `failed` are terminal; `archived` archives the row. Use `running` to unarchive from `archived`." }, "linesAdded": { "type": "integer", "minimum": 0, "description": "Updates the persisted `lines_added` column on the session (≥ 0)." }, "linesRemoved": { "type": "integer", "minimum": 0, "description": "Updates the persisted `lines_removed` column on the session (≥ 0)." } } }, "GetSessionResponse": { "type": "object", "required": [ "session" ], "properties": { "session": { "$ref": "#/components/schemas/Session" } } }, "Error": { "type": "object", "required": [ "status", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Always `\"error\"` for error responses." }, "error": { "type": "string", "description": "Human-readable error message." } } }, "Session": { "type": "object", "description": "Agent session returned by [`POST /api/sessions`](/docs/api-reference/sessions/create), [`GET /api/sessions/{sessionId}`](/docs/api-reference/sessions/get), and [`PATCH /api/sessions/{sessionId}`](/docs/api-reference/sessions/patch). The api serializes every field listed in `required` on each response, including `isNewBranch` (boolean, from the non-null `sessions.is_new_branch` column) and `artistId` (UUID or null).", "required": [ "id", "userId", "artistId", "title", "status", "isNewBranch", "globalSkillRefs", "lifecycleVersion", "createdAt", "updatedAt" ], "properties": { "id": { "type": "string", "description": "Session id (nanoid)." }, "userId": { "type": "string", "format": "uuid", "description": "Owning account id. Named `userId` here for compatibility with existing clients; on the server side this is the account_id foreign key into the accounts table." }, "title": { "type": "string" }, "status": { "type": "string", "enum": [ "running", "completed", "failed", "archived" ] }, "repoOwner": { "type": "string", "nullable": true, "description": "GitHub repo owner if the session is bound to a repository." }, "repoName": { "type": "string", "nullable": true }, "branch": { "type": "string", "nullable": true }, "cloneUrl": { "type": "string", "nullable": true, "description": "Clone URL the sandbox should fetch from." }, "isNewBranch": { "type": "boolean", "description": "Always present on session responses. True when this session created and pushed a new branch (not committing back to the original); false otherwise." }, "globalSkillRefs": { "type": "array", "description": "Skills attached to the agent at provision time.", "items": { "type": "object", "additionalProperties": true } }, "sandboxState": { "type": "object", "nullable": true, "additionalProperties": true, "description": "Sandbox runtime state (Vercel Sandbox)." }, "lifecycleState": { "type": "string", "nullable": true, "enum": [ "provisioning", "active", "hibernating", "hibernated", "restoring", "archived", "failed" ], "description": "Lifecycle orchestration state for the sandbox." }, "lifecycleVersion": { "type": "integer", "description": "Optimistic concurrency token for lifecycle transitions." }, "lastActivityAt": { "type": "string", "format": "date-time", "nullable": true }, "sandboxExpiresAt": { "type": "string", "format": "date-time", "nullable": true }, "hibernateAfter": { "type": "string", "format": "date-time", "nullable": true }, "lifecycleRunId": { "type": "string", "nullable": true }, "lifecycleError": { "type": "string", "nullable": true }, "linesAdded": { "type": "integer", "nullable": true, "description": "Lines added across the session's diff. Defaults to 0; null when stats have not been computed." }, "linesRemoved": { "type": "integer", "nullable": true, "description": "Lines removed across the session's diff. Defaults to 0; null when stats have not been computed." }, "snapshotUrl": { "type": "string", "nullable": true }, "snapshotCreatedAt": { "type": "string", "format": "date-time", "nullable": true }, "snapshotSizeBytes": { "type": "integer", "nullable": true }, "cachedDiff": { "type": "object", "nullable": true, "additionalProperties": true }, "cachedDiffUpdatedAt": { "type": "string", "format": "date-time", "nullable": true }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "artistId": { "type": [ "string", "null" ], "format": "uuid", "description": "Artist account id this session was created in the context of, or `null` when no artist was associated. Set via `POST /api/sessions { artistId }`; surfaces here for clients (e.g. the chat sidebar) that filter sessions/chats by artist." } } } }, "securitySchemes": { "ApiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key" }, "BearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # List Session Chats Source: https://recoupable.dev/docs/api-reference/sessions/list-chats List session chats ## GET /api/sessions/{sessionId}/chats Full OpenAPI specification: https://recoupable.dev/docs/spec/sessions.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Sessions", "description": "Sessions — sandboxed runs of an LLM-driven agent with tool use, lifecycle hooks, and persistence. Each session pairs one Vercel Sandbox with one or more chat threads.", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "security": [ { "ApiKeyAuth": [] }, { "BearerAuth": [] } ], "paths": { "/api/sessions/{sessionId}/chats": { "get": { "summary": "List session chats", "description": "Lists every chat in the given session as a `ChatSummary` (chat row plus per-account `hasUnread` and `isStreaming` flags), along with the caller's default model id. Chats are sorted by `createdAt` ascending.", "parameters": [ { "name": "sessionId", "in": "path", "required": true, "description": "The id of the parent session.", "schema": { "type": "string" } } ], "responses": { "200": { "description": "Chats retrieved successfully.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ListSessionChatsResponse" } } } }, "401": { "description": "Unauthorized — invalid or missing API key / Bearer token.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "Forbidden — the authenticated account does not own this session.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "Not found — no session exists with the given id.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "ListSessionChatsResponse": { "type": "object", "required": [ "chats", "defaultModelId" ], "properties": { "chats": { "type": "array", "description": "Every chat in the session, sorted by `createdAt` ascending.", "items": { "$ref": "#/components/schemas/ChatSummary" } }, "defaultModelId": { "type": "string", "description": "Default model id surfaced to clients with no explicit preference (e.g. `openai/gpt-5.4`)." } } }, "Error": { "type": "object", "required": [ "status", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Always `\"error\"` for error responses." }, "error": { "type": "string", "description": "Human-readable error message." } } }, "ChatSummary": { "type": "object", "description": "Chat row enriched with per-account `hasUnread` and computed `isStreaming` flags for chat-list rendering.", "required": [ "id", "sessionId", "title", "createdAt", "updatedAt", "hasUnread", "isStreaming" ], "properties": { "id": { "type": "string" }, "sessionId": { "type": "string" }, "title": { "type": "string" }, "modelId": { "type": "string", "nullable": true }, "activeStreamId": { "type": "string", "nullable": true }, "lastAssistantMessageAt": { "type": "string", "format": "date-time", "nullable": true }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "hasUnread": { "type": "boolean", "description": "True when `lastAssistantMessageAt` is newer than the caller's `chat_reads.last_read_at` (or no read row exists yet)." }, "isStreaming": { "type": "boolean", "description": "True when `activeStreamId` is non-null." } } } }, "securitySchemes": { "ApiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key" }, "BearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Create Session Chat Source: https://recoupable.dev/docs/api-reference/sessions/create-chat Create session chat ## POST /api/sessions/{sessionId}/chats Full OpenAPI specification: https://recoupable.dev/docs/spec/sessions.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Sessions", "description": "Sessions — sandboxed runs of an LLM-driven agent with tool use, lifecycle hooks, and persistence. Each session pairs one Vercel Sandbox with one or more chat threads.", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "security": [ { "ApiKeyAuth": [] }, { "BearerAuth": [] } ], "paths": { "/api/sessions/{sessionId}/chats": { "post": { "summary": "Create session chat", "description": "Creates a new chat inside the given session. Callers may pass `{ id }` to claim a deterministic chat id — useful for optimistic UI flows where the client generates the id locally and then persists it. If a chat with that id already exists in **this** session the call is idempotent and returns the existing row; if it exists in **another** session, 409 is returned.", "parameters": [ { "name": "sessionId", "in": "path", "required": true, "description": "The id of the parent session.", "schema": { "type": "string" } } ], "requestBody": { "required": false, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateSessionChatRequest" } } } }, "responses": { "200": { "description": "Chat created, or existing chat returned (idempotent on same-session reuse).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateSessionChatResponse" } } } }, "400": { "description": "Invalid chat id — body contained `id` but it was an empty string or not a string.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/InvalidChatIdError" } } } }, "401": { "description": "Unauthorized — invalid or missing API key / Bearer token.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "Forbidden — the authenticated account does not own this session.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "Not found — no session exists with the given id.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "409": { "description": "Chat id conflict — a chat with the requested id already exists on a different session.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ChatIdConflictError" } } } }, "500": { "description": "Server error — the chat could not be persisted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "CreateSessionChatRequest": { "type": "object", "description": "Body for `POST /api/sessions/{sessionId}/chats`. Both an empty body and an omitted body are valid.", "properties": { "id": { "type": "string", "minLength": 1, "description": "Optional client-supplied chat id (used for optimistic UI flows). When omitted, the server generates a UUID. When supplied, must be a non-empty string." } } }, "CreateSessionChatResponse": { "type": "object", "required": [ "chat" ], "properties": { "chat": { "$ref": "#/components/schemas/Chat" } } }, "InvalidChatIdError": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "enum": [ "Invalid chat id" ] } } }, "Error": { "type": "object", "required": [ "status", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Always `\"error\"` for error responses." }, "error": { "type": "string", "description": "Human-readable error message." } } }, "ChatIdConflictError": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "enum": [ "Chat ID conflict" ] } } }, "Chat": { "type": "object", "required": [ "id", "sessionId", "title", "modelId", "createdAt", "updatedAt" ], "properties": { "id": { "type": "string", "description": "Chat id (nanoid)." }, "sessionId": { "type": "string", "description": "Owning session id." }, "title": { "type": "string", "description": "Display title for the chat. The initial chat created with a session is titled `New chat`." }, "modelId": { "type": "string", "description": "AI Gateway model identifier the chat is configured to use (e.g. `openai/gpt-5.4`)." }, "activeStreamId": { "type": "string", "nullable": true, "description": "Id of an in-flight assistant stream, if one is active." }, "lastAssistantMessageAt": { "type": "string", "format": "date-time", "nullable": true, "description": "Timestamp of the most recent assistant message in this chat." }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } } } }, "securitySchemes": { "ApiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key" }, "BearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Get Session Chat Source: https://recoupable.dev/docs/api-reference/sessions/get-chat Get session chat ## GET /api/sessions/{sessionId}/chats/{chatId} Full OpenAPI specification: https://recoupable.dev/docs/spec/sessions.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Sessions", "description": "Sessions — sandboxed runs of an LLM-driven agent with tool use, lifecycle hooks, and persistence. Each session pairs one Vercel Sandbox with one or more chat threads.", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "security": [ { "ApiKeyAuth": [] }, { "BearerAuth": [] } ], "paths": { "/api/sessions/{sessionId}/chats/{chatId}": { "get": { "summary": "Get session chat", "description": "Returns the chat's persisted UI message stream plus its current streaming state so callers can hydrate or refresh a chat view. `messages` is an array of `parts` payloads — one per `chat_messages` row — ordered by `created_at` ascending (ties broken by id). `isStreaming` is derived from `activeStreamId`.", "parameters": [ { "name": "sessionId", "in": "path", "required": true, "description": "The id of the parent session.", "schema": { "type": "string" } }, { "name": "chatId", "in": "path", "required": true, "description": "The id of the chat being fetched.", "schema": { "type": "string" } } ], "responses": { "200": { "description": "Chat retrieved successfully.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SessionChatResponse" } } } }, "401": { "description": "Unauthorized — invalid or missing API key / Bearer token.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "Forbidden — the authenticated account does not own this session.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "Not found — no session exists with the given id, the chat does not exist, or the chat belongs to a different session.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "SessionChatResponse": { "type": "object", "description": "Response body for `GET /api/sessions/{sessionId}/chats/{chatId}`. Carries the full chat row plus its streaming state and persisted UI message stream — enough for both initial render and in-tab refresh.", "required": [ "chat", "isStreaming", "messages" ], "properties": { "chat": { "$ref": "#/components/schemas/Chat" }, "isStreaming": { "type": "boolean", "description": "True when `chat.activeStreamId` is non-null." }, "messages": { "type": "array", "description": "Persisted UI message stream — one serialized `UIMessage` per `chat_messages` row, ordered by `created_at` ascending (ties broken by `id`). Each item is the full message object, not a bare parts array.", "items": { "type": "object", "description": "A serialized `UIMessage` as stored by the agent.", "required": [ "id", "role", "parts" ], "properties": { "id": { "type": "string", "description": "Message id." }, "role": { "type": "string", "enum": [ "user", "assistant" ] }, "parts": { "type": "array", "description": "Ordered message parts (text, tool calls, data parts, etc.).", "items": { "type": "object", "additionalProperties": true } } }, "additionalProperties": true } } } }, "Error": { "type": "object", "required": [ "status", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Always `\"error\"` for error responses." }, "error": { "type": "string", "description": "Human-readable error message." } } }, "Chat": { "type": "object", "required": [ "id", "sessionId", "title", "modelId", "createdAt", "updatedAt" ], "properties": { "id": { "type": "string", "description": "Chat id (nanoid)." }, "sessionId": { "type": "string", "description": "Owning session id." }, "title": { "type": "string", "description": "Display title for the chat. The initial chat created with a session is titled `New chat`." }, "modelId": { "type": "string", "description": "AI Gateway model identifier the chat is configured to use (e.g. `openai/gpt-5.4`)." }, "activeStreamId": { "type": "string", "nullable": true, "description": "Id of an in-flight assistant stream, if one is active." }, "lastAssistantMessageAt": { "type": "string", "format": "date-time", "nullable": true, "description": "Timestamp of the most recent assistant message in this chat." }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } } } }, "securitySchemes": { "ApiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key" }, "BearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Update Session Chat Source: https://recoupable.dev/docs/api-reference/sessions/patch-chat Update session chat ## PATCH /api/sessions/{sessionId}/chats/{chatId} Full OpenAPI specification: https://recoupable.dev/docs/spec/sessions.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Sessions", "description": "Sessions — sandboxed runs of an LLM-driven agent with tool use, lifecycle hooks, and persistence. Each session pairs one Vercel Sandbox with one or more chat threads.", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "security": [ { "ApiKeyAuth": [] }, { "BearerAuth": [] } ], "paths": { "/api/sessions/{sessionId}/chats/{chatId}": { "patch": { "summary": "Update session chat", "description": "Applies a partial update to the chat. Body must include at least one of `title` or `modelId` and any provided value must be a non-empty string (whitespace is trimmed for `title`).", "parameters": [ { "name": "sessionId", "in": "path", "required": true, "description": "The id of the parent session.", "schema": { "type": "string" } }, { "name": "chatId", "in": "path", "required": true, "description": "The id of the chat being updated.", "schema": { "type": "string" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdateSessionChatRequest" } } } }, "responses": { "200": { "description": "Chat updated successfully.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdateSessionChatResponse" } } } }, "400": { "description": "Invalid body — JSON parse failed, neither `title` nor `modelId` was provided, or one of them was empty.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "Unauthorized — invalid or missing API key / Bearer token.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "Forbidden — the authenticated account does not own this session.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "Not found — no session exists with the given id, the chat does not exist, or the chat belongs to a different session.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "description": "Server error — the chat could not be updated.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "UpdateSessionChatRequest": { "type": "object", "description": "Body for `PATCH /api/sessions/{sessionId}/chats/{chatId}`. At least one of `title` or `modelId` must be provided; whichever is provided is trimmed and must be non-empty after trimming.", "additionalProperties": false, "anyOf": [ { "required": [ "title" ] }, { "required": [ "modelId" ] } ], "properties": { "title": { "type": "string", "minLength": 1, "pattern": "\\S", "description": "New display title for the chat. Trimmed; must be non-empty after trimming." }, "modelId": { "type": "string", "minLength": 1, "pattern": "\\S", "description": "AI Gateway model identifier the chat should be configured to use. Trimmed; must be non-empty after trimming." } } }, "UpdateSessionChatResponse": { "type": "object", "required": [ "chat" ], "properties": { "chat": { "$ref": "#/components/schemas/Chat" } } }, "Error": { "type": "object", "required": [ "status", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Always `\"error\"` for error responses." }, "error": { "type": "string", "description": "Human-readable error message." } } }, "Chat": { "type": "object", "required": [ "id", "sessionId", "title", "modelId", "createdAt", "updatedAt" ], "properties": { "id": { "type": "string", "description": "Chat id (nanoid)." }, "sessionId": { "type": "string", "description": "Owning session id." }, "title": { "type": "string", "description": "Display title for the chat. The initial chat created with a session is titled `New chat`." }, "modelId": { "type": "string", "description": "AI Gateway model identifier the chat is configured to use (e.g. `openai/gpt-5.4`)." }, "activeStreamId": { "type": "string", "nullable": true, "description": "Id of an in-flight assistant stream, if one is active." }, "lastAssistantMessageAt": { "type": "string", "format": "date-time", "nullable": true, "description": "Timestamp of the most recent assistant message in this chat." }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } } } }, "securitySchemes": { "ApiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key" }, "BearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Delete Session Chat Source: https://recoupable.dev/docs/api-reference/sessions/delete-chat Delete session chat ## DELETE /api/sessions/{sessionId}/chats/{chatId} Full OpenAPI specification: https://recoupable.dev/docs/spec/sessions.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Sessions", "description": "Sessions — sandboxed runs of an LLM-driven agent with tool use, lifecycle hooks, and persistence. Each session pairs one Vercel Sandbox with one or more chat threads.", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "security": [ { "ApiKeyAuth": [] }, { "BearerAuth": [] } ], "paths": { "/api/sessions/{sessionId}/chats/{chatId}": { "delete": { "summary": "Delete session chat", "description": "Removes the chat (cascade clears `chat_messages` and `chat_reads`). Refuses with 400 if this is the only chat in the session — sessions must always retain at least one chat.", "parameters": [ { "name": "sessionId", "in": "path", "required": true, "description": "The id of the parent session.", "schema": { "type": "string" } }, { "name": "chatId", "in": "path", "required": true, "description": "The id of the chat being deleted.", "schema": { "type": "string" } } ], "responses": { "200": { "description": "Chat deleted successfully.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeleteSessionChatResponse" } } } }, "400": { "description": "Cannot delete the only chat in a session.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "Unauthorized — invalid or missing API key / Bearer token.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "Forbidden — the authenticated account does not own this session.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "Not found — no session exists with the given id, the chat does not exist, or the chat belongs to a different session.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "description": "Server error — the chat could not be deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "DeleteSessionChatResponse": { "type": "object", "required": [ "success" ], "properties": { "success": { "type": "boolean", "enum": [ true ] } } }, "Error": { "type": "object", "required": [ "status", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Always `\"error\"` for error responses." }, "error": { "type": "string", "description": "Human-readable error message." } } } }, "securitySchemes": { "ApiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key" }, "BearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Mark Chat Read Source: https://recoupable.dev/docs/api-reference/sessions/mark-chat-read Marks a session chat as read for the authenticated account. ## POST /api/sessions/{sessionId}/chats/{chatId}/read Full OpenAPI specification: https://recoupable.dev/docs/spec/sessions.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Sessions", "description": "Sessions — sandboxed runs of an LLM-driven agent with tool use, lifecycle hooks, and persistence. Each session pairs one Vercel Sandbox with one or more chat threads.", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "security": [ { "ApiKeyAuth": [] }, { "BearerAuth": [] } ], "paths": { "/api/sessions/{sessionId}/chats/{chatId}/read": { "post": { "operationId": "markChatRead", "summary": "Mark chat as read", "description": "Records that the authenticated account has read this chat up to the current timestamp. This upserts a row in `chat_reads` setting `last_read_at = now()`. After a successful call the `hasUnread` flag for this chat will be `false` in subsequent `GET /api/sessions/{sessionId}/chats` responses.", "parameters": [ { "name": "sessionId", "in": "path", "required": true, "description": "The id of the parent session.", "schema": { "type": "string" } }, { "name": "chatId", "in": "path", "required": true, "description": "The id of the chat to mark as read.", "schema": { "type": "string" } } ], "responses": { "200": { "description": "Chat marked as read successfully.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MarkChatReadResponse" } } } }, "401": { "description": "Unauthorized — invalid or missing API key / Bearer token.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "Forbidden — the authenticated account does not own this session.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "Not found — no session exists with the given id, the chat does not exist, or the chat belongs to a different session.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "description": "Server error — the read record could not be persisted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "MarkChatReadResponse": { "type": "object", "required": [ "success" ], "properties": { "success": { "type": "boolean", "enum": [ true ] } } }, "Error": { "type": "object", "required": [ "status", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Always `\"error\"` for error responses." }, "error": { "type": "string", "description": "Human-readable error message." } } } }, "securitySchemes": { "ApiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key" }, "BearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Create or restore session sandbox Source: https://recoupable.dev/docs/api-reference/sandbox/create Create or restore session sandbox ## POST /api/sandbox Full OpenAPI specification: https://recoupable.dev/docs/spec/sandbox.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Session Sandboxes", "description": "Per-session sandbox lifecycle for agent runs. These endpoints provision, restore, and report status on the Sandbox bound to a single agent session. Distinct from the legacy `/api/sandboxes` (plural) account-scoped endpoints — these are session-scoped and used by the open-agents-style chat UI to drive the \"loading sandbox…\" state on session entry.", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "security": [ { "ApiKeyAuth": [] }, { "BearerAuth": [] } ], "paths": { "/api/sandbox": { "post": { "summary": "Create or restore session sandbox", "description": "Provisions a Sandbox for the given session. If a per-org base snapshot exists, the sandbox boots from it (skipping the full repo clone, ~75s saved). Otherwise the sandbox boots from the default base snapshot and a background workflow builds an org-specific snapshot for next time. When the session has prior runtime state (a paused or running sandbox under the same `sandboxName`), the call resumes it instead of creating a new one. On success, the session row is updated with the new `sandboxState` and lifecycle is bumped to `active`; the lifecycle workflow is kicked to manage hibernation and expiry from there.", "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateSandboxRequest" } } } }, "responses": { "200": { "description": "Sandbox provisioned and bound to the session.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateSandboxResponse" } } } }, "400": { "description": "Invalid request body — malformed JSON, missing required fields, or invalid GitHub repository URL.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "Unauthorized — invalid or missing API key / Bearer token.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "Forbidden — the authenticated account does not own the supplied `sessionId`.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "Not found — no session exists with the given `sessionId`.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "502": { "description": "Upstream error — the sandbox provider failed to provision a sandbox.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "CreateSandboxRequest": { "type": "object", "required": [ "repoUrl" ], "properties": { "repoUrl": { "type": "string", "description": "GitHub repository URL the sandbox should clone (e.g. `https://github.com/owner/repo`). Validated against GitHub URL rules; private repos require the service GitHub token configured server-side." }, "sessionId": { "type": "string", "description": "Owning session id. Required for the chat UX flow — the sandbox is named deterministically from the session id, enabling resume across reconnects. When omitted, a one-shot ephemeral sandbox is created (legacy)." } } }, "CreateSandboxResponse": { "type": "object", "required": [ "createdAt", "timeout", "currentBranch", "mode", "timing" ], "properties": { "createdAt": { "type": "integer", "format": "int64", "description": "Epoch milliseconds when the sandbox handle was returned." }, "timeout": { "type": "integer", "format": "int64", "description": "Sandbox idle-timeout in milliseconds. The lifecycle workflow uses this to schedule hibernation." }, "currentBranch": { "type": "string", "description": "Branch the sandbox checked out." }, "mode": { "type": "string", "enum": [ "vercel" ], "description": "Sandbox provider. Currently always `vercel`." }, "timing": { "type": "object", "required": [ "readyMs" ], "properties": { "readyMs": { "type": "integer", "format": "int64", "description": "Wall-clock milliseconds from request receipt until the sandbox was ready. Useful for tracking cold-start vs warm-resume performance." } } } } }, "Error": { "type": "object", "required": [ "status", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Always `\"error\"` for error responses." }, "error": { "type": "string", "description": "Human-readable error message." } } } }, "securitySchemes": { "ApiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key" }, "BearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Get session sandbox status Source: https://recoupable.dev/docs/api-reference/sandbox/status Get session sandbox status ## GET /api/sandbox/status Full OpenAPI specification: https://recoupable.dev/docs/spec/sandbox.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Session Sandboxes", "description": "Per-session sandbox lifecycle for agent runs. These endpoints provision, restore, and report status on the Sandbox bound to a single agent session. Distinct from the legacy `/api/sandboxes` (plural) account-scoped endpoints — these are session-scoped and used by the open-agents-style chat UI to drive the \"loading sandbox…\" state on session entry.", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "security": [ { "ApiKeyAuth": [] }, { "BearerAuth": [] } ], "paths": { "/api/sandbox/status": { "get": { "summary": "Get session sandbox status", "description": "Returns the current lifecycle and runtime state for the sandbox bound to a session. The chat UI polls this endpoint while showing the \"loading sandbox…\" state and flips to \"ready\" when `status` becomes `active`. The response includes `lifecycleVersion` (an optimistic concurrency token) and a `lifecycle` envelope with `serverTime`, the lifecycle FSM `state`, and timestamps for last activity, hibernation deadline, and sandbox expiry. As a side effect, if the runtime state is stale (expired or overdue for hibernation), the lifecycle workflow is kicked to clean up — callers do not need to do this themselves.", "parameters": [ { "name": "sessionId", "in": "query", "required": true, "description": "The id of the session whose sandbox status to read.", "schema": { "type": "string" } } ], "responses": { "200": { "description": "Sandbox status retrieved successfully.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxStatusResponse" } } } }, "400": { "description": "Missing `sessionId` query parameter.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "Unauthorized — invalid or missing API key / Bearer token.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "Forbidden — the authenticated account does not own this session.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "Not found — no session exists with the given `sessionId`.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "SandboxStatusResponse": { "type": "object", "required": [ "status", "hasSnapshot", "lifecycleVersion", "lifecycle" ], "properties": { "status": { "type": "string", "enum": [ "active", "no_sandbox" ], "description": "`active` when a non-expired sandbox is bound to the session; `no_sandbox` otherwise. The chat UI flips out of its loading state when this becomes `active`." }, "hasSnapshot": { "type": "boolean", "description": "True when a paused/snapshotted sandbox exists and can be resumed. Used by the UI to decide whether to show \"resume\" vs \"create\" affordances when `status` is `no_sandbox`." }, "lifecycleVersion": { "type": "integer", "description": "Optimistic concurrency token for lifecycle transitions. Clients can pass this back to lifecycle-mutating endpoints to detect races." }, "lifecycle": { "$ref": "#/components/schemas/SandboxLifecycle" } } }, "Error": { "type": "object", "required": [ "status", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Always `\"error\"` for error responses." }, "error": { "type": "string", "description": "Human-readable error message." } } }, "SandboxLifecycle": { "type": "object", "required": [ "serverTime", "state", "lastActivityAt", "hibernateAfter", "sandboxExpiresAt" ], "description": "Lifecycle envelope shared between `GET /api/sandbox/status` and `GET /api/sandbox/reconnect`. Server-clock-stamped snapshot of the sandbox's lifecycle FSM state and the timestamps the UI uses to render countdown timers.", "properties": { "serverTime": { "type": "integer", "format": "int64", "description": "Server's current epoch milliseconds. Use this — not the client clock — when computing how much time is left before `hibernateAfter` or `sandboxExpiresAt`." }, "state": { "type": "string", "nullable": true, "enum": [ "provisioning", "active", "hibernating", "hibernated", "restoring", "archived", "failed" ], "description": "Lifecycle FSM state. `null` for sessions that have never had a sandbox." }, "lastActivityAt": { "type": "integer", "format": "int64", "nullable": true, "description": "Epoch milliseconds of the last recorded sandbox activity, or null if there has been none." }, "hibernateAfter": { "type": "integer", "format": "int64", "nullable": true, "description": "Epoch milliseconds after which the sandbox is eligible for hibernation, or null when not applicable." }, "sandboxExpiresAt": { "type": "integer", "format": "int64", "nullable": true, "description": "Epoch milliseconds when the sandbox runtime expires, or null when not applicable." } } } }, "securitySchemes": { "ApiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key" }, "BearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Reconnect to session sandbox Source: https://recoupable.dev/docs/api-reference/sandbox/reconnect Reconnect to session sandbox ## GET /api/sandbox/reconnect Full OpenAPI specification: https://recoupable.dev/docs/spec/sandbox.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Session Sandboxes", "description": "Per-session sandbox lifecycle for agent runs. These endpoints provision, restore, and report status on the Sandbox bound to a single agent session. Distinct from the legacy `/api/sandboxes` (plural) account-scoped endpoints — these are session-scoped and used by the open-agents-style chat UI to drive the \"loading sandbox…\" state on session entry.", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "security": [ { "ApiKeyAuth": [] }, { "BearerAuth": [] } ], "paths": { "/api/sandbox/reconnect": { "get": { "summary": "Reconnect to session sandbox", "description": "Live runtime probe for the sandbox bound to a session. Unlike `GET /api/sandbox/status` (DB-only read), this endpoint actually runs a quick command inside the sandbox to verify it is reachable. Used by the chat UI on session re-entry / tab refocus to decide whether to flip out of \"loading sandbox…\" or surface a \"resume\" affordance. Returns one of three operational outcomes via the `status` field: `\"connected\"` (sandbox is alive, included `expiresAt` reflects current expiry), `\"expired\"` (the runtime state is gone — the UI should offer to resume from snapshot if `hasSnapshot` is true, otherwise create a fresh sandbox), or `\"no_sandbox\"` (no sandbox has been provisioned for this session yet).", "parameters": [ { "name": "sessionId", "in": "query", "required": true, "description": "The id of the session whose sandbox to reconnect to.", "schema": { "type": "string" } } ], "responses": { "200": { "description": "Reconnect probe completed. Inspect `status` for the outcome.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ReconnectResponse" } } } }, "400": { "description": "Missing `sessionId` query parameter.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "Unauthorized — invalid or missing API key / Bearer token.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "Forbidden — the authenticated account does not own this session.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "Not found — no session exists with the given `sessionId`.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "ReconnectResponse": { "type": "object", "required": [ "status", "hasSnapshot", "lifecycle" ], "properties": { "status": { "type": "string", "enum": [ "connected", "expired", "no_sandbox" ], "description": "`connected` when the live runtime probe succeeded; `expired` when the recorded runtime state is no longer reachable (the UI should offer resume-from-snapshot or fresh-create); `no_sandbox` when the session has never had a sandbox provisioned." }, "hasSnapshot": { "type": "boolean", "description": "True when a paused/snapshotted sandbox exists and can be resumed. Used by the UI to decide whether to show \"resume\" vs \"create\" affordances on `expired` / `no_sandbox`." }, "expiresAt": { "type": "integer", "format": "int64", "description": "Epoch milliseconds when the sandbox runtime will expire. Present only when `status` is `\"connected\"`; reflects the freshly-probed expiry, which may differ from `lifecycle.sandboxExpiresAt` if the sandbox extended itself between writes." }, "lifecycle": { "$ref": "#/components/schemas/SandboxLifecycle" } } }, "Error": { "type": "object", "required": [ "status", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Always `\"error\"` for error responses." }, "error": { "type": "string", "description": "Human-readable error message." } } }, "SandboxLifecycle": { "type": "object", "required": [ "serverTime", "state", "lastActivityAt", "hibernateAfter", "sandboxExpiresAt" ], "description": "Lifecycle envelope shared between `GET /api/sandbox/status` and `GET /api/sandbox/reconnect`. Server-clock-stamped snapshot of the sandbox's lifecycle FSM state and the timestamps the UI uses to render countdown timers.", "properties": { "serverTime": { "type": "integer", "format": "int64", "description": "Server's current epoch milliseconds. Use this — not the client clock — when computing how much time is left before `hibernateAfter` or `sandboxExpiresAt`." }, "state": { "type": "string", "nullable": true, "enum": [ "provisioning", "active", "hibernating", "hibernated", "restoring", "archived", "failed" ], "description": "Lifecycle FSM state. `null` for sessions that have never had a sandbox." }, "lastActivityAt": { "type": "integer", "format": "int64", "nullable": true, "description": "Epoch milliseconds of the last recorded sandbox activity, or null if there has been none." }, "hibernateAfter": { "type": "integer", "format": "int64", "nullable": true, "description": "Epoch milliseconds after which the sandbox is eligible for hibernation, or null when not applicable." }, "sandboxExpiresAt": { "type": "integer", "format": "int64", "nullable": true, "description": "Epoch milliseconds when the sandbox runtime expires, or null when not applicable." } } } }, "securitySchemes": { "ApiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key" }, "BearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Get Account ID Source: https://recoupable.dev/docs/api-reference/accounts/id Retrieve the ID of the authenticated account associated with the provided credentials. This is useful when you have an API key or access token but do not yet know the corresponding accountId. ## GET /api/accounts/id Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/accounts/id": { "get": { "description": "Retrieve the ID of the authenticated account associated with the provided credentials. This is useful when you have an API key or access token but do not yet know the corresponding accountId.", "parameters": [], "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "responses": { "200": { "description": "Account ID retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GetAccountIdResponse" } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } } } } } }, "components": { "schemas": { "GetAccountIdResponse": { "type": "object", "required": [ "status", "accountId" ], "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the request" }, "accountId": { "type": "string", "format": "uuid", "description": "The unique identifier (UUID) of the authenticated account" } } }, "AccountErrorResponse": { "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" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Get Account Source: https://recoupable.dev/docs/api-reference/accounts/get Retrieve detailed account information by ID. Returns the account with associated profile info, emails, and wallet addresses. ## GET /api/accounts/{id} Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication The supplied specification does not declare an OpenAPI security object for this operation. Its declared headers and parameters still apply. Consult the authentication guide and the full specification before calling it. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/accounts/{id}": { "get": { "description": "Retrieve detailed account information by ID. Returns the account with associated profile info, emails, and wallet addresses.", "parameters": [ { "name": "id", "in": "path", "description": "The unique identifier (UUID) of the account", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Account retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GetAccountResponse" } } } }, "404": { "description": "Account not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } } } } } }, "components": { "schemas": { "GetAccountResponse": { "type": "object", "required": [ "status", "account" ], "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the request" }, "account": { "$ref": "#/components/schemas/Account", "description": "The account details" } } }, "AccountErrorResponse": { "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" } } }, "Account": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid", "description": "Unique identifier for the account" }, "name": { "type": "string", "description": "Account display name" }, "image": { "type": "string", "nullable": true, "description": "Profile image URL" }, "instruction": { "type": "string", "nullable": true, "description": "Custom AI instructions for this account" }, "knowledges": { "type": "array", "nullable": true, "items": { "$ref": "#/components/schemas/Knowledge" }, "description": "Knowledge base files attached to this account" }, "email": { "type": "string", "nullable": true, "description": "Primary email address" }, "wallet_address": { "type": "string", "nullable": true, "description": "Connected wallet address" } } }, "Knowledge": { "type": "object", "properties": { "url": { "type": "string", "description": "URL to the knowledge file" }, "name": { "type": "string", "description": "Name of the knowledge file" }, "type": { "type": "string", "description": "MIME type of the file" } } } } } } ``` # Create Account Source: https://recoupable.dev/docs/api-reference/accounts/create Create a new account or retrieve an existing account by email or wallet address. If an account with the provided email or wallet already exists, returns that account. Otherwise creates a new account and initializes credits. ## POST /api/accounts Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication The supplied specification does not declare an OpenAPI security object for this operation. Its declared headers and parameters still apply. Consult the authentication guide and the full specification before calling it. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/accounts": { "post": { "description": "Create a new account or retrieve an existing account by email or wallet address. If an account with the provided email or wallet already exists, returns that account. Otherwise creates a new account and initializes credits.", "requestBody": { "description": "Account credentials to create or lookup", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateAccountRequest" } } } }, "responses": { "200": { "description": "Account created or retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountDataResponse" } } } }, "400": { "description": "Bad request - failed to create account", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } } } } } }, "components": { "schemas": { "CreateAccountRequest": { "type": "object", "properties": { "email": { "type": "string", "format": "email", "description": "Email address to associate with the account. If an account with this email exists, it will be returned." }, "wallet": { "type": "string", "description": "Wallet address to associate with the account. If an account with this wallet exists, it will be returned." } }, "description": "At least one of email or wallet should be provided to identify or create an account." }, "AccountDataResponse": { "type": "object", "required": [ "data" ], "properties": { "data": { "$ref": "#/components/schemas/AccountData", "description": "The account data" } } }, "AccountErrorResponse": { "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" } } }, "AccountData": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid", "description": "The unique identifier of the account" }, "account_id": { "type": "string", "format": "uuid", "description": "The account ID (same as id, for consistency)" }, "name": { "type": "string", "description": "Display name of the account" }, "email": { "type": "string", "format": "email", "description": "Email address associated with the account" }, "wallet": { "type": "string", "description": "Wallet address associated with the account" }, "image": { "type": "string", "format": "uri", "description": "URL of the account's profile image" }, "instruction": { "type": "string", "description": "Custom instruction or bio" }, "organization": { "type": "string", "description": "Organization name" }, "job_title": { "type": "string", "description": "Job title" }, "role_type": { "type": "string", "description": "Role type" }, "company_name": { "type": "string", "description": "Company name" }, "knowledges": { "type": "array", "items": { "$ref": "#/components/schemas/Knowledge" }, "description": "Knowledge base files attached to this account" } } }, "Knowledge": { "type": "object", "properties": { "url": { "type": "string", "description": "URL to the knowledge file" }, "name": { "type": "string", "description": "Name of the knowledge file" }, "type": { "type": "string", "description": "MIME type of the file" } } } } } } ``` # Update Account Source: https://recoupable.dev/docs/api-reference/accounts/update Update an existing account's profile information including name, organization, image, instruction, job title, role type, company name, and knowledges. Requires authentication via x-api-key or Authorization Bearer token. The authenticated account may update itself or a permitted account_id override. ## PATCH /api/accounts Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication The supplied specification does not declare an OpenAPI security object for this operation. Its declared headers and parameters still apply. Consult the authentication guide and the full specification before calling it. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/accounts": { "patch": { "description": "Update an existing account's profile information including name, organization, image, instruction, job title, role type, company name, and knowledges. Requires authentication via x-api-key or Authorization Bearer token. The authenticated account may update itself or a permitted account_id override.", "requestBody": { "description": "Account fields to update", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdateAccountRequest" } } } }, "responses": { "200": { "description": "Account updated successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountDataResponse" } } } }, "400": { "description": "Bad request - account not found or update failed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } }, "403": { "description": "Forbidden - account override not permitted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } } } } } }, "components": { "schemas": { "UpdateAccountRequest": { "type": "object", "required": [ "accountId" ], "properties": { "accountId": { "type": "string", "format": "uuid", "description": "The unique identifier of the account to update" }, "name": { "type": "string", "description": "Display name for the account" }, "instruction": { "type": "string", "description": "Custom instruction or bio for the account" }, "organization": { "type": "string", "description": "Organization name associated with the account" }, "image": { "type": "string", "format": "uri", "description": "URL of the account's profile image" }, "jobTitle": { "type": "string", "description": "Job title of the account holder" }, "roleType": { "type": "string", "description": "Role type within the organization" }, "companyName": { "type": "string", "description": "Company name associated with the account" }, "knowledges": { "type": "array", "items": { "$ref": "#/components/schemas/Knowledge" }, "description": "Knowledge base files attached to this account" } } }, "AccountDataResponse": { "type": "object", "required": [ "data" ], "properties": { "data": { "$ref": "#/components/schemas/AccountData", "description": "The account data" } } }, "AccountErrorResponse": { "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" } } }, "Knowledge": { "type": "object", "properties": { "url": { "type": "string", "description": "URL to the knowledge file" }, "name": { "type": "string", "description": "Name of the knowledge file" }, "type": { "type": "string", "description": "MIME type of the file" } } }, "AccountData": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid", "description": "The unique identifier of the account" }, "account_id": { "type": "string", "format": "uuid", "description": "The account ID (same as id, for consistency)" }, "name": { "type": "string", "description": "Display name of the account" }, "email": { "type": "string", "format": "email", "description": "Email address associated with the account" }, "wallet": { "type": "string", "description": "Wallet address associated with the account" }, "image": { "type": "string", "format": "uri", "description": "URL of the account's profile image" }, "instruction": { "type": "string", "description": "Custom instruction or bio" }, "organization": { "type": "string", "description": "Organization name" }, "job_title": { "type": "string", "description": "Job title" }, "role_type": { "type": "string", "description": "Role type" }, "company_name": { "type": "string", "description": "Company name" }, "knowledges": { "type": "array", "items": { "$ref": "#/components/schemas/Knowledge" }, "description": "Knowledge base files attached to this account" } } } } } } ``` # Add Artist to Account Source: https://recoupable.dev/docs/api-reference/accounts/add-artist Add an artist to an account's list of associated artists. If the artist is already associated with the account, returns success without modification. ## POST /api/accounts/artists Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication The supplied specification does not declare an OpenAPI security object for this operation. Its declared headers and parameters still apply. Consult the authentication guide and the full specification before calling it. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/accounts/artists": { "post": { "description": "Add an artist to an account's list of associated artists. If the artist is already associated with the account, returns success without modification.", "requestBody": { "description": "Account and artist identifiers", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AddArtistToAccountRequest" } } } }, "responses": { "200": { "description": "Artist added to account successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AddArtistSuccessResponse" } } } }, "400": { "description": "Bad request - account not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } } } } } }, "components": { "schemas": { "AddArtistToAccountRequest": { "type": "object", "required": [ "email", "artistId" ], "properties": { "email": { "type": "string", "format": "email", "description": "Email address of the account to add the artist to" }, "artistId": { "type": "string", "format": "uuid", "description": "The unique identifier of the artist to add" } } }, "AddArtistSuccessResponse": { "type": "object", "required": [ "success" ], "properties": { "success": { "type": "boolean", "enum": [ true ], "description": "Indicates the artist was successfully added" } } }, "AccountErrorResponse": { "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" } } } } } } ``` # Create Subscription Session Source: https://recoupable.dev/docs/api-reference/subscriptions/sessions-create Create a checkout session to start a subscription for the authenticated account. Returns a hosted checkout URL that the client should redirect to. `plan` picks the price: `pro` (default) is pre-configured with a 30-day trial period; `starter` is charged at checkout with no trial. ## POST /api/subscriptions/sessions Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication The supplied specification does not declare an OpenAPI security object for this operation. Its declared headers and parameters still apply. Consult the authentication guide and the full specification before calling it. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/subscriptions/sessions": { "post": { "description": "Create a checkout session to start a subscription for the authenticated account. Returns a hosted checkout URL that the client should redirect to. `plan` picks the price: `pro` (default) is pre-configured with a 30-day trial period; `starter` is charged at checkout with no trial.", "requestBody": { "description": "Session creation parameters", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateSubscriptionSessionRequest" } } } }, "responses": { "200": { "description": "Checkout session created successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateSubscriptionSessionResponse" } } } }, "400": { "description": "Bad request - missing or invalid parameters, or `plan` is `starter` while the Starter price is not configured (`error` is literally `starter_unavailable`).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SubscriptionSessionErrorResponse" }, "example": { "error": "starter_unavailable" } } } }, "401": { "description": "Unauthorized - an auth header was supplied but is invalid (omit it entirely for an anonymous session)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SubscriptionSessionErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SubscriptionSessionErrorResponse" } } } } } } } }, "components": { "schemas": { "CreateSubscriptionSessionRequest": { "type": "object", "required": [ "successUrl" ], "properties": { "plan": { "type": "string", "enum": [ "starter", "pro" ], "description": "The plan to buy. Defaults to `pro` when omitted (back-compat for authenticated callers). `pro` carries the 30-day trial; `starter` charges on completion.", "example": "pro" }, "successUrl": { "type": "string", "format": "uri", "description": "Where Stripe sends the buyer after payment. For anonymous checkout include the literal `{CHECKOUT_SESSION_ID}` placeholder so the client can call Claim Subscription if the sign-in email differs.", "example": "https://chat.recoupable.dev?checkout=success&session_id={CHECKOUT_SESSION_ID}" }, "cancelUrl": { "type": "string", "format": "uri", "description": "Where Stripe sends the buyer if they leave Checkout without paying.", "example": "https://recoupable.dev/pricing" } } }, "CreateSubscriptionSessionResponse": { "type": "object", "required": [ "id", "url" ], "properties": { "id": { "type": "string", "description": "The checkout session ID.", "example": "cs_test_a1b2c3d4e5f6g7h8i9j0" }, "url": { "type": "string", "format": "uri", "description": "The hosted checkout URL. Redirect to this URL to complete Checkout.", "example": "https://checkout.stripe.com/pay/cs_test_a1b2c3d4e5f6g7h8i9j0" } } }, "SubscriptionSessionErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "description": "Human-readable error message.", "example": "successUrl is required" } } } } } } ``` # Claim Subscription Source: https://recoupable.dev/docs/api-reference/subscriptions/claim-create Attach a subscription bought through [Create Subscription Session](/docs/api-reference/subscriptions/sessions-create) (anonymous path) to the authenticated account. Needed only when the signed-in email differs from the email typed into Stripe Checkout; when they match, the webhook already linked the subscription and this call is a no-op that returns 200. ## POST /api/subscriptions/claim Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/subscriptions/claim": { "post": { "description": "Attach a subscription bought through [Create Subscription Session](/docs/api-reference/subscriptions/sessions-create) (anonymous path) to the authenticated account. Needed only when the signed-in email differs from the email typed into Stripe Checkout; when they match, the webhook already linked the subscription and this call is a no-op that returns 200.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "requestBody": { "description": "The Checkout session to claim", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ClaimSubscriptionRequest" } } } }, "responses": { "200": { "description": "Subscription attached to the authenticated account (or already was)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ClaimSubscriptionResponse" } } } }, "400": { "description": "Bad request - missing `session_id`, or the session has no subscription", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ClaimSubscriptionErrorResponse" } } } }, "401": { "description": "Unauthorized - invalid or missing authentication", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ClaimSubscriptionErrorResponse" } } } }, "404": { "description": "Unknown Checkout session", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ClaimSubscriptionErrorResponse" }, "example": { "status": "error", "error": "session_not_found" } } } }, "409": { "description": "The subscription belongs to another account that has signed in", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ClaimSubscriptionErrorResponse" }, "example": { "status": "error", "error": "already_claimed" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ClaimSubscriptionErrorResponse" } } } } } } } }, "components": { "schemas": { "ClaimSubscriptionRequest": { "type": "object", "required": [ "session_id" ], "properties": { "session_id": { "type": "string", "description": "The Stripe Checkout session id from the `session_id` query parameter on the success URL.", "example": "cs_live_a1b2c3d4e5f6g7h8i9j0" } } }, "ClaimSubscriptionResponse": { "type": "object", "required": [ "status", "subscription_id", "plan" ], "properties": { "status": { "type": "string", "enum": [ "success" ] }, "subscription_id": { "type": "string", "description": "The Stripe subscription now attached to the authenticated account.", "example": "sub_1Rz0abc123" }, "plan": { "type": "string", "enum": [ "starter", "pro" ], "description": "The plan the subscription is on, resolved from its Stripe price.", "example": "pro" } } }, "ClaimSubscriptionErrorResponse": { "type": "object", "required": [ "status", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ] }, "error": { "type": "string", "description": "Machine-readable code (`session_not_found`, `already_claimed`, `no_subscription`) or a validation message.", "example": "already_claimed" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Get Account Subscription Source: https://recoupable.dev/docs/api-reference/accounts/subscription-get Retrieve the subscription that confers an account's plan, directly or via an organization: `isPro`, `status`, `plan`, `source`, plus the billing fields `name`, `amountCents`, `currency`, `interval`, `collectionMethod` and `currentPeriodEnd`. Everything but `isPro` and `status` is null when `status` is `none`. `id` may be the authenticated account or an organization the caller belongs to. ## GET /api/accounts/{id}/subscription Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/accounts/{id}/subscription": { "get": { "description": "Retrieve the subscription that confers an account's plan, directly or via an organization: `isPro`, `status`, `plan`, `source`, plus the billing fields `name`, `amountCents`, `currency`, `interval`, `collectionMethod` and `currentPeriodEnd`. Everything but `isPro` and `status` is null when `status` is `none`. `id` may be the authenticated account or an organization the caller belongs to.", "parameters": [ { "name": "id", "in": "path", "description": "The unique identifier (UUID) of the account. Must be the authenticated account or another accessible via organization membership.", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "responses": { "200": { "description": "Subscription retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SubscriptionResponse" } } } }, "401": { "description": "Unauthorized - invalid or missing authentication", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SubscriptionErrorResponse" }, "example": { "error": "Unauthorized" } } } }, "403": { "description": "Forbidden - account not accessible to the authenticated account", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SubscriptionErrorResponse" } } } }, "404": { "description": "Account not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SubscriptionErrorResponse" } } } } } } } }, "components": { "schemas": { "SubscriptionResponse": { "type": "object", "required": [ "isPro", "status", "plan", "source", "name", "amountCents", "currency", "interval", "collectionMethod", "currentPeriodEnd" ], "properties": { "isPro": { "type": "boolean", "description": "True when the account has an active paid subscription, directly or via an organization." }, "status": { "type": "string", "enum": [ "active", "trialing", "canceled", "past_due", "none" ], "description": "Lifecycle state of the subscription. `none` when no subscription exists.", "example": "active" }, "plan": { "type": "string", "nullable": true, "description": "Plan identifier (e.g. `pro`). Null when no subscription exists.", "example": "pro" }, "source": { "type": "string", "enum": [ "account", "organization" ], "nullable": true, "description": "Origin of the subscription. `account` for direct, `organization` when covered by an organization. Null when no subscription exists.", "example": "account" }, "name": { "type": "string", "nullable": true, "description": "Display name of the plan's Stripe price (nickname) or product. Null when no subscription exists.", "example": "Pro" }, "amountCents": { "type": "integer", "nullable": true, "description": "Recurring amount in the smallest currency unit (cents for USD). Null when no subscription exists.", "example": 9900 }, "currency": { "type": "string", "nullable": true, "description": "ISO 4217 currency code, lowercase.", "example": "usd" }, "interval": { "type": "string", "enum": [ "day", "week", "month", "year" ], "nullable": true, "description": "Billing interval of the recurring price.", "example": "month" }, "collectionMethod": { "type": "string", "enum": [ "charge_automatically", "send_invoice" ], "nullable": true, "description": "`charge_automatically` bills the card on file at renewal; `send_invoice` emails an invoice that is paid manually (enterprise plans). A saved card is never charged for a `send_invoice` plan.", "example": "charge_automatically" }, "currentPeriodEnd": { "type": "string", "format": "date-time", "nullable": true, "description": "End of the current billing period: the renewal date, or the next invoice date for invoiced plans.", "example": "2026-10-04T00:00:00Z" } }, "description": "Subscription that confers the account's plan, directly or via an organization. `plan`, `source`, and the six billing fields (`name`, `amountCents`, `currency`, `interval`, `collectionMethod`, `currentPeriodEnd`) are all null when `status` is `none`." }, "SubscriptionErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "description": "Human-readable error message.", "example": "Account not found" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Create Billing Portal Session Source: https://recoupable.dev/docs/api-reference/accounts/portal-create Create a Stripe Customer Portal session for an account. Returns a hosted URL where the customer can update the card on file, view invoices, or cancel a Starter or Pro subscription; the client should redirect the user to that URL. `id` may be the authenticated account or an organization the caller belongs to. Enterprise (invoiced) plans are managed with your Recoup contact and the app does not offer this portal for them, but the endpoint works for any account with a Stripe customer. ## POST /api/accounts/{id}/portal Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/accounts/{id}/portal": { "post": { "description": "Create a Stripe Customer Portal session for an account. Returns a hosted URL where the customer can update the card on file, view invoices, or cancel a Starter or Pro subscription; the client should redirect the user to that URL. `id` may be the authenticated account or an organization the caller belongs to. Enterprise (invoiced) plans are managed with your Recoup contact and the app does not offer this portal for them, but the endpoint works for any account with a Stripe customer.", "parameters": [ { "name": "id", "in": "path", "description": "The unique identifier (UUID) of the account. Must be the authenticated account or another accessible via organization membership.", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "requestBody": { "description": "Portal session parameters", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateSubscriptionPortalRequest" } } } }, "responses": { "200": { "description": "Portal session created successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateSubscriptionPortalResponse" } } } }, "400": { "description": "Bad request - invalid parameters, or the account has no Stripe customer yet", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SubscriptionPortalErrorResponse" }, "example": { "error": "No active subscription found" } } } }, "401": { "description": "Unauthorized - invalid or missing authentication", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SubscriptionPortalErrorResponse" }, "example": { "error": "Unauthorized" } } } }, "403": { "description": "Forbidden - the account is not the caller's and not an organization they belong to (an unknown id also returns 403, never 404)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SubscriptionPortalErrorResponse" } } } } } } } }, "components": { "schemas": { "CreateSubscriptionPortalRequest": { "type": "object", "required": [ "returnUrl" ], "description": "Request body for creating a billing portal session. The account is taken from the `id` path parameter and must be accessible to the authenticated caller.", "properties": { "returnUrl": { "type": "string", "format": "uri", "description": "The URL to redirect to when the customer leaves the portal.", "example": "https://chat.recoupable.com/settings/billing" } } }, "CreateSubscriptionPortalResponse": { "type": "object", "required": [ "id", "url" ], "properties": { "id": { "type": "string", "description": "The portal session ID.", "example": "portal_sess_a1b2c3d4" }, "url": { "type": "string", "format": "uri", "description": "The hosted portal URL. Redirect to this URL so the customer can manage their subscription.", "example": "https://billing.example.com/manage/portal_sess_a1b2c3d4" } } }, "SubscriptionPortalErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "description": "Human-readable error message.", "example": "returnUrl is required" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Get Default Payment Method Source: https://recoupable.dev/docs/api-reference/accounts/payment-method-get Retrieve the default payment method on file for an account. Returns `card: null` when no payment method has been saved yet — the top-up dialog uses this to decide whether to show a pre-charge confirmation (card present) or route to a checkout session to collect one (`card: null`). Cards are returned even when expired; callers should compare `exp_month` / `exp_year` against the current date and warn the customer, since an off-session charge against an expired card will decline. ## GET /api/accounts/{id}/payment-method Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/accounts/{id}/payment-method": { "get": { "description": "Retrieve the default payment method on file for an account. Returns `card: null` when no payment method has been saved yet — the top-up dialog uses this to decide whether to show a pre-charge confirmation (card present) or route to a checkout session to collect one (`card: null`). Cards are returned even when expired; callers should compare `exp_month` / `exp_year` against the current date and warn the customer, since an off-session charge against an expired card will decline.", "parameters": [ { "name": "id", "in": "path", "description": "The unique identifier (UUID) of the account. Must be the authenticated account or another accessible via organization membership.", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "responses": { "200": { "description": "Default payment method retrieved successfully (may be `null` if none on file).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountPaymentMethodResponse" } } } }, "401": { "description": "Unauthorized - invalid or missing authentication", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountPaymentMethodErrorResponse" }, "example": { "error": "Unauthorized" } } } }, "403": { "description": "Forbidden - the account is not the caller's and not an organization they belong to (an unknown id also returns 403, never 404)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountPaymentMethodErrorResponse" } } } } } } } }, "components": { "schemas": { "AccountPaymentMethodResponse": { "type": "object", "required": [ "account_id", "card" ], "properties": { "account_id": { "type": "string", "format": "uuid", "description": "The unique identifier of the account this payment method belongs to.", "example": "550e8400-e29b-41d4-a716-446655440000" }, "card": { "oneOf": [ { "$ref": "#/components/schemas/SavedCard" }, { "type": "null" } ], "description": "Default card on file. `null` when the account has never saved a payment method — the top-up flow will route through a checkout session in that case." } } }, "AccountPaymentMethodErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "description": "Human-readable error message.", "example": "Unauthorized" } } }, "SavedCard": { "type": "object", "required": [ "brand", "last4", "exp_month", "exp_year", "funding" ], "properties": { "brand": { "type": "string", "description": "Card brand. Common values: `visa`, `mastercard`, `amex`, `discover`, `diners`, `jcb`, `unionpay`, `unknown`.", "example": "visa" }, "last4": { "type": "string", "pattern": "^\\d{4}$", "description": "Last four digits of the card number. Safe to display in UI for identification.", "example": "4242" }, "exp_month": { "type": "integer", "minimum": 1, "maximum": 12, "description": "Card expiration month (1-12). Compare against the current month to detect expired cards.", "example": 12 }, "exp_year": { "type": "integer", "description": "Card expiration year (full 4-digit year). Compare against the current year to detect expired cards.", "example": 2026 }, "funding": { "type": "string", "description": "Funding type. Common values: `credit`, `debit`, `prepaid`, `unknown`.", "example": "credit" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Save a Payment Method Source: https://recoupable.dev/docs/api-reference/accounts/payment-method-create Create a $0 card-on-file checkout session for an account. Stripe `setup` mode saves a payment method without charging anything or starting a subscription; the saved card becomes the account's default and is what credit purchases and auto top-up charge. The account's Stripe customer is created (and tagged with the account id) if it does not exist yet. Returns a hosted checkout URL that the client should redirect to. `id` may be the authenticated account or an organization the caller belongs to. ## POST /api/accounts/{id}/payment-method Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/accounts/{id}/payment-method": { "post": { "description": "Create a $0 card-on-file checkout session for an account. Stripe `setup` mode saves a payment method without charging anything or starting a subscription; the saved card becomes the account's default and is what credit purchases and auto top-up charge. The account's Stripe customer is created (and tagged with the account id) if it does not exist yet. Returns a hosted checkout URL that the client should redirect to. `id` may be the authenticated account or an organization the caller belongs to.", "parameters": [ { "name": "id", "in": "path", "description": "The unique identifier (UUID) of the account. Must be the authenticated account or another accessible via organization membership.", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "requestBody": { "description": "Card-on-file session parameters", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateCardOnFileSessionRequest" } } } }, "responses": { "200": { "description": "Card-on-file session created successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateSubscriptionSessionResponse" } } } }, "400": { "description": "Bad request - missing or invalid parameters", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountPaymentMethodErrorResponse" }, "example": { "error": "successUrl must be a valid URL" } } } }, "401": { "description": "Unauthorized - invalid or missing authentication", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountPaymentMethodErrorResponse" }, "example": { "error": "Unauthorized" } } } }, "403": { "description": "Forbidden - the account is not the caller's and not an organization they belong to (an unknown id also returns 403, never 404)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountPaymentMethodErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountPaymentMethodErrorResponse" }, "example": { "error": "Internal server error" } } } } } } } }, "components": { "schemas": { "CreateCardOnFileSessionRequest": { "type": "object", "required": [ "successUrl" ], "properties": { "successUrl": { "type": "string", "format": "uri", "description": "The URL Stripe redirects to after the card is saved.", "example": "https://chat.recoupable.dev?card=saved" } } }, "CreateSubscriptionSessionResponse": { "type": "object", "required": [ "id", "url" ], "properties": { "id": { "type": "string", "description": "The checkout session ID.", "example": "cs_test_a1b2c3d4e5f6g7h8i9j0" }, "url": { "type": "string", "format": "uri", "description": "The hosted checkout URL. Redirect to this URL to complete Checkout.", "example": "https://checkout.stripe.com/pay/cs_test_a1b2c3d4e5f6g7h8i9j0" } } }, "AccountPaymentMethodErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "description": "Human-readable error message.", "example": "Unauthorized" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Remove the Payment Method Source: https://recoupable.dev/docs/api-reference/accounts/payment-method-delete Detach the default card on file from an account. After this call `GET /api/accounts/{id}/payment-method` returns `card: null`, credit purchases route through checkout again, and auto top-up (if enabled) is turned off because it has nothing to charge. Subscriptions collected by invoice are unaffected. `id` may be the authenticated account or an organization the caller belongs to. ## DELETE /api/accounts/{id}/payment-method Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/accounts/{id}/payment-method": { "delete": { "description": "Detach the default card on file from an account. After this call `GET /api/accounts/{id}/payment-method` returns `card: null`, credit purchases route through checkout again, and auto top-up (if enabled) is turned off because it has nothing to charge. Subscriptions collected by invoice are unaffected. `id` may be the authenticated account or an organization the caller belongs to.", "parameters": [ { "name": "id", "in": "path", "description": "The unique identifier (UUID) of the account. Must be the authenticated account or another accessible via organization membership.", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "responses": { "204": { "description": "Payment method detached; no body." }, "401": { "description": "Unauthorized - invalid or missing authentication", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountPaymentMethodErrorResponse" }, "example": { "error": "Unauthorized" } } } }, "403": { "description": "Forbidden - the account is not the caller's and not an organization they belong to (an unknown id also returns 403, never 404)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountPaymentMethodErrorResponse" } } } } } } } }, "components": { "schemas": { "AccountPaymentMethodErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "description": "Human-readable error message.", "example": "Unauthorized" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # List Account Payments Source: https://recoupable.dev/docs/api-reference/accounts/payments-get List the invoices paid or owed by an account, newest first: subscription renewals, credit purchases, and invoiced enterprise plans alike. Non-draft rows carry the hosted invoice URL for the receipt; `url` is null for drafts. Returns an empty list (not 404) when the account has no Stripe customer or no invoices yet. `id` may be the authenticated account or an organization the caller belongs to. ## GET /api/accounts/{id}/payments Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/accounts/{id}/payments": { "get": { "description": "List the invoices paid or owed by an account, newest first: subscription renewals, credit purchases, and invoiced enterprise plans alike. Non-draft rows carry the hosted invoice URL for the receipt; `url` is null for drafts. Returns an empty list (not 404) when the account has no Stripe customer or no invoices yet. `id` may be the authenticated account or an organization the caller belongs to.", "parameters": [ { "name": "id", "in": "path", "description": "The unique identifier (UUID) of the account. Must be the authenticated account or another accessible via organization membership.", "required": true, "schema": { "type": "string", "format": "uuid" } }, { "name": "limit", "in": "query", "description": "Maximum rows to return.", "required": false, "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 } }, { "name": "startingAfter", "in": "query", "description": "Cursor for the next page: the `id` of the last payment from the previous response.", "required": false, "schema": { "type": "string" }, "example": "in_1U5xj400JObOnOb5BE0CmxCt" } ], "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "responses": { "200": { "description": "Payments retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountPaymentsResponse" } } } }, "400": { "description": "Bad request - invalid `id`, `limit`, or `startingAfter`", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountPaymentsErrorResponse" }, "example": { "error": "limit must be between 1 and 100" } } } }, "401": { "description": "Unauthorized - invalid or missing authentication", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountPaymentsErrorResponse" }, "example": { "error": "Unauthorized" } } } }, "403": { "description": "Forbidden - the account is not the caller's and not an organization they belong to (an unknown id also returns 403, never 404)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountPaymentsErrorResponse" } } } } } } } }, "components": { "schemas": { "AccountPaymentsResponse": { "type": "object", "required": [ "account_id", "payments", "hasMore" ], "properties": { "account_id": { "type": "string", "format": "uuid", "description": "The account these payments belong to.", "example": "550e8400-e29b-41d4-a716-446655440000" }, "payments": { "type": "array", "items": { "$ref": "#/components/schemas/AccountPayment" }, "description": "Invoices, newest first. Empty when the account has no Stripe customer or no invoices." }, "hasMore": { "type": "boolean", "description": "True when another page exists; pass the last `id` as `startingAfter`.", "example": false } } }, "AccountPaymentsErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "description": "Human-readable error message.", "example": "Account not found" } } }, "AccountPayment": { "type": "object", "required": [ "id", "createdAt", "description", "amountCents", "currency", "status", "url" ], "properties": { "id": { "type": "string", "description": "Stripe invoice id; use as `startingAfter` to page.", "example": "in_1U5xj400JObOnOb5BE0CmxCt" }, "createdAt": { "type": "string", "format": "date-time", "description": "When the invoice was created.", "example": "2026-09-04T14:33:00Z" }, "description": { "type": "string", "description": "What was billed: the first line item's description, or the plan name.", "example": "Pro, monthly" }, "amountCents": { "type": "integer", "description": "Amount due in the smallest currency unit.", "example": 9900 }, "currency": { "type": "string", "description": "ISO 4217 currency code, lowercase.", "example": "usd" }, "status": { "type": "string", "enum": [ "draft", "open", "paid", "uncollectible", "void" ], "description": "Stripe invoice status.", "example": "paid" }, "url": { "type": "string", "format": "uri", "nullable": true, "description": "Hosted invoice page (receipt). Null for draft invoices.", "example": "https://invoice.stripe.com/i/acct_123/test_456" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Get Auto Top-up Settings Source: https://recoupable.dev/docs/api-reference/accounts/auto-top-up-get Read the auto top-up settings for an account. Auto top-up is opt-in: it is off for every account until the account turns it on and chooses both the amount to buy and the balance that triggers it. When it is on, the api charges the default card on file for `amountCents` the first time a credit deduction leaves the balance below `thresholdCents`, then grants the credits and emails a receipt. `id` may be the authenticated account or an organization the caller belongs to. ## GET /api/accounts/{id}/auto-top-up Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/accounts/{id}/auto-top-up": { "get": { "description": "Read the auto top-up settings for an account. Auto top-up is opt-in: it is off for every account until the account turns it on and chooses both the amount to buy and the balance that triggers it. When it is on, the api charges the default card on file for `amountCents` the first time a credit deduction leaves the balance below `thresholdCents`, then grants the credits and emails a receipt. `id` may be the authenticated account or an organization the caller belongs to.", "parameters": [ { "name": "id", "in": "path", "description": "The unique identifier (UUID) of the account. Must be the authenticated account or another accessible via organization membership.", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "responses": { "200": { "description": "Auto top-up settings (defaults when never configured).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AutoTopUpResponse" } } } }, "401": { "description": "Unauthorized - invalid or missing authentication", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AutoTopUpErrorResponse" }, "example": { "error": "Unauthorized" } } } }, "403": { "description": "Forbidden - the account is not the caller's and not an organization they belong to (an unknown id also returns 403, never 404)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AutoTopUpErrorResponse" } } } } } } } }, "components": { "schemas": { "AutoTopUpResponse": { "type": "object", "required": [ "account_id", "enabled", "amountCents", "thresholdCents", "lastRunAt", "lastError" ], "properties": { "account_id": { "type": "string", "format": "uuid", "example": "550e8400-e29b-41d4-a716-446655440000" }, "enabled": { "type": "boolean", "description": "Whether auto top-up is on. Off by default.", "example": true }, "amountCents": { "type": "integer", "nullable": true, "description": "Amount charged and granted per top-up, in cents. Null until set.", "example": 10000 }, "thresholdCents": { "type": "integer", "nullable": true, "description": "Balance, in cents, below which a top-up runs. Null until set.", "example": 100 }, "lastRunAt": { "type": "string", "format": "date-time", "nullable": true, "description": "When the last auto top-up was attempted. Null until the first run.", "example": "2026-09-04T15:05:00Z" }, "lastError": { "type": "string", "nullable": true, "description": "Stripe decline message from the attempt that turned auto top-up off. Null while healthy or once re-enabled.", "example": "Your card was declined." } } }, "AutoTopUpErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "description": "Human-readable error message.", "example": "Add a payment method before turning on auto top-up" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Update Auto Top-up Settings Source: https://recoupable.dev/docs/api-reference/accounts/auto-top-up-update Set the auto top-up settings for an account. All three fields are required on every call; there is no partial update. Turning it on requires a card on file (`GET /api/accounts/{id}/payment-method` returns a card), an amount between 5.00 and 1,000.00 USD, and a threshold below the amount. Turning it off keeps the last amount and threshold so the account can turn it back on without retyping them. Guardrails that apply once it is on: at most one top-up per account per 10 minutes; a card decline turns `enabled` back to `false`, records `lastError`, and emails the account instead of retrying; removing the card turns it off. `id` may be the authenticated account or an organization the caller belongs to. ## PUT /api/accounts/{id}/auto-top-up Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/accounts/{id}/auto-top-up": { "put": { "description": "Set the auto top-up settings for an account. All three fields are required on every call; there is no partial update. Turning it on requires a card on file (`GET /api/accounts/{id}/payment-method` returns a card), an amount between 5.00 and 1,000.00 USD, and a threshold below the amount. Turning it off keeps the last amount and threshold so the account can turn it back on without retyping them. Guardrails that apply once it is on: at most one top-up per account per 10 minutes; a card decline turns `enabled` back to `false`, records `lastError`, and emails the account instead of retrying; removing the card turns it off. `id` may be the authenticated account or an organization the caller belongs to.", "parameters": [ { "name": "id", "in": "path", "description": "The unique identifier (UUID) of the account. Must be the authenticated account or another accessible via organization membership.", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "requestBody": { "description": "Auto top-up settings", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdateAutoTopUpRequest" } } } }, "responses": { "200": { "description": "Settings saved.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AutoTopUpResponse" } } } }, "400": { "description": "Bad request - invalid body, `enabled: true` without a card on file, `amountCents` outside 500..100000, `thresholdCents` negative, or `thresholdCents` not below `amountCents`", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AutoTopUpErrorResponse" }, "example": { "error": "Add a payment method before turning on auto top-up" } } } }, "401": { "description": "Unauthorized - invalid or missing authentication", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AutoTopUpErrorResponse" }, "example": { "error": "Unauthorized" } } } }, "403": { "description": "Forbidden - the account is not the caller's and not an organization they belong to (an unknown id also returns 403, never 404)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AutoTopUpErrorResponse" } } } }, "404": { "description": "The account has no credits row and one could not be created for it", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AutoTopUpErrorResponse" } } } } } } } }, "components": { "schemas": { "UpdateAutoTopUpRequest": { "type": "object", "required": [ "enabled", "amountCents", "thresholdCents" ], "properties": { "enabled": { "type": "boolean", "example": true }, "amountCents": { "type": "integer", "minimum": 500, "maximum": 100000, "description": "Amount to charge and grant per top-up, in cents (5.00 to 1,000.00 USD).", "example": 10000 }, "thresholdCents": { "type": "integer", "minimum": 0, "description": "Balance, in cents, below which a top-up runs. Must be below `amountCents`.", "example": 100 } } }, "AutoTopUpResponse": { "type": "object", "required": [ "account_id", "enabled", "amountCents", "thresholdCents", "lastRunAt", "lastError" ], "properties": { "account_id": { "type": "string", "format": "uuid", "example": "550e8400-e29b-41d4-a716-446655440000" }, "enabled": { "type": "boolean", "description": "Whether auto top-up is on. Off by default.", "example": true }, "amountCents": { "type": "integer", "nullable": true, "description": "Amount charged and granted per top-up, in cents. Null until set.", "example": 10000 }, "thresholdCents": { "type": "integer", "nullable": true, "description": "Balance, in cents, below which a top-up runs. Null until set.", "example": 100 }, "lastRunAt": { "type": "string", "format": "date-time", "nullable": true, "description": "When the last auto top-up was attempted. Null until the first run.", "example": "2026-09-04T15:05:00Z" }, "lastError": { "type": "string", "nullable": true, "description": "Stripe decline message from the attempt that turned auto top-up off. Null while healthy or once re-enabled.", "example": "Your card was declined." } } }, "AutoTopUpErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "description": "Human-readable error message.", "example": "Add a payment method before turning on auto top-up" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Create Credits Top-Up Session Source: https://recoupable.dev/docs/api-reference/credits/sessions-create Top up credits on the authenticated account. ## POST /api/credits/sessions Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/credits/sessions": { "post": { "description": "Top up credits on the authenticated account.\n\n**Credits are integer micro-dollars (1,000,000 = \\$1.00, the same 6-decimal unit as USDC).** The customer is charged the USD value of `credits` plus a Stripe processing fee (US card pricing: 2.9% + \\$0.30) — e.g. `credits: 100000000` charges \\$103.30 total (\\$100.00 credits + \\$3.30 fee).\n\n**Two outcomes, distinguished by response shape:**\n\n- **Auto-charged** — if the account has a card on file (from a prior subscription or top-up), the card is charged immediately and the response is `{ paymentIntentId, creditsPurchased, totalCents }`. Credits land in the account's balance asynchronously via Stripe webhook (typically within seconds). No human interaction required.\n\n- **Checkout required** — if no card is on file, or the saved card requires 3-D Secure authentication, the response is `{ id, url }` with a hosted Stripe Checkout URL. Redirect to that URL; credits land on successful payment.\n\nClients should discriminate on the presence of `url` (Checkout) vs `paymentIntentId` (auto-charged). Cards entered through the Checkout fallback are saved for future top-ups, so a customer's second top-up typically auto-charges.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "requestBody": { "description": "Top-up parameters", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateCreditsSessionRequest" } } } }, "responses": { "200": { "description": "Top-up initiated successfully. Some fields are conditional on which path was taken — see each field's description.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreditsTopupResponse" } } } }, "400": { "description": "Bad request - missing or invalid parameters", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreditsSessionErrorResponse" } } } }, "401": { "description": "Unauthorized - invalid or missing authentication", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreditsSessionErrorResponse" } } } } } } } }, "components": { "schemas": { "CreateCreditsSessionRequest": { "type": "object", "required": [ "credits" ], "properties": { "successUrl": { "type": "string", "format": "uri", "description": "The URL to redirect the customer to after a successful Stripe Checkout payment. Only used when the response is a Checkout fallback (no card on file or 3-D Secure required) — ignored when the card on file is charged directly. Defaults to a generic Recoup success page if omitted.", "example": "https://chat.recoupable.com?credits=success" }, "credits": { "type": "integer", "minimum": 1, "description": "The number of credits to purchase, in integer micro-dollars (1,000,000 = \\$1.00), so the value in USD is `credits / 1000000`. Example: `2500000` is \\$2.50 worth of credits (the customer is also charged a Stripe processing fee on top).", "example": 250 }, "accountId": { "type": "string", "format": "uuid", "description": "UUID of the account to create the session for. Only applicable when the authenticated account has admin access to multiple accounts. If not provided, creates a session for the API key's own account.", "example": "123e4567-e89b-12d3-a456-426614174000" } } }, "CreditsTopupResponse": { "type": "object", "description": "Top-up response. Two paths share this shape — clients can discriminate on the presence of `url` (Checkout fallback) vs `paymentIntentId` (auto-charged).", "properties": { "paymentIntentId": { "type": "string", "description": "**Auto-charge path only.** The Stripe PaymentIntent ID for the off-session charge. Useful for reconciliation and support. Omitted on the Checkout fallback path.", "example": "pi_3O1a2b3c4d5e6f7g_h8i9j0kL" }, "creditsPurchased": { "type": "integer", "minimum": 1, "description": "**Auto-charge path only.** The number of credits purchased, matching the `credits` request field. These credits are added to the account's balance asynchronously via Stripe webhook (typically within seconds). Omitted on the Checkout fallback path.", "example": 250 }, "totalCents": { "type": "integer", "minimum": 1, "description": "**Auto-charge path only.** Total amount in cents charged to the saved card, equal to `creditsPurchased` plus the Stripe processing fee. Omitted on the Checkout fallback path.", "example": 289 }, "id": { "type": "string", "description": "**Checkout fallback only.** The Stripe Checkout Session ID. Omitted on the auto-charge path.", "example": "cs_test_a1b2c3d4e5f6g7h8i9j0" }, "url": { "type": "string", "format": "uri", "description": "**Checkout fallback only.** The hosted Stripe Checkout URL — redirect the customer here to enter card details and complete payment. Omitted on the auto-charge path.", "example": "https://checkout.stripe.com/pay/cs_test_a1b2c3d4e5f6g7h8i9j0" } } }, "CreditsSessionErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "description": "Human-readable error message.", "example": "credits must be a positive integer" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Get Account Credits Source: https://recoupable.dev/docs/api-reference/accounts/credits-get Retrieve the current credit balance for an account. Returns the remaining credits along with the plan-derived monthly total and used count, plus a flag indicating whether the account is on a pro plan (directly or via an organization). Credits refill monthly; the `timestamp` field reflects the last refill or balance update. ## GET /api/accounts/{id}/credits Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/accounts/{id}/credits": { "get": { "description": "Retrieve the current credit balance for an account. Returns the remaining credits along with the plan-derived monthly total and used count, plus a flag indicating whether the account is on a pro plan (directly or via an organization). Credits refill monthly; the `timestamp` field reflects the last refill or balance update.", "parameters": [ { "name": "id", "in": "path", "description": "The unique identifier (UUID) of the account. Must be the authenticated account or another accessible via organization membership.", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "responses": { "200": { "description": "Credit balance retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountCreditsResponse" } } } }, "401": { "description": "Unauthorized - invalid or missing authentication", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountCreditsErrorResponse" }, "example": { "error": "Unauthorized" } } } }, "403": { "description": "Forbidden - account not accessible to the authenticated account", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountCreditsErrorResponse" } } } } } } } }, "components": { "schemas": { "AccountCreditsResponse": { "type": "object", "required": [ "account_id", "remaining_credits", "total_credits", "used_credits", "is_pro", "timestamp" ], "properties": { "account_id": { "type": "string", "format": "uuid", "description": "The unique identifier of the account this balance belongs to.", "example": "550e8400-e29b-41d4-a716-446655440000" }, "remaining_credits": { "type": "integer", "minimum": 0, "description": "Credits still available in the current monthly period. Refills automatically once the period elapses.", "example": 87 }, "total_credits": { "type": "integer", "minimum": 0, "description": "Total credits granted per monthly period for the account's current plan. Free accounts and pro accounts receive different allotments.", "example": 100 }, "used_credits": { "type": "integer", "minimum": 0, "description": "Credits consumed in the current monthly period. Equal to `total_credits - remaining_credits`.", "example": 13 }, "is_pro": { "type": "boolean", "description": "True when the account is on a pro plan, directly via an account subscription or via an organization subscription.", "example": false }, "timestamp": { "type": "string", "format": "date-time", "nullable": true, "description": "ISO 8601 timestamp of the last balance update or monthly refill. Null when the account has never spent or refilled credits.", "example": "2026-05-01T12:34:56.000Z" } } }, "AccountCreditsErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "description": "Human-readable error message.", "example": "Account not found" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Get Account Usage Source: https://recoupable.dev/docs/api-reference/accounts/usage-get List the charges that consumed an account's credits: one line item per deduction from `usage_events`, newest first, over a period. Each item carries the amount as the raw ledger integer (`credits_deducted`, micro-dollars: 1,000,000 = $1.00) and the same amount formatted as a dollar string (`usd`), and the response carries the total for the whole period, not just the page. Access is the same as [`GET /api/accounts/{id}/credits`](/docs/api-reference/accounts/credits-get): the authenticated account itself, or another account reachable through organization membership. ## GET /api/accounts/{id}/usage Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/accounts/{id}/usage": { "get": { "description": "List the charges that consumed an account's credits: one line item per deduction from `usage_events`, newest first, over a period. Each item carries the amount as the raw ledger integer (`credits_deducted`, micro-dollars: 1,000,000 = $1.00) and the same amount formatted as a dollar string (`usd`), and the response carries the total for the whole period, not just the page. Access is the same as [`GET /api/accounts/{id}/credits`](/docs/api-reference/accounts/credits-get): the authenticated account itself, or another account reachable through organization membership.", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "The unique identifier (UUID) of the account. Must be the authenticated account or another accessible via organization membership.", "schema": { "type": "string", "format": "uuid" } }, { "name": "limit", "in": "query", "required": false, "description": "Maximum number of line items to return per page.", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 } }, { "name": "sort", "in": "query", "required": false, "description": "Order of the line items, both descending: `created_at` (newest first, the default) or `cost` (largest `credits_deducted` first, ties by newest).", "schema": { "type": "string", "enum": [ "created_at", "cost" ], "default": "created_at" } }, { "name": "cursor", "in": "query", "required": false, "description": "Opaque paging token: pass the `next_cursor` value from the previous page, with the same `sort`, `from` and `to`. With `sort=created_at` it is the last item's `created_at`; with `sort=cost` it encodes the last item's amount and id. Returns the items that follow it.", "schema": { "type": "string" } }, { "name": "from", "in": "query", "required": false, "description": "Start of the period (inclusive), ISO 8601. Defaults to the start of the current UTC month.", "schema": { "type": "string", "format": "date-time" } }, { "name": "to", "in": "query", "required": false, "description": "End of the period (exclusive), ISO 8601. Defaults to now.", "schema": { "type": "string", "format": "date-time" } } ], "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "responses": { "200": { "description": "Usage line items retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountUsageResponse" } } } }, "400": { "description": "Bad request - invalid query parameter (for example `limit` above 100 or a `cursor` that is not a timestamp)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountCreditsErrorResponse" }, "example": { "error": "limit must be between 1 and 100" } } } }, "401": { "description": "Unauthorized - invalid or missing authentication", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountCreditsErrorResponse" }, "example": { "error": "Unauthorized" } } } }, "403": { "description": "Forbidden - account not accessible to the authenticated account", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountCreditsErrorResponse" } } } } } } } }, "components": { "schemas": { "AccountUsageResponse": { "type": "object", "required": [ "account_id", "period", "total_credits_deducted", "total_usd", "events", "next_cursor" ], "properties": { "account_id": { "type": "string", "format": "uuid", "description": "The account whose charges are listed.", "example": "550e8400-e29b-41d4-a716-446655440000" }, "period": { "type": "object", "required": [ "from", "to" ], "description": "The period the totals cover.", "properties": { "from": { "type": "string", "format": "date-time", "example": "2026-08-01T00:00:00.000Z" }, "to": { "type": "string", "format": "date-time", "example": "2026-08-27T12:00:00.000Z" } } }, "total_credits_deducted": { "type": "integer", "description": "Sum of `credits_deducted` over every charge in the period, in micro-dollars. Covers the whole period, not only this page.", "example": 70000 }, "total_usd": { "type": "string", "description": "`total_credits_deducted` formatted as US dollars.", "example": "$0.07" }, "events": { "type": "array", "description": "Charges in the period in the requested `sort` order (newest first by default).", "items": { "$ref": "#/components/schemas/AccountUsageEvent" } }, "next_cursor": { "type": "string", "nullable": true, "description": "Opaque; pass as `cursor` (with the same `sort`, `from` and `to`) to fetch the next page. Null when the page was the last one in the period.", "example": "2026-08-27T11:56:58.000Z" }, "series_bucket": { "type": "string", "enum": [ "hour", "day", "week", "month" ], "description": "Granularity of `series`, derived from the span of the period: `hour` up to 2 days, `day` up to 90 days, `week` up to 12 months, `month` beyond. Present only on a first page (no `cursor`).", "example": "day" }, "series": { "type": "array", "description": "Spend over the period, one entry per `series_bucket` that had at least one charge, ascending by `start`, in UTC. The sum of `credits_deducted` across the entries equals `total_credits_deducted`. Present only on a first page (no `cursor`); omitted on cursor pages so paging never recomputes it.", "items": { "type": "object", "required": [ "start", "credits_deducted", "usd", "events" ], "properties": { "start": { "type": "string", "format": "date-time", "description": "Start of the bucket, UTC.", "example": "2026-08-12T00:00:00.000Z" }, "credits_deducted": { "type": "integer", "description": "Charges in the bucket, in micro-dollars.", "example": 221090000 }, "usd": { "type": "string", "description": "`credits_deducted` formatted as US dollars.", "example": "$221.09" }, "events": { "type": "integer", "description": "Number of charges in the bucket.", "example": 2462 } } }, "example": [ { "start": "2026-08-11T00:00:00.000Z", "credits_deducted": 56080000, "usd": "$56.08", "events": 727 }, { "start": "2026-08-12T00:00:00.000Z", "credits_deducted": 221090000, "usd": "$221.09", "events": 2462 }, { "start": "2026-08-13T00:00:00.000Z", "credits_deducted": 440930000, "usd": "$440.93", "events": 5479 } ] } } }, "AccountCreditsErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string", "description": "Human-readable error message.", "example": "Account not found" } } }, "AccountUsageEvent": { "type": "object", "required": [ "id", "created_at", "source", "agent_type", "provider", "model_id", "input_tokens", "cached_input_tokens", "output_tokens", "tool_call_count", "credits_deducted", "usd", "resource_url" ], "properties": { "id": { "type": "string", "description": "Identifier of the `usage_events` row.", "example": "3AANn3Ij9uF-zZIlW_zlP" }, "created_at": { "type": "string", "format": "date-time", "description": "When the charge was recorded.", "example": "2026-08-27T11:56:58.000Z" }, "source": { "type": "string", "description": "Which surface originated the charge: `api` for a request to the API, `web` for the chat app.", "example": "api" }, "agent_type": { "type": "string", "description": "`main` for a top-level request or any non-agent charge; `subagent` for a nested task step.", "example": "main" }, "provider": { "type": "string", "nullable": true, "description": "Provider slug when the charge is a model or generation call (for example `anthropic`, `fal`). Null for research and other fixed-price endpoints.", "example": "fal" }, "model_id": { "type": "string", "nullable": true, "description": "The model for LLM-backed charges (a chat turn, a song generation), otherwise the API endpoint that was billed, as `METHOD /route/pattern` (for example `POST /api/artist/socials/scrape`). Null on rows written before 2026-08-27.", "example": "POST /api/artist/socials/scrape" }, "input_tokens": { "type": "integer", "description": "Input tokens for the call, including cached. 0 for non-LLM charges.", "example": 0 }, "cached_input_tokens": { "type": "integer", "description": "Subset of `input_tokens` served from the provider cache.", "example": 0 }, "output_tokens": { "type": "integer", "description": "Output tokens for the call. 0 for non-LLM charges.", "example": 0 }, "tool_call_count": { "type": "integer", "description": "Number of tool calls in this step.", "example": 0 }, "credits_deducted": { "type": "integer", "description": "Amount charged, as the raw ledger integer in micro-dollars (1,000,000 = $1.00). Divide by 1,000,000 for dollars.", "example": 20000 }, "usd": { "type": "string", "description": "`credits_deducted` formatted as US dollars with two decimals.", "example": "$0.02" }, "resource_url": { "type": "string", "nullable": true, "description": "App-relative path of what produced the charge, when there is something to open: `/chat?roomId=` for a chat turn, `/music/` for a song, `/tasks//runs/` for a scheduled task run. Null for plain API calls and for rows written before the field existed.", "example": "/music/0c35429f-deb8-48f0-b0f2-fd5145de2583" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Update Pulse Source: https://recoupable.dev/docs/api-reference/pulses/update Update the pulse settings for an account. Use this to enable or disable the pulse. Returns an array of pulses for consistency with the GET endpoint. ## PATCH /api/pulses Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication The supplied specification does not declare an OpenAPI security object for this operation. Its declared headers and parameters still apply. Consult the authentication guide and the full specification before calling it. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/pulses": { "patch": { "description": "Update the pulse settings for an account. Use this to enable or disable the pulse. Returns an array of pulses for consistency with the GET endpoint.", "requestBody": { "description": "Pulse fields to update", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdatePulseRequest" } } } }, "responses": { "200": { "description": "Pulse updated successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PulsesResponse" } } } }, "400": { "description": "Bad request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "Unauthorized - invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "UpdatePulseRequest": { "type": "object", "required": [ "active" ], "properties": { "active": { "type": "boolean", "description": "Whether to enable or disable the pulse", "example": true }, "account_id": { "type": "string", "format": "uuid", "description": "UUID of the account to update the pulse for. Only applicable when the authenticated account has access to multiple accounts via organization membership. If not provided, updates the pulse for the API key's own account." } } }, "PulsesResponse": { "type": "object", "required": [ "status", "pulses" ], "properties": { "status": { "type": "string", "enum": [ "success", "error" ], "description": "Status of the request" }, "pulses": { "type": "array", "items": { "$ref": "#/components/schemas/Pulse" }, "description": "Array of pulse objects. Contains pulses for the authenticated account. If the account has access to organizations, all organization accounts are included." }, "error": { "type": "string", "description": "Error message (only present if status is error)" } } }, "Error": { "required": [ "error", "message" ], "type": "object", "properties": { "error": { "type": "integer", "format": "int32" }, "message": { "type": "string" } } }, "Pulse": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid", "description": "Unique identifier for the pulse" }, "account_id": { "type": "string", "format": "uuid", "description": "Unique identifier for the associated account" }, "active": { "type": "boolean", "description": "Whether the pulse is active (defaults to true)" } } } } } } ``` # List Pulses Source: https://recoupable.dev/docs/api-reference/pulses/list Retrieve pulse information for the authenticated account. If the account has access to organizations, pass account_id to filter to a specific account within those organizations. ## GET /api/pulses Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication The supplied specification does not declare an OpenAPI security object for this operation. Its declared headers and parameters still apply. Consult the authentication guide and the full specification before calling it. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/pulses": { "get": { "description": "Retrieve pulse information for the authenticated account. If the account has access to organizations, pass account_id to filter to a specific account within those organizations.", "parameters": [ { "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" } }, { "name": "active", "in": "query", "description": "Filter by active status. Set to 'true' to return only active pulses, 'false' for inactive pulses. If not provided, returns all pulses regardless of active status.", "required": false, "schema": { "type": "string", "enum": [ "true", "false" ] } } ], "responses": { "200": { "description": "Pulses retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PulsesResponse" } } } }, "400": { "description": "Bad request - invalid query 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" } } } }, "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" } } } } } } } }, "components": { "schemas": { "PulsesResponse": { "type": "object", "required": [ "status", "pulses" ], "properties": { "status": { "type": "string", "enum": [ "success", "error" ], "description": "Status of the request" }, "pulses": { "type": "array", "items": { "$ref": "#/components/schemas/Pulse" }, "description": "Array of pulse objects. Contains pulses for the authenticated account. If the account has access to organizations, all organization accounts are included." }, "error": { "type": "string", "description": "Error message (only present if status is error)" } } }, "Error": { "required": [ "error", "message" ], "type": "object", "properties": { "error": { "type": "integer", "format": "int32" }, "message": { "type": "string" } } }, "Pulse": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid", "description": "Unique identifier for the pulse" }, "account_id": { "type": "string", "format": "uuid", "description": "Unique identifier for the associated account" }, "active": { "type": "boolean", "description": "Whether the pulse is active (defaults to true)" } } } } } } ``` # Get Organizations Source: https://recoupable.dev/docs/api-reference/organizations/list Retrieve all organizations that the authenticated account belongs to. Pass account_id to retrieve organizations for a specific account the API key has access to. ## GET /api/organizations Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/organizations": { "get": { "description": "Retrieve all organizations that the authenticated account belongs to. Pass account_id to retrieve organizations for a specific account the API key has access to.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "parameters": [ { "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": "Organizations retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GetOrganizationsResponse" } } } }, "400": { "description": "Bad request - invalid query parameters", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OrganizationsErrorResponse" } } } }, "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" } } } } } } } }, "components": { "schemas": { "GetOrganizationsResponse": { "type": "object", "required": [ "status", "organizations" ], "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the request" }, "organizations": { "type": "array", "items": { "$ref": "#/components/schemas/Organization" }, "description": "List of organizations the account belongs to" } } }, "OrganizationsErrorResponse": { "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" } } }, "Error": { "required": [ "error", "message" ], "type": "object", "properties": { "error": { "type": "integer", "format": "int32" }, "message": { "type": "string" } } }, "Organization": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid", "description": "UUID of the membership record" }, "organization_id": { "type": "string", "format": "uuid", "description": "UUID of the organization account" }, "organization_name": { "type": "string", "description": "Display name of the organization" }, "organization_image": { "type": "string", "nullable": true, "description": "Organization logo/image URL" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Create Organization Source: https://recoupable.dev/docs/api-reference/organizations/create Create a new organization. The creator is automatically added as a member of the organization. ## POST /api/organizations Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/organizations": { "post": { "description": "Create a new organization. The creator is automatically added as a member of the organization.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "requestBody": { "description": "Organization creation parameters", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateOrganizationRequest" } } } }, "responses": { "200": { "description": "Organization created successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateOrganizationResponse" } } } }, "400": { "description": "Bad request - missing required parameters", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OrganizationsErrorResponse" } } } } } } } }, "components": { "schemas": { "CreateOrganizationRequest": { "type": "object", "required": [ "name", "accountId" ], "properties": { "name": { "type": "string", "description": "The name of the organization to create", "example": "My New Label" }, "accountId": { "type": "string", "format": "uuid", "description": "The account ID of the creator", "example": "123e4567-e89b-12d3-a456-426614174000" } } }, "CreateOrganizationResponse": { "type": "object", "required": [ "status", "organization" ], "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the request" }, "organization": { "$ref": "#/components/schemas/CreatedOrganization", "description": "The created organization" } } }, "OrganizationsErrorResponse": { "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" } } }, "CreatedOrganization": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid", "description": "UUID of the new organization account" }, "name": { "type": "string", "description": "Name of the organization" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Add Artist to Organization Source: https://recoupable.dev/docs/api-reference/organizations/add-artist Add an artist to an organization. This allows organization members to access and manage the artist. This endpoint is idempotent - calling it multiple times with the same artistId and organizationId will not create duplicate records. ## POST /api/organizations/artists Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/organizations/artists": { "post": { "description": "Add an artist to an organization. This allows organization members to access and manage the artist. This endpoint is idempotent - calling it multiple times with the same artistId and organizationId will not create duplicate records.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "requestBody": { "description": "Artist-organization association parameters", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AddArtistToOrganizationRequest" } } } }, "responses": { "200": { "description": "Artist added to organization successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AddArtistToOrganizationResponse" } } } }, "400": { "description": "Bad request - missing required parameters", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OrganizationsErrorResponse" } } } }, "401": { "description": "Missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OrganizationsErrorResponse" } } } }, "403": { "description": "Caller is not a member of the organization", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OrganizationsErrorResponse" } } } } } } } }, "components": { "schemas": { "AddArtistToOrganizationRequest": { "type": "object", "required": [ "artistId", "organizationId" ], "properties": { "artistId": { "type": "string", "format": "uuid", "description": "The account ID of the artist to add", "example": "artist-account-uuid" }, "organizationId": { "type": "string", "format": "uuid", "description": "The account ID of the organization", "example": "org-account-uuid" } } }, "AddArtistToOrganizationResponse": { "type": "object", "required": [ "status", "id" ], "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the request" }, "id": { "type": "string", "format": "uuid", "description": "UUID of the created artist-organization link" } } }, "OrganizationsErrorResponse": { "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" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Add Member to Organization Source: https://recoupable.dev/docs/api-reference/organizations/add-member Add a member to an organization. This endpoint is idempotent - Adding an existing member returns the existing membership. ## POST /api/organizations/members Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/organizations/members": { "post": { "description": "Add a member to an organization. This endpoint is idempotent - Adding an existing member returns the existing membership.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "requestBody": { "description": "Member-organization association parameters", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AddOrganizationMemberRequest" } } } }, "responses": { "200": { "description": "Member added to organization successfully (or already a member)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AddOrganizationMemberResponse" } } } }, "400": { "description": "Bad request - invalid or missing parameters", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } } }, "401": { "description": "Unauthorized - missing or invalid authentication", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } } }, "403": { "description": "Forbidden - caller is not a member of the organization", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } } } } } } }, "components": { "schemas": { "AddOrganizationMemberRequest": { "type": "object", "required": [ "organizationId" ], "properties": { "organizationId": { "type": "string", "format": "uuid", "description": "The account ID of the organization" }, "accountId": { "type": "string", "format": "uuid", "description": "The account ID of the member to add. Provide exactly one of accountId or email." }, "email": { "type": "string", "format": "email", "description": "Email of the member to add. The account is created if it does not exist yet. Provide exactly one of accountId or email." } } }, "AddOrganizationMemberResponse": { "type": "object", "required": [ "status", "id", "account_id" ], "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the request" }, "id": { "type": "string", "format": "uuid", "description": "UUID of the membership record" }, "account_id": { "type": "string", "format": "uuid", "description": "The account ID of the member (resolved from email when email was provided)" } } }, "ErrorEnvelope": { "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" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Remove Member from Organization Source: https://recoupable.dev/docs/api-reference/organizations/remove-member Remove a member from an organization. This endpoint is idempotent - Removing an account that is not a member succeeds without error. ## DELETE /api/organizations/members Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/organizations/members": { "delete": { "description": "Remove a member from an organization. This endpoint is idempotent - Removing an account that is not a member succeeds without error.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "parameters": [ { "name": "organization_id", "in": "query", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "The account ID of the organization" }, { "name": "account_id", "in": "query", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "The account ID of the member to remove" } ], "responses": { "200": { "description": "Member removed from organization (or was not a member)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RemoveOrganizationMemberResponse" } } } }, "400": { "description": "Bad request - invalid or missing parameters", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } } }, "401": { "description": "Unauthorized - missing or invalid authentication", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } } }, "403": { "description": "Forbidden - caller is not a member of the organization", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } } } } } } }, "components": { "schemas": { "RemoveOrganizationMemberResponse": { "type": "object", "required": [ "status" ], "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the request" } } }, "ErrorEnvelope": { "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" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # List Organization Domains Source: https://recoupable.dev/docs/api-reference/organizations/list-domains List the email domains mapped to an organization. Accounts that sign up with an email at a mapped domain automatically join the organization. ## GET /api/organizations/domains Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/organizations/domains": { "get": { "description": "List the email domains mapped to an organization. Accounts that sign up with an email at a mapped domain automatically join the organization.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "parameters": [ { "name": "organization_id", "in": "query", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "The account ID of the organization" } ], "responses": { "200": { "description": "Domains retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GetOrganizationDomainsResponse" } } } }, "400": { "description": "Bad request - invalid or missing parameters", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } } }, "401": { "description": "Unauthorized - missing or invalid authentication", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } } }, "403": { "description": "Forbidden - caller is not a member of the organization", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } } } } } } }, "components": { "schemas": { "GetOrganizationDomainsResponse": { "type": "object", "required": [ "status", "domains" ], "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the request" }, "domains": { "type": "array", "items": { "$ref": "#/components/schemas/OrganizationDomain" }, "description": "Domains mapped to the organization" } } }, "ErrorEnvelope": { "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" } } }, "OrganizationDomain": { "type": "object", "required": [ "id", "domain", "organization_id" ], "properties": { "id": { "type": "string", "format": "uuid", "description": "UUID of the domain mapping record" }, "domain": { "type": "string", "description": "The email domain (lowercase, no @)", "example": "seekermusic.com" }, "organization_id": { "type": "string", "format": "uuid", "description": "The account ID of the organization" }, "created_at": { "type": "string", "format": "date-time", "description": "When the mapping was created" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Add Organization Domain Source: https://recoupable.dev/docs/api-reference/organizations/add-domain Map an email domain to an organization for automatic membership. After mapping, any account that signs up (or signs in) with an email at this domain is automatically added to the organization. A domain can belong to at most one organization. This endpoint is idempotent - re-adding the same domain to the same organization returns the existing mapping. ## POST /api/organizations/domains Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/organizations/domains": { "post": { "description": "Map an email domain to an organization for automatic membership. After mapping, any account that signs up (or signs in) with an email at this domain is automatically added to the organization. A domain can belong to at most one organization. This endpoint is idempotent - re-adding the same domain to the same organization returns the existing mapping.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "requestBody": { "description": "Domain-organization mapping parameters", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AddOrganizationDomainRequest" } } } }, "responses": { "200": { "description": "Domain mapped to organization successfully (or already mapped)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AddOrganizationDomainResponse" } } } }, "400": { "description": "Bad request - invalid or missing parameters", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } } }, "401": { "description": "Unauthorized - missing or invalid authentication", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } } }, "403": { "description": "Forbidden - caller is not a member of the organization", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } } }, "409": { "description": "Conflict - domain is already mapped to a different organization", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } } } } } } }, "components": { "schemas": { "AddOrganizationDomainRequest": { "type": "object", "required": [ "organizationId", "domain" ], "properties": { "organizationId": { "type": "string", "format": "uuid", "description": "The account ID of the organization" }, "domain": { "type": "string", "description": "The email domain to map (lowercase, no @)", "example": "seekermusic.com" } } }, "AddOrganizationDomainResponse": { "type": "object", "required": [ "status", "id", "domain", "organization_id" ], "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the request" }, "id": { "type": "string", "format": "uuid", "description": "UUID of the domain mapping record" }, "domain": { "type": "string", "description": "The mapped email domain", "example": "seekermusic.com" }, "organization_id": { "type": "string", "format": "uuid", "description": "The account ID of the organization" } } }, "ErrorEnvelope": { "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" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Remove Organization Domain Source: https://recoupable.dev/docs/api-reference/organizations/remove-domain Remove an email domain mapping from an organization. New signups at this domain will no longer auto-join the organization; existing members are not affected. This endpoint is idempotent. ## DELETE /api/organizations/domains Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/organizations/domains": { "delete": { "description": "Remove an email domain mapping from an organization. New signups at this domain will no longer auto-join the organization; existing members are not affected. This endpoint is idempotent.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "parameters": [ { "name": "organization_id", "in": "query", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "The account ID of the organization" }, { "name": "domain", "in": "query", "required": true, "schema": { "type": "string" }, "description": "The email domain to unmap (e.g. \"seekermusic.com\")" } ], "responses": { "200": { "description": "Domain mapping removed (or did not exist)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RemoveOrganizationDomainResponse" } } } }, "400": { "description": "Bad request - invalid or missing parameters", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } } }, "401": { "description": "Unauthorized - missing or invalid authentication", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } } }, "403": { "description": "Forbidden - caller is not a member of the organization", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } } } } } } }, "components": { "schemas": { "RemoveOrganizationDomainResponse": { "type": "object", "required": [ "status" ], "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the request" } } }, "ErrorEnvelope": { "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" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Check Admin Status Source: https://recoupable.dev/docs/api-reference/admins/check Check if the authenticated account is a Recoup admin. An account is considered an admin if it is a member of the Recoup organization. No input parameters required — authentication is performed via the x-api-key or Authorization header. ## GET /api/admins Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/admins": { "get": { "description": "Check if the authenticated account is a Recoup admin. An account is considered an admin if it is a member of the Recoup organization. No input parameters required — authentication is performed via the x-api-key or Authorization header.", "parameters": [], "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "responses": { "200": { "description": "Admin status retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CheckAdminResponse" } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } } } } } }, "components": { "schemas": { "CheckAdminResponse": { "type": "object", "required": [ "status", "isAdmin" ], "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the request" }, "isAdmin": { "type": "boolean", "description": "Whether the authenticated account is a Recoup admin" } } }, "AccountErrorResponse": { "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" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # List Account Sandboxes (Admin) Source: https://recoupable.dev/docs/api-reference/admins/sandboxes Returns a list of all accounts and their sandbox usage statistics. Each item includes the account email, total number of sandboxes created, and the timestamp of the most recently created sandbox. Requires the authenticated account to be a Recoup admin. Authentication via x-api-key or Authorization Bearer token. ## GET /api/admins/sandboxes Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/admins/sandboxes": { "get": { "description": "Returns a list of all accounts and their sandbox usage statistics. Each item includes the account email, total number of sandboxes created, and the timestamp of the most recently created sandbox. Requires the authenticated account to be a Recoup admin. Authentication via x-api-key or Authorization Bearer token.", "parameters": [], "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "responses": { "200": { "description": "Account sandbox statistics retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminSandboxesResponse" } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } }, "403": { "description": "Forbidden - authenticated account is not a Recoup admin", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } } } } } }, "components": { "schemas": { "AdminSandboxesResponse": { "type": "object", "required": [ "status", "accounts" ], "description": "Response containing per-account sandbox statistics for admin use", "properties": { "status": { "type": "string", "enum": [ "success", "error" ], "description": "Status of the request" }, "accounts": { "type": "array", "description": "List of accounts with their sandbox statistics, ordered by most recently active first", "items": { "$ref": "#/components/schemas/AccountSandboxRow" } } } }, "AccountErrorResponse": { "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" } } }, "AccountSandboxRow": { "type": "object", "required": [ "account_id", "total_sandboxes", "last_created_at" ], "properties": { "account_id": { "type": "string", "format": "uuid", "description": "The unique identifier of the account", "example": "04e3aba9-c130-4fb8-8b92-34e95d43e66b" }, "total_sandboxes": { "type": "integer", "description": "Total number of sandboxes created for this account", "example": 5 }, "last_created_at": { "type": "string", "format": "date-time", "description": "ISO 8601 timestamp of the most recently created sandbox for this account", "example": "2026-03-10T12:00:00Z" }, "account_email": { "type": "string", "nullable": true, "description": "The email address of the account. Null if the account has no email set.", "example": "alice@example.com" } }, "description": "" } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # List Org Repo Stats (Admin) Source: https://recoupable.dev/docs/api-reference/admins/sandboxes-orgs Returns commit statistics for each repository in the recoupable GitHub organization. Each item includes the repo name, URL, total commit count, latest 5 commit messages, earliest and latest commit timestamps, and the list of account repo URLs that include this org repo as a submodule. Requires the authenticated account to be a Recoup admin. Authentication via x-api-key or Authorization Bearer token. ## GET /api/admins/sandboxes/orgs Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/admins/sandboxes/orgs": { "get": { "description": "Returns commit statistics for each repository in the recoupable GitHub organization. Each item includes the repo name, URL, total commit count, latest 5 commit messages, earliest and latest commit timestamps, and the list of account repo URLs that include this org repo as a submodule. Requires the authenticated account to be a Recoup admin. Authentication via x-api-key or Authorization Bearer token.", "parameters": [], "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "responses": { "200": { "description": "Org repo statistics retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminSandboxOrgsResponse" } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } }, "403": { "description": "Forbidden - authenticated account is not a Recoup admin", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } } } } } }, "components": { "schemas": { "AdminSandboxOrgsResponse": { "type": "object", "required": [ "status", "repos" ], "description": "Response containing commit statistics for all org repositories", "properties": { "status": { "type": "string", "enum": [ "success", "error" ], "description": "Status of the request" }, "repos": { "type": "array", "description": "List of org repos with their commit statistics, ordered by total_commits descending", "items": { "$ref": "#/components/schemas/OrgRepoRow" } } } }, "AccountErrorResponse": { "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" } } }, "OrgRepoRow": { "type": "object", "required": [ "repo_name", "repo_url", "total_commits", "latest_commit_messages", "earliest_committed_at", "latest_committed_at", "account_repos" ], "description": "Commit statistics for a single GitHub org repository", "properties": { "repo_name": { "type": "string", "description": "Repository name", "example": "chat" }, "repo_url": { "type": "string", "description": "Full GitHub HTML URL of the repository", "example": "https://github.com/recoupable/chat" }, "total_commits": { "type": "integer", "description": "Total number of commits in the repository", "example": 5696 }, "latest_commit_messages": { "type": "array", "description": "Messages from the 5 most recent commits", "items": { "type": "string" }, "example": [ "Merge test into main", "fix: duration stuck at 0ms for in-progress tasks" ] }, "earliest_committed_at": { "type": "string", "format": "date-time", "description": "ISO 8601 timestamp of the earliest (first) commit", "example": "2024-09-27T17:16:01Z" }, "latest_committed_at": { "type": "string", "format": "date-time", "description": "ISO 8601 timestamp of the most recent commit", "example": "2026-03-10T19:57:37Z" }, "account_repos": { "type": "array", "items": { "type": "object", "properties": { "account_id": { "type": "string" }, "email": { "type": "string", "nullable": true }, "repo_url": { "type": "string" } }, "required": [ "account_id", "repo_url" ] }, "description": "List of accounts using this org repo as a submodule, with account_id, email, and repo_url" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # List Account Emails (Admin) Source: https://recoupable.dev/docs/api-reference/admins/emails Returns Resend emails including HTML content. Provide either account_id (returns all emails for an account) or email_id (returns a single email by Resend ID). Requires the authenticated account to be a Recoup admin. Authentication via x-api-key or Authorization Bearer token. Email response shape matches the [Resend Retrieve Email API](https://resend.com/docs/api-reference/emails/retrieve-email). ## GET /api/admins/emails Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/admins/emails": { "get": { "description": "Returns Resend emails including HTML content. Provide either account_id (returns all emails for an account) or email_id (returns a single email by Resend ID). Requires the authenticated account to be a Recoup admin. Authentication via x-api-key or Authorization Bearer token. Email response shape matches the [Resend Retrieve Email API](https://resend.com/docs/api-reference/emails/retrieve-email).", "parameters": [ { "name": "account_id", "in": "query", "description": "The account ID to fetch all emails for. Required if email_id is not provided.", "required": false, "schema": { "type": "string", "format": "uuid" } }, { "name": "email_id", "in": "query", "description": "A Resend email ID to fetch a single email. Required if account_id is not provided.", "required": false, "schema": { "type": "string" } } ], "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "responses": { "200": { "description": "Emails retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminEmailsResponse" } } } }, "400": { "description": "Bad request - must provide either account_id or email_id", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } }, "403": { "description": "Forbidden - authenticated account is not a Recoup admin", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } } } } } }, "components": { "schemas": { "AdminEmailsResponse": { "type": "object", "required": [ "status", "emails" ], "description": "Response containing Resend emails sent for an account", "properties": { "status": { "type": "string", "enum": [ "success", "error" ], "description": "Status of the request" }, "emails": { "type": "array", "description": "List of emails sent for the account, ordered by created_at descending", "items": { "$ref": "#/components/schemas/PulseEmailRow" } } } }, "Error": { "required": [ "error", "message" ], "type": "object", "properties": { "error": { "type": "integer", "format": "int32" }, "message": { "type": "string" } } }, "AccountErrorResponse": { "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" } } }, "PulseEmailRow": { "type": "object", "required": [ "id", "from", "to", "subject", "created_at", "last_event" ], "description": "A single email record from Resend (full GetEmailResponseSuccess). See [Resend API docs](https://resend.com/docs/api-reference/emails/retrieve-email) for the source of truth.", "properties": { "id": { "type": "string", "description": "The Resend email ID" }, "from": { "type": "string", "description": "Sender email address" }, "to": { "type": "array", "items": { "type": "string" }, "description": "Recipient email addresses" }, "cc": { "type": "array", "items": { "type": "string" }, "nullable": true, "description": "CC recipient email addresses" }, "bcc": { "type": "array", "items": { "type": "string" }, "nullable": true, "description": "BCC recipient email addresses" }, "reply_to": { "type": "array", "items": { "type": "string" }, "nullable": true, "description": "Reply-to email addresses" }, "subject": { "type": "string", "description": "Email subject line" }, "html": { "type": "string", "nullable": true, "description": "HTML content of the email" }, "text": { "type": "string", "nullable": true, "description": "Plain text content of the email" }, "created_at": { "type": "string", "format": "date-time", "description": "Timestamp when the email was created" }, "scheduled_at": { "type": "string", "format": "date-time", "nullable": true, "description": "Scheduled send time, if any" }, "last_event": { "type": "string", "enum": [ "bounced", "canceled", "clicked", "complained", "delivered", "delivery_delayed", "failed", "opened", "queued", "scheduled", "sent" ], "description": "Most recent delivery event for this email" }, "tags": { "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string" }, "value": { "type": "string" } }, "required": [ "name", "value" ] }, "nullable": true, "description": "Custom tags attached to the email" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # List Privy Logins (Admin) Source: https://recoupable.dev/docs/api-reference/admins/privy Returns Privy login statistics for a given time period. Results include counts for new accounts (created_at), active accounts (latest_verified_at), total Privy accounts across all time, and the full, unmodified Privy account objects. See [Privy User object documentation](https://docs.privy.io/api-reference/users/get-all) for the complete type definition. Defaults to period=all (no date filter). Requires the authenticated account to be a Recoup admin. Authentication via x-api-key or Authorization Bearer token. ## GET /api/admins/privy Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/admins/privy": { "get": { "description": "Returns Privy login statistics for a given time period. Results include counts for new accounts (created_at), active accounts (latest_verified_at), total Privy accounts across all time, and the full, unmodified Privy account objects. See [Privy User object documentation](https://docs.privy.io/api-reference/users/get-all) for the complete type definition. Defaults to period=all (no date filter). Requires the authenticated account to be a Recoup admin. Authentication via x-api-key or Authorization Bearer token.", "parameters": [ { "name": "period", "in": "query", "description": "Time period to filter logins. One of: all (no date filter), daily (last 24 hours), weekly (last 7 days), monthly (last 30 days). Defaults to all.", "required": false, "schema": { "type": "string", "enum": [ "all", "daily", "weekly", "monthly" ], "default": "all" } } ], "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "responses": { "200": { "description": "Privy login statistics retrieved successfully", "content": { "application/json": { "schema": { "type": "object", "required": [ "status", "total", "total_new", "total_active", "logins" ], "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the request" }, "total": { "type": "integer", "description": "Total number of accounts in Privy across all time (not filtered by period)", "example": 42 }, "total_new": { "type": "integer", "description": "Number of accounts created (created_at) within the requested period", "example": 15 }, "total_active": { "type": "integer", "description": "Total number of accounts matching either new or active criteria in the requested period", "example": 30 }, "logins": { "type": "array", "description": "Full, unmodified Privy account objects. See [Privy User object docs](https://docs.privy.io/api-reference/users/get-all) for the complete type definition.", "items": { "type": "object" } } } } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } }, "403": { "description": "Forbidden - authenticated account is not a Recoup admin", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } } } } } }, "components": { "schemas": { "AccountErrorResponse": { "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" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # List Coding Agent Slack Tags (Admin) Source: https://recoupable.dev/docs/api-reference/admins/coding-agent-slack-tags Returns a list of Slack mentions of the Recoup Coding Agent bot, pulled directly from the Slack API as the source of truth. Each entry includes the tagger's information, the prompt they sent, the timestamp, the channel, and any GitHub pull request URLs opened in response. Also returns aggregate pull request statistics. Supports optional time-period filtering. Requires the authenticated account to be a Recoup admin. Authentication via x-api-key or Authorization Bearer token. ## GET /api/admins/coding/slack Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/admins/coding/slack": { "get": { "description": "Returns a list of Slack mentions of the Recoup Coding Agent bot, pulled directly from the Slack API as the source of truth. Each entry includes the tagger's information, the prompt they sent, the timestamp, the channel, and any GitHub pull request URLs opened in response. Also returns aggregate pull request statistics. Supports optional time-period filtering. Requires the authenticated account to be a Recoup admin. Authentication via x-api-key or Authorization Bearer token.", "parameters": [ { "name": "period", "in": "query", "description": "Time period to filter tags. One of: all (no date filter), daily (last 24 hours), weekly (last 7 days), monthly (last 30 days). Defaults to all.", "required": false, "schema": { "type": "string", "enum": [ "all", "daily", "weekly", "monthly" ], "default": "all" } } ], "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "responses": { "200": { "description": "Slack tag analytics retrieved successfully", "content": { "application/json": { "schema": { "type": "object", "required": [ "status", "total", "total_pull_requests", "tags_with_pull_requests", "tags" ], "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the request" }, "total": { "type": "integer", "description": "Total number of times the Coding Agent was tagged in the requested period", "example": 134 }, "total_pull_requests": { "type": "integer", "description": "Total number of pull requests opened by the Coding Agent across all tags in the requested period", "example": 121 }, "tags_with_pull_requests": { "type": "integer", "description": "Number of tags that resulted in at least one pull request being opened", "example": 84 }, "tags": { "type": "array", "description": "List of Slack tag events", "items": { "type": "object", "required": [ "user_id", "user_name", "prompt", "timestamp", "channel_id", "channel_name" ], "properties": { "user_id": { "type": "string", "description": "Slack ID of the person who tagged the agent", "example": "U012AB3CD" }, "user_name": { "type": "string", "description": "Display name of the person who tagged the agent", "example": "Jane Smith" }, "user_avatar": { "type": [ "string", "null" ], "description": "URL of the Slack avatar", "example": "https://avatars.slack-edge.com/..." }, "prompt": { "type": "string", "description": "The text of the message sent to the agent", "example": "add dark mode support to the settings page" }, "timestamp": { "type": "string", "format": "date-time", "description": "ISO 8601 timestamp of the tag event", "example": "2024-01-15T10:30:00.000Z" }, "channel_id": { "type": "string", "description": "Slack channel ID where the tag occurred", "example": "C012AB3CD" }, "channel_name": { "type": "string", "description": "Human-readable name of the Slack channel", "example": "dev-team" }, "pull_requests": { "type": "array", "description": "GitHub pull request URLs opened by the Coding Agent in response to this prompt, parsed from bot replies in the Slack thread", "items": { "type": "string", "format": "uri", "example": "https://github.com/recoupable/api/pull/42" }, "example": [ "https://github.com/recoupable/api/pull/42" ] } } } } } } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } }, "403": { "description": "Forbidden - authenticated account is not a Recoup admin", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } } } } } }, "components": { "schemas": { "AccountErrorResponse": { "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" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Get Coding Agent PR Merged Status (Admin) Source: https://recoupable.dev/docs/api-reference/admins/coding-pr Returns the status (open, closed, or merged) for each provided GitHub pull request URL. Accepts one or more `pull_requests` query parameters containing GitHub PR URLs. Uses the GitHub REST API to check each pull request's state. Requires the authenticated account to be a Recoup admin. Authentication via x-api-key or Authorization Bearer token. ## GET /api/admins/coding/pr Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/admins/coding/pr": { "get": { "description": "Returns the status (open, closed, or merged) for each provided GitHub pull request URL. Accepts one or more `pull_requests` query parameters containing GitHub PR URLs. Uses the GitHub REST API to check each pull request's state. Requires the authenticated account to be a Recoup admin. Authentication via x-api-key or Authorization Bearer token.", "parameters": [ { "name": "pull_requests", "in": "query", "description": "One or more GitHub pull request URLs to check. Repeat this parameter for each URL. Example: ?pull_requests=https://github.com/org/repo/pull/1&pull_requests=https://github.com/org/repo/pull/2", "required": true, "schema": { "type": "array", "items": { "type": "string", "format": "uri" }, "minItems": 1, "maxItems": 50 }, "style": "form", "explode": true } ], "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "responses": { "200": { "description": "PR merged status retrieved successfully", "content": { "application/json": { "schema": { "type": "object", "required": [ "status", "pull_requests" ], "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the request" }, "pull_requests": { "type": "array", "description": "Status for each provided pull request URL", "items": { "type": "object", "required": [ "url", "status" ], "properties": { "url": { "type": "string", "format": "uri", "description": "The GitHub pull request URL", "example": "https://github.com/recoupable/api/pull/42" }, "status": { "type": "string", "enum": [ "open", "closed", "merged" ], "description": "The current status of the pull request", "example": "merged" } } } } } } } } }, "400": { "description": "Bad request - missing or invalid pull_requests parameter", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } }, "403": { "description": "Forbidden - authenticated account is not a Recoup admin", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } } } } } }, "components": { "schemas": { "AccountErrorResponse": { "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" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # List Content Agent Slack Tags (Admin) Source: https://recoupable.dev/docs/api-reference/admins/content-slack-tags Returns a list of Slack mentions of the Recoup Content Agent bot, pulled directly from the Slack API as the source of truth. Each entry includes the tagger's information, the prompt they sent, the timestamp, the channel, and any video link responses. Also returns aggregate video statistics for measuring tag-to-video conversion. Supports optional time-period filtering. Requires the authenticated account to be a Recoup admin. Authentication via x-api-key or Authorization Bearer token. ## GET /api/admins/content/slack Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/admins/content/slack": { "get": { "description": "Returns a list of Slack mentions of the Recoup Content Agent bot, pulled directly from the Slack API as the source of truth. Each entry includes the tagger's information, the prompt they sent, the timestamp, the channel, and any video link responses. Also returns aggregate video statistics for measuring tag-to-video conversion. Supports optional time-period filtering. Requires the authenticated account to be a Recoup admin. Authentication via x-api-key or Authorization Bearer token.", "parameters": [ { "name": "period", "in": "query", "description": "Time period to filter tags. One of: all (no date filter), daily (last 24 hours), weekly (last 7 days), monthly (last 30 days). Defaults to all.", "required": false, "schema": { "type": "string", "enum": [ "all", "daily", "weekly", "monthly" ], "default": "all" } } ], "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "responses": { "200": { "description": "Slack tag analytics retrieved successfully", "content": { "application/json": { "schema": { "type": "object", "required": [ "status", "total", "total_videos", "tags_with_videos", "tags" ], "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the request" }, "total": { "type": "integer", "description": "Total number of times the Content Agent was tagged in the requested period", "example": 18 }, "total_videos": { "type": "integer", "description": "Total number of videos generated by the Content Agent across all tags in the requested period", "example": 12 }, "tags_with_videos": { "type": "integer", "description": "Number of tags that resulted in at least one video being generated", "example": 10 }, "tags": { "type": "array", "description": "List of Slack tag events", "items": { "type": "object", "required": [ "user_id", "user_name", "prompt", "timestamp", "channel_id", "channel_name" ], "properties": { "user_id": { "type": "string", "description": "Slack ID of the person who tagged the agent", "example": "U012AB3CD" }, "user_name": { "type": "string", "description": "Display name of the person who tagged the agent", "example": "Jane Smith" }, "user_avatar": { "type": [ "string", "null" ], "description": "URL of the Slack avatar", "example": "https://avatars.slack-edge.com/..." }, "prompt": { "type": "string", "description": "The text of the message sent to the agent", "example": "create a highlight reel for the new single release" }, "timestamp": { "type": "string", "format": "date-time", "description": "ISO 8601 timestamp of the tag event", "example": "2024-01-15T10:30:00.000Z" }, "channel_id": { "type": "string", "description": "Slack channel ID where the tag occurred", "example": "C012AB3CD" }, "channel_name": { "type": "string", "description": "Human-readable name of the Slack channel", "example": "content-team" }, "video_links": { "type": "array", "description": "Video URLs generated by the Content Agent in response to this prompt, parsed from bot replies in the Slack thread", "items": { "type": "string", "format": "uri", "example": "https://recoupable.com/v/abc123" }, "example": [ "https://recoupable.com/v/abc123" ] } } } } } } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } }, "403": { "description": "Forbidden - authenticated account is not a Recoup admin", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } } } } } }, "components": { "schemas": { "AccountErrorResponse": { "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" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # List Agent Sign-Ups (Admin) Source: https://recoupable.dev/docs/api-reference/admins/agent-signups Returns API key sign-up records created by AI agents. Agent sign-ups are identified by the agent+ email prefix in the associated account email. Supports period filtering and returns individual API key records with their associated email and creation timestamp, suitable for building sign-up trend charts. Requires admin authentication. ## GET /api/admins/agent/signups Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/admins/agent/signups": { "get": { "description": "Returns API key sign-up records created by AI agents. Agent sign-ups are identified by the agent+ email prefix in the associated account email. Supports period filtering and returns individual API key records with their associated email and creation timestamp, suitable for building sign-up trend charts. Requires admin authentication.", "parameters": [ { "name": "period", "in": "query", "description": "Time period to filter sign-ups. One of: all (no date filter), daily (last 24 hours), weekly (last 7 days), monthly (last 30 days). Defaults to all.", "required": false, "schema": { "type": "string", "enum": [ "all", "daily", "weekly", "monthly" ], "default": "all" } } ], "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "responses": { "200": { "description": "Agent sign-up records retrieved successfully", "content": { "application/json": { "schema": { "type": "object", "required": [ "status", "total", "signups" ], "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the request" }, "total": { "type": "integer", "description": "Total number of agent API key sign-ups matching the period filter", "example": 42 }, "signups": { "type": "array", "description": "Array of agent API key sign-up records, ordered by creation date descending", "items": { "type": "object", "required": [ "id", "name", "email", "created_at" ], "properties": { "id": { "type": "string", "format": "uuid", "description": "Unique identifier of the API key record" }, "name": { "type": "string", "description": "Name given to the API key" }, "email": { "type": "string", "format": "email", "description": "The agent+ prefixed email used to create the API key" }, "created_at": { "type": "string", "format": "date-time", "description": "Timestamp when the API key was created" } } } } } } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } }, "403": { "description": "Forbidden - authenticated account is not an admin", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } } } } } }, "components": { "schemas": { "AccountErrorResponse": { "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" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # List Pro Artists (Admin) Source: https://recoupable.dev/docs/api-reference/admins/artists-pro Retrieve the deduplicated list of artist IDs owned by pro accounts. An account is "pro" when its email belongs to an enterprise domain or it has an active Stripe subscription. Admin-scoped: requires an admin-flagged API key because the response identifies paying customer accounts. ## GET /api/admins/artists/pro Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/admins/artists/pro": { "get": { "description": "Retrieve the deduplicated list of artist IDs owned by pro accounts. An account is \"pro\" when its email belongs to an enterprise domain or it has an active Stripe subscription. Admin-scoped: requires an admin-flagged API key because the response identifies paying customer accounts.", "security": [ { "apiKeyAuth": [] } ], "responses": { "200": { "description": "Pro artist IDs retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ArtistsProResponse" } } } }, "401": { "description": "Unauthorized - missing or invalid authentication" }, "403": { "description": "Forbidden - caller is authenticated but not admin-scoped" }, "500": { "description": "Internal server error while fetching pro artists", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ArtistsProResponse" } } } } } } } }, "components": { "schemas": { "ArtistsProResponse": { "type": "object", "required": [ "status", "artists" ], "properties": { "status": { "type": "string", "enum": [ "success", "error" ], "description": "Status of the request" }, "artists": { "type": "array", "items": { "type": "string", "format": "uuid" }, "description": "List of artist IDs owned by pro accounts" }, "error": { "type": "string", "description": "Error message (only present if status is 'error')" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." } } } } ``` # Credit Usage Rollup (Admin) Source: https://recoupable.dev/docs/api-reference/admins/credits-rollup Returns the per-account rollup of credit usage over the selected period, sorted by total credits deducted descending. Each row carries the account identity (UUID + display name + primary email) and the aggregated spend so the admin dashboard can render a top-spenders table without joining client-side. Pair with [`GET /api/admins/credits/events?account_id=…&period=…`](/docs/api-reference/admins/credits-events) to drill into the individual `usage_events` rows for a single account. Requires the authenticated account to be a Recoup admin. ## GET /api/admins/credits/rollup Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/admins/credits/rollup": { "get": { "description": "Returns the per-account rollup of credit usage over the selected period, sorted by total credits deducted descending. Each row carries the account identity (UUID + display name + primary email) and the aggregated spend so the admin dashboard can render a top-spenders table without joining client-side. Pair with [`GET /api/admins/credits/events?account_id=…&period=…`](/docs/api-reference/admins/credits-events) to drill into the individual `usage_events` rows for a single account. Requires the authenticated account to be a Recoup admin.", "parameters": [ { "name": "period", "in": "query", "required": false, "description": "Rollup window. `daily` = today (last 1 day), `weekly` = last 7 days, `monthly` = last 30 days, `all` = no cutoff (every event ever recorded). Defaults to `monthly`. Follows the existing admin period convention.", "schema": { "type": "string", "enum": [ "all", "daily", "weekly", "monthly" ], "default": "monthly" } }, { "name": "limit", "in": "query", "required": false, "description": "Page size — number of accounts to return per request, sorted by total credits deducted descending. Defaults to 100; max 500.", "schema": { "type": "integer", "minimum": 1, "maximum": 500, "default": 100 } }, { "name": "page", "in": "query", "required": false, "description": "1-indexed page number. Server returns rows `(page - 1) * limit` through `page * limit - 1`. Defaults to 1. Use `total_count` in the response to drive a 'load more' control (`page * limit < total_count`).", "schema": { "type": "integer", "minimum": 1, "default": 1 } } ], "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "responses": { "200": { "description": "Credit usage rollup retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminCreditsRollupResponse" } } } }, "400": { "description": "Invalid `period` or `limit` parameter", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } }, "401": { "description": "Unauthorized - missing or invalid credentials" }, "403": { "description": "Forbidden - authenticated account is not a Recoup admin" }, "500": { "description": "Internal server error while computing the rollup", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } } } } } }, "components": { "schemas": { "AdminCreditsRollupResponse": { "type": "object", "required": [ "status", "period", "page", "limit", "total_count", "rows" ], "properties": { "status": { "type": "string", "enum": [ "success", "error" ], "description": "Status of the request" }, "period": { "type": "string", "enum": [ "all", "daily", "weekly", "monthly" ], "description": "The period the response was computed over (echoes the request)" }, "page": { "type": "integer", "minimum": 1, "description": "1-indexed page returned (echoes the request, or 1 if omitted)" }, "limit": { "type": "integer", "minimum": 1, "description": "Page size used for this response (echoes the request, or the default if omitted)" }, "total_count": { "type": "integer", "minimum": 0, "description": "Total number of distinct accounts with at least one `usage_events` row in the selected period (i.e. the size of the full result set before pagination). The client derives `has_more` as `page * limit < total_count` and shows 'X of Y accounts'. Computed via a `COUNT(DISTINCT account_id)` query alongside the paginated row fetch." }, "rows": { "type": "array", "description": "Per-account credit usage totals, sorted by `total_credits_deducted` descending", "items": { "type": "object", "required": [ "account_id", "total_credits_deducted", "event_count" ], "properties": { "account_id": { "type": "string", "format": "uuid", "description": "UUID of the account" }, "account_name": { "type": "string", "nullable": true, "description": "Display name from the `accounts` table; null if the account has no name set" }, "account_email": { "type": "string", "format": "email", "nullable": true, "description": "Primary email from `account_emails` joined for human-readable rows; null if no email is on file" }, "total_credits_deducted": { "type": "integer", "description": "Sum of `usage_events.credits_deducted` across every event for this account in the selected period, in micro-dollars (1,000,000 = $1.00)" }, "event_count": { "type": "integer", "description": "Number of `usage_events` rows aggregated into this total" } } } }, "error": { "type": "string", "description": "Error message (only present if status is 'error')" } } }, "AccountErrorResponse": { "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" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Credit Usage Events (Admin) Source: https://recoupable.dev/docs/api-reference/admins/credits-events Returns the raw `usage_events` rows for a single account over the selected period, sorted by `created_at` descending. Powers the drilldown view that expands when a row in the [`/api/admins/credits/rollup`](/docs/api-reference/admins/credits-rollup) table is opened. Each event represents one debit (main agent turn, subagent step, chat completion, or research call), with token counts and `credits_deducted` matching the wallet drop on `credits_usage` for that account. ## GET /api/admins/credits/events Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/admins/credits/events": { "get": { "description": "Returns the raw `usage_events` rows for a single account over the selected period, sorted by `created_at` descending. Powers the drilldown view that expands when a row in the [`/api/admins/credits/rollup`](/docs/api-reference/admins/credits-rollup) table is opened. Each event represents one debit (main agent turn, subagent step, chat completion, or research call), with token counts and `credits_deducted` matching the wallet drop on `credits_usage` for that account.\n\nThe response also carries a `grants` array covering the same account and period: the admin credit grants made through [`POST /api/admins/credits`](/docs/api-reference/admins/credits-grant). The two arrays answer different questions — `events` is what the system spent, `grants` is what a person set by hand, and only a grant carries an actor and a reason. Requires the authenticated account to be a Recoup admin.", "parameters": [ { "name": "account_id", "in": "query", "required": true, "description": "UUID of the account whose `usage_events` rows to return.", "schema": { "type": "string", "format": "uuid" } }, { "name": "period", "in": "query", "required": false, "description": "Window to filter `created_at` against. Same semantics as the rollup endpoint. Defaults to `monthly`.", "schema": { "type": "string", "enum": [ "all", "daily", "weekly", "monthly" ], "default": "monthly" } }, { "name": "limit", "in": "query", "required": false, "description": "Page size — number of events to return per request, sorted by `created_at` descending. Defaults to 100; max 500.", "schema": { "type": "integer", "minimum": 1, "maximum": 500, "default": 100 } }, { "name": "page", "in": "query", "required": false, "description": "1-indexed page number. Server returns rows `(page - 1) * limit` through `page * limit - 1`. Defaults to 1. Use `total_count` in the response to drive a 'load more' control on the drilldown (`page * limit < total_count`).", "schema": { "type": "integer", "minimum": 1, "default": 1 } } ], "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "responses": { "200": { "description": "Usage events retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminCreditsEventsResponse" } } } }, "400": { "description": "Missing or invalid `account_id`, `period`, or `limit`", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } }, "401": { "description": "Unauthorized - missing or invalid credentials" }, "403": { "description": "Forbidden - authenticated account is not a Recoup admin" }, "500": { "description": "Internal server error while fetching usage events", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } } } } } }, "components": { "schemas": { "AdminCreditsEventsResponse": { "type": "object", "required": [ "status", "account_id", "period", "page", "limit", "total_count", "events", "grants" ], "properties": { "status": { "type": "string", "enum": [ "success", "error" ], "description": "Status of the request" }, "account_id": { "type": "string", "format": "uuid", "description": "UUID of the account whose events were returned (echoes the request)" }, "period": { "type": "string", "enum": [ "all", "daily", "weekly", "monthly" ], "description": "The period the response was filtered to (echoes the request)" }, "page": { "type": "integer", "minimum": 1, "description": "1-indexed page returned (echoes the request, or 1 if omitted)" }, "limit": { "type": "integer", "minimum": 1, "description": "Page size used for this response (echoes the request, or the default if omitted)" }, "total_count": { "type": "integer", "minimum": 0, "description": "Total number of `usage_events` rows for this account in the selected period (i.e. the size of the full result set before pagination). The client derives `has_more` as `page * limit < total_count` and shows 'X of Y events'. Computed via a `COUNT(*)` query alongside the paginated row fetch." }, "events": { "type": "array", "description": "Raw `usage_events` rows, sorted by `created_at` descending", "items": { "type": "object", "required": [ "id", "created_at", "source", "agent_type", "input_tokens", "cached_input_tokens", "output_tokens", "tool_call_count", "credits_deducted" ], "properties": { "id": { "type": "string", "description": "nanoid primary key of the `usage_events` row" }, "created_at": { "type": "string", "format": "date-time", "description": "When the event was recorded" }, "source": { "type": "string", "enum": [ "web", "api" ], "description": "Which surface originated the debit. `web` = open-agents chat. `api` = recoupable api (chat completion or research call)." }, "agent_type": { "type": "string", "enum": [ "main", "subagent" ], "description": "`main` for the top-level agent turn or any non-agent debit; `subagent` for a nested task-tool step" }, "provider": { "type": "string", "nullable": true, "description": "Provider slug (e.g. `anthropic`). Null when the debit isn't tied to a specific provider (e.g. research)." }, "model_id": { "type": "string", "nullable": true, "description": "The model for LLM-backed debits (e.g. `anthropic/claude-opus-4.6`), otherwise the billed API endpoint as `METHOD /route/pattern` (e.g. `POST /api/research/web`). Null on rows written before 2026-08-27." }, "input_tokens": { "type": "integer", "description": "Total input tokens for the call (including cached). 0 for non-LLM debits." }, "cached_input_tokens": { "type": "integer", "description": "Subset of `input_tokens` that hit the provider cache (priced at the cache-read rate)." }, "output_tokens": { "type": "integer", "description": "Output tokens for the call. 0 for non-LLM debits." }, "tool_call_count": { "type": "integer", "description": "Number of tool calls observed in this step" }, "credits_deducted": { "type": "integer", "description": "The amount debited from `credits_usage.remaining_credits` for this event, in micro-dollars (1,000,000 = $1.00). Same value the wallet dropped by on this turn." }, "resource_url": { "type": "string", "nullable": true, "description": "App-relative path of what produced the debit (`/chat?roomId=`, `/music/`, `/tasks//runs/`). Null for plain API calls and for rows written before the field existed." } } } }, "grants": { "type": "array", "description": "Admin credit grants made against this account in the selected period, sorted by `created_at` descending — the write-side counterpart to `events`. A grant is a staff member setting the balance through [`POST /api/admins/credits`](/docs/api-reference/admins/credits-grant), so unlike an event it carries an actor and a reason. Not paginated by `page`/`limit` and not counted in `total_count`, which describe `usage_events` only; capped at the 500 most recent grants in the period. Empty for the overwhelming majority of accounts, which have never been granted anything.", "items": { "type": "object", "required": [ "id", "account_id", "created_at", "granted_by", "reason", "previous_credits", "remaining_credits" ], "properties": { "id": { "type": "string", "format": "uuid", "description": "UUID of the grant row — the `grant_id` returned by `POST /api/admins/credits`" }, "account_id": { "type": "string", "format": "uuid", "description": "UUID of the account the grant was made against. Always equal to the top-level `account_id`, since the endpoint reports on one account at a time; present because each entry is the full stored grant row." }, "created_at": { "type": "string", "format": "date-time", "description": "When the grant was made" }, "granted_by": { "type": "string", "format": "uuid", "description": "UUID of the admin account that made the grant" }, "reason": { "type": "string", "description": "The reason the admin recorded for the grant, verbatim" }, "previous_credits": { "type": "integer", "nullable": true, "description": "Balance immediately before the grant. Null when the account had no credits row and the grant created one." }, "remaining_credits": { "type": "integer", "description": "Balance the account was left holding by the grant" } } } }, "error": { "type": "string", "description": "Error message (only present if status is 'error')" } } }, "AccountErrorResponse": { "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" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Grant Credits (Admin) Source: https://recoupable.dev/docs/api-reference/admins/credits-grant Sets an account's credit balance, and records who set it and why. Requires the authenticated account to be a Recoup admin. ## POST /api/admins/credits Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/admins/credits": { "post": { "description": "Sets an account's credit balance, and records who set it and why. Requires the authenticated account to be a Recoup admin.\n\n**Absolute, not a delta.** `remaining_credits` is the balance the account is left holding, whatever it held before — the outcome never depends on the balance at the moment the request lands. The prior balance comes back as `previous_credits` for the record. There is deliberately no add/subtract variant.\n\n**Every grant is audited.** `reason` is required and must be non-empty. The grant is stored against the acting admin's account ID and is readable afterwards in the `grants` array of [`GET /api/admins/credits/events`](/docs/api-reference/admins/credits-events).\n\n**A grant is never reduced by the monthly refill.** Credit balances are topped up to the plan total by a monthly refill that runs lazily on the next read of [`GET /api/accounts/{id}/credits`](/docs/api-reference/accounts/credits-get), once the underlying row is more than a month old. That refill is a floor, not an assignment: it raises a balance up to the plan total and never lowers one. A grant above the plan total therefore survives every refill and holds until it is spent, so headroom granted once stays granted and does not need re-granting on a schedule.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "requestBody": { "description": "The account to set, the balance to leave it at, and why", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminGrantCreditsRequest" } } } }, "responses": { "200": { "description": "Balance set and the grant recorded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminGrantCreditsResponse" } } } }, "400": { "description": "Malformed JSON body, or a missing/invalid `account_id`, `remaining_credits`, or `reason`", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminGrantCreditsValidationError" } } } }, "401": { "description": "Unauthorized - missing or invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } } }, "403": { "description": "Forbidden - authenticated account is not a Recoup admin", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } }, "404": { "description": "No account exists with the supplied `account_id`", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } }, "500": { "description": "Internal server error while setting the balance or recording the grant", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountErrorResponse" } } } } } } } }, "components": { "schemas": { "AdminGrantCreditsRequest": { "type": "object", "required": [ "account_id", "remaining_credits", "reason" ], "properties": { "account_id": { "type": "string", "format": "uuid", "description": "UUID of the account whose balance is being set. Must be an existing account — an unknown UUID is a 404, not a silent no-op." }, "remaining_credits": { "type": "integer", "minimum": 0, "description": "The balance to leave the account holding, in credits (1 credit = 1 US cent). Absolute, not a delta. Zero is allowed — it is how an account is deliberately zeroed out. Negative values are rejected with a 400, even though ordinary usage can overdraw a balance below zero on its own." }, "reason": { "type": "string", "minLength": 1, "description": "Why the grant was made, in plain language — e.g. `Trial headroom for the Aug 12 label demo`. Required and non-empty (a whitespace-only string is rejected): this is the field that makes a grant distinguishable from a Stripe top-up or a monthly reset when someone asks months later. Stored verbatim and returned by the events endpoint." } } }, "AdminGrantCreditsResponse": { "type": "object", "required": [ "status", "grant_id", "account_id", "remaining_credits", "previous_credits", "reason", "granted_by", "granted_at" ], "properties": { "status": { "type": "string", "enum": [ "success" ], "description": "Status of the request" }, "grant_id": { "type": "string", "format": "uuid", "description": "UUID of the recorded grant row. Matches the `id` of the corresponding entry in the `grants` array of `GET /api/admins/credits/events`." }, "account_id": { "type": "string", "format": "uuid", "description": "UUID of the account whose balance was set (echoes the request)" }, "remaining_credits": { "type": "integer", "description": "The balance the account now holds — the value supplied in the request" }, "previous_credits": { "type": "integer", "nullable": true, "description": "The balance immediately before the grant. Null when the account had no credits row at all and one was created by this request." }, "reason": { "type": "string", "description": "The reason recorded with the grant (echoes the request)" }, "granted_by": { "type": "string", "format": "uuid", "description": "UUID of the admin account that made the grant, resolved from the credentials on the request. Never taken from the body." }, "granted_at": { "type": "string", "format": "date-time", "description": "When the grant was recorded" } } }, "AdminGrantCreditsValidationError": { "type": "object", "required": [ "status", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Status of the request" }, "missing_fields": { "type": "array", "description": "JSON path segments of the first field that failed validation, e.g. `[\"reason\"]`. Absent when the body was not valid JSON at all.", "items": { "oneOf": [ { "type": "string" }, { "type": "integer" } ] } }, "error": { "type": "string", "description": "Error message describing what went wrong" } } }, "ErrorEnvelope": { "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" } } }, "AccountErrorResponse": { "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" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Send Email Source: https://recoupable.dev/docs/api-reference/emails/create Send an email to one or more recipients. Emails are sent from `Agent by Recoup `. ## POST /api/emails Full OpenAPI specification: https://recoupable.dev/docs/spec/accounts.json ## Authentication The supplied specification does not declare an OpenAPI security object for this operation. Its declared headers and parameters still apply. Consult the authentication guide and the full specification before calling it. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Accounts", "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/emails": { "post": { "description": "Send an email to one or more recipients. Emails are sent from `Agent by Recoup `.", "requestBody": { "description": "Email payload", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SendEmailRequest" } } } }, "responses": { "200": { "description": "Email sent successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EmailResponse" } } } }, "400": { "description": "Validation error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "Authentication required", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "502": { "description": "Email delivery failed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "SendEmailRequest": { "type": "object", "properties": { "to": { "type": "array", "minItems": 1, "items": { "type": "string", "format": "email" }, "description": "Recipient email address(es). Optional — when omitted, the email is sent to the authenticated account's own email address. Without a payment method on file, restricted to the account's own email; [add a payment method](/docs/api-reference/credits/sessions-create) to send to any recipient.", "example": [ "recipient@example.com" ] }, "cc": { "type": "array", "items": { "type": "string", "format": "email" }, "description": "Optional CC email addresses. Without a payment method on file, restricted to the account's own email; [add a payment method](/docs/api-reference/credits/sessions-create) to send to any recipient.", "example": [ "cc@example.com" ] }, "subject": { "type": "string", "description": "Email subject line. Optional — when omitted, defaults to the first heading or line of the body, falling back to `Message from Recoup` if the body is empty.", "example": "Weekly Pulse Report" }, "text": { "type": "string", "description": "Plain text or Markdown body. Rendered as HTML via Markdown if no `html` is provided.", "example": "# Pulse Report\n\nHere's your weekly summary." }, "html": { "type": "string", "description": "Raw HTML body. Takes precedence over `text` when both are provided.", "example": "

Pulse Report

Here's your weekly summary.

" }, "headers": { "type": "object", "additionalProperties": { "type": "string" }, "description": "Optional custom email headers" }, "chat_id": { "type": "string", "description": "Chat ID to include a chat link in the email footer. Use [Create Chat](/docs/api-reference/chat/create) to create one." }, "account_id": { "type": "string", "format": "uuid", "description": "UUID of the account to send the email for. Only applicable when the authenticated account has access to multiple accounts via organization membership. If not provided, sends for the API key's own account." } } }, "EmailResponse": { "type": "object", "required": [ "success", "message", "id" ], "properties": { "success": { "type": "boolean", "description": "Whether the email was sent successfully", "example": true }, "message": { "type": "string", "description": "Human-readable result message", "example": "Email sent successfully from Agent by Recoup to recipient@example.com." }, "id": { "type": "string", "description": "Resend email ID", "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" } } }, "Error": { "required": [ "error", "message" ], "type": "object", "properties": { "error": { "type": "integer", "format": "int32" }, "message": { "type": "string" } } } } } } ``` # Get Project Source: https://recoupable.dev/docs/api-reference/projects/get Get project ## GET /api/projects/{projectId} Full OpenAPI specification: https://recoupable.dev/docs/spec/projects.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.0.1", "info": { "title": "Recoup API - Projects", "description": "Client-facing project status: the tasks on a client engagement, who they are waiting on, and the comment thread against each one.", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "paths": { "/api/projects/{projectId}": { "get": { "summary": "Get project", "description": "A client project and every task on it, oldest first, with the project's collaborators. This is what the project page renders.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "parameters": [ { "name": "projectId", "in": "path", "required": true, "description": "The project's UUID.", "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "The project, its tasks and its collaborators.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectResponse" } } } }, "400": { "description": "Bad request — invalid path parameter or request body.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "Unauthorized — missing or invalid credentials.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "Not found — either no project or task with this id exists, or the authenticated account is not a collaborator on it. The two cases are deliberately indistinguishable so the response cannot be used to discover which project ids are real.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "description": "Internal server error.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "ProjectResponse": { "type": "object", "required": [ "status", "project", "tasks", "collaborators" ], "properties": { "status": { "type": "string", "example": "success" }, "project": { "$ref": "#/components/schemas/Project" }, "tasks": { "type": "array", "items": { "$ref": "#/components/schemas/ProjectTask" }, "description": "Every task on the project, oldest first. Both the active and completed sets; a client splits them on `completed_at`." }, "collaborators": { "type": "array", "items": { "$ref": "#/components/schemas/ProjectCollaborator" } } } }, "Error": { "type": "object", "required": [ "status", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Always `\"error\"` for error responses." }, "error": { "type": "string", "description": "Human-readable error message." }, "message": { "type": "string", "description": "Carries the message in place of `error` when the failure comes from the authentication layer, so a 401 raised while verifying the credential reads `message` and every other error reads `error`." } } }, "Project": { "type": "object", "required": [ "id", "name", "created_at" ], "properties": { "id": { "type": "string", "format": "uuid" }, "name": { "type": "string" }, "created_at": { "type": "string", "format": "date-time" } } }, "ProjectTask": { "type": "object", "required": [ "id", "project_id", "title", "created_at" ], "properties": { "id": { "type": "string", "format": "uuid" }, "project_id": { "type": "string", "format": "uuid" }, "title": { "type": "string" }, "description": { "type": "string", "nullable": true }, "due_date": { "type": "string", "format": "date", "nullable": true, "description": "A calendar date with no time of day, e.g. `2026-09-12`." }, "assignee_account_id": { "type": "string", "format": "uuid", "nullable": true, "description": "Who the task is waiting on. A client renders its \"needs you\" treatment when this matches the viewing account." }, "completed_at": { "type": "string", "format": "date-time", "nullable": true, "description": "Null means the task is not complete. There is no separate boolean." }, "completed_by": { "type": "string", "format": "uuid", "nullable": true }, "comment_count": { "type": "integer", "format": "int32", "description": "Number of comments on this task. Present on the project read so a list can render a count without a call per task." }, "created_at": { "type": "string", "format": "date-time", "description": "Also the sort key: tasks come back oldest first." }, "updated_at": { "type": "string", "format": "date-time" } } }, "ProjectCollaborator": { "type": "object", "required": [ "account_id" ], "properties": { "account_id": { "type": "string", "format": "uuid" }, "name": { "type": "string", "nullable": true, "description": "The collaborator's `accounts.name`. Frequently null — nothing captures a name at sign-up today — so clients must render a fallback." } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer", "description": "A Recoup API key or a Privy access token. [Learn more](/docs/authentication)." } } } } ``` # Get Project Task Source: https://recoupable.dev/docs/api-reference/projects/task-get Get project task ## GET /api/projects/{projectId}/tasks/{taskId} Full OpenAPI specification: https://recoupable.dev/docs/spec/projects.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.0.1", "info": { "title": "Recoup API - Projects", "description": "Client-facing project status: the tasks on a client engagement, who they are waiting on, and the comment thread against each one.", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "paths": { "/api/projects/{projectId}/tasks/{taskId}": { "get": { "summary": "Get project task", "description": "One task with its comment feed and the project's collaborators. This is what the task detail page renders.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "parameters": [ { "name": "projectId", "in": "path", "required": true, "description": "The project's UUID.", "schema": { "type": "string", "format": "uuid" } }, { "name": "taskId", "in": "path", "required": true, "description": "The task's UUID.", "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "The task, its comments and the project's collaborators.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectTaskResponse" } } } }, "400": { "description": "Bad request — invalid path parameter or request body.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "Unauthorized — missing or invalid credentials.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "Not found — either no project or task with this id exists, or the authenticated account is not a collaborator on it. The two cases are deliberately indistinguishable so the response cannot be used to discover which project ids are real.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "description": "Internal server error.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "ProjectTaskResponse": { "type": "object", "required": [ "status", "task", "comments", "collaborators" ], "properties": { "status": { "type": "string", "example": "success" }, "task": { "$ref": "#/components/schemas/ProjectTask" }, "comments": { "type": "array", "items": { "$ref": "#/components/schemas/ProjectComment" }, "description": "Oldest first, so the feed reads top to bottom." }, "collaborators": { "type": "array", "items": { "$ref": "#/components/schemas/ProjectCollaborator" } } } }, "Error": { "type": "object", "required": [ "status", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Always `\"error\"` for error responses." }, "error": { "type": "string", "description": "Human-readable error message." }, "message": { "type": "string", "description": "Carries the message in place of `error` when the failure comes from the authentication layer, so a 401 raised while verifying the credential reads `message` and every other error reads `error`." } } }, "ProjectTask": { "type": "object", "required": [ "id", "project_id", "title", "created_at" ], "properties": { "id": { "type": "string", "format": "uuid" }, "project_id": { "type": "string", "format": "uuid" }, "title": { "type": "string" }, "description": { "type": "string", "nullable": true }, "due_date": { "type": "string", "format": "date", "nullable": true, "description": "A calendar date with no time of day, e.g. `2026-09-12`." }, "assignee_account_id": { "type": "string", "format": "uuid", "nullable": true, "description": "Who the task is waiting on. A client renders its \"needs you\" treatment when this matches the viewing account." }, "completed_at": { "type": "string", "format": "date-time", "nullable": true, "description": "Null means the task is not complete. There is no separate boolean." }, "completed_by": { "type": "string", "format": "uuid", "nullable": true }, "comment_count": { "type": "integer", "format": "int32", "description": "Number of comments on this task. Present on the project read so a list can render a count without a call per task." }, "created_at": { "type": "string", "format": "date-time", "description": "Also the sort key: tasks come back oldest first." }, "updated_at": { "type": "string", "format": "date-time" } } }, "ProjectComment": { "type": "object", "required": [ "id", "task_id", "account_id", "body", "created_at" ], "properties": { "id": { "type": "string", "format": "uuid" }, "task_id": { "type": "string", "format": "uuid" }, "account_id": { "type": "string", "format": "uuid" }, "author_name": { "type": "string", "nullable": true, "description": "The author's `accounts.name`, resolved server-side. Frequently null; render a fallback." }, "body": { "type": "string" }, "created_at": { "type": "string", "format": "date-time" } } }, "ProjectCollaborator": { "type": "object", "required": [ "account_id" ], "properties": { "account_id": { "type": "string", "format": "uuid" }, "name": { "type": "string", "nullable": true, "description": "The collaborator's `accounts.name`. Frequently null — nothing captures a name at sign-up today — so clients must render a fallback." } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer", "description": "A Recoup API key or a Privy access token. [Learn more](/docs/authentication)." } } } } ``` # Create Project Task Source: https://recoupable.dev/docs/api-reference/projects/task-create Create project task ## POST /api/projects/{projectId}/tasks Full OpenAPI specification: https://recoupable.dev/docs/spec/projects.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.0.1", "info": { "title": "Recoup API - Projects", "description": "Client-facing project status: the tasks on a client engagement, who they are waiting on, and the comment thread against each one.", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "paths": { "/api/projects/{projectId}/tasks": { "post": { "summary": "Create project task", "description": "Add a task to a project. New tasks are always incomplete; set `completed` with [the update endpoint](/docs/api-reference/projects/task-update) instead.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "parameters": [ { "name": "projectId", "in": "path", "required": true, "description": "The project's UUID.", "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "description": "The task to create.", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateProjectTaskRequest" }, "examples": { "needsClient": { "summary": "A task waiting on the client", "value": { "title": "Distribution portal access", "description": "Create a read only login for your distributor dashboard and share it with us.", "due_date": "2026-09-12", "assignee_account_id": "0f0e6d8a-1b2c-4d3e-8f90-a1b2c3d4e5f6" } } } } } }, "responses": { "201": { "description": "Task created.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectTaskMutationResponse" } } } }, "400": { "description": "Bad request — invalid path parameter or request body.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "Unauthorized — missing or invalid credentials.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "Not found — either no project or task with this id exists, or the authenticated account is not a collaborator on it. The two cases are deliberately indistinguishable so the response cannot be used to discover which project ids are real.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "description": "Internal server error.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "CreateProjectTaskRequest": { "type": "object", "required": [ "title" ], "properties": { "title": { "type": "string", "minLength": 1 }, "description": { "type": "string", "nullable": true }, "due_date": { "type": "string", "format": "date", "nullable": true }, "assignee_account_id": { "type": "string", "format": "uuid", "nullable": true, "description": "Must be a collaborator on this project." } } }, "ProjectTaskMutationResponse": { "type": "object", "required": [ "status", "task" ], "properties": { "status": { "type": "string", "example": "success" }, "task": { "$ref": "#/components/schemas/ProjectTask" } } }, "Error": { "type": "object", "required": [ "status", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Always `\"error\"` for error responses." }, "error": { "type": "string", "description": "Human-readable error message." }, "message": { "type": "string", "description": "Carries the message in place of `error` when the failure comes from the authentication layer, so a 401 raised while verifying the credential reads `message` and every other error reads `error`." } } }, "ProjectTask": { "type": "object", "required": [ "id", "project_id", "title", "created_at" ], "properties": { "id": { "type": "string", "format": "uuid" }, "project_id": { "type": "string", "format": "uuid" }, "title": { "type": "string" }, "description": { "type": "string", "nullable": true }, "due_date": { "type": "string", "format": "date", "nullable": true, "description": "A calendar date with no time of day, e.g. `2026-09-12`." }, "assignee_account_id": { "type": "string", "format": "uuid", "nullable": true, "description": "Who the task is waiting on. A client renders its \"needs you\" treatment when this matches the viewing account." }, "completed_at": { "type": "string", "format": "date-time", "nullable": true, "description": "Null means the task is not complete. There is no separate boolean." }, "completed_by": { "type": "string", "format": "uuid", "nullable": true }, "comment_count": { "type": "integer", "format": "int32", "description": "Number of comments on this task. Present on the project read so a list can render a count without a call per task." }, "created_at": { "type": "string", "format": "date-time", "description": "Also the sort key: tasks come back oldest first." }, "updated_at": { "type": "string", "format": "date-time" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer", "description": "A Recoup API key or a Privy access token. [Learn more](/docs/authentication)." } } } } ``` # Update Project Task Source: https://recoupable.dev/docs/api-reference/projects/task-update Update project task ## PATCH /api/projects/{projectId}/tasks/{taskId} Full OpenAPI specification: https://recoupable.dev/docs/spec/projects.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.0.1", "info": { "title": "Recoup API - Projects", "description": "Client-facing project status: the tasks on a client engagement, who they are waiting on, and the comment thread against each one.", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "paths": { "/api/projects/{projectId}/tasks/{taskId}": { "patch": { "summary": "Update project task", "description": "Update a task. Send only the fields that change.\n\nCompletion is toggled with the `completed` boolean rather than by writing a timestamp: the server stamps `completed_at` and records `completed_by` from the authenticated account, so it is never ambiguous who closed an item. Any collaborator may toggle any task, including the client.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "parameters": [ { "name": "projectId", "in": "path", "required": true, "description": "The project's UUID.", "schema": { "type": "string", "format": "uuid" } }, { "name": "taskId", "in": "path", "required": true, "description": "The task's UUID.", "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "description": "The fields to change.", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdateProjectTaskRequest" }, "examples": { "markComplete": { "summary": "Mark complete", "value": { "completed": true } }, "markIncomplete": { "summary": "Reopen", "value": { "completed": false } }, "reschedule": { "summary": "Change the due date", "value": { "due_date": "2026-09-19" } } } } } }, "responses": { "200": { "description": "Task updated.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectTaskMutationResponse" } } } }, "400": { "description": "Bad request — invalid path parameter or request body.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "Unauthorized — missing or invalid credentials.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "Not found — either no project or task with this id exists, or the authenticated account is not a collaborator on it. The two cases are deliberately indistinguishable so the response cannot be used to discover which project ids are real.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "description": "Internal server error.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "UpdateProjectTaskRequest": { "type": "object", "minProperties": 1, "description": "Every field is optional; send only what changes. An empty body is a 400.", "properties": { "title": { "type": "string", "minLength": 1 }, "description": { "type": "string", "nullable": true }, "due_date": { "type": "string", "format": "date", "nullable": true }, "assignee_account_id": { "type": "string", "format": "uuid", "nullable": true }, "completed": { "type": "boolean", "description": "`true` stamps `completed_at` with the current time and `completed_by` with the authenticated account. `false` clears both. This is the only way to change completion; `completed_at` is not writable directly." } } }, "ProjectTaskMutationResponse": { "type": "object", "required": [ "status", "task" ], "properties": { "status": { "type": "string", "example": "success" }, "task": { "$ref": "#/components/schemas/ProjectTask" } } }, "Error": { "type": "object", "required": [ "status", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Always `\"error\"` for error responses." }, "error": { "type": "string", "description": "Human-readable error message." }, "message": { "type": "string", "description": "Carries the message in place of `error` when the failure comes from the authentication layer, so a 401 raised while verifying the credential reads `message` and every other error reads `error`." } } }, "ProjectTask": { "type": "object", "required": [ "id", "project_id", "title", "created_at" ], "properties": { "id": { "type": "string", "format": "uuid" }, "project_id": { "type": "string", "format": "uuid" }, "title": { "type": "string" }, "description": { "type": "string", "nullable": true }, "due_date": { "type": "string", "format": "date", "nullable": true, "description": "A calendar date with no time of day, e.g. `2026-09-12`." }, "assignee_account_id": { "type": "string", "format": "uuid", "nullable": true, "description": "Who the task is waiting on. A client renders its \"needs you\" treatment when this matches the viewing account." }, "completed_at": { "type": "string", "format": "date-time", "nullable": true, "description": "Null means the task is not complete. There is no separate boolean." }, "completed_by": { "type": "string", "format": "uuid", "nullable": true }, "comment_count": { "type": "integer", "format": "int32", "description": "Number of comments on this task. Present on the project read so a list can render a count without a call per task." }, "created_at": { "type": "string", "format": "date-time", "description": "Also the sort key: tasks come back oldest first." }, "updated_at": { "type": "string", "format": "date-time" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer", "description": "A Recoup API key or a Privy access token. [Learn more](/docs/authentication)." } } } } ``` # Delete Project Task Source: https://recoupable.dev/docs/api-reference/projects/task-delete Delete project task ## DELETE /api/projects/{projectId}/tasks/{taskId} Full OpenAPI specification: https://recoupable.dev/docs/spec/projects.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.0.1", "info": { "title": "Recoup API - Projects", "description": "Client-facing project status: the tasks on a client engagement, who they are waiting on, and the comment thread against each one.", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "paths": { "/api/projects/{projectId}/tasks/{taskId}": { "delete": { "summary": "Delete project task", "description": "Permanently delete a task and every comment on it.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "parameters": [ { "name": "projectId", "in": "path", "required": true, "description": "The project's UUID.", "schema": { "type": "string", "format": "uuid" } }, { "name": "taskId", "in": "path", "required": true, "description": "The task's UUID.", "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Task deleted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeleteProjectTaskResponse" } } } }, "400": { "description": "Bad request — invalid path parameter or request body.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "Unauthorized — missing or invalid credentials.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "Not found — either no project or task with this id exists, or the authenticated account is not a collaborator on it. The two cases are deliberately indistinguishable so the response cannot be used to discover which project ids are real.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "description": "Internal server error.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "DeleteProjectTaskResponse": { "type": "object", "required": [ "status", "id" ], "properties": { "status": { "type": "string", "example": "success" }, "id": { "type": "string", "format": "uuid", "description": "The deleted task's id." } } }, "Error": { "type": "object", "required": [ "status", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Always `\"error\"` for error responses." }, "error": { "type": "string", "description": "Human-readable error message." }, "message": { "type": "string", "description": "Carries the message in place of `error` when the failure comes from the authentication layer, so a 401 raised while verifying the credential reads `message` and every other error reads `error`." } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer", "description": "A Recoup API key or a Privy access token. [Learn more](/docs/authentication)." } } } } ``` # List Task Comments Source: https://recoupable.dev/docs/api-reference/projects/comments-get List task comments ## GET /api/projects/{projectId}/tasks/{taskId}/comments Full OpenAPI specification: https://recoupable.dev/docs/spec/projects.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.0.1", "info": { "title": "Recoup API - Projects", "description": "Client-facing project status: the tasks on a client engagement, who they are waiting on, and the comment thread against each one.", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "paths": { "/api/projects/{projectId}/tasks/{taskId}/comments": { "get": { "summary": "List task comments", "description": "Every comment on a task, oldest first.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "parameters": [ { "name": "projectId", "in": "path", "required": true, "description": "The project's UUID.", "schema": { "type": "string", "format": "uuid" } }, { "name": "taskId", "in": "path", "required": true, "description": "The task's UUID.", "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "The task's comments.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectCommentsResponse" } } } }, "400": { "description": "Bad request — invalid path parameter or request body.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "Unauthorized — missing or invalid credentials.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "Not found — either no project or task with this id exists, or the authenticated account is not a collaborator on it. The two cases are deliberately indistinguishable so the response cannot be used to discover which project ids are real.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "description": "Internal server error.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "ProjectCommentsResponse": { "type": "object", "required": [ "status", "comments" ], "properties": { "status": { "type": "string", "example": "success" }, "comments": { "type": "array", "items": { "$ref": "#/components/schemas/ProjectComment" } } } }, "Error": { "type": "object", "required": [ "status", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Always `\"error\"` for error responses." }, "error": { "type": "string", "description": "Human-readable error message." }, "message": { "type": "string", "description": "Carries the message in place of `error` when the failure comes from the authentication layer, so a 401 raised while verifying the credential reads `message` and every other error reads `error`." } } }, "ProjectComment": { "type": "object", "required": [ "id", "task_id", "account_id", "body", "created_at" ], "properties": { "id": { "type": "string", "format": "uuid" }, "task_id": { "type": "string", "format": "uuid" }, "account_id": { "type": "string", "format": "uuid" }, "author_name": { "type": "string", "nullable": true, "description": "The author's `accounts.name`, resolved server-side. Frequently null; render a fallback." }, "body": { "type": "string" }, "created_at": { "type": "string", "format": "date-time" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer", "description": "A Recoup API key or a Privy access token. [Learn more](/docs/authentication)." } } } } ``` # Add Task Comment Source: https://recoupable.dev/docs/api-reference/projects/comments-create Add task comment ## POST /api/projects/{projectId}/tasks/{taskId}/comments Full OpenAPI specification: https://recoupable.dev/docs/spec/projects.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.0.1", "info": { "title": "Recoup API - Projects", "description": "Client-facing project status: the tasks on a client engagement, who they are waiting on, and the comment thread against each one.", "license": { "name": "MIT" }, "version": "1.0.0" }, "servers": [ { "url": "https://api.recoupable.dev" } ], "paths": { "/api/projects/{projectId}/tasks/{taskId}/comments": { "post": { "summary": "Add task comment", "description": "Post a comment on a task, attributed to the authenticated account. Comments are append-only plain text: there is no edit and no delete.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "parameters": [ { "name": "projectId", "in": "path", "required": true, "description": "The project's UUID.", "schema": { "type": "string", "format": "uuid" } }, { "name": "taskId", "in": "path", "required": true, "description": "The task's UUID.", "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "description": "The comment to post.", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateProjectCommentRequest" }, "examples": { "comment": { "summary": "A client reply", "value": { "body": "Got it. Sending the login over this week." } } } } } }, "responses": { "201": { "description": "Comment posted.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectCommentResponse" } } } }, "400": { "description": "Bad request — invalid path parameter or request body.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "Unauthorized — missing or invalid credentials.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "Not found — either no project or task with this id exists, or the authenticated account is not a collaborator on it. The two cases are deliberately indistinguishable so the response cannot be used to discover which project ids are real.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "description": "Internal server error.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "CreateProjectCommentRequest": { "type": "object", "required": [ "body" ], "properties": { "body": { "type": "string", "minLength": 1, "maxLength": 4000, "description": "Plain text. No markdown, no mentions, no attachments. Comments are append-only: there is no edit or delete." } } }, "ProjectCommentResponse": { "type": "object", "required": [ "status", "comment" ], "properties": { "status": { "type": "string", "example": "success" }, "comment": { "$ref": "#/components/schemas/ProjectComment" } } }, "Error": { "type": "object", "required": [ "status", "error" ], "properties": { "status": { "type": "string", "enum": [ "error" ], "description": "Always `\"error\"` for error responses." }, "error": { "type": "string", "description": "Human-readable error message." }, "message": { "type": "string", "description": "Carries the message in place of `error` when the failure comes from the authentication layer, so a 401 raised while verifying the credential reads `message` and every other error reads `error`." } } }, "ProjectComment": { "type": "object", "required": [ "id", "task_id", "account_id", "body", "created_at" ], "properties": { "id": { "type": "string", "format": "uuid" }, "task_id": { "type": "string", "format": "uuid" }, "account_id": { "type": "string", "format": "uuid" }, "author_name": { "type": "string", "nullable": true, "description": "The author's `accounts.name`, resolved server-side. Frequently null; render a fallback." }, "body": { "type": "string" }, "created_at": { "type": "string", "format": "date-time" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer", "description": "A Recoup API key or a Privy access token. [Learn more](/docs/authentication)." } } } } ``` # Connection Ownership Source: https://recoupable.dev/docs/connectors/ownership The connector connection-ownership and authority contract: who owns a connection, who can act through it, and why roster membership never grants execute rights. ## Overview Connectors link third-party services (Google Sheets, TikTok, YouTube, X, LinkedIn, and others) to Recoup so agents can act on an artist's behalf. This page defines the **ownership and authority contract** for those connections: who a connection belongs to, and who is allowed to authorize, execute through, or disconnect it. This contract is the source of truth for connector access control. Endpoint request and response shapes are documented separately in the [Connectors API reference](https://recoupable.dev/docs/api-reference/connectors/list). --- ## Association ≠ Authority Artists in Recoup are **canonical and shared**. The same artist can appear in many accounts' rosters and in multiple organizations. Being associated with an artist is not the same as having authority to act through that artist's connections: - An artist appearing in an account's roster (`account_artist_ids`) grants **read and analytics access only**. - An artist appearing in an organization's roster likewise grants **read and analytics access only** to org members. - Neither form of association ever grants the right to authorize, execute, or disconnect a connection. > **Warning** > Roster membership never inherits connection authority. If Account A connects an artist's TikTok, Account B having that same artist on its roster does **not** let Account B post through that connection. --- ## Connection Scope Connections are scoped to the **relationship between an owner and an artist**, not to the bare canonical artist: | Connection type | Scoped to | | --- | --- | | Personal | The `(account, artist)` pair | | Org-shared | The `(organization, artist)` pair | The connection is keyed on the **join row** (the specific account-to-artist or org-to-artist relationship), never on the canonical artist alone. Two accounts that both roster the same artist hold two independent connection scopes for that artist. **Whoever establishes a connection owns it.** Ownership is set at authorization time and does not transfer through roster changes. Because artists stay canonical and shared, **isolation lives on the connection**: sharing an artist across accounts and orgs is safe precisely because each connection is confined to the relationship that created it. --- ## Authority Matrix | Capability | Who has it | | --- | --- | | Read / analytics on an artist | Any account or org member with the artist in their roster | | Authorize a new connection | The account (or org member, for org-shared) establishing the connection, who then owns it | | Execute actions through a connection | The owning account only; for org-shared connections, members of the owning org | | Disconnect a connection | The owning account only; for org-shared connections, members of the owning org | | Admin override | Recoup staff only (admins of the Recoup internal organization) | > **Note** > The admin override exists for support and abuse response. No customer account or organization, regardless of roster or org membership, can override another owner's connection. --- ## Relationship to the Current API Reference The [Connectors endpoint reference](https://recoupable.dev/docs/api-reference/connectors/list) currently describes connections as scoped to the authenticated account alone. That reflects behavior that shipped before this contract; the API is being re-keyed to the per-`(account, artist)` / per-`(organization, artist)` model defined here. Where the two disagree, **this page is the authority contract**. Endpoint docs will be updated as the re-keyed API ships. Endpoint request and response shapes for the re-keyed model are deliberately not documented here yet. # List Connectors Source: https://recoupable.dev/docs/api-reference/connectors/list List available connectors and their connection status. Returns all supported third-party integrations (e.g., Google Sheets, TikTok, YouTube, X (Twitter), LinkedIn) along with whether they are currently connected. ## GET /api/connectors Full OpenAPI specification: https://recoupable.dev/docs/spec/social.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Social", "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/connectors": { "get": { "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "description": "List available connectors and their connection status. Returns all supported third-party integrations (e.g., Google Sheets, TikTok, YouTube, X (Twitter), LinkedIn) along with whether they are currently connected.", "parameters": [ { "name": "account_id", "in": "query", "description": "Optional account ID to get connectors for a different account (e.g., an artist or organization). The authenticated account must have access to the specified account. Omit to get your own account's connectors.", "required": false, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Connectors retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ConnectorsResponse" } } } }, "400": { "description": "Bad request - invalid account_id format", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "Unauthorized - invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "Forbidden - no access to the specified account", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "ConnectorsResponse": { "type": "object", "required": [ "success", "connectors" ], "properties": { "success": { "type": "boolean" }, "connectors": { "type": "array", "items": { "$ref": "#/components/schemas/ConnectorInfo" }, "description": "List of available connectors with connection status" } } }, "Error": { "required": [ "error", "message" ], "type": "object", "properties": { "error": { "type": "integer", "format": "int32" }, "message": { "type": "string" } } }, "ConnectorInfo": { "type": "object", "required": [ "slug", "name", "isConnected" ], "properties": { "slug": { "type": "string", "description": "Unique identifier for the connector (e.g., 'googlesheets', 'tiktok', 'youtube', 'twitter', 'linkedin')" }, "name": { "type": "string", "description": "Human-readable name of the connector" }, "isConnected": { "type": "boolean", "description": "Whether the connector is currently connected" }, "connectedAccountId": { "type": "string", "description": "The connected account ID (only present when isConnected is true)" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Authorize Connector Source: https://recoupable.dev/docs/api-reference/connectors/authorize Generate an OAuth authorization URL for connecting a third-party service. Redirect to the returned URL to complete the OAuth flow. ## POST /api/connectors Full OpenAPI specification: https://recoupable.dev/docs/spec/social.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Social", "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/connectors": { "post": { "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "description": "Generate an OAuth authorization URL for connecting a third-party service. Redirect to the returned URL to complete the OAuth flow.", "requestBody": { "description": "Authorization request details", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AuthorizeConnectorRequest" } } } }, "responses": { "200": { "description": "Authorization URL generated successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AuthorizeConnectorResponse" } } } }, "400": { "description": "Bad request - invalid connector or 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" } } } }, "403": { "description": "Forbidden - no access to the specified account", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "AuthorizeConnectorRequest": { "type": "object", "required": [ "connector" ], "properties": { "connector": { "type": "string", "description": "The connector slug to authorize (e.g., 'googlesheets', 'tiktok', 'youtube', 'twitter', 'linkedin')" }, "callback_url": { "type": "string", "format": "uri", "description": "Optional custom callback URL after OAuth completion" }, "account_id": { "type": "string", "format": "uuid", "description": "Optional account ID to connect a service for a different account (e.g., an artist or organization). Use this when connecting an artist's TikTok or other service. The authenticated account must have access. Omit to connect for your own account." } } }, "AuthorizeConnectorResponse": { "type": "object", "required": [ "success", "data" ], "properties": { "success": { "type": "boolean" }, "data": { "type": "object", "required": [ "connector", "redirectUrl" ], "properties": { "connector": { "type": "string", "description": "The connector slug being authorized" }, "redirectUrl": { "type": "string", "format": "uri", "description": "URL to redirect to for OAuth authorization" } } } } }, "Error": { "required": [ "error", "message" ], "type": "object", "properties": { "error": { "type": "integer", "format": "int32" }, "message": { "type": "string" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Disconnect Connector Source: https://recoupable.dev/docs/api-reference/connectors/disconnect Disconnect a connected account from a third-party service. This revokes the OAuth connection and removes stored credentials. ## DELETE /api/connectors Full OpenAPI specification: https://recoupable.dev/docs/spec/social.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Social", "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/connectors": { "delete": { "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "description": "Disconnect a connected account from a third-party service. This revokes the OAuth connection and removes stored credentials.", "requestBody": { "description": "Connection to disconnect", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DisconnectConnectorRequest" } } } }, "responses": { "200": { "description": "Connector disconnected successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DisconnectConnectorResponse" } } } }, "400": { "description": "Bad request - missing or 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" } } } }, "403": { "description": "Forbidden - no access to the specified account or connection", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "DisconnectConnectorRequest": { "type": "object", "required": [ "connected_account_id" ], "properties": { "connected_account_id": { "type": "string", "description": "The connected account ID to disconnect (from ConnectorInfo.connectedAccountId)" }, "account_id": { "type": "string", "format": "uuid", "description": "Optional account ID when disconnecting a connection that belongs to a different account (e.g., an artist). Required when the connection was created for another account rather than your own. The authenticated account must have access." } } }, "DisconnectConnectorResponse": { "type": "object", "required": [ "success" ], "properties": { "success": { "type": "boolean" }, "message": { "type": "string", "description": "Status message" } } }, "Error": { "required": [ "error", "message" ], "type": "object", "properties": { "error": { "type": "integer", "format": "int32" }, "message": { "type": "string" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # List Connector Actions Source: https://recoupable.dev/docs/api-reference/connectors/list-actions List the executable actions available across the authenticated account's connectors. Each action is a single tool that can be invoked via POST /api/connectors/actions — for example, the `googlesheets` connector exposes actions like `GOOGLESHEETS_WRITE_SPREADSHEET`. Actions whose parent connector is not yet connected are returned with `isConnected: false` and cannot be executed until the connector is authorized via POST /api/connectors. ## GET /api/connectors/actions Full OpenAPI specification: https://recoupable.dev/docs/spec/social.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Social", "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/connectors/actions": { "get": { "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "description": "List the executable actions available across the authenticated account's connectors. Each action is a single tool that can be invoked via POST /api/connectors/actions — for example, the `googlesheets` connector exposes actions like `GOOGLESHEETS_WRITE_SPREADSHEET`. Actions whose parent connector is not yet connected are returned with `isConnected: false` and cannot be executed until the connector is authorized via POST /api/connectors.", "parameters": [ { "name": "account_id", "in": "query", "description": "Optional account ID to list actions for a different account (e.g., an artist or organization). The authenticated account must have access. Omit to list actions for your own account.", "required": false, "schema": { "type": "string", "format": "uuid" } } ], "responses": { "200": { "description": "Actions retrieved successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ConnectorActionsResponse" } } } }, "400": { "description": "Bad request — invalid account_id format", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "Unauthorized — invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "Forbidden — no access to the specified account", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "ConnectorActionsResponse": { "type": "object", "required": [ "success", "actions" ], "properties": { "success": { "type": "boolean" }, "actions": { "type": "array", "items": { "$ref": "#/components/schemas/ConnectorAction" }, "description": "Available actions across all connectors the authenticated account has access to. Both connected and unconnected actions are returned — check `isConnected` per action before attempting execution." } } }, "Error": { "required": [ "error", "message" ], "type": "object", "properties": { "error": { "type": "integer", "format": "int32" }, "message": { "type": "string" } } }, "ConnectorAction": { "type": "object", "required": [ "slug", "name", "description", "parameters", "connectorSlug", "isConnected" ], "properties": { "slug": { "type": "string", "description": "Unique identifier for the action — pass this as `actionSlug` when calling POST /api/connectors/actions. Action slugs are always UPPERCASE_SNAKE_CASE (e.g. `GITHUB_CREATE_ISSUE`, `GMAIL_FETCH_EMAILS`, `GOOGLESHEETS_WRITE_SPREADSHEET`)." }, "name": { "type": "string", "description": "Human-readable action name." }, "description": { "type": "string", "description": "What the action does. Use this to pick the right action from the catalog." }, "parameters": { "type": "object", "description": "JSON Schema describing the parameters this action expects. Pass values matching this schema in the `parameters` field of the execute request.", "additionalProperties": true }, "connectorSlug": { "type": "string", "description": "Slug of the parent connector this action belongs to (e.g. 'googlesheets', 'tiktok', 'youtube', 'twitter', 'linkedin'). Matches the `slug` returned by GET /api/connectors." }, "isConnected": { "type": "boolean", "description": "Whether the parent connector is currently authorized for this account. Actions can only be executed when this is true." } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Upload Connector File Source: https://recoupable.dev/docs/api-reference/connectors/upload-file Stage an image into Connector file storage so it can be attached to a connector action that accepts a file_uploadable field. The returned descriptor is embedded in a file_uploadable array on [Execute Connector Action](/docs/api-reference/connectors/execute-action). ## POST /api/connectors/files Full OpenAPI specification: https://recoupable.dev/docs/spec/social.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Social", "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/connectors/files": { "post": { "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "description": "Stage an image into Connector file storage so it can be attached to a connector action that accepts a file_uploadable field. The returned descriptor is embedded in a file_uploadable array on [Execute Connector Action](/docs/api-reference/connectors/execute-action).", "requestBody": { "description": "The image URL to stage and the action it will be attached to.", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UploadConnectorFileRequest" } } } }, "responses": { "200": { "description": "Image staged. Returns the Connector file descriptor to embed in the action's file_uploadable array.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UploadConnectorFileResponse" } } } }, "400": { "description": "Bad request — missing or invalid `url` (must be a reachable URL) or `toolSlug`.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "Unauthorized — invalid or missing API key.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "502": { "description": "Upstream failure staging the file into Connector storage (the image fetch failed or Connector errored).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "UploadConnectorFileRequest": { "type": "object", "required": [ "url", "toolSlug" ], "properties": { "url": { "type": "string", "format": "uri", "description": "Publicly reachable URL of the image to stage. Fetched server-side and uploaded to Connector storage. Required." }, "toolSlug": { "type": "string", "description": "The action slug the image will be attached to, UPPERCASE_SNAKE_CASE (e.g. `LINKEDIN_CREATE_LINKED_IN_POST`). Scopes the upload to that tool/toolkit. Required." } } }, "UploadConnectorFileResponse": { "type": "object", "required": [ "success", "name", "mimetype", "s3key" ], "properties": { "success": { "type": "boolean" }, "name": { "type": "string", "description": "Stored filename." }, "mimetype": { "type": "string", "description": "Detected MIME type of the stored file." }, "s3key": { "type": "string", "description": "Connector storage key. Pass this together with `name` and `mimetype` in the action's file_uploadable array." } } }, "Error": { "required": [ "error", "message" ], "type": "object", "properties": { "error": { "type": "integer", "format": "int32" }, "message": { "type": "string" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Execute Connector Action Source: https://recoupable.dev/docs/api-reference/connectors/execute-action Execute a connector action with the given parameters. The `actionSlug` must come from a prior call to GET /api/connectors/actions, and `parameters` must match the `parameters` JSON Schema returned for that action. The action's parent connector must be currently connected (`isConnected: true` in the catalog) — otherwise this endpoint returns 409. The `result` field passes through whatever the underlying connector returns; its shape is action-specific. To attach an image to a `file_uploadable` parameter (e.g. `images`), first stage it with [Upload Connector File](/docs/api-reference/connectors/upload-file) and pass the returned descriptor. ## POST /api/connectors/actions Full OpenAPI specification: https://recoupable.dev/docs/spec/social.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Social", "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/connectors/actions": { "post": { "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "description": "Execute a connector action with the given parameters. The `actionSlug` must come from a prior call to GET /api/connectors/actions, and `parameters` must match the `parameters` JSON Schema returned for that action. The action's parent connector must be currently connected (`isConnected: true` in the catalog) — otherwise this endpoint returns 409. The `result` field passes through whatever the underlying connector returns; its shape is action-specific. To attach an image to a `file_uploadable` parameter (e.g. `images`), first stage it with [Upload Connector File](/docs/api-reference/connectors/upload-file) and pass the returned descriptor.", "requestBody": { "description": "Action to execute and the parameters for it", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ExecuteConnectorActionRequest" } } } }, "responses": { "200": { "description": "Action executed successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ExecuteConnectorActionResponse" } } } }, "400": { "description": "Bad request — missing or invalid parameters (e.g. `parameters` does not match the action's schema)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "Unauthorized — invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "Forbidden — no access to the specified account", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "Action slug not found in any of the account's connectors", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "409": { "description": "The action's parent connector is not currently connected. Authorize it via POST /api/connectors before retrying.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "502": { "description": "Upstream connector failure (the third-party service errored or timed out)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "ExecuteConnectorActionRequest": { "type": "object", "required": [ "actionSlug", "parameters" ], "properties": { "actionSlug": { "type": "string", "description": "Action slug from GET /api/connectors/actions. Always UPPERCASE_SNAKE_CASE (e.g. `GOOGLESHEETS_WRITE_SPREADSHEET`). Required." }, "parameters": { "type": "object", "description": "Action-specific parameters matching the `parameters` JSON Schema returned by GET /api/connectors/actions for this `actionSlug`. The connector validates these against the cached schema before executing — invalid shapes return 400. Required.", "additionalProperties": true }, "account_id": { "type": "string", "format": "uuid", "description": "Optional account ID to execute the action on a different account (e.g., an artist). The authenticated account must have access. Omit to execute on your own account." } } }, "ExecuteConnectorActionResponse": { "type": "object", "required": [ "success", "result" ], "properties": { "success": { "type": "boolean" }, "result": { "type": "object", "additionalProperties": true, "description": "Pass-through of the underlying connector's response payload. Shape is action-specific — consult the action's parameters schema and the third-party service's own documentation for what to expect. The server-side wrapper does not transform this field." }, "executedAt": { "type": "string", "format": "date-time", "description": "ISO 8601 timestamp of when the action was executed server-side." } } }, "Error": { "required": [ "error", "message" ], "type": "object", "properties": { "error": { "type": "integer", "format": "int32" }, "message": { "type": "string" } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Compose music Source: https://recoupable.dev/docs/api-reference/complete/content/post-music-compose Generate a song from a text prompt or a composition plan. The prompt can describe mood, genre, instruments, lyrics, and structure. Alternatively, pass a full composition_plan for fine-grained control over sections, styles, and lyrics. Returns binary audio in the requested format (default MP3). ## POST /api/music/compose Full OpenAPI specification: https://recoupable.dev/docs/spec/content.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Content", "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/music/compose": { "post": { "description": "Generate a song from a text prompt or a composition plan. The prompt can describe mood, genre, instruments, lyrics, and structure. Alternatively, pass a full composition_plan for fine-grained control over sections, styles, and lyrics. Returns binary audio in the requested format (default MP3).", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "requestBody": { "description": "Music composition parameters", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicComposeRequest" } } } }, "responses": { "200": { "description": "Audio generated successfully", "headers": { "song-id": { "schema": { "type": "string" }, "description": "Unique identifier for the generated song" } }, "content": { "audio/mpeg": { "schema": { "type": "string", "format": "binary" } } } }, "400": { "description": "Validation failed — invalid or missing request body fields", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicErrorResponse" } } } }, "401": { "description": "Unauthorized — invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicErrorResponse" } } } } } } } }, "components": { "schemas": { "MusicComposeRequest": { "type": "object", "description": "Parameters for generating a song. Provide either a text prompt or a composition_plan.", "properties": { "prompt": { "type": "string", "description": "Text prompt describing the desired song — mood, genre, instruments, lyrics, structure." }, "composition_plan": { "type": "object", "description": "Full composition plan for fine-grained control over sections, styles, and lyrics. Use POST /api/music/plan to generate one." }, "duration": { "type": "number", "description": "Desired duration in seconds.", "default": 30 }, "output_format": { "type": "string", "description": "Audio output format (e.g. mp3_44100_192).", "default": "mp3_44100_192" }, "sign_with_c2pa": { "type": "boolean", "description": "Sign the output with C2PA content credentials.", "default": false } } }, "MusicErrorResponse": { "type": "object", "required": [ "error" ], "description": "Error response from a music endpoint.", "properties": { "error": { "type": "string", "description": "Error message describing what went wrong." } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Compose Music with Metadata Source: https://recoupable.dev/docs/api-reference/complete/content/post-music-compose-detailed Generate a song with metadata and optional word-level timestamps. Returns a multipart/mixed response: the first part is JSON metadata (song ID, duration, seed, etc.) and the second part is binary audio. Use with_timestamps to get precise timing for each word in the lyrics. ## POST /api/music/compose/detailed Full OpenAPI specification: https://recoupable.dev/docs/spec/content.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Content", "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/music/compose/detailed": { "post": { "description": "Generate a song with metadata and optional word-level timestamps. Returns a multipart/mixed response: the first part is JSON metadata (song ID, duration, seed, etc.) and the second part is binary audio. Use with_timestamps to get precise timing for each word in the lyrics.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "requestBody": { "description": "Music composition parameters with optional timestamps", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicComposeDetailedRequest" } } } }, "responses": { "200": { "description": "Audio and metadata generated successfully. Response is multipart/mixed: JSON metadata followed by binary audio.", "content": { "multipart/mixed": { "schema": { "type": "string", "format": "binary" } } } }, "400": { "description": "Validation failed — invalid or missing request body fields", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicErrorResponse" } } } }, "401": { "description": "Unauthorized — invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicErrorResponse" } } } } } } } }, "components": { "schemas": { "MusicComposeDetailedRequest": { "type": "object", "description": "Parameters for generating a song with metadata and optional word-level timestamps.", "properties": { "prompt": { "type": "string", "description": "Text prompt describing the desired song — mood, genre, instruments, lyrics, structure." }, "composition_plan": { "type": "object", "description": "Full composition plan for fine-grained control over sections, styles, and lyrics. Use POST /api/music/plan to generate one." }, "duration": { "type": "number", "description": "Desired duration in seconds.", "default": 30 }, "output_format": { "type": "string", "description": "Audio output format (e.g. mp3_44100_192).", "default": "mp3_44100_192" }, "sign_with_c2pa": { "type": "boolean", "description": "Sign the output with C2PA content credentials.", "default": false }, "with_timestamps": { "type": "boolean", "description": "Include word-level timestamps in the metadata response.", "default": false } } }, "MusicErrorResponse": { "type": "object", "required": [ "error" ], "description": "Error response from a music endpoint.", "properties": { "error": { "type": "string", "description": "Error message describing what went wrong." } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Stream music Source: https://recoupable.dev/docs/api-reference/complete/content/post-music-stream Generate a song and stream audio chunks in real time. Use this endpoint when you want to begin playback before the full song is generated. The response streams binary audio chunks as they are produced. ## POST /api/music/stream Full OpenAPI specification: https://recoupable.dev/docs/spec/content.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Content", "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/music/stream": { "post": { "description": "Generate a song and stream audio chunks in real time. Use this endpoint when you want to begin playback before the full song is generated. The response streams binary audio chunks as they are produced.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "requestBody": { "description": "Music streaming parameters", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicStreamRequest" } } } }, "responses": { "200": { "description": "Audio stream started successfully", "headers": { "song-id": { "schema": { "type": "string" }, "description": "Unique identifier for the generated song" } }, "content": { "audio/mpeg": { "schema": { "type": "string", "format": "binary" } } } }, "400": { "description": "Validation failed — invalid or missing request body fields", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicErrorResponse" } } } }, "401": { "description": "Unauthorized — invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicErrorResponse" } } } } } } } }, "components": { "schemas": { "MusicStreamRequest": { "type": "object", "description": "Parameters for generating a song with real-time audio streaming.", "properties": { "prompt": { "type": "string", "description": "Text prompt describing the desired song — mood, genre, instruments, lyrics, structure." }, "composition_plan": { "type": "object", "description": "Full composition plan for fine-grained control over sections, styles, and lyrics. Use POST /api/music/plan to generate one." }, "duration": { "type": "number", "description": "Desired duration in seconds.", "default": 30 }, "output_format": { "type": "string", "description": "Audio output format (e.g. mp3_44100_192).", "default": "mp3_44100_192" }, "sign_with_c2pa": { "type": "boolean", "description": "Sign the output with C2PA content credentials.", "default": false } } }, "MusicErrorResponse": { "type": "object", "required": [ "error" ], "description": "Error response from a music endpoint.", "properties": { "error": { "type": "string", "description": "Error message describing what went wrong." } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Create a composition plan Source: https://recoupable.dev/docs/api-reference/complete/content/post-music-plan Create a composition plan from a text prompt. A composition plan is a structured representation of a song — sections, styles, lyrics, and durations — that you can review and tweak before passing to the compose endpoint. This endpoint is free and does not consume credits. ## POST /api/music/plan Full OpenAPI specification: https://recoupable.dev/docs/spec/content.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Content", "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/music/plan": { "post": { "description": "Create a composition plan from a text prompt. A composition plan is a structured representation of a song — sections, styles, lyrics, and durations — that you can review and tweak before passing to the compose endpoint. This endpoint is free and does not consume credits.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "requestBody": { "description": "Plan creation parameters", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicCreatePlanRequest" } } } }, "responses": { "200": { "description": "Composition plan created successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicCreatePlanResponse" } } } }, "400": { "description": "Validation failed — invalid or missing request body fields", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicErrorResponse" } } } }, "401": { "description": "Unauthorized — invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicErrorResponse" } } } } } } } }, "components": { "schemas": { "MusicCreatePlanRequest": { "type": "object", "required": [ "prompt" ], "description": "Parameters for creating a composition plan from a text prompt.", "properties": { "prompt": { "type": "string", "description": "Text prompt describing the desired song." } } }, "MusicCreatePlanResponse": { "type": "object", "description": "A structured composition plan that can be reviewed, edited, and passed to the compose endpoint.", "properties": { "plan": { "type": "object", "description": "The composition plan with sections, styles, lyrics, and durations.", "properties": { "sections": { "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string", "description": "Section name (e.g. intro, verse, chorus)" }, "duration": { "type": "number", "description": "Section duration in seconds" }, "style": { "type": "string", "description": "Style description for this section" }, "lyrics": { "type": "string", "description": "Lyrics for this section" } } } } } } } }, "MusicErrorResponse": { "type": "object", "required": [ "error" ], "description": "Error response from a music endpoint.", "properties": { "error": { "type": "string", "description": "Error message describing what went wrong." } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Create music from video Source: https://recoupable.dev/docs/api-reference/complete/content/post-music-video-to-music Generate background music from video files. Upload 1–10 video files via multipart/form-data (max 200 MB total). The AI analyzes the video content and generates music that matches the mood, pacing, and style. Optionally provide a text description and style tags to guide the output. ## POST /api/music/video-to-music Full OpenAPI specification: https://recoupable.dev/docs/spec/content.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Content", "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/music/video-to-music": { "post": { "description": "Generate background music from video files. Upload 1–10 video files via multipart/form-data (max 200 MB total). The AI analyzes the video content and generates music that matches the mood, pacing, and style. Optionally provide a text description and style tags to guide the output.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "requestBody": { "description": "Video files and optional generation parameters", "required": true, "content": { "multipart/form-data": { "schema": { "type": "object", "properties": { "files": { "type": "array", "items": { "type": "string", "format": "binary" }, "minItems": 1, "maxItems": 10, "description": "Video files to analyze (max 200 MB total)" }, "description": { "type": "string", "maxLength": 1000, "description": "Optional text description to guide the music generation" }, "tags": { "type": "array", "items": { "type": "string" }, "maxItems": 10, "description": "Optional style tags to influence the output (max 10)" }, "sign_with_c2pa": { "type": "boolean", "default": false, "description": "Sign the output with C2PA content credentials" }, "output_format": { "type": "string", "description": "Audio output format (e.g. mp3_44100_192)" } }, "required": [ "files" ] } } } }, "responses": { "200": { "description": "Background music generated successfully", "headers": { "song-id": { "schema": { "type": "string" }, "description": "Unique identifier for the generated song" } }, "content": { "audio/mpeg": { "schema": { "type": "string", "format": "binary" } } } }, "400": { "description": "Validation failed — invalid or missing request body fields", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicErrorResponse" } } } }, "401": { "description": "Unauthorized — invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicErrorResponse" } } } } } } } }, "components": { "schemas": { "MusicErrorResponse": { "type": "object", "required": [ "error" ], "description": "Error response from a music endpoint.", "properties": { "error": { "type": "string", "description": "Error message describing what went wrong." } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ``` # Separate audio stems Source: https://recoupable.dev/docs/api-reference/complete/content/post-music-stem-separation Separate an audio file into individual stems (vocals, drums, bass, etc.). Upload an audio file via multipart/form-data. Choose between two-stem separation (vocals + instrumental) or six-stem separation (vocals, drums, bass, guitar, piano, other). Returns a ZIP archive containing the separated stems. ## POST /api/music/stem-separation Full OpenAPI specification: https://recoupable.dev/docs/spec/content.json ## Authentication This operation requires one of the security alternatives in the specification below. Security scheme definitions are included where present in the published specification. [Authentication guide](https://recoupable.dev/docs/authentication) ## Operation and referenced schemas ```json { "openapi": "3.1.0", "info": { "title": "Recoup API - Content", "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/music/stem-separation": { "post": { "description": "Separate an audio file into individual stems (vocals, drums, bass, etc.). Upload an audio file via multipart/form-data. Choose between two-stem separation (vocals + instrumental) or six-stem separation (vocals, drums, bass, guitar, piano, other). Returns a ZIP archive containing the separated stems.", "security": [ { "apiKeyAuth": [] }, { "bearerAuth": [] } ], "requestBody": { "description": "Audio file and separation parameters", "required": true, "content": { "multipart/form-data": { "schema": { "type": "object", "properties": { "file": { "type": "string", "format": "binary", "description": "Audio file to separate into stems" }, "stem_variation_id": { "type": "string", "enum": [ "two_stems_v1", "six_stems_v1" ], "default": "six_stems_v1", "description": "Stem separation mode. `two_stems_v1` produces vocals + instrumental. `six_stems_v1` produces vocals, drums, bass, guitar, piano, and other." }, "sign_with_c2pa": { "type": "boolean", "default": false, "description": "Sign the output with C2PA content credentials" }, "output_format": { "type": "string", "description": "Audio output format (e.g. mp3_44100_192)" } }, "required": [ "file" ] } } } }, "responses": { "200": { "description": "Stems separated successfully. Returns a ZIP archive containing the individual stems.", "content": { "application/zip": { "schema": { "type": "string", "format": "binary" } } } }, "400": { "description": "Validation failed — invalid or missing request body fields", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicErrorResponse" } } } }, "401": { "description": "Unauthorized — invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicErrorResponse" } } } }, "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicErrorResponse" } } } } } } } }, "components": { "schemas": { "MusicErrorResponse": { "type": "object", "required": [ "error" ], "description": "Error response from a music endpoint.", "properties": { "error": { "type": "string", "description": "Error message describing what went wrong." } } } }, "securitySchemes": { "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "Your Recoup API key. [Learn more](/docs/quickstart#api-keys)." }, "bearerAuth": { "type": "http", "scheme": "bearer" } } } } ```