MCPAPI Reference

API Reference

HTTP endpoints for ONCE MCP. User-scoped endpoints usually require Authorization: Bearer <token>. Large-file upload chunk + complete requests can also use the scoped x-mcp-upload-token returned by prepare_local_file_upload.

Base URL: https://beta.once.app

Note: MCP submissions debit credits from the authenticated ONCE account.

Tool name resolution: calling an unknown tool name returns an error with did-you-mean suggestions plus a pointer to tools/list, and common aliases (for example release_status or check_release_status for get_release_status) resolve to the canonical tool automatically.

Provenance & Billing

ONCE classifies every MCP JSON-RPC request with a provenance used for auditing and analytics. Billing is the same across every surface: provenance does not change the rate.

ValueDetected via
AIMDRequest Origin / Referer matches app.aimusicdistributor.com, or the JSON-RPC arguments include "provenance": "AIMD" (for server-to-server calls).
MCPEvery other authenticated MCP client (default).

Billing rule (applies to every surface):

  • 1 credit per human song
  • 2 credits per AI song (AI-detected by the audio scanner, or generated through OMG)

The provenance is persisted on the release row (releases.mcp_provenance) and surfaced in the admin AI dashboard alongside the AI model classification.


Authentication

OAuth Metadata Discovery

GET/.well-known/oauth-protected-resource

Protected Resource Metadata for MCP OAuth discovery.

GET/.well-known/oauth-authorization-server

OAuth Authorization Server metadata (RFC 8414).

GET/.well-known/openid-configuration

OpenID Connect metadata compatibility endpoint.

OAuth Endpoints

GET/oauth/authorize

Authorization endpoint for MCP client browser sign-in.

POST/oauth/token

Token endpoint for authorization code + PKCE exchange and refresh token grants.

POST/oauth/register

Dynamic client registration endpoint for MCP client interoperability.

Most MCP clients use these automatically after a 401 Unauthorized challenge from /api/mcp.


First-Party Account Tools

The MCP server exposes two unauthenticated account tools for allowlisted first-party clients such as AIMD. They are still called through POST /api/mcp as JSON-RPC tools/call requests, require PKCE (code_challenge_method: "S256"), and return a short-lived, one-shot authorization_code that must be exchanged at /oauth/token with the matching code_verifier.

Random dynamic-client-registration clients cannot call these tools. ONCE checks the supplied client_id against a server-side allowlist and rate-limits by both IP and client_id.

Tool: create_account

Creates an ONCE account for AIMD and returns an OAuth authorization code.

{
  "email": "artist@example.com",
  "password": "correct-horse-battery-staple",
  "first_name": "Ada",
  "last_name": "Lovelace",
  "client_id": "AIMD_DCR_CLIENT_ID",
  "redirect_uri": "https://app.aimusicdistributor.com/oauth/callback",
  "code_challenge": "BASE64URL_SHA256_VERIFIER",
  "code_challenge_method": "S256",
  "scope": "once:mcp",
  "provenance": "AIMD"
}
{
  "userId": "uuid",
  "email": "artist@example.com",
  "authorization_code": "..."
}

Typed error.data.code values: email_taken, weak_password, invalid_client, pkce_required.

Tool: password_authorize

Authorizes an existing ONCE account for AIMD and returns the same PKCE-bound authorization code shape.

{
  "email": "artist@example.com",
  "password": "correct-horse-battery-staple",
  "client_id": "AIMD_DCR_CLIENT_ID",
  "redirect_uri": "https://app.aimusicdistributor.com/oauth/callback",
  "code_challenge": "BASE64URL_SHA256_VERIFIER",
  "code_challenge_method": "S256",
  "scope": "once:mcp",
  "provenance": "AIMD"
}

Typed error.data.code values: invalid_credentials, account_locked, invalid_client, pkce_required.


Uploads

Multipart Upload

POST/api/mcp/upload

Upload cover art or audio via multipart form-data.

Form Fields

FieldTypeDescription
filebinaryThe file to upload
typestringcoverArt or audio

Response

{
  "success": true,
  "fileUrl": "/api/files/cover-art/USER_ID/file.jpg",
  "fileName": "file.jpg",
  "userId": "..."
}

Local Upload Session

For large local files in Claude Code or Cursor, call the prepare_local_file_upload MCP tool first. It returns:

  • session_id
  • upload_token
  • upload_header_name (x-mcp-upload-token)
  • chunk_endpoint
  • complete_endpoint
  • recommended_chunk_bytes
  • max_chunk_bytes

Upload Chunk

POST/api/mcp/upload/chunk

Upload a single chunk using the scoped token returned by prepare_local_file_upload.

