Back to Audiverify

Partner API Documentation

Integrate Audiverify certificate creation, verification, claims management, and webhooks into your platform. API access is included with every account.

REST APIJSONv1Stable

Base URL

https://audiverify.com/api/v1

Authentication

All requests require an API key. API access is included with every account. You can generate keys from the API Keys page in your dashboard, or contact info@motivagroove.com for help. Keys are prefixed with mot_.

Pass your key via either method:

X-API-Key header

curl -H "X-API-Key: mot_your_key_here" \
  https://audiverify.com/api/v1/certificates

Bearer token

curl -H "Authorization: Bearer mot_your_key_here" \
  https://audiverify.com/api/v1/certificates

Scopes

Each API key is issued with specific scopes that control access. Keys are assigned scopes during onboarding -- you cannot change them yourself.

certificates:read

List and retrieve certificate details

certificates:write

Create new certificates via the API

claims:read

View claims and dispute status

claims:write

File and manage claims

verify:read

Verify certificates by number, ISRC, or SHA-256

webhooks:read

List registered webhook endpoints

webhooks:manage

Create, update, rotate secrets, and delete webhooks

Insufficient scope returns 403 with the required scope in the error body.

Rate Limiting

API keys are rate-limited per minute. Default limit is 100 requests/minute. Exceeding the limit returns 429 Too Many Requests.

Request IDs & Traceability

Every API response includes an X-Request-Id header (e.g. req_a1b2c3d4...). Include this ID in any support request for fast debugging.

For POST and PUT requests, you can pass an Idempotency-Key header to ensure the same operation is not executed twice. Cached responses are replayed for 24 hours and include an Idempotency-Replayed: true header.

Idempotent request

curl -X POST https://audiverify.com/api/v1/certificates \
  -H "X-API-Key: mot_your_key" \
  -H "Idempotency-Key: unique-request-id-123" \
  -H "Content-Type: application/json" \
  -d '{"track_title":"My Song","artist_name":"Artist","sha256_fingerprint":"a1b2c3..."}'

Cursor pagination: For large result sets, use cursor instead of offset. The response includes a next_cursor value when more results are available.

Error Codes

400bad_requestMissing or invalid parameters
401unauthorizedInvalid or missing API key
402payment_requiredInsufficient credits
403forbiddenInsufficient permissions
404not_foundResource not found
409duplicateCertificate with this fingerprint already exists
429rate_limitedRate limit exceeded
500internal_errorServer error

Error response format

{
  "error": "Missing required fields: track_title, artist_name, sha256_fingerprint",
  "code": "bad_request"
}

402 - Includes credits remaining and purchase link

{
  "error": "Insufficient credits",
  "code": "payment_required",
  "credits_remaining": 0,
  "purchase_url": "https://audiverify.com/dashboard/billing",
  "docs": "https://audiverify.com/docs#credits"
}

409 - Includes existing certificate details

{
  "error": "A certificate with this audio fingerprint already exists",
  "code": "duplicate",
  "existing_certificate_number": "MOT-2026-XXXXXXX",
  "existing_certificate_id": "uuid",
  "verification_url": "https://audiverify.com/verify/MOT-2026-XXXXXXX"
}

Endpoints

Certificates

GET/api/v1/certificatescertificates:read
List certificates for your workspace. Supports pagination and filtering.

Query Parameters

limitnumberMax results (default 20, max 100)
offsetnumberPagination offset
statusstringFilter: active, revoked
artiststringFilter by artist name (partial match)

Response

{
  "certificates": [
    {
      "id": "uuid",
      "certificate_number": "MOT-2026-O8RZNDOO",
      "track_title": "c",
      "artist_name": "sasa radic",
      "status": "active",
      "isrc": "GXHE72600042",
      "sha256_fingerprint": "e3b0c44...",
      "issued_at": "2026-02-13T14:32:00Z",
      "verification_url": "https://audiverify.com/verify/MOT-2026-O8RZNDOO"
    }
  ],
  "pagination": {
    "total": 12,
    "limit": 20,
    "offset": 0,
    "has_more": false
  }
}
POST/api/v1/certificatescertificates:write
Create a new certificate. Costs 1 credit. The SHA-256 fingerprint must be unique.

Request Body (JSON)

track_titlestringrequiredTitle of the track
artist_namestringrequiredName of the artist
sha256_fingerprintstringrequired64-char hex SHA-256 hash of the audio file
isrcstringISRC code (auto-uppercased)
typestringsound_recording or composition (default: sound_recording)
declaration_textstringDeclaration type (default: Original Work)
audio_file_namestringOriginal filename of the audio

