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.

Provenance & Partner Billing

ONCE classifies every MCP JSON-RPC request with a provenance used for both auditing and billing:

ValueDetected viaBilling rule
AIMDRequest Origin / Referer matches app.aimusicdistributor.com, or the JSON-RPC arguments include "provenance": "AIMD" (for server-to-server calls).2 credits per AI-detected song (1 base + 1 surcharge). All other songs remain 1 credit.
MCPEvery other authenticated MCP client (default).1 credit per song.

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

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",
    "release_date": "2026-02-01",
    "cover_art_file_url": "/api/files/cover-art/USER_ID/cover.jpg"
  },
  "tracks": [
    {
      "title": "My Release",
      "primary_artist_name": "Artist Name",
      "audio_file_url": "/api/files/audio/USER_ID/track.wav",
      "explicit_flag": false,
      "writers": [{ "name": "First Last" }]
    }
  ],
  "releaseId": "optional",
  "conversationId": "optional"
}

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.


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_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 an embedded Stripe Checkout Session for buying ONCE credits from an MCP client such as AIMD. The response includes the Stripe clientSecret and ONCE’s Stripe publishableKey, so the client can render embedded Checkout without separately configuring Stripe environment variables.

{
  "credits": 5,
  "successPath": "https://app.aimusicdistro.com/api/once/checkout-return",
  "provenance": "AIMD"
}

credits is required and must be an integer from 1 to 1000. successPath is optional; ONCE defaults to AIMD’s checkout return endpoint and automatically includes session_id={CHECKOUT_SESSION_ID} for Stripe’s hosted fallback. The checkout session metadata is written as user_id, credits, and source: "aimd" so the existing Stripe webhook can grant the purchased credits.

{
  "clientSecret": "cs_test_...",
  "sessionId": "cs_test_...",
  "publishableKey": "pk_live_...",
  "amount": 500,
  "currency": "usd",
  "credits": 5,
  "expiresAt": "2026-05-19T15:30:00.000Z"
}

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