Headers

  • x-mcp-upload-token: <upload_token>
  • For raw bytes: x-session-id, x-chunk-index, x-upload-type

Multipart Fields

  • chunk
  • chunkIndex
  • sessionId
  • type

JSON Request

{
  "chunkBase64": "...",
  "chunkIndex": 0,
  "sessionId": "uuid-v4",
  "type": "audio"
}

Raw Bytes Body

Send the chunk as application/octet-stream with the headers listed above.

For large uploads, prefer raw bytes or multipart over JSON base64 to avoid extra payload overhead.

Complete Chunked Upload

POST/api/mcp/upload/complete

Finalize a chunked upload session.

Request

{
  "sessionId": "uuid-v4",
  "fileName": "Track.wav",
  "fileType": "audio/wav",
  "type": "audio"
}

Include x-mcp-upload-token: <upload_token> on the completion request.


Drafts

Save Draft

POST/api/mcp/draft

Persist a draft snapshot before submission.

Request

{
  "releaseId": "optional",
  "conversationId": "optional",
  "mode": "delta",
  "release": { ... },
  "tracks": [ ... ],
  "trackPatches": [ ... ],
  "uploadRequests": [ ... ],
  "status": "collecting"
}

Releases

Get Distribution Stores

Tool: get_distribution_stores

Returns supported DSP/store IDs for release.distribution_store_ids. Send a non-empty array to select DSPs, or null to distribute to all supported stores.

Set Distribution Stores

Tool: set_distribution_stores

Set or reset a release’s target store list. The change applies to the next submission or redistribution of the release.

ArgumentTypeDescription
releaseIdstringRequired. ONCE release id. Must be owned by the user.
storeIdsnumber[] or nullRevelator store ids from get_distribution_stores to target, or null to reset to the default set.

Response

{
  "ok": true,
  "releaseId": "uuid",
  "distributionStoreIds": [1, 9, 13],
  "note": "Store targeting saved. It applies to the next submission or redistribution of this release."
}

Passing an array with no valid store ids returns an error pointing back to get_distribution_stores.

Submit Release

POST/api/mcp/submit

Submit a release for distribution. Rate limited; check retryAfterSeconds on 429.

Request

{
  "release": {
    "title": "My Release",
    "primary_artist_name": "Artist Name",
    "genre": "Pop",
    "sub_genre": "Dance Pop",
    "release_date": "2026-02-01",
    "label": "Artist Name Records",
    "distribution_store_ids": [1, 9, 13, 319],
    "audio_language": "en",
    "metadata_language": "en",
    "pline_year": "2026",
    "pline_owner": "Artist Name Records",
    "cline_year": "2026",
    "cline_owner": "Artist Name Records",
    "cover_art_file_url": "/api/files/cover-art/USER_ID/cover.jpg",
    "contributors": [
      { "name": "First Producer", "role": "Producer" },
      { "name": "First Engineer", "role": "Engineer" }
    ]
  },
  "tracks": [
    {
      "title": "My Release",
      "primary_artist_name": "Artist Name",
      "audio_file_url": "/api/files/audio/USER_ID/track.wav",
      "explicit_flag": false,
      "track_type": "original",
      "language": "en",
      "pline_year": "2026",
      "pline_owner": "Artist Name Records",
      "cline_year": "2026",
      "cline_owner": "Artist Name Records",
      "writers": [{ "name": "First Last" }],
      "contributors": [
        { "name": "First Producer", "role": "Producer" },
        { "name": "First Engineer", "role": "Engineer" }
      ]
    }
  ],
  "releaseId": "optional",
  "conversationId": "optional",
  "tokenOffset": false
}

The runtime MVP requirements remain backwards-compatible, but new clients should provide DSP selection, record label, C/P copyright credits at release and track level, track_type, and Producer/Engineer role credits. For remixes, use title_version: "Remix" plus a Remixer contributor; do not send remix as track_type.

Optional add-on

FieldTypeDescription
tokenOffsetbooleanDefault false. When true, charges a flat +1 credit ($1) that funds tokenoffset.com to offset the AI environmental cost of the release. Recorded in release_token_offsets and as a separate credit_transactions row with ref=token_offset:<release_id>. Available on every MCP surface: agents should offer the choice to the user before calling submit_release.

List Releases

GET/api/mcp/releases

Get recent releases for the authenticated user.

Query Parameters

ParameterTypeDescription
limitnumberMax results (default 100)

Get Release Metadata

GET/api/mcp/releases/:releaseId/metadata

Get merged metadata for a release.

Get Release Status

GET/api/mcp/releases/:releaseId/status

