Authentication (OAuth2)

Client-credentials flow, token lifecycle and scopes.

The MusiMap Web API uses the standard OAuth2 client-credentials flow for server-to-server calls. You exchange a client_id and client_secret for a short-lived access token, then include it in the Authorization header of every subsequent request.

Letting users sign in with their MusiMap account?

That's a different flow — OpenID Connect Authorization Code + PKCE. It's documented under Sign in with MusiMap. This page is about server-to-server auth.

Overview

In one picture:

  1. Create an OAuth client from the developers dashboard. You get a client_id and a one-time client_secret.
  2. POST both to https://api.musimap.com/oauth/token with grant_type=client_credentials.
  3. You receive an access_token valid for one hour. Cache it — refresh only when it's about to expire.
  4. Send it as Authorization: Bearer <token> on every /v1/* Web API call.

OAuth protocol vs versioned Web API

MusiMap exposes two related but distinct surfaces on https://api.musimap.com:

  • /oauth/* — OAuth 2.0 and OpenID Connect protocol endpoints (token exchange, revocation, UserInfo). Responses follow RFC/OIDC shapes directly. They are not wrapped in the MusiMap {status, message, data} envelope.
  • /v1/* — the versioned Web API for catalogue, ingestion, tagging, and identity. Responses use the MusiMap envelope, including authentication errors (data.error_code such as invalid_token).

Use /oauth/token to obtain tokens; use /v1/* for business operations. Token introspection (POST /oauth/introspect) is reserved for internal and trusted partner integrations and is not part of the public Web API documentation.

Prerequisites

You create OAuth clients from the developers dashboard (available at launch — for now, contact us). Each client belongs to an organization and is scoped to specific capabilities — see Organizations & OAuth clients for the full model.

Store the client_secret somewhere safe. We show it exactly once, at creation; after that, you'll need to rotate it from the dashboard if you lose it.

Token endpoint

Exchange your credentials for an access token by POSTing to /oauth/token. The request is standard application/x-www-form-urlencoded — same as every other OAuth2 server you've seen.

curl -X POST https://api.musimap.com/oauth/token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=client_credentials' \
  -d 'client_id=YOUR_CLIENT_ID' \
  -d 'client_secret=YOUR_CLIENT_SECRET' \
  -d 'scope=audio.tagging audio.ingest'

You'll receive the token, its type, its lifetime in seconds, and the scopes that were actually granted (they may be a subset of what you asked for if your client isn't configured for all of them):

200 OK
{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type":   "Bearer",
  "expires_in":   3600,
  "scope":        "audio.tagging audio.ingest"
}

Scopes

Access tokens are scoped. Request only what you need — if a token leaks, its blast radius is limited to the scopes it was issued with. A single OAuth client can request multiple scopes separated by spaces.

Scope Grants access to
audio.ingest Upload audio files and sidecar metadata into your organization's catalogue.
audio.tagging Run tagging analyses and read their results.
audio.profiling Build and query listener profiles.
audio.fingerprinting Identify tracks from short snippets against your catalogue.
catalogue.read Search and read your catalogue of analysed tracks.
catalogue.write Update track metadata and manage your catalogue.

Using the token

Pass the token in the Authorization header, with the Bearer prefix, on every API call:

curl https://api.musimap.com/v1/me \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Check your token context

After you have an access token, call GET /v1/me to confirm which principal, organisation, and catalog context the token operates under, and which OAuth scopes were granted. This is the recommended sanity check during integration. For the full list of catalogues available to your OAuth client, call GET /v1/catalogs (see Catalogues). The catalog_id on /v1/me is the default or current catalogue context, not necessarily every catalogue you can access.

curl https://api.musimap.com/v1/me \
  -H "Authorization: Bearer $ACCESS_TOKEN"

The response uses the MusiMap envelope:

200 OK
{
  "status": 200,
  "message": "OK",
  "data": {
    "principal_type": "client",
    "subject_id": "019c41e3-6532-7020-8f81-b9c9405387dd",
    "client_id": "your-client-id",
    "organization_id": "019c41e3-6532-7020-8f81-b9c9405383ee",
    "catalog_id": "019c41e3-6532-7020-8f81-b9c9405384aa",
    "scopes": ["audio.tagging", "audio.ingest"],
    "permissions": []
  }
}

Use GET /v1/me/scopes to read the scopes attached to the current token. The permissions field is reserved for fine-grained access control and currently returns an empty list.

Token lifecycle

Access tokens live for one hour (expires_in: 3600). There is no refresh token for the client-credentials flow — to extend your session, you simply exchange your credentials again. The recommended pattern is to cache the token in memory and re-issue it a minute before expiry:

import time, requests

_token = {'value': None, 'expires_at': 0}

def get_access_token() -> str:
    # Refresh ~60s before the real expiry to stay on the safe side.
    if _token['value'] and _token['expires_at'] - 60 > time.time():
        return _token['value']

    r = requests.post(
        'https://api.musimap.com/oauth/token',
        data={
            'grant_type':    'client_credentials',
            'client_id':     os.environ['MUSIMAP_CLIENT_ID'],
            'client_secret': os.environ['MUSIMAP_CLIENT_SECRET'],
        },
        timeout=10,
    )
    r.raise_for_status()
    data = r.json()
    _token['value']      = data['access_token']
    _token['expires_at'] = time.time() + data['expires_in']
    return _token['value']
Don't request a new token on every call.

The token endpoint is rate-limited more aggressively than the rest of the API. If you issue a new token for every request, you'll hit 429 errors at moderate volume. Caching is not optional — the SDKs do it for you automatically.

Common errors

When something goes wrong, you get back a JSON body following the OAuth2 error format (error + error_description). Here are the ones you're most likely to see:

HTTP Error When it happens How to fix
401 invalid_client Wrong client_id / client_secret, or the client has been revoked. Double-check your credentials. Rotate the secret from the dashboard if it leaked.
400 invalid_grant Grant type other than client_credentials for a server-to-server client. Use grant_type=client_credentials. For user sign-in, see Sign in with MusiMap.
400 invalid_scope You requested a scope your client isn't allowed to have. Check the OAuth client's scopes in the dashboard, or remove the scope from the request.
401 invalid_token The Authorization header is missing, malformed, or the token has expired. Request a new token. SDKs do this automatically.
403 insufficient_scope Your token is valid but doesn't include the scope required by this endpoint. Request a token with the correct scope — see the Scopes section below.
429 rate_limited Too many token exchanges in a short window, often from not caching the access token. Cache the token until a few minutes before expiry. SDKs handle this for you.

For the full MusiMap error envelope on /v1/* (including data.error_code and optional data.details), see Errors. OAuth protocol endpoints keep the RFC error shape shown above.

Where to go next