Response

{
  "success": true,
  "certificate": {
    "id": "uuid",
    "certificate_number": "MOT-2026-XXXXXXX",
    "track_title": "My Song",
    "artist_name": "Artist Name",
    "status": "active",
    "sha256_fingerprint": "a1b2c3d4...",
    "issued_at": "2026-02-15T10:00:00Z",
    "verification_url": "https://audiverify.com/verify/MOT-2026-XXXXXXX"
  }
}

Verification

GET/api/v1/verifyverify:read
Verify a certificate by certificate number, SHA-256 hash, or ISRC. Rate-limited by IP.

Query Parameters

certificate_numberstringe.g. MOT-2026-O8RZNDOO
sha256string64-char hex SHA-256 hash
isrcstringISRC code

Response

{
  "verified": true,
  "certificate": {
    "certificate_number": "MOT-2026-O8RZNDOO",
    "track_title": "c",
    "artist_name": "sasa radic",
    "status": "active",
    "isrc": "GXHE72600042",
    "sha256_fingerprint": "e3b0c44...",
    "declaration": "Original Work"
  },
  "verification_url": "https://audiverify.com/verify/MOT-2026-O8RZNDOO"
}
POST/api/v1/verifyverify:read
Verify by submitting an audio file's SHA-256 hash in the request body.

Request Body (JSON)

sha256stringrequired64-char hex SHA-256 hash of the audio file

Response

{
  "verified": false,
  "message": "No matching certificate found for this hash",
  "sha256": "abc123..."
}

Deep Verification (Premium)

GET/api/v1/verify/deepverify:read
Premium verification that returns full certificate data including DWD results, AI detection, contributors, and version history. Costs 1 credit per call.

Query Parameters

certificate_numberstringe.g. MOT-2026-XXXXXXX
sha256string64-char hex SHA-256 hash
isrcstringISRC code

Response

{
  "verified": true,
  "certificate": { ... },
  "ai_disclosure": {
    "involvement": "assisted",
    "tools": ["Suno"],
    "plan_tier": "paid"
  },
  "deep_scan": {
    "dwd_status": "clean",
    "ai_detection_status": "not_detected"
  },
  "contributors": [...],
  "version_history": [...],
  "credits_remaining": 42
}

API Billing

GET/api/v1/billing/checkoutbilling:write
View available API tiers, current tier, and usage stats.

Response

{
  "current_tier": "free",
  "monthly_requests_used": 450,
  "monthly_requests_limit": 10000,
  "available_tiers": {
    "free": { "price": "$0/month", "requests": "10,000/month" },
    "starter": { "price": "$49/month", "requests": "50,000/month" },
    "pro": { "price": "$199/month", "requests": "500,000/month" }
  }
}
POST/api/v1/billing/checkoutbilling:write
Create a Stripe Checkout session to upgrade your API tier.

Request Body (JSON)

tierstringrequired"starter" or "pro"
success_urlstringCustom redirect after payment

Response

{
  "checkout_url": "https://checkout.stripe.com/...",
  "session_id": "cs_...",
  "tier": "starter",
  "price": "$49.00/month"
}

Claims

GET/api/v1/claimsclaims:read
List claims filed against certificates in your workspace.

Query Parameters

limitnumberMax results (default 20, max 100)
offsetnumberPagination offset
statusstringFilter: pending, responded, resolved, expired

Response

{
  "claims": [
    {
      "id": "uuid",
      "type": "ownership",
      "status": "pending",
      "description": "I am the original author...",
      "claimant": {
        "name": "Jane Doe",
        "email": "jane@example.com"
      },
      "certificate": {
        "certificate_number": "MOT-2026-O8RZNDOO",
        "track_title": "c",
        "artist_name": "sasa radic"
      },
      "response_deadline": "2026-03-15T00:00:00Z",
      "created_at": "2026-02-15T10:00:00Z"
    }
  ],
  "pagination": { "total": 1, "limit": 20, "offset": 0, "has_more": false }
}

Webhooks

GET/api/v1/webhookswebhooks:read
List webhook endpoints configured for your workspace.

Response

{
  "webhooks": [
    {
      "id": "uuid",
      "url": "https://yourapp.com/hooks/audiverify",
      "events": ["certificate.created", "claim.filed"],
      "is_active": true,
      "stats": {
        "success_count": 42,
        "failure_count": 1
      }
    }
  ],
  "available_events": [
    "certificate.created", "certificate.updated", "certificate.revoked",
    "claim.filed", "claim.responded", "claim.resolved",
    "contributor.invited", "contributor.acknowledged",
    "payment.completed", "credits.purchased", "credits.used"
  ]
}
POST/api/v1/webhookswebhooks:manage
Create a new webhook endpoint. The secret is returned only once -- save it immediately.