Get store delivery and aggregate status.

Get Job Status

GET/api/mcp/release-jobs/:releaseId

Get processing job status and errors.


AI Cover Art Generation

The generate_cover_art MCP tool produces (or edits) an album cover from a text prompt, runs the result through the same square-crop pipeline as user uploads, and stores it in the caller’s cover-art bucket. The returned fileUrl is a drop-in replacement for release.cover_art_file_url in submit_release.

Tool: generate_cover_art

ArgumentTypeDescription
promptstringRequired. 1-1500 character description of the desired artwork. When editing, describe what should change.
baseFileUrlstringOptional. A fileUrl from a previous generate_cover_art or upload_file call. Must point to the caller’s cover-art bucket. The new image becomes an edit of this base image.
baseImageBase64stringOptional. Base64-encoded base image (alternative to baseFileUrl). Accepts a data: prefix or raw base64.
baseImageMimeTypestringOptional MIME override for baseImageBase64 (e.g. image/png, image/jpeg).
releaseIdstringOptional release ID (must be owned by the user) for cost attribution.
conversationIdstringOptional conversation ID for cost attribution.
returnBase64booleanIf true, also return the raw image base64 alongside the fileUrl.
fileNamestringOptional filename hint (defaults to mcp-cover-<timestamp>.png).

Response

{
  "fileUrl": "/api/files/cover-art/USER_ID/<uuid>.png",
  "bucket": "cover-art",
  "key": "USER_ID/<uuid>.png",
  "fileName": "<uuid>.png",
  "prompt": "Lo-fi vinyl in a sunset cafe, warm pastels, 70s poster art",
  "model": "gemini-3.1-flash-image-preview",
  "modelVariant": "nano_banana_2",
  "imageSize": "1K",
  "aspectRatio": "1:1",
  "edited": false,
  "rateLimit": { "remaining": 4 }
}

edited is true whenever the call passed a base image so Gemini was asked to refine it.

Iterative Editing

To iterate on a previous generation (the same flow as the in-app cover art generator), pass the prior fileUrl and a delta prompt:

{
  "jsonrpc": "2.0",
  "id": 12,
  "method": "tools/call",
  "params": {
    "name": "generate_cover_art",
    "arguments": {
      "prompt": "Make the sky more dramatic and add neon signage in the window",
      "baseFileUrl": "/api/files/cover-art/USER_ID/<previous-uuid>.png"
    }
  }
}

baseFileUrl must belong to the authenticated user and live in the cover-art bucket. Use baseImageBase64 instead if you already have the bytes locally (e.g. you set returnBase64: true on a prior call).

Model Tier

generate_cover_art always uses the non-patron Nano Banana 2 model (gemini-3.1-flash-image-preview) at 1K. Patron-only model variants are not exposed through the MCP, patrons that want higher fidelity should use the in-app cover art generator.

Rate Limits

Shared with the in-app generator under the cover_art_generate budget (default: 5 requests per 2 minutes per user). On rejection, the JSON-RPC response carries error.code = 429 and error.data.retryAfterSeconds.


AI Detection

ONCE runs every audio upload through the Vobile/Pex AI Song Detector. The detect_audio_ai MCP tool exposes the cached result so agents can show the AI flag and predicted-model classification before submission.

Tool: detect_audio_ai

ArgumentTypeDescription
fileUrlstringRequired. fileUrl returned by an MCP upload tool (must point to the audio bucket).
fileNamestringOptional original filename for diagnostics.
releaseIdstringOptional release ID to associate the detection event with.
conversationIdstringOptional conversation ID for snapshot writeback.
trackIndexnumberOptional 0-based track index used to update the matching draft track.

Response

{
  "status": "ok",
  "containsAi": true,
  "aiScore": 0.93,
  "predictedModel": "Suno",
  "predictedModelScore": 0.81,
  "provider": "pex",
  "model": "ai-song-detector",
  "message": null,
  "cached": false
}

status is ok whenever the detector returned a result (regardless of containsAi). Other values include skipped, download_failed, and too_large. Detection results are cached per file, so calling this tool repeatedly for the same fileUrl returns the cached result with "cached": true.


Performance Analytics

Two read-only MCP tools report streaming performance for the authenticated user’s releases. Figures come from ONCE’s cached Revelator analytics (refreshed periodically) with a live fallback, so they match the in-app performance dashboard. YouTube Music view deltas are folded in as a synthetic store with distributorId: -1300.

Both tools default to the trailing 30 days. Pass fromDate/toDate (YYYY-MM-DD) for any window up to 2 years. KPIs share this shape:

