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

903 lines
15 KiB
Markdown

# 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).
```graphql
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**:
```graphql
{
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.
```graphql
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**:
```graphql
{
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.
```graphql
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**:
```graphql
{
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.
```graphql
type Query {
node(id: ID!): Node
}
interface Node {
id: ID!
}
```
**Example**:
```graphql
{
node(id: "QXJ0aXN0OjViMTFmNGNlLWE2MmQtNDcxZS04MWZjLWE2OWE4Mjc4YzdkYQ==") {
... on Artist {
name
country
}
}
}
```
## Entity Types
### Artist
```graphql
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
```graphql
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
```graphql
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
```graphql
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
```graphql
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
```graphql
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
```graphql
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
```graphql
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
```graphql
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
```graphql
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
```graphql
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
```graphql
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:
```graphql
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**:
```graphql
{
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:
```graphql
{
browse {
releases(artist: "...", first: 10) {
nodes {
title
date
}
}
}
}
```
## Extension Fields
### Cover Art Archive
Added to `Release` type:
```graphql
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**:
```graphql
{
lookup {
release(mbid: "...") {
title
coverArtArchive {
front
images {
image
thumbnails {
large
}
types
}
}
}
}
}
```
### fanart.tv
Added to `Artist` type:
```graphql
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**:
```graphql
{
lookup {
artist(mbid: "...") {
name
fanArt {
backgrounds {
url
likes
}
logosHD {
url
color
}
}
}
}
}
```
### MediaWiki
Added to `Artist` type:
```graphql
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**:
```graphql
{
lookup {
artist(mbid: "...") {
name
mediaWikiImages {
url
width
height
metadata {
name
value
}
}
}
}
}
```
### TheAudioDB
Added to `Artist` type:
```graphql
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**:
```graphql
{
lookup {
artist(mbid: "...") {
name
theAudioDB {
biographyEN
logo
fanArt {
url
}
}
}
}
}
```
## Scalar Types
```graphql
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:
```bash
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**:
```bash
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:
```json
{
"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