Request Body (JSON)

urlstringrequiredHTTPS endpoint to receive webhook events
eventsstring[]Events to subscribe to (default: certificate.created)
descriptionstringHuman-readable label for this webhook

Response

{
  "success": true,
  "webhook": {
    "id": "uuid",
    "url": "https://yourapp.com/hooks/audiverify",
    "events": ["certificate.created"],
    "secret": "abc123...",
    "created_at": "2026-02-15T10:00:00Z"
  },
  "message": "Webhook created. Save the secret - it will not be shown again."
}
DELETE/api/v1/webhookswebhooks:manage
Delete a webhook endpoint by ID.

Query Parameters

idstringrequiredUUID of the webhook to delete

Response

{
  "success": true,
  "message": "Webhook deleted"
}
PATCH/api/v1/webhookswebhooks:manage
Update a webhook: rotate secret, change URL, toggle active state, or update event filters. Use this to re-enable dead-lettered webhooks.

Query Parameters

idstringrequiredUUID of the webhook (query param)
rotate_secretbooleanGenerate a new HMAC secret (old secret stops working immediately)
urlstringNew HTTPS endpoint URL
is_activebooleanEnable or disable the webhook (resets failure count when re-enabling)
eventsstring[]Updated list of event types to subscribe to

Response

{
  "success": true,
  "message": "Webhook updated",
  "updated_fields": ["rotate_secret", "is_active"],
  "new_secret": "avwhsec_abc123...",
  "warning": "Save this secret now. It will not be shown again."
}

Webhook Payloads

Every webhook delivery sends a JSON body with a consistent structure. All timestamps are ISO 8601 UTC.

Payload Structure

All events

{
  "event": "certificate.created",
  "timestamp": "2026-02-15T14:32:00.000Z",
  "data": {
    // event-specific fields
  }
}

Example Payloads

certificate.created

{
  "event": "certificate.created",
  "timestamp": "2026-02-15T14:32:00.000Z",
  "data": {
    "certificate_id": "550e8400-e29b-41d4-a716-446655440000",
    "certificate_number": "MOT-2026-O8RZNDOO",
    "track_title": "Crystal Rain",
    "artist_name": "Sasa Radic",
    "status": "active",
    "isrc": "GXHE72600042",
    "sha256_fingerprint": "a7f3d1b2c9e84f6a0b5d2e7c3f8a1d4b...",
    "verification_url": "https://audiverify.com/verify/MOT-2026-O8RZNDOO"
  }
}

claim.filed

{
  "event": "claim.filed",
  "timestamp": "2026-02-15T14:32:00.000Z",
  "data": {
    "claim_id": "550e8400-e29b-41d4-a716-446655440001",
    "claim_type": "ownership",
    "status": "pending",
    "certificate_number": "MOT-2026-O8RZNDOO",
    "claimant_name": "Jane Doe"
  }
}

contributor.invited

{
  "event": "contributor.invited",
  "timestamp": "2026-02-15T14:32:00.000Z",
  "data": {
    "contributor_id": "550e8400-e29b-41d4-a716-446655440002",
    "contributor_name": "John Producer",
    "role": "Producer",
    "certificate_number": "MOT-2026-O8RZNDOO",
    "status": "pending"
  }
}

credits.purchased

{
  "event": "credits.purchased",
  "timestamp": "2026-02-15T14:32:00.000Z",
  "data": {
    "amount": 100,
    "price_paid": "29.00",
    "currency": "GBP",
    "payment_id": "pi_3abc123..."
  }
}

Delivery Headers

Content-Typeapplication/json
X-Audiverify-Signaturesha256=<hex-signature>
X-Audiverify-Eventcertificate.created
X-Audiverify-Delivery<webhook-id>
X-Audiverify-Timestamp<ISO-8601 timestamp>
User-AgentAudiverify-Webhooks/1.0

Signature Verification

Every webhook delivery is signed using HMAC-SHA256 with the secret returned when you created the webhook. Always verify the signature before processing the payload.

How it works:

  1. Audiverify computes HMAC-SHA256(webhook_secret, raw_body)
  2. The hex digest is sent as X-Audiverify-Signature: sha256=<hex>
  3. Your server recomputes the HMAC and compares using a timing-safe function

Node.js verification

import crypto from 'crypto';