KPIDescription
totalStreamsTotal streams in the window.
avgDailyStreamstotalStreams divided by the number of days in the window.
periodChangePctPercent change vs the immediately-preceding window of equal length. null when there is no prior data.
topStore{ name, share } for the leading store (share is 0–1). null when filtered to a single store.

Newly distributed releases can return zeros until DSPs report and the cache backfills.

Tool: get_performance_summary — catalog overview

ArgumentTypeDescription
fromDatestringOptional window start (YYYY-MM-DD). Defaults to 30 days before toDate.
toDatestringOptional window end (YYYY-MM-DD). Defaults to today.
topReleasesLimitnumberOptional. How many top releases to return (1–50, default 10).
topStoresLimitnumberOptional. How many top stores to return (1–50, default 10).
{
  "fromDate": "2026-05-20",
  "toDate": "2026-06-19",
  "releases": { "total": 8, "withRevelatorId": 6 },
  "metrics": [
    { "eventDate": "2026-05-20", "streamsCount": 184 },
    { "eventDate": "2026-05-21", "streamsCount": 203 }
  ],
  "kpis": {
    "totalStreams": 5821,
    "avgDailyStreams": 187.8,
    "periodChangePct": 12.4,
    "topStore": { "name": "Spotify", "share": 0.62 }
  },
  "topStores": [
    { "id": 9, "name": "Spotify", "total": 3609 },
    { "id": 1, "name": "Apple Music", "total": 1402 },
    { "id": -1300, "name": "YouTube Music", "total": 810 }
  ],
  "topReleases": [
    {
      "releaseId": "uuid",
      "title": "My Release",
      "primaryArtistName": "Artist Name",
      "coverArtFileUrl": "/api/files/cover-art/USER_ID/cover.jpg",
      "streamsCount": 4123
    }
  ]
}

Tool: get_release_performance — single release (and tracks)

ArgumentTypeDescription
releaseIdstringRequired. ONCE release id (from list_releases). Must be owned by the user.
fromDatestringOptional window start (YYYY-MM-DD). Defaults to 30 days before toDate.
toDatestringOptional window end (YYYY-MM-DD). Defaults to today.
distributorIdnumberOptional store filter. Use an id from distributors/topStores (-1300 = YouTube Music). Omit for all stores.
includeTracksbooleanWhen true, include a per-track streams breakdown for the window.
{
  "releaseId": "uuid",
  "fromDate": "2026-05-20",
  "toDate": "2026-06-19",
  "distributorId": null,
  "metrics": [
    { "eventDate": "2026-05-20", "streamsCount": 42 }
  ],
  "kpis": {
    "totalStreams": 1280,
    "avgDailyStreams": 41.3,
    "periodChangePct": -3.1,
    "topStore": { "name": "Spotify", "share": 0.71 }
  },
  "distributors": [
    { "id": 9, "name": "Spotify", "total": 909 },
    { "id": 1, "name": "Apple Music", "total": 371 }
  ],
  "tracks": [
    { "trackId": 12345, "trackName": "Lead Single", "artistName": "Artist Name", "streamsCount": 880 }
  ],
  "source": "cache"
}

tracks is only populated when includeTracks: true. source is "cache" or "live" so you can tell whether the figures came from the cache or a fresh Revelator pull. Performance is only available for distributed releases the user owns; requests for other releases return 403 Forbidden.


Profile & Credits

These tools mirror the REST endpoints used by the ONCE app and are convenient when the agent needs to greet the user, pre-fill artist metadata, or check that the user has enough credits before submitting.

Tool: get_profile (no arguments)

{
  "profile": {
    "id": "...",
    "email": "artist@example.com",
    "first_name": "Ada",
    "last_name": "Lovelace",
    "created_at": "2026-02-01T12:00:00.000Z",
    "avatar_url": "https://...signed-avatar-url...",
    "is_patron": false,
    "is_admin": false
  }
}

Tool: get_pricing (no arguments)

Static price list — call it to pick the cheapest way to cover a submission. Returns the per-credit price, per-song cost (1 credit per human song, 2 per AI song), the Token Offset add-on, every discounted bulk bundle with its packageId, and how auto-reload works. Mirrors the public GET /v1/pricing.

{
  "currency": "usd",
  "perCredit": { "usd": 1, "cents": 100 },
  "perSong": { "human": 1, "ai": 2, "aiSurcharge": 1 },
  "tokenOffset": { "credits": 1, "costUsd": 1, "optional": true },
  "bundles": [
    { "packageId": "credits_20", "credits": 20, "priceUsd": 19, "perCreditUsd": 0.95, "discountPercent": 5, "savingsUsd": 1, "tag": "EP & album" },
    { "packageId": "credits_100", "credits": 100, "priceUsd": 88, "perCreditUsd": 0.88, "discountPercent": 12, "savingsUsd": 12, "tag": "Most popular" }
  ],
  "autoReload": { "available": true, "requiresSavedCard": true, "mcp": { "read": "get_autoreload", "configure": "set_autoreload" } },
  "creditsNeverExpire": true
}

