JSI Distribute API Documentation

Everything you need to build against the JSI Distribute API — get your data, react to events in real time, and integrate from WordPress, a custom backend, or anywhere else that can make an HTTP request.

← Back to Developer Settings

1. Quickstart

  1. Go to Developer Settings, create an API key, and pick the scopes you need.
  2. Copy the key immediately — it's shown once, right after creation, and never again. If you lose it, revoke it and create a new one.
  3. Call any endpoint below with Authorization: Bearer <your_key>.

Base URL: https://staging.jsamindustry.com.ng/wp-json/jsi/v1

2. Authentication

Every request (except the Paystack webhook receiver, which isn't part of this API) needs a Bearer token — your raw API key — in the Authorization header:

Authorization: Bearer jsi_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Keys are scoped — a key only works against the endpoints covered by its assigned scope(s), even if the request is otherwise valid. Available scopes:

  • releases:read
  • releases:write
  • artists:read
  • packages:read
  • orders:read
  • royalties:read
  • wallet:read
  • payouts:read
  • webhooks:manage

A request with a valid key but the wrong scope gets 403 insufficient_scope, not a silent empty result — check the error code field, not just the status.

3. Response & error format

Every successful response is wrapped the same way:

{
  "data": { ... or [ ... ] }
}

Every error uses WordPress's standard REST error shape:

{
  "code": "insufficient_scope",
  "message": "This key doesn't have the \"releases:write\" scope.",
  "data": { "status": 403 }
}

Common error codes: unauthorized (401 — missing/invalid/revoked key), insufficient_scope (403), rate_limited (429), not_found (404).

Rate limit: 60 requests per minute, per API key. Every response counts toward the same key's rolling window regardless of which endpoint you call.

4. Endpoints

MethodPathScopeWhat it does
GET/releasesreleases:readList your releases
POST/releasesreleases:writeCreate a release
GET/releases/{id}releases:readGet one release
GET/artistsartists:readList your artist profiles
GET/packagespackages:readList available plans/packages
GET/ordersorders:readList your orders/payments
GET/royaltiesroyalties:readList your royalty records
GET/walletwallet:readYour wallet balance (via the Fintech Wallet bridge)
GET/payoutspayouts:readList your payout history
GET/webhookswebhooks:manageList your webhook endpoints
POST/webhookswebhooks:manageRegister a new webhook endpoint
DELETE/webhooks/{id}webhooks:manageRemove a webhook endpoint

5. Example — cURL

curl "https://staging.jsamindustry.com.ng/wp-json/jsi/v1/releases" \
  -H "Authorization: Bearer jsi_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

6. Building this into a WordPress site

If your integration also runs on WordPress (a separate site, a custom plugin, or a theme's functions.php), use wp_remote_get / wp_remote_post — never raw cURL — so redirects, SSL, and timeouts are handled the way WordPress core expects:

<?php
$response = wp_remote_get( 'https://staging.jsamindustry.com.ng/wp-json/jsi/v1/releases', [
    'headers' => [
        'Authorization' => 'Bearer ' . JSI_API_KEY, // store this in an option or constant, never hard-code it in a committed file
    ],
    'timeout' => 15,
] );

if ( is_wp_error( $response ) ) {
    error_log( 'JSI API error: ' . $response->get_error_message() );
} else {
    $body = json_decode( wp_remote_retrieve_body( $response ), true );
    $releases = $body['data'] ?? [];
    // ...use $releases
}

Creating a release the same way:

<?php
$response = wp_remote_post( 'https://staging.jsamindustry.com.ng/wp-json/jsi/v1/releases', [
    'headers' => [
        'Authorization' => 'Bearer ' . JSI_API_KEY,
        'Content-Type'  => 'application/json',
    ],
    'body'    => wp_json_encode( [
        'title'        => 'My New Single',
        'release_type' => 'single',
    ] ),
    'timeout' => 15,
] );

Store the API key using update_option() (autoload off) or an environment-defined constant in wp-config.php — never in a file that ends up in version control.

7. Example — JavaScript / Node

const res = await fetch("https://staging.jsamindustry.com.ng/wp-json/jsi/v1/releases", {
  headers: { Authorization: "Bearer " + process.env.JSI_API_KEY }
});
const { data: releases } = await res.json();

8. Webhooks

Instead of polling, register a webhook (from Developer Settings or via POST /webhooks) and JSI Distribute will POST a JSON payload to your target_url the moment one of these events happens:

  • release.created
  • release.submitted
  • release.need_fix
  • release.approved
  • release.rejected
  • release.live
  • release.takedown_requested
  • release.taken_down
  • payment.pending
  • payment.paid
  • payment.failed
  • subscription.created
  • subscription.renewed
  • subscription.failed
  • subscription.expired
  • royalty.imported
  • payout.requested
  • payout.completed

Every delivery includes two headers:

X-JSI-Event: release.approved
X-JSI-Signature: 5f2b1c...   (HMAC-SHA256 of the raw request body, using your webhook's signing secret)

Always verify the signature before trusting a payload — anyone can POST to a public URL claiming to be JSI. Your webhook's signing secret is shown once, at creation, exactly like an API key.

Verifying in PHP:

<?php
$raw_body = file_get_contents( 'php://input' );
$signature = $_SERVER['HTTP_X_JSI_SIGNATURE'] ?? '';
$expected = hash_hmac( 'sha256', $raw_body, JSI_WEBHOOK_SECRET );

if ( ! hash_equals( $expected, $signature ) ) {
    http_response_code( 401 );
    exit;
}

$event = json_decode( $raw_body, true );
// handle $event['event'] / $event['data']

Verifying in Node/Express:

const crypto = require("crypto");

app.post("/webhooks/jsi", express.raw({ type: "*/*" }), (req, res) => {
  const expected = crypto.createHmac("sha256", process.env.JSI_WEBHOOK_SECRET)
    .update(req.body).digest("hex");
  if (expected !== req.headers["x-jsi-signature"]) return res.sendStatus(401);

  const event = JSON.parse(req.body);
  // handle event.event / event.data
  res.sendStatus(200);
});

Respond 2xx quickly (under a few seconds) — do slow work (sending emails, calling other APIs) after responding, in a queue or cron job, not inline in the webhook handler.

9. Plan requirements

API and webhook access requires a plan with API access enabled (currently the Developers plan, or any plan whose rules include API access). If your key stops working and you haven't revoked it, check that your subscription is still active on your dashboard.

10. Support

Something not covered here, or behaving unexpectedly? Reach out through your account dashboard's support channel with the request's approximate timestamp and (if relevant) the response body you received — that's normally enough for us to find it in wp_jsi_api_logs and tell you exactly what happened.