Files
metadata-agregator/docs/research/graphbrainz/analysis/API.md
T
Alexander a1f6701bac feat: initial implementation of metadata aggregator
- gRPC service with MusicBrainz provider
- PostgreSQL schema with migrations
- Service layer with database-first caching
- Repository pattern for data access
- YAML configuration support
- Research documentation for 17 music metadata projects
2026-04-28 16:28:53 +02:00

15 KiB

GraphBrainz API Reference

Endpoint Configuration

Parameter Environment Variable Default
Path GRAPHBRAINZ_PATH /
Port PORT 3000
CORS Origin GRAPHBRAINZ_CORS_ORIGIN false
GraphiQL GRAPHBRAINZ_GRAPHIQL true (development)

Query Types

GraphBrainz exposes four primary query entry points:

1. Lookup Queries

Direct entity retrieval by MusicBrainz ID (MBID).

type Query {
  lookup: LookupQuery
}

type LookupQuery {
  area(mbid: String!): Area
  artist(mbid: String!): Artist
  collection(mbid: String!): Collection
  event(mbid: String!): Event
  instrument(mbid: String!): Instrument
  label(mbid: String!): Label
  place(mbid: String!): Place
  recording(mbid: String!): Recording
  release(mbid: String!): Release
  releaseGroup(mbid: String!): ReleaseGroup
  series(mbid: String!): Series
  url(mbid: String!): URL
  work(mbid: String!): Work
}

Example:

{
  lookup {
    artist(mbid: "5b11f4ce-a62d-471e-81fc-a69a8278c7da") {
      name
      type
      country
      lifeSpan {
        begin
        end
      }
    }
  }
}

2. Browse Queries

Retrieve entities linked to a parent entity with cursor-based pagination.

type Query {
  browse: BrowseQuery
}

type BrowseQuery {
  areas(
    collection: String
    first: Int
    after: String
  ): AreaConnection
  
  artists(
    area: String
    collection: String
    recording: String
    release: String
    releaseGroup: String
    work: String
    first: Int
    after: String
  ): ArtistConnection
  
  collections(
    area: String
    artist: String
    editor: String
    event: String
    label: String
    place: String
    recording: String
    release: String
    releaseGroup: String
    work: String
    first: Int
    after: String
  ): CollectionConnection
  
  events(
    area: String
    artist: String
    collection: String
    place: String
    first: Int
    after: String
  ): EventConnection
  
  labels(
    area: String
    collection: String
    release: String
    first: Int
    after: String
  ): LabelConnection
  
  places(
    area: String
    collection: String
    first: Int
    after: String
  ): PlaceConnection
  
  recordings(
    artist: String
    collection: String
    release: String
    first: Int
    after: String
  ): RecordingConnection
  
  releases(
    area: String
    artist: String
    collection: String
    label: String
    recording: String
    releaseGroup: String
    track: String
    trackArtist: String
    first: Int
    after: String
  ): ReleaseConnection
  
  releaseGroups(
    artist: String
    collection: String
    release: String
    first: Int
    after: String
  ): ReleaseGroupConnection
}

Example:

{
  browse {
    releases(
      artist: "5b11f4ce-a62d-471e-81fc-a69a8278c7da"
      first: 10
    ) {
      edges {
        node {
          title
          date
          status
        }
      }
      pageInfo {
        hasNextPage
        endCursor
      }
      totalCount
    }
  }
}

3. Search Queries

Lucene-based full-text search across entity types.

type Query {
  search: SearchQuery
}

type SearchQuery {
  areas(query: String!, first: Int, after: String): AreaConnection
  artists(query: String!, first: Int, after: String): ArtistConnection
  events(query: String!, first: Int, after: String): EventConnection
  instruments(query: String!, first: Int, after: String): InstrumentConnection
  labels(query: String!, first: Int, after: String): LabelConnection
  places(query: String!, first: Int, after: String): PlaceConnection
  recordings(query: String!, first: Int, after: String): RecordingConnection
  releases(query: String!, first: Int, after: String): ReleaseConnection
  releaseGroups(query: String!, first: Int, after: String): ReleaseGroupConnection
  works(query: String!, first: Int, after: String): WorkConnection
}

