{
  "openapi": "3.1.0",
  "info": {
    "title": "Ghost Producer World Public API",
    "version": "1.0.0",
    "description": "Read-only public API for the Ghost Producer World marketplace — exclusive ghost-produced EDM tracks. Each track is sold once with a full rights transfer; after purchase it leaves the catalog. All prices are in USD. No authentication is required for the endpoints in this spec. Purchases can only be completed on the website (Stripe card checkout). Versioning: /api/v1/ is the stable surface (unversioned /api/ paths are aliases of v1). Only additive changes within v1; breaking changes ship as /api/v2. Endpoints scheduled for removal send Deprecation and Sunset headers at least 90 days in advance — see https://ghostproducerworld.com/docs.",
    "contact": {
      "email": "support@ghostproducerworld.com",
      "url": "https://ghostproducerworld.com/support-center"
    },
    "termsOfService": "https://ghostproducerworld.com/legal",
    "x-versioning": {
      "strategy": "url-path",
      "current": "v1",
      "deprecationSignal": "Deprecation + Sunset headers",
      "noticePeriodDays": 90,
      "policyUrl": "https://ghostproducerworld.com/docs",
      "deprecationHeaders": [
        "Deprecation",
        "Sunset",
        "Link"
      ],
      "sunsetScheduled": null,
      "deprecatedAliases": [
        "/api/* (unversioned) — alias of /api/v1/*, no sunset scheduled"
      ]
    }
  },
  "servers": [
    {
      "url": "https://ghostproducerworld.com"
    }
  ],
  "paths": {
    "/api/v1/tracks": {
      "get": {
        "operationId": "listTracks",
        "summary": "List tracks in the catalog",
        "description": "Returns published (available) tracks with filters, sorting and pagination. Prices are in USD. A sold track disappears from this list unless include_sold=true.",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "Page number, starting at 1.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Results per page (1–48).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 48,
              "default": 12
            }
          },
          {
            "name": "search",
            "in": "query",
            "description": "Free-text search over title, genre and tags.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "genre",
            "in": "query",
            "description": "Exact genre name (e.g. \"Techno\", \"Progressive House\"). Full list on /tracks filters.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "premium",
            "in": "query",
            "description": "true = only Premium tier tracks (top curation tier, from $600).",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "hot",
            "in": "query",
            "description": "true = only Hot tier tracks (team-selected, $300–$550).",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "just_dropped",
            "in": "query",
            "description": "true = only World Tracks tier (open catalog).",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "discount",
            "in": "query",
            "description": "true = only tracks currently discounted.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "min_price",
            "in": "query",
            "description": "Minimum price in USD.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "max_price",
            "in": "query",
            "description": "Maximum price in USD.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "min_bpm",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "max_bpm",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "daw",
            "in": "query",
            "description": "DAW used to produce the track (substring match, e.g. \"FL Studio\", \"Ableton\").",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "key",
            "in": "query",
            "description": "Musical key (substring match, e.g. \"Am\", \"F#\").",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "vocal_type",
            "in": "query",
            "description": "Vocal type exactly as stored (e.g. \"none\", \"full_vocals\", \"vocal_chops\").",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "vocal_gender",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "male",
                "female"
              ]
            }
          },
          {
            "name": "has_project_file",
            "in": "query",
            "description": "true = only tracks that also sell the full DAW project file.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "description": "Catalog order. newest/oldest follow the order tracks entered the catalog.",
            "schema": {
              "type": "string",
              "enum": [
                "newest",
                "oldest",
                "fresh",
                "price_asc",
                "price_desc"
              ],
              "default": "newest"
            }
          },
          {
            "name": "include_sold",
            "in": "query",
            "description": "true = include already-sold tracks (marked by status).",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated track list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tracks": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Track"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            },
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "Link": {
                "$ref": "#/components/headers/Link"
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (default 200 requests/min per IP). Every response carries RateLimit-Limit/Remaining/Reset; a 429 adds Retry-After.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Limit": {
                "description": "Request limit for the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Temporary server problem — retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Body is always the Error object; the HTTP status carries the category.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "deprecated": false
      }
    },
    "/api/v1/tracks/{id}": {
      "get": {
        "operationId": "getTrack",
        "summary": "Get one track by id",
        "description": "Full public detail of a track, plus a short list of more tracks by the same producer. The human page for a track is https://ghostproducerworld.com/{producer-slug}/{track-slug}.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Track UUID (from listTracks).",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Track detail.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "track": {
                      "$ref": "#/components/schemas/Track"
                    },
                    "more": {
                      "type": "array",
                      "description": "Other available tracks by the same producer.",
                      "items": {
                        "$ref": "#/components/schemas/Track"
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "Link": {
                "$ref": "#/components/headers/Link"
              }
            }
          },
          "400": {
            "description": "Malformed request (e.g. invalid UUID).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource does not exist or is not public.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (default 200 requests/min per IP). Every response carries RateLimit-Limit/Remaining/Reset; a 429 adds Retry-After.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Limit": {
                "description": "Request limit for the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Temporary server problem — retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Body is always the Error object; the HTTP status carries the category.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "deprecated": false
      }
    },
    "/api/v1/producers/list": {
      "get": {
        "operationId": "listProducers",
        "summary": "List producers with published tracks",
        "description": "Public directory of identity-verified producers that currently have tracks in the catalog. The human page for a producer is https://ghostproducerworld.com/{producer-slug}.",
        "responses": {
          "200": {
            "description": "Producer list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "producers": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Producer"
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "Link": {
                "$ref": "#/components/headers/Link"
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (default 200 requests/min per IP). Every response carries RateLimit-Limit/Remaining/Reset; a 429 adds Retry-After.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Limit": {
                "description": "Request limit for the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Temporary server problem — retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Body is always the Error object; the HTTP status carries the category.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "deprecated": false
      }
    },
    "/api/v1/tags/popular": {
      "get": {
        "operationId": "listPopularTags",
        "summary": "Most-used tags on published tracks",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tag list, most used first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tags": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "tag": {
                            "type": "string"
                          },
                          "uses": {
                            "type": "integer"
                          },
                          "query": {
                            "type": "string",
                            "description": "Value to pass as the search parameter of listTracks."
                          }
                        }
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "Link": {
                "$ref": "#/components/headers/Link"
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (default 200 requests/min per IP). Every response carries RateLimit-Limit/Remaining/Reset; a 429 adds Retry-After.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Limit": {
                "description": "Request limit for the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Temporary server problem — retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Body is always the Error object; the HTTP status carries the category.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "deprecated": false
      }
    }
  },
  "components": {
    "schemas": {
      "Track": {
        "type": "object",
        "description": "Public view of a track. price_eur is a legacy internal column name — the value is in USD.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string"
          },
          "genre": {
            "type": "string"
          },
          "bpm": {
            "type": "integer"
          },
          "music_key": {
            "type": "string",
            "description": "Musical key, e.g. \"C#m\"."
          },
          "price_eur": {
            "type": "number",
            "description": "List price in USD (field name is legacy)."
          },
          "discount_percent": {
            "type": "integer"
          },
          "is_premium": {
            "type": "boolean",
            "description": "Premium curation tier."
          },
          "is_hot": {
            "type": "boolean",
            "description": "Hot curation tier."
          },
          "has_project_file": {
            "type": "boolean"
          },
          "status": {
            "type": "string",
            "enum": [
              "published",
              "sold"
            ]
          },
          "duration_sec": {
            "type": "integer"
          },
          "cover_url": {
            "type": "string",
            "format": "uri"
          },
          "preview_url": {
            "type": "string",
            "format": "uri",
            "description": "Streamable watermarked preview."
          },
          "description": {
            "type": "string"
          },
          "metadata": {
            "type": "object",
            "description": "Public metadata subset: genres[], tags[], vocal fields, DAW/plugins info, files_manifest (which deliverables the track includes).",
            "additionalProperties": true
          },
          "producer": {
            "$ref": "#/components/schemas/Producer"
          }
        }
      },
      "Producer": {
        "type": "object",
        "description": "Public producer profile (anonymous ghost-producer identity).",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "username": {
            "type": "string"
          },
          "producer_level": {
            "type": "integer"
          },
          "tracks_sold": {
            "type": "integer"
          },
          "country": {
            "type": "string"
          },
          "verified": {
            "type": "boolean",
            "description": "Identity (KYC) verified."
          },
          "avatar_url": {
            "type": "string",
            "format": "uri"
          },
          "rating_avg": {
            "type": "number"
          },
          "rating_count": {
            "type": "integer"
          }
        }
      },
      "Pagination": {
        "type": "object",
        "properties": {
          "page": {
            "type": "integer"
          },
          "page_size": {
            "type": "integer"
          },
          "total": {
            "type": "integer"
          },
          "total_pages": {
            "type": "integer"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable message describing the problem and how to fix the request."
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Malformed request (e.g. invalid UUID).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource does not exist or is not public.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limit exceeded (default 200 requests/min per IP). Every response carries RateLimit-Limit/Remaining/Reset; a 429 adds Retry-After.",
        "headers": {
          "Retry-After": {
            "description": "Seconds to wait before retrying.",
            "schema": {
              "type": "integer"
            }
          },
          "RateLimit-Limit": {
            "description": "Request limit for the current window.",
            "schema": {
              "type": "integer"
            }
          },
          "RateLimit-Remaining": {
            "description": "Requests remaining in the current window.",
            "schema": {
              "type": "integer"
            }
          },
          "RateLimit-Reset": {
            "description": "Seconds until the window resets.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "ServerError": {
        "description": "Temporary server problem — retry later.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error": {
        "description": "Any other error. Body is always the Error object; the HTTP status carries the category.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "headers": {
      "RateLimit-Limit": {
        "description": "Request limit for the current window.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimit-Remaining": {
        "description": "Requests remaining in the current window.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimit-Reset": {
        "description": "Seconds until the window resets.",
        "schema": {
          "type": "integer"
        }
      },
      "Deprecation": {
        "description": "Present only when this endpoint or API version is scheduled for removal (RFC 9745). Value is the date the deprecation was announced, e.g. @1735689600.",
        "schema": {
          "type": "string"
        }
      },
      "Sunset": {
        "description": "Present only when a removal date is scheduled (RFC 8594) — at least 90 days ahead. HTTP-date of removal.",
        "schema": {
          "type": "string"
        }
      },
      "Link": {
        "description": "On deprecated endpoints: rel=\"deprecation\" link to the migration notes at https://ghostproducerworld.com/docs#versioning-and-deprecation.",
        "schema": {
          "type": "string"
        }
      }
    }
  }
}