Tool: get_credits

ArgumentTypeDescription
transactionLimitnumberOptional. How many recent transactions to include (0–100, default 10).
{
  "balance": 12,
  "updatedAt": "2026-05-01T08:00:00.000Z",
  "transactions": [
    { "id": "...", "type": "debit", "amount": 1, "balance_after": 12, "created_at": "...", "ref": "release:..." }
  ],
  "aggregates": {
    "usedTotal": 4,
    "purchasedTotal": 16
  }
}

Tool: create_credit_checkout_session

Creates a Stripe Checkout Session for buying ONCE credits. Buy a discounted bulk bundle by passing packageId (see get_pricing), or a custom amount at the flat $1/credit rate by passing credits. Two modes:

  • uiMode: "hosted" (recommended for agents): returns a Stripe-hosted Checkout url. Any payment-capable agent or agentic-commerce flow can complete it directly; otherwise hand the link to the user as the single manual step. Credits are granted automatically by webhook when payment succeeds — confirm with get_credits.
  • uiMode: "embedded" (default): returns a Stripe clientSecret plus ONCE’s publishableKey for rendering embedded Checkout in a web UI (this is what AIMD uses).
ArgumentTypeDescription
creditsinteger1–1000 credits to purchase at $1 each. Omit when buying a bundle.
packageIdstringBulk bundle id from get_pricing (e.g. "credits_500"). Wins over credits, charged at the discounted price.
savePaymentMethodbooleanOptional. Save the card for reuse so you can then enable auto-reload via set_autoreload. Default false.
uiModestringOptional. "hosted" or "embedded" (default).
successPathstringOptional post-payment redirect (absolute https URL or path). Hosted mode defaults to the ONCE dashboard; embedded mode defaults to AIMD’s checkout return endpoint. session_id={CHECKOUT_SESSION_ID} is appended automatically.
provenancestringOptional. AIMD passes "AIMD".

Hosted request and response:

{
  "credits": 5,
  "uiMode": "hosted"
}
{
  "uiMode": "hosted",
  "url": "https://checkout.stripe.com/c/pay/cs_live_...",
  "sessionId": "cs_live_...",
  "publishableKey": "pk_live_...",
  "amount": 500,
  "currency": "usd",
  "credits": 5,
  "expiresAt": "2026-05-19T15:30:00.000Z"
}

Embedded responses keep the previous shape (clientSecret instead of url, plus uiMode: "embedded"). When a bundle is purchased the response also includes its packageId. The checkout session metadata is written as user_id and credits (with source: "mcp" for hosted or "aimd" for embedded) so the existing Stripe webhook grants the purchased credits automatically.

get_profile and get_credits also have REST equivalents at GET /api/profile and GET /api/credits (cookie-authenticated) for the ONCE web app.

Auto-reload

Auto-reload tops the balance back up off-session by charging a saved card whenever the balance reaches the threshold (0 by default), so a release never stalls mid-submission. Read it with get_autoreload; configure it with set_autoreload. REST equivalents: GET /v1/me/autoreload and POST /v1/me/autoreload.

Tool: get_autoreload (no arguments)

{
  "enabled": true,
  "configured": true,
  "thresholdCredits": 0,
  "packageId": "credits_100",
  "reloadCredits": 100,
  "reloadAmountCents": 5000,
  "card": { "brand": "visa", "last4": "4242", "expMonth": 12, "expYear": 2030 },
  "lastStatus": "succeeded",
  "lastTriggeredAt": "2026-05-19T15:30:00.000Z"
}

Tool: set_autoreload

ArgumentTypeDescription
enabledbooleanRequired. Turn auto-reload on or off.
packageIdstringBulk bundle to reload (see get_pricing). Wins over quantity.
quantityintegerCustom credits to reload at $1/credit when no bundle is chosen.
paymentMethodIdstringSaved card to charge. Omit to reuse the most recent saved card.
thresholdCreditsintegerReload when the balance falls to/below this (default 0).

Enabling needs a saved card. The simplest agent flow is to call create_credit_checkout_session with savePaymentMethod: true, then:

{
  "enabled": true,
  "packageId": "credits_100"
}

The most recent saved card is selected automatically. Disable with { "enabled": false }. The response is the same shape as get_autoreload.