Lucene Query Syntax:

  • artist:"Radiohead" - Exact phrase match
  • artist:Radiohead AND country:GB - Boolean operators
  • artist:Radio* - Wildcard search
  • begin:[1990 TO 2000] - Range queries
  • tag:rock^2 tag:alternative - Boosting

Example:

{
  search {
    artists(query: "artist:Radiohead AND country:GB", first: 5) {
      edges {
        node {
          name
          country
          type
          score
        }
      }
    }
  }
}

4. Node Query (Relay)

Global object identification via Relay-compliant node interface.

type Query {
  node(id: ID!): Node
}

interface Node {
  id: ID!
}

Example:

{
  node(id: "QXJ0aXN0OjViMTFmNGNlLWE2MmQtNDcxZS04MWZjLWE2OWE4Mjc4YzdkYQ==") {
    ... on Artist {
      name
      country
    }
  }
}

Entity Types

Artist

type Artist implements Node {
  id: ID!
  mbid: MBID!
  name: String
  sortName: String
  disambiguation: String
  type: String
  typeID: MBID
  country: String
  area: Area
  beginArea: Area
  endArea: Area
  lifeSpan: LifeSpan
  gender: String
  genderID: MBID
  ipis: [IPI]
  isnis: [ISNI]
  aliases: [Alias]
  recordings: RecordingConnection
  releases: ReleaseConnection
  releaseGroups: ReleaseGroupConnection
  works: WorkConnection
  relationships: RelationshipConnection
  collections: CollectionConnection
  tags: TagConnection
  
  # Extension fields
  fanArt: FanArtImages
  mediaWikiImages: [MediaWikiImage]
  theAudioDB: TheAudioDBArtist
}

Release

type Release implements Node {
  id: ID!
  mbid: MBID!
  title: String
  disambiguation: String
  asin: String
  status: String
  statusID: MBID
  packaging: String
  packagingID: MBID
  quality: String
  date: Date
  country: String
  barcode: String
  artists: [Artist]
  artistCredit: [ArtistCredit]
  labels: [ReleaseLabel]
  media: [Medium]
  releaseGroup: ReleaseGroup
  relationships: RelationshipConnection
  collections: CollectionConnection
  tags: TagConnection
  
  # Extension fields
  coverArtArchive: CoverArtArchiveRelease
}

Recording

type Recording implements Node {
  id: ID!
  mbid: MBID!
  title: String
  disambiguation: String
  length: Duration
  video: Boolean
  isrcs: [ISRC]
  artists: [Artist]
  artistCredit: [ArtistCredit]
  releases: ReleaseConnection
  relationships: RelationshipConnection
  collections: CollectionConnection
  tags: TagConnection
}

ReleaseGroup

type ReleaseGroup implements Node {
  id: ID!
  mbid: MBID!
  title: String
  disambiguation: String
  type: String
  typeID: MBID
  primaryType: String
  primaryTypeID: MBID
  secondaryTypes: [String]
  secondaryTypeIDs: [MBID]
  firstReleaseDate: Date
  artists: [Artist]
  artistCredit: [ArtistCredit]
  releases: ReleaseConnection
  relationships: RelationshipConnection
  collections: CollectionConnection
  tags: TagConnection
}

Area

type Area implements Node {
  id: ID!
  mbid: MBID!
  name: String
  sortName: String
  disambiguation: String
  type: String
  typeID: MBID
  iso31661Codes: [String]
  iso31662Codes: [String]
  iso31663Codes: [String]
  lifeSpan: LifeSpan
  aliases: [Alias]
  relationships: RelationshipConnection
  collections: CollectionConnection
  tags: TagConnection
}

Label

type Label implements Node {
  id: ID!
  mbid: MBID!
  name: String
  sortName: String
  disambiguation: String
  type: String
  typeID: MBID
  labelCode: Int
  ipis: [IPI]
  area: Area
  lifeSpan: LifeSpan
  aliases: [Alias]
  releases: ReleaseConnection
  relationships: RelationshipConnection
  collections: CollectionConnection
  tags: TagConnection
}

Work

type Work implements Node {
  id: ID!
  mbid: MBID!
  title: String
  disambiguation: String
  type: String
  typeID: MBID
  language: String
  languages: [String]
  iswcs: [ISWC]
  artists: [Artist]
  relationships: RelationshipConnection
  collections: CollectionConnection
  tags: TagConnection
}

Event

type Event implements Node {
  id: ID!
  mbid: MBID!
  name: String
  disambiguation: String
  type: String
  typeID: MBID
  time: String
  cancelled: Boolean
  setlist: String
  lifeSpan: LifeSpan
  aliases: [Alias]
  relationships: RelationshipConnection
  collections: CollectionConnection
  tags: TagConnection
}

Place

type Place implements Node {
  id: ID!
  mbid: MBID!
  name: String
  disambiguation: String
  type: String
  typeID: MBID
  address: String
  area: Area
  coordinates: Coordinates
  lifeSpan: LifeSpan
  aliases: [Alias]
  relationships: RelationshipConnection
  collections: CollectionConnection
  tags: TagConnection
}

Instrument

type Instrument implements Node {
  id: ID!
  mbid: MBID!
  name: String
  disambiguation: String
  type: String
  typeID: MBID
  description: String
  aliases: [Alias]
  relationships: RelationshipConnection
  collections: CollectionConnection
  tags: TagConnection
}

Series

type Series implements Node {
  id: ID!
  mbid: MBID!
  name: String
  disambiguation: String
  type: String
  typeID: MBID
  aliases: [Alias]
  relationships: RelationshipConnection
  collections: CollectionConnection
  tags: TagConnection
}

Collection

type Collection implements Node {
  id: ID!
  mbid: MBID!
  name: String
  editor: String
  type: String
  typeID: MBID
  entityType: String
  areas: AreaConnection
  artists: ArtistConnection
  events: EventConnection
  instruments: InstrumentConnection
  labels: LabelConnection
  places: PlaceConnection
  recordings: RecordingConnection
  releases: ReleaseConnection
  releaseGroups: ReleaseGroupConnection
  series: SeriesConnection
  works: WorkConnection
}

Relay Connection Types

All list fields return Relay-compliant connection types:

type ArtistConnection {
  edges: [ArtistEdge]
  nodes: [Artist]
  pageInfo: PageInfo!
  totalCount: Int
}

type ArtistEdge {
  node: Artist
  cursor: String!
  score: Int  # Only present in search results
}

type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
  endCursor: String
}

Pagination

  • first: Int - Number of items to return
  • after: String - Cursor for pagination

Example:

{
  browse {
    releases(artist: "...", first: 10) {
      edges {
        node { title }
        cursor
      }
      pageInfo {
        hasNextPage
        endCursor
      }
    }
  }
}

# Next page
{
  browse {
    releases(artist: "...", first: 10, after: "Y3Vyc29yOjEw") {
      edges {
        node { title }
      }
    }
  }
}

Nodes Shortcut

Access nodes directly without edges:

{
  browse {
    releases(artist: "...", first: 10) {
      nodes {
        title
        date
      }
    }
  }
}

Extension Fields

Cover Art Archive

Added to Release type:

type Release {
  coverArtArchive: CoverArtArchiveRelease
}

type CoverArtArchiveRelease {
  front: Boolean
  back: Boolean
  artwork: Boolean
  count: Int
  release: String
  images: [CoverArtArchiveImage]
}

type CoverArtArchiveImage {
  fileID: String
  image: String
  thumbnails: CoverArtArchiveThumbnails
  front: Boolean
  back: Boolean
  types: [String]
  edit: Int
  approved: Boolean
  comment: String
}

type CoverArtArchiveThumbnails {
  small: String
  large: String
}

Example:

{
  lookup {
    release(mbid: "...") {
      title
      coverArtArchive {
        front
        images {
          image
          thumbnails {
            large
          }
          types
        }
      }
    }
  }
}

fanart.tv

