BEATSTORE

Docs / BeatStore WordPress Plugin

REST API

The BeatStore player is one client of beatstore/v1. Every public catalog the player can list, filter, and play is available as JSON on your WordPress domain—not on BeatStore’s servers, and not behind an API key we issue.

That is the product: software on hosting you control. The storefront UI is optional.

Base URL

https://yoursite.com/wp-json/beatstore/v1

Pretty permalinks (Settings → Permalinks → Post name) are recommended. Without them, WordPress still serves REST at ?rest_route=/beatstore/v1/beats.

What this is (and is not)

Use it when you want a custom storefront, a native app, a Discord bot, or a landing page that reads the same catalog the player does.

Do not expect platform-style API keys, OAuth apps, signed webhooks, or a BeatStore-hosted gateway. Writes that change a cart or increment plays use a WordPress wp_rest nonce from that same origin. Checkout and file delivery stay Easy Digital Downloads or WooCommerce.

You wantUse
The BeatStore player as-is[beatstore] or the BeatStore Player block
The same player on another siteiframe embed at /beatstore-embed/
Custom chrome, same catalogGET /beats (this page)
Custom checkoutDo not. Send buyers to EDD/Woo checkout

Endpoints

MethodPathAuth
GET/beatsPublic
GET/analyticsLogged-in user with edit_posts
GET/cartPublic
POST/cartX-WP-Nonce or _wpnonce (wp_rest)
POST/playSame nonce
POST/subscribeSame nonce

GET /beats

List catalog tracks. Default page size is 24; per_page max is 40.

QueryTypeNotes
pageintegerDefault 1
per_pageintegerDefault 24, max 40
searchstringTitle search
kindstringTerm slug (beat, kit, …)
genrestringTerm slug
moodstringTerm slug
bpm_minintegerInclusive; 0 means unset
bpm_maxintegerInclusive; 0 means unset
playlistintegerPlaylist post ID (beatstore_playlist)
curl -sS "https://yoursite.com/wp-json/beatstore/v1/beats?per_page=2&genre=hip-hop"
{
  "tracks": [
    {
      "id": 412,
      "title": "Midnight Run",
      "permalink": "https://yoursite.com/downloads/midnight-run/",
      "artworkUrl": "https://yoursite.com/wp-content/uploads/midnight.jpg",
      "previewUrl": "https://yoursite.com/wp-content/uploads/midnight-preview.mp3",
      "sold": false,
      "featured": true,
      "plays": 1804,
      "bpm": 140,
      "key": "Am",
      "duration": 187,
      "kind": "beat",
      "kindName": "Beat",
      "genres": ["hip-hop"],
      "genreNames": ["Hip-Hop"],
      "moods": ["dark"],
      "sections": [
        { "label": "Intro", "time": 0 },
        { "label": "Hook", "time": 32 }
      ],
      "offers": [
        {
          "id": "1",
          "name": "MP3 Lease",
          "amount": "29.99",
          "exclusive": false,
          "commerceRef": "edd:412:1",
          "includes": [
            { "kind": "delivery", "label": "MP3" },
            { "kind": "right", "label": "2,000 copies" },
            { "kind": "right", "label": "500k streams" }
          ]
        }
      ]
    }
  ],
  "page": 1,
  "perPage": 2,
  "total": 86,
  "hasMore": true
}

kind, genre, and mood query values are slugs. Display names are on the track as kindName / genreNames.

Track object

FieldTypeNotes
idintegerEDD download ID or Woo product ID
titlestring
permalinkstringProduct URL on your site
artworkUrlstringFeatured image, or the default artwork setting; may be empty
previewUrlstringStreamable preview URL; may be empty
soldbooleanExclusive already sold
featuredbooleantrue when the beat is pinned to the top of matching catalog/playlist results
playsinteger
bpminteger0 if unset
keystringMusical key; may be empty
durationintegerPreview length in seconds
kindstringKind term slug
kindNamestringKind label
genresstring[]Genre slugs
genreNamesstring[]Genre labels
moodsstring[]Mood slugs
sectionsobject[]{ label, time }time is seconds from start
offersobject[]License tiers (see below)
freeDownloadbooleantrue when Mailchimp is connected and this beat offers a tagged preview MP3 for email

Offer object

FieldTypeNotes
idstringEDD price ID or Woo variation ID
namestringLicense name
amountstringFormatted price from the commerce plugin
exclusivebooleanExclusive purchase marks the beat sold
commerceRefstringStable {adapter}:{beatId}:{offerId} even if the license is renamed
includesobject[]Optional. { kind, label, value? }. kind is delivery (MP3 / WAV / ZIP from the files on that price or variation) or right (usage cap from a mapped Contracts template). Omitted when empty. Labels only. Never file URLs or names.

Buyer download URLs are not in this payload. Delivery stays on the EDD/Woo order. includes may list file types so the license picker can show what each offer ships.

GET /analytics

Privileged plays leaderboard for BeatStore → Analytics. Requires a logged-in user with edit_posts. Unauthenticated requests return 401. This is not the public catalog: it includes hidden tracks, sorts by plays, and caps at 200 rows.

curl -sS -u admin:password "https://yoursite.com/wp-json/beatstore/v1/analytics?days=30"

days is 7, 30, 90, or 0 (all time, default). All time ranks by lifetime _beatstore_plays. The other values rank by plays in that window.

{
  "tracks": [
    {
      "id": 412,
      "title": "Midnight Run",
      "artworkUrl": "",
      "plays": 1804,
      "featured": true,
      "hidden": false,
      "kind": "beat",
      "editUrl": "https://yoursite.com/wp-admin/post.php?post=412&action=edit",
      "permalink": "https://yoursite.com/downloads/midnight-run/"
    }
  ],
  "totals": { "plays": 12000, "tracks": 86, "listeners": 410 },
  "days": 0,
  "daily": [
    { "date": "2026-08-11", "plays": 40 },
    { "date": "2026-09-09", "plays": 18 }
  ],
  "reportsUrl": "https://yoursite.com/wp-admin/edit.php?post_type=download&page=edd-reports",
  "settingsUrl": "https://yoursite.com/wp-admin/admin.php?page=beatstore-settings",
  "analyticsPlays": true
}

reportsUrl is EDD reports, WooCommerce Analytics, or empty when no commerce plugin is active. analyticsPlays is the Settings toggle for gtag / GTM play events. totals.listeners is distinct visitors who started a play after this update (hashed IP and browser). For days 7/30/90 that is visitors active in the window. daily is the matching site-timezone days (zeros included). All time still sends the last 90 daily rows. Daily plays, range leaderboard plays, and unique listeners start counting when a track plays after this update. They are not a backfill of lifetime _beatstore_plays. totals.tracks is still the published catalog size (used when the list is capped).

Do not use this endpoint for a public storefront. Keep using GET /beats.

Cart

GET /cart returns the current commerce cart (cookies / session on that origin).

{
  "count": 1,
  "total": "29.99",
  "checkoutUrl": "https://yoursite.com/checkout/",
  "items": [
    { "key": "abc", "id": 412, "priceId": "1" }
  ]
}

POST /cart adds a license. JSON body:

{ "beatId": 412, "offerId": "1" }
curl -sS -X POST "https://yoursite.com/wp-json/beatstore/v1/cart" \
  -H "Content-Type: application/json" \
  -H "X-WP-Nonce: YOUR_WP_REST_NONCE" \
  -d '{"beatId":412,"offerId":"1"}'

The nonce is a WordPress wp_rest nonce. The player gets one when it renders on your site (wp_create_nonce( 'wp_rest' )). Logged-out visitors can receive a nonce on a page of your WordPress install; this is not a secret API key, and it is not valid as a cross-origin app credential.

On success the response is the same shape as GET /cart. Errors:

StatusCodeWhen
404beatstore_not_foundUnknown beatId
409beatstore_soldExclusive already sold
400beatstore_offerUnknown offerId
403Missing or invalid nonce
500beatstore_cartCommerce adapter failed

After a successful add, send the buyer to checkoutUrl. BeatStore does not take payment over REST.

POST /play

Increments the play count for a beat. Same nonce as cart.

{ "beatId": 412 }
{ "plays": 1805 }

404 if the beat does not exist.

POST /subscribe

Mailchimp free-download gate. Same nonce as cart. Adds the listener to your audience and emails a signed link to the Media Library preview MP3 (not paid license files).

{ "beatId": 412, "email": "listener@example.com", "name": "Alex" }

id is accepted as an alias for beatId.

{ "ok": true, "message": "Check your email for the download link." }
StatusCodeWhen
400beatstore_email / beatstore_name / beatstore_freeInvalid email, missing name, or this beat does not offer free download
403Missing or invalid nonce
429beatstore_rateToo many requests from this IP
500beatstore_mailWordPress could not send the email
503beatstore_mailchimpMailchimp is not connected

Setup: Integrations.

PHP hooks

For themes and add-ons on the same WordPress install. These are not a second HTTP API. Each hook’s parameters, return value, and an example live on PHP Hooks.

HookUse
beatstore_beat_offersFilter license offers before they hit REST / the player
beatstore_resolve_preview_urlResolve or override a preview URL
beatstore_preview_sourcesRegister preview source modules
beatstore_player_footerMarkup after the player
beatstore_order_line_fulfilledAfter a line is delivered (contracts, extra mail)
beatstore_sold_state_changedExclusive marked sold
beatstore_checkout_before_submit / beatstore_checkout_required_fieldsCheckout extras
beatstore_offer_cart_actionsExtra cart actions per offer
beatstore_register_modules / beatstore_commerce_adaptersAdvanced wiring

Player vs REST

The player, the off-site iframe, and GET /beats share one catalog. Use the shortcode when you want the player with no extra code:

[beatstore]

Optional attributes (limit, playlist, kind, genre, mood) match the REST filters. Off-site: https://yoursite.com/beatstore-embed/. Iframe Buy is https://yoursite.com/beatstore-buy/?beat=123&offer=1 (top window, then checkout).

Install and first catalog: Getting Started.