function verifyAudiverify(rawBody, signatureHeader, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  const received = signatureHeader.replace('sha256=', '');
  return crypto.timingSafeEqual(
    Buffer.from(expected, 'hex'),
    Buffer.from(received, 'hex')
  );
}

// In your Express/Next.js handler:
app.post('/hooks/audiverify', (req, res) => {
  const sig = req.headers['x-audiverify-signature'];
  if (!verifyAudiverify(req.body, sig, process.env.AUDIVERIFY_WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }
  const { event, data } = JSON.parse(req.body);
  console.log('Received:', event, data);
  res.status(200).send('OK');
});

Python verification

import hmac, hashlib, json

def verify_audiverify(raw_body: bytes, signature_header: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(), raw_body, hashlib.sha256
    ).hexdigest()
    received = signature_header.replace("sha256=", "")
    return hmac.compare_digest(expected, received)

# In your Flask/FastAPI handler:
@app.post("/hooks/audiverify")
async def webhook(request: Request):
    body = await request.body()
    sig = request.headers.get("x-audiverify-signature", "")
    if not verify_audiverify(body, sig, os.environ["AUDIVERIFY_WEBHOOK_SECRET"]):
        return JSONResponse(status_code=401, content={"error": "Invalid signature"})
    payload = json.loads(body)
    print(f"Received: {payload['event']}")
    return {"ok": True}

Security note

Always use timing-safe comparison (crypto.timingSafeEqual / hmac.compare_digest) to prevent timing attacks. Never use === or == for signature comparison.

Quick Start

Create your first certificate in 3 API calls:

1. Generate the SHA-256 fingerprint of your audio file

bash

sha256sum my-song.wav
# a7f3d1b2c9e84f6a0b5d2e7c3f8a1d4b...

2. Create the certificate

bash

curl -X POST https://audiverify.com/api/v1/certificates \
  -H "X-API-Key: mot_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "track_title": "My Song",
    "artist_name": "My Name",
    "sha256_fingerprint": "a7f3d1b2c9e84f6a0b5d2e7c3f8a1d4b..."
  }'

3. Verify it exists

bash

curl "https://audiverify.com/api/v1/verify?certificate_number=MOT-2026-XXXXXXX" \
  -H "X-API-Key: mot_your_key_here"

Code Examples

Copy-paste examples for common operations. Replace mot_your_key_here with your actual API key.

Node.js

List certificates

const response = await fetch('https://audiverify.com/api/v1/certificates?limit=10', {
  headers: { 'X-API-Key': process.env.MOTIVA_API_KEY }
});
const { certificates, pagination } = await response.json();
console.log(`Found ${pagination.total} certificates`);

Create a certificate

import { createHash } from 'crypto';
import { readFileSync } from 'fs';

// 1. Hash the audio file
const audio = readFileSync('./my-song.wav');
const sha256 = createHash('sha256').update(audio).digest('hex');

// 2. Create the certificate
const res = await fetch('https://audiverify.com/api/v1/certificates', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.MOTIVA_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    track_title: 'Crystal Rain',
    artist_name: 'Sasa Radic',
    sha256_fingerprint: sha256,
    isrc: 'GXHE72600042',
    type: 'sound_recording',
  }),
});

const { certificate } = await res.json();
console.log('Certificate:', certificate.certificate_number);
console.log('Verify at:', certificate.verification_url);

Verify a certificate

const res = await fetch(
  'https://audiverify.com/api/v1/verify?certificate_number=MOT-2026-O8RZNDOO',
  { headers: { 'X-API-Key': process.env.MOTIVA_API_KEY } }
);
const data = await res.json();

if (data.verified) {
  console.log('Verified:', data.certificate.track_title, 'by', data.certificate.artist_name);
} else {
  console.log('Not found');
}

Register a webhook

const res = await fetch('https://audiverify.com/api/v1/webhooks', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.MOTIVA_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    url: 'https://yourapp.com/hooks/audiverify',
    events: ['certificate.created', 'claim.filed', 'contributor.invited'],
    description: 'Production webhook',
  }),
});

const { webhook } = await res.json();
// Save this secret securely -- it is only shown once
console.log('Webhook secret:', webhook.secret);

Python

List certificates

import requests, os

headers = {"X-API-Key": os.environ["MOTIVA_API_KEY"]}
r = requests.get("https://audiverify.com/api/v1/certificates", headers=headers, params={"limit": 10})
data = r.json()
print(f"Found {data['pagination']['total']} certificates")

Create a certificate

import hashlib, requests, os

# 1. Hash the audio file
with open("my-song.wav", "rb") as f:
    sha256 = hashlib.sha256(f.read()).hexdigest()

# 2. Create the certificate
r = requests.post(
    "https://audiverify.com/api/v1/certificates",
    headers={
        "X-API-Key": os.environ["MOTIVA_API_KEY"],
        "Content-Type": "application/json",
    },
    json={
        "track_title": "Crystal Rain",
        "artist_name": "Sasa Radic",
        "sha256_fingerprint": sha256,
        "isrc": "GXHE72600042",
        "type": "sound_recording",
    },
)

cert = r.json()["certificate"]
print(f"Certificate: {cert['certificate_number']}")
print(f"Verify at: {cert['verification_url']}")

Verify a certificate

import requests, os

r = requests.get(
    "https://audiverify.com/api/v1/verify",
    headers={"X-API-Key": os.environ["MOTIVA_API_KEY"]},
    params={"certificate_number": "MOT-2026-O8RZNDOO"},
)
data = r.json()

if data["verified"]:
    c = data["certificate"]
    print(f"Verified: {c['track_title']} by {c['artist_name']}")
else:
    print("Not found")

cURL

List certificates

curl -s "https://audiverify.com/api/v1/certificates?limit=5" \
  -H "X-API-Key: mot_your_key_here" | jq .

Create a certificate

# Generate SHA-256 hash
SHA=$(sha256sum my-song.wav | cut -d' ' -f1)

curl -X POST "https://audiverify.com/api/v1/certificates" \
  -H "X-API-Key: mot_your_key_here" \
  -H "Content-Type: application/json" \
  -d "{
    \"track_title\": \"Crystal Rain\",
    \"artist_name\": \"Sasa Radic\",
    \"sha256_fingerprint\": \"$SHA\",
    \"isrc\": \"GXHE72600042\"
  }"

Verify by ISRC

curl -s "https://audiverify.com/api/v1/verify?isrc=GXHE72600042" \
  -H "X-API-Key: mot_your_key_here" | jq .verified

Embed Verify Widget

Drop a verification widget directly into your website, distributor portal, or label dashboard. No API key required -- works with a single HTML snippet.

Quick Start (HTML)

HTML

<!-- Add this where you want the widget to appear -->
<div
  class="audiverify-verify"
  data-certificate="MOT-2026-ABCDEF12"
  data-theme="dark"
></div>

<!-- Add this before </body> -->
<script src="https://audiverify.com/embed.js" async></script>

iframe (Manual)

If you prefer full control, use an iframe directly:

HTML

<iframe
  src="https://audiverify.com/embed/verify/MOT-2026-ABCDEF12?theme=dark"
  width="100%"
  height="340"
  style="border:none;border-radius:8px"
  title="Audiverify Certificate Verification"
  loading="lazy"
></iframe>

Parameters

ParameterDefaultDescription
data-certificaterequiredThe certificate number to verify
data-themedark"dark" or "light" -- matches your site's theme
data-compactfalseSet to true for a minimal view (no contributors or fingerprint)
data-hide-fingerprintfalseSet to true to hide the SHA-256 audio fingerprint
data-width100%CSS width value for the widget
data-heightautoCSS height -- auto picks 340px (or 160px in compact mode)

Examples

Compact widget for sidebars

HTML

<div
  class="audiverify-verify"
  data-certificate="MOT-2026-ABCDEF12"
  data-compact="true"
  data-theme="dark"
></div>

Light theme for light backgrounds

HTML

<div
  class="audiverify-verify"
  data-certificate="MOT-2026-ABCDEF12"
  data-theme="light"
  data-hide-fingerprint="true"
></div>

Multiple certificates on one page

HTML

<div class="audiverify-verify" data-certificate="MOT-2026-AAAA1111"></div>
<div class="audiverify-verify" data-certificate="MOT-2026-BBBB2222"></div>
<div class="audiverify-verify" data-certificate="MOT-2026-CCCC3333"></div>

<script src="https://audiverify.com/embed.js" async></script>

No API key required. The embed widget uses public verification data only. It does not expose any private data, audio files, or user information. The widget is sandboxed and cannot access the host page. Embed opens are tracked in the Audiverify analytics dashboard.

OpenAPI Specification

A machine-readable OpenAPI 3.1 spec is available for SDK generation, Postman import, or automated testing.

Fetch the spec

curl https://audiverify.com/api/v1/openapi.json | jq .

Compatible with Swagger UI, Postman, Insomnia, and any OpenAPI 3.1 tooling.

Need help integrating? Contact info@motivagroove.com

API access is included with every account. Generate keys from your dashboard or email us for help getting started.

OpenAPI spec available at /api/v1/openapi.json for SDK generation and Postman import.