openapi: 3.1.0
info:
  title: Muso Platform API
  version: "2.0"
  summary: Verified music credits, catalog metadata, and analytics.
  description: |
    The Muso Platform API serves verified credits (who did what on a recording),
    catalog metadata (profiles, albums, tracks, roles), and audience analytics —
    the modern, self-serve evolution of the Muso developer API.

    **Auth** — pass your secret API key in the `x-api-key` header. Keys are
    prefixed `msk_…`. Every key is live — there is no separate test mode or
    sandbox host; the key you mint hits the real API immediately.

    **Metering** — every request counts against the `api_requests` meter (REST,
    MCP, and CLI share one meter). Pay-as-you-go: add a card, start with $5 in
    credits, then a graduated per-call rate. No base fee, no subscription.

    **Rate limits** — enforced per key, per minute (`X-RateLimit-*` response
    headers). Exceeding your rpm returns `429` with `Retry-After`.

    **Envelope** — every response is wrapped as
    `{ "result": "ok", "code": 200, "data": … }`; errors are
    `{ "result": "error", "code": 4xx, "message": "…" }`.

    **Data use** — responses are licensed for in-app consumption only. No resale,
    no redistribution, no catalog reconstruction. Every response is traceable to
    your key.
  termsOfService: https://platform.muso.ai/trust
  contact:
    name: Muso Platform Support
    url: https://platform.muso.ai/docs
    email: support@muso.ai
servers:
  - url: https://api.platform.muso.ai/v1
    description: Production
security:
  - apiKey: []
tags:
  - name: credits
    description: Verified credits for recordings and profiles.
  - name: catalog
    description: Profiles, albums, tracks, and the role taxonomy.
  - name: analytics
    description: Chart positions and audience analytics.
  - name: search
    description: Catalog search and autocomplete.

paths:
  /profile/{id}:
    get:
      operationId: getProfile
      tags: [catalog]
      summary: Profile by id
      description: Returns a profile — name, location, links, and identifiers.
      parameters:
        - $ref: "#/components/parameters/ProfileId"
      responses:
        "200":
          description: The profile.
          headers: { $ref: "#/components/headers/RateLimitSet" }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ProfileResponse" }
              example:
                result: ok
                code: 200
                data:
                  id: e979669b-d99b-46be-91d6-bf1f1e2f44bf
                  name: Drake
                  country: Canada
                  city: null
                  website: https://drakerelated.com/
                  facebook: https://www.facebook.com/Drake/
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /profile/{id}/credits:
    get:
      operationId: getProfileCredits
      tags: [credits]
      summary: Credits for a profile
      description: >
        The verified credit set for a profile — each item pairs a track with the
        roles this profile held on it. Paginated via `limit`/`offset`.
      parameters:
        - $ref: "#/components/parameters/ProfileId"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200":
          description: Credit rows for the profile.
          headers: { $ref: "#/components/headers/RateLimitSet" }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CreditsResponse" }
              example:
                result: ok
                code: 200
                data:
                  limit: 20
                  offset: 0
                  totalCount: 11690
                  items:
                    - credits:
                        - { child: Primary Artist, parent: Artist }
                        - { child: Composer, parent: Songwriter }
                      track:
                        id: bedcc139-9403-4f75-ba89-5ba7deef64d4
                        title: Janice STFU
                        popularity: 97
                        isrcs: [USUG12604763]
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /profile/{id}/charts:
    get:
      operationId: getProfileCharts
      tags: [analytics]
      summary: Chart positions for a profile
      description: >
        Audience analytics — the profile's position, score, and credit counts
        across Muso's chart categories (Artists, Songwriters, Producers, …),
        with period-over-period change.
      parameters:
        - $ref: "#/components/parameters/ProfileId"
      responses:
        "200":
          description: Chart rows for the profile.
          headers: { $ref: "#/components/headers/RateLimitSet" }
          content:
            application/json:
              example:
                result: ok
                code: 200
                data:
                  - chartName: Artists
                    position: 1
                    positionChange: 0
                    score: 175316
                    scoreChange: 0
                    creditsCount: 712
                    percentage: 0.01
                  - chartName: Songwriters
                    position: 1
                    positionChange: 0
                    score: 133936
                    creditsCount: 11486
                    percentage: 0.01
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /profile/{id}/collaborators:
    get:
      operationId: getProfileCollaborators
      tags: [catalog]
      summary: Collaborators for a profile
      description: The profiles this profile has the most shared credits with.
      parameters:
        - $ref: "#/components/parameters/ProfileId"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200":
          description: Collaborator rows.
          headers: { $ref: "#/components/headers/RateLimitSet" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /track/{idKey}/{idValue}:
    get:
      operationId: getTrack
      tags: [catalog]
      summary: Track by identifier
      description: >
        Look up a track by an identifier. `idKey` selects the identifier type
        (`isrc`, `id`, `spotify`); `idValue` is the value.
      parameters:
        - $ref: "#/components/parameters/IdKey"
        - $ref: "#/components/parameters/IdValue"
      responses:
        "200":
          description: The track.
          headers: { $ref: "#/components/headers/RateLimitSet" }
          content:
            application/json:
              example:
                result: ok
                code: 200
                data:
                  id: bedcc139-9403-4f75-ba89-5ba7deef64d4
                  title: Janice STFU
                  isrcs: [USUG12604763]
                  spotifyIds: [514joG57v4yKTsfQmz7stz]
                  label: OVO/Republic
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /track/{idKey}/{idValue}/albums:
    get:
      operationId: getTrackAlbums
      tags: [catalog]
      summary: Albums that contain a track
      parameters:
        - $ref: "#/components/parameters/IdKey"
        - $ref: "#/components/parameters/IdValue"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200":
          description: Albums containing the track.
          headers: { $ref: "#/components/headers/RateLimitSet" }
          content:
            application/json:
              example:
                result: ok
                code: 200
                data:
                  totalCount: 2
                  limit: 20
                  offset: 0
                  items:
                    - id: 9b78e38b-785d-4e52-8bee-3110fe1bd3a1
                      title: ICEMAN
                      releaseDate: "2026-05-15"
                      popularity: 99
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /album/{idKey}/{idValue}:
    get:
      operationId: getAlbum
      tags: [catalog]
      summary: Album by identifier
      description: >
        Look up an album by an identifier. `idKey` selects the type (`id`,
        `upc`, `spotify`); `idValue` is the value.
      parameters:
        - $ref: "#/components/parameters/IdKey"
        - $ref: "#/components/parameters/IdValue"
      responses:
        "200":
          description: The album.
          headers: { $ref: "#/components/headers/RateLimitSet" }
          content:
            application/json:
              example:
                result: ok
                code: 200
                data:
                  id: 9b78e38b-785d-4e52-8bee-3110fe1bd3a1
                  title: ICEMAN
                  upc: "00600574207005"
                  label: OVO/Republic
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /album/{idKey}/{idValue}/credits:
    get:
      operationId: getAlbumCredits
      tags: [credits]
      summary: Credits for an album
      parameters:
        - $ref: "#/components/parameters/IdKey"
        - $ref: "#/components/parameters/IdValue"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200":
          description: Credit rows for the album's tracks.
          headers: { $ref: "#/components/headers/RateLimitSet" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /search:
    post:
      operationId: search
      tags: [search]
      summary: Search the catalog
      description: >
        Full-text search across the catalog. Provide a `keyword` and one or more
        entity `type`s (`track`, `profile`, `album`). Results are grouped by type.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [keyword]
              properties:
                keyword:
                  type: string
                type:
                  type: array
                  items:
                    type: string
                    enum: [track, profile, album]
            example:
              keyword: Drake
              type: [track]
      responses:
        "200":
          description: Grouped search results.
          headers: { $ref: "#/components/headers/RateLimitSet" }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SearchResponse" }
              example:
                result: ok
                code: 200
                data:
                  tracks:
                    total: 9071
                    items:
                      - id: 9bcae91b-0690-4f3b-aa24-bb4a7cf7ca53
                        title: Drake
                        popularity: 49
                        releaseDate: "2021-08-13T00:00:00"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /search/autocomplete:
    get:
      operationId: autocomplete
      tags: [search]
      summary: Autocomplete suggestions
      description: Type-ahead suggestions across profiles, tracks, and albums.
      parameters:
        - name: q
          in: query
          required: true
          schema:
            type: string
          example: drake
      responses:
        "200":
          description: Suggestions.
          headers: { $ref: "#/components/headers/RateLimitSet" }
          content:
            application/json:
              example:
                result: ok
                code: 200
                data:
                  items:
                    - id: e979669b-d99b-46be-91d6-bf1f1e2f44bf
                      name: Drake
                      entityType: profile
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /roles:
    get:
      operationId: listRoles
      tags: [catalog]
      summary: Credit-role taxonomy
      description: The full taxonomy of credit roles and their parent categories.
      responses:
        "200":
          description: The role taxonomy.
          headers: { $ref: "#/components/headers/RateLimitSet" }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RolesResponse" }
              example:
                result: ok
                code: 200
                data:
                  limit: 20
                  offset: 0
                  totalCount: 142
                  items:
                    - { child: Composer, parent: Songwriter }
                    - { child: Producer, parent: Production }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }

components:
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: x-api-key
      description: >
        Your secret API key from the console (https://platform.muso.ai/keys),
        sent in the `x-api-key` header. Keys are prefixed `msk_…`. Every key is
        live — there is no test mode.

  parameters:
    ProfileId:
      name: id
      in: path
      required: true
      schema:
        type: string
      description: Muso profile id (UUID).
      example: e979669b-d99b-46be-91d6-bf1f1e2f44bf
    IdKey:
      name: idKey
      in: path
      required: true
      schema:
        type: string
        enum: [id, isrc, spotify, upc]
      description: The identifier type used in `idValue`.
      example: isrc
    IdValue:
      name: idValue
      in: path
      required: true
      schema:
        type: string
      description: The identifier value.
      example: USUG12604763
    Limit:
      name: limit
      in: query
      description: >-
        Page size. The API rejects anything above 50 with
        `400 Too big page, max page size is 50` rather than truncating.
      schema:
        type: integer
        minimum: 1
        maximum: 50
        default: 20
    Offset:
      name: offset
      in: query
      schema:
        type: integer
        minimum: 0
        default: 0

  headers:
    # Header names verified against live gateway responses 2026-07-27.
    RateLimitSet:
      RateLimit-Limit:
        schema: { type: integer }
        description: Requests allowed per minute for your key.
      RateLimit-Remaining:
        schema: { type: integer }
        description: Requests remaining in the current window.
      RateLimit-Reset:
        schema: { type: integer }
        description: SECONDS until the window refills (not a timestamp).
      X-RateLimit-Limit-Minute:
        schema: { type: integer }
        description: Per-minute view of the same budget, sent alongside the above.
      X-Kong-Request-Id:
        schema: { type: string }
        description: Identifier for this exact request; quote it in support reports.

  schemas:
    # Shapes verified against live api.platform.muso.ai responses 2026-07-27.
    # Every endpoint answers with this envelope; `data` is an object for
    # single-entity reads and a page ({limit, offset, totalCount, items}) for lists.
    Envelope:
      type: object
      required: [result, code]
      properties:
        result: { type: string, examples: [ok] }
        code: { type: integer, examples: [200] }
    ErrorEnvelope:
      type: object
      required: [result, code, message]
      properties:
        result: { type: string, examples: [error] }
        code: { type: integer, examples: [400] }
        message:
          type: string
          examples: ["Too big page, max page size is 50"]
    Page:
      type: object
      description: Offset pagination. There is no cursor.
      properties:
        limit: { type: integer, examples: [20] }
        offset: { type: integer, examples: [0] }
        totalCount:
          type: integer
          description: Total rows available; stop when offset >= totalCount.
          examples: [11701]
    Role:
      type: object
      description: A credit role and the family it belongs to.
      properties:
        child: { type: string, examples: [Composer] }
        parent: { type: string, examples: [Songwriter] }
    TrackRef:
      type: object
      properties:
        id: { type: string, format: uuid }
        title: { type: string }
        popularity: { type: integer }
        isrcs: { type: array, items: { type: string } }
        spotifyId: { type: string }
        spotifyPreviewUrl: { type: string, format: uri }
    AlbumRef:
      type: object
      properties:
        id: { type: string, format: uuid }
        title: { type: string }
        albumArt: { type: string, format: uri }
    ArtistRef:
      type: object
      properties:
        id: { type: string, format: uuid }
        name: { type: string }
        avatarUrl: { type: string, format: uri }
    CreditRow:
      type: object
      description: One credited work, with the roles this profile held on it.
      properties:
        credits: { type: array, items: { $ref: "#/components/schemas/Role" } }
        track: { $ref: "#/components/schemas/TrackRef" }
        album: { $ref: "#/components/schemas/AlbumRef" }
        releaseDate: { type: string, format: date }
        artists: { type: array, items: { $ref: "#/components/schemas/ArtistRef" } }
    Profile:
      type: object
      properties:
        id: { type: string, format: uuid }
        name: { type: string }
        city: { type: [string, "null"] }
        country: { type: [string, "null"] }
        bio: { type: [string, "null"] }
        website: { type: [string, "null"] }
        facebook: { type: [string, "null"] }
        twitter: { type: [string, "null"] }
        instagram: { type: [string, "null"] }
        spotifyId: { type: [string, "null"] }
        avatarUrl: { type: [string, "null"] }
    SearchProfile:
      type: object
      properties:
        id: { type: string, format: uuid }
        name: { type: string }
        popularity: { type: integer }
        creditCount: { type: integer }
        collaboratorsCount: { type: integer }
        avatarUrl: { type: string }
        commonCredits: { type: array, items: { type: string } }
    SearchBucket:
      type: object
      description: One result bucket. Buckets not requested come back empty, not absent.
      properties:
        total: { type: integer }
        items: { type: array, items: {} }
    # ---- response envelopes -------------------------------------------------
    ProfileResponse:
      allOf:
        - $ref: "#/components/schemas/Envelope"
        - type: object
          properties:
            data: { $ref: "#/components/schemas/Profile" }
    CreditsResponse:
      allOf:
        - $ref: "#/components/schemas/Envelope"
        - type: object
          properties:
            data:
              allOf:
                - $ref: "#/components/schemas/Page"
                - type: object
                  properties:
                    items:
                      type: array
                      items: { $ref: "#/components/schemas/CreditRow" }
    RolesResponse:
      allOf:
        - $ref: "#/components/schemas/Envelope"
        - type: object
          properties:
            data:
              allOf:
                - $ref: "#/components/schemas/Page"
                - type: object
                  properties:
                    items:
                      type: array
                      items: { $ref: "#/components/schemas/Role" }
    SearchResponse:
      allOf:
        - $ref: "#/components/schemas/Envelope"
        - type: object
          properties:
            data:
              type: object
              properties:
                tracks: { $ref: "#/components/schemas/SearchBucket" }
                albums: { $ref: "#/components/schemas/SearchBucket" }
                profiles:
                  allOf:
                    - $ref: "#/components/schemas/SearchBucket"
                    - type: object
                      properties:
                        items:
                          type: array
                          items: { $ref: "#/components/schemas/SearchProfile" }

  responses:
    BadRequest:
      description: Invalid request parameters.
      content:
        application/json:
          example: { result: error, code: 400, message: Invalid parameters supplied }
    Unauthorized:
      description: Missing or invalid API key.
      content:
        application/json:
          example: { result: error, code: 401, message: Unauthorized }
    NotFound:
      description: No entity matched the identifier.
      content:
        application/json:
          example: { result: error, code: 404, message: Not found }
    RateLimited:
      description: Rate limit exceeded — see `Retry-After`.
      content:
        application/json:
          example: { result: error, code: 429, message: Rate limit exceeded }