Added to Artist type:

type Artist {
  fanArt: FanArtImages
}

type FanArtImages {
  backgrounds: [FanArtImage]
  banners: [FanArtImage]
  logos: [FanArtLabelImage]
  logosHD: [FanArtLabelImage]
  thumbnails: [FanArtImage]
}

type FanArtImage {
  imageID: String
  url: String
  likes: Int
}

type FanArtLabelImage {
  imageID: String
  url: String
  likes: Int
  color: String
}

Configuration: Requires FANART_API_KEY environment variable.

Example:

{
  lookup {
    artist(mbid: "...") {
      name
      fanArt {
        backgrounds {
          url
          likes
        }
        logosHD {
          url
          color
        }
      }
    }
  }
}

MediaWiki

Added to Artist type:

type Artist {
  mediaWikiImages: [MediaWikiImage]
}

type MediaWikiImage {
  url: String
  descriptionURL: String
  title: String
  user: String
  size: Int
  width: Int
  height: Int
  canonicalTitle: String
  objectName: String
  descriptionShortURL: String
  metadata: [MediaWikiImageMetadata]
}

type MediaWikiImageMetadata {
  name: String
  value: String
}

Example:

{
  lookup {
    artist(mbid: "...") {
      name
      mediaWikiImages {
        url
        width
        height
        metadata {
          name
          value
        }
      }
    }
  }
}

TheAudioDB

Added to Artist type:

type Artist {
  theAudioDB: TheAudioDBArtist
}

type TheAudioDBArtist {
  artistID: String
  biography: String
  biographyEN: String
  memberCount: Int
  banner: String
  logo: String
  thumbnail: String
  fanArt: [TheAudioDBImage]
}

type TheAudioDBImage {
  url: String
}

Configuration: Requires THEAUDIODB_API_KEY environment variable.

Example:

{
  lookup {
    artist(mbid: "...") {
      name
      theAudioDB {
        biographyEN
        logo
        fanArt {
          url
        }
      }
    }
  }
}

Scalar Types

scalar MBID        # MusicBrainz ID (UUID format)
scalar Date        # ISO 8601 date (YYYY-MM-DD)
scalar Duration    # Milliseconds (integer)
scalar IPI         # Interested Parties Information code
scalar ISNI        # International Standard Name Identifier
scalar ISRC        # International Standard Recording Code
scalar ISWC        # International Standard Musical Work Code

Authentication

Core GraphBrainz API requires no authentication. Extensions may require API keys:

Extension Environment Variable Required
fanart.tv FANART_API_KEY Yes
TheAudioDB THEAUDIODB_API_KEY Yes
Cover Art Archive - No
MediaWiki - No

CORS Configuration

Enable CORS via environment variable:

GRAPHBRAINZ_CORS_ORIGIN="https://example.com"
# or
GRAPHBRAINZ_CORS_ORIGIN="*"

Default: false (CORS disabled)

GraphiQL Interface

Interactive GraphQL IDE enabled by default in development mode.

Configuration:

GRAPHBRAINZ_GRAPHIQL=true   # Enable
GRAPHBRAINZ_GRAPHIQL=false  # Disable

Access at configured path (default: http://localhost:3000/)

Rate Limits

GraphBrainz enforces MusicBrainz API rate limits:

  • MusicBrainz: 5 requests per 5.5 seconds
  • Extensions: 10 requests per second (default)

Rate limit errors return HTTP 429 with retry-after header.

Error Handling

GraphQL errors follow standard format:

{
  "errors": [
    {
      "message": "Artist not found",
      "locations": [{ "line": 2, "column": 3 }],
      "path": ["lookup", "artist"],
      "extensions": {
        "code": "NOT_FOUND",
        "mbid": "invalid-mbid"
      }
    }
  ],
  "data": null
}

Error codes:

  • NOT_FOUND - Entity not found
  • INVALID_MBID - Invalid MusicBrainz ID format
  • RATE_LIMIT - Rate limit exceeded
  • NETWORK_ERROR - Upstream API error
  • VALIDATION_ERROR - Invalid query parameters