API and webhooks

Every account can run music outreach from its own tools. The API uses the same guarded paths as the interface, so the same entitlement, verification and sender checks apply. API access is available on the Pro Industry plan and above.

Base URL and authentication

https://spotifymonthlylisteners.com/api/v1

Send your key as a bearer token on every request. Keys start with ask_live_.

Authorization: Bearer ask_live_…

Create a key in your workspace under Developers. The plaintext is shown once and stored only as a SHA-256 hash, so a lost key cannot be recovered — create a new one and revoke the old. Each key is scoped to a single account; it can never read another account's lists, campaigns or replies.

Endpoints

MethodPathBodyReturns
GET/leads—Your lead lists and their stored contact counts. This is not a recipient export.
POST/leads{"name":"…","text":"email,name lines"}Parses, verifies and imports contacts. Returns the stored list id and how many were added.
GET/templates—Your saved email templates: id, name and subject.
POST/templates{"name":"…","subject":"…","body":"…"}Creates a template. Plain text is converted to simple HTML paragraphs.
GET/campaigns—Your campaigns with running state, counts and list bindings.
POST/campaigns{"name":"…","listIds":["…"],"templateId":"…","replyTo":"…","activate":false}Creates one campaign per list through the normal send gates. `activate` defaults to false.
GET/stats—Aggregated totals across your own campaigns: campaigns, sent, opens and replies.
GET/replies—Your most recent persisted replies.
GET/verify—Verification usage and dashboard for the current month.
POST/verify{"emails":["a@b.com"]} or {"text":"…"}Runs recipient verification and returns the per-address results and held addresses.

Examples

Set the key in your own environment; never commit it.

export PORTAL_CLIENT_API_KEY="ask_live_…"

# List your lead lists
curl --fail-with-body --silent --show-error \
  -H "Authorization: Bearer $PORTAL_CLIENT_API_KEY" \
  https://spotifymonthlylisteners.com/api/v1/leads

# Check verification usage
curl --fail-with-body --silent --show-error \
  -H "Authorization: Bearer $PORTAL_CLIENT_API_KEY" \
  https://spotifymonthlylisteners.com/api/v1/verify

Create a campaign. Save JSON to a file so newlines survive the shell.

cat > campaign.json <<'JSON'
{
  "name": "Single release outreach",
  "listIds": ["<owned-list-id>"],
  "templateId": "<owned-template-id>",
  "replyTo": "you@your-domain.com",
  "activate": false
}
JSON

curl --fail-with-body --silent --show-error \
  -X POST -H "Authorization: Bearer $PORTAL_CLIENT_API_KEY" \
  -H "Content-Type: application/json" \
  --data @campaign.json \
  https://spotifymonthlylisteners.com/api/v1/campaigns

Every response is { "ok": true, "data": … } on success and{ "ok": false, "error": "CODE" } on failure. Verify the stored ids and counts in the response rather than assuming the write landed.

Error codes

CodeHTTPMeaning
INVALID_API_KEY401The key is missing, revoked or does not belong to an active account.
VERIFICATION_PAID_REQUIRED402The account is on a trial. Verification, lead import and sending require an activated paid plan or an admin grant.
NOT_FOUND404That resource path is not implemented, or the requested object is not yours.
INVALID_INPUT400A required field is missing or malformed.
INVALID_NAME400A list or template name must be between 2 and 80 characters.
NO_LEADS_FOUND400No usable email addresses were found in the submitted text.
CAMPAIGN_CREATE_FAILED400Campaign creation was rejected. The response carries a more specific code when one is known.
VERIFICATION_REQUIRED409The list must be verified before it can be sent to.
VERIFICATION_HELD_RECIPIENTS409The list still contains invalid or unresolved addresses. Remove them and verify again.
VERIFICATION_LIMIT_REACHED409The plan verification allowance for this month is used up.
VERIFICATION_RATE_LIMIT409Too many verification requests in a short period.
LIST_IS_SENDING409The list is in use by a running campaign.
LIST_CHANGED_RETRY409The list changed while it was being read. Read it again and retry.
LAUNCH_IN_PROGRESS409Another launch for this account is already in progress.
PROVIDER_UNAVAILABLE502The email provider could not be read. This is a transient failure — it never means your data is empty.
WORKSPACE_UNAVAILABLE503The sending workspace is not available for this account.
VERIFICATION_UNAVAILABLE503Verification could not run. Retry; unknown addresses stay held.

Signed webhooks

Register an HTTPS endpoint in your workspace and choose which events you want. Each delivery is a POST with a JSON body and an HMAC-SHA256 signature over the raw request body.

EventFires when
campaign.createdA campaign was created and is saved.
campaign.launchedA campaign was activated and starts sending.
campaign.pausedA campaign was paused or stopped.
leads.importedA lead list finished importing with verified contacts.
reply.receivedA reply was read from a connected mailbox.

Headers

Content-Type: application/json
X-Webhook-Event: campaign.launched
X-Webhook-Signature: sha256=<hex HMAC of the raw body, keyed with your signing secret>

Verify a delivery

Sign the raw body bytes before any JSON parsing or re-serialisation, and compare in constant time.

import { createHmac, timingSafeEqual } from 'node:crypto';

export function verifyPortalWebhook(rawBody, signature, secret) {
  if (!secret || typeof signature !== 'string') return false;
  const expected = Buffer.from(
    'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex'),
    'utf8',
  );
  const supplied = Buffer.from(signature, 'utf8');
  return supplied.length === expected.length && timingSafeEqual(supplied, expected);
}

Limits you should design for

Honest scope of this API

The API gives you direct access to your own campaign data and the music contact library. It does not sell or guarantee playlist placements, radio spins, press coverage, streams or audience growth, and it cannot bypass entitlement or verification checks. Respect the recipients you contact and any applicable anti-spam law in their country.