ArtyShield APIs

This page is the shared API hub for ArtyShield services.

Start with quickstart + results polling for each product. Use the Results Webhooks section when you are ready for push-based result delivery.

You can create API keys and manage webhook subscriptions in your Dashboard under API Keys and Webhooks. Starter is the entry tier for developer access.

Base URL: https://api.artyshield.ai/functions/v1

Authentication

Get both keys from DashboardAPI Keys:

  • <USER_API_KEY>: create it in the API Keys table (full value is shown only once at creation).
  • <ANON_KEY>: copy it from the Gateway Key panel on DashboardAPI Keys.
  • X-API-Key: <USER_API_KEY>
  • apikey: <ANON_KEY>
  • Authorization: Bearer <ANON_KEY>

Header example:

X-API-Key: USER_API_KEY
apikey: ANON_KEY
Authorization: Bearer ANON_KEY

MusicShield

MusicShield protects audio against unauthorized model training and music generation misuse. The API supports a single-pass flow: submit a remote HTTPS audio URL and MusicShield will ingest it and queue protection in one call.

Supported formats: WAV, MP3, FLAC. Remote file size limits are 25MB on free plans and 100MB on paid plans. Free-plan uploads must be MP3.

Method Path Description
POST /musicshield-from-url Single-pass endpoint: ingest URL + queue MusicShield protection + return protectionId and version.
GET /protections-get/{id} Poll one protection and read signed download URLs for protected track/noise footprint.
GET /credits-balance Current credits, plan, and timestamp.

MusicShield Pricing Logic

  • • MusicShield protection is billed at 20 credits per minute.
  • • If analysis: true, add 15 credits per track for protection analysis.
  • • ArtyShield estimates the initial charge when the URL is queued and reconciles final billing from the processed audio duration on completion.
  • • If balance is insufficient, queueing returns or completes with 402 Payment Required.

MusicShield Quickstart

Submit a remote HTTPS audio URL and queue MusicShield protection in one request.

If you send analysis: true, the protection job may complete before analysis is finalized. Continue polling /protections-get/{id} and check protection_metrics.protection_analysis.status.

import requests

base = "https://api.artyshield.ai/functions/v1"
headers = {
    "X-API-Key": "USER_API_KEY",
    "apikey": "ANON_KEY",
    "Authorization": "Bearer ANON_KEY",
    "Content-Type": "application/json",
}

queue = requests.post(
    f"{base}/musicshield-from-url",
    headers=headers,
    json={
        "url": "https://cdn.example.com/catalog/mixdown.wav",
        "filename": "mixdown.wav",
        "protectionStrength": 5,
        "analysis": True,
        # Optional request-level callback:
        # "webhookUrl": "https://your-app.example.com/webhooks/artyshield",
        # "webhookSecret": "whsec_your_signing_secret",
    },
    timeout=300,
)
queue.raise_for_status()
data = queue.json()
print("fileId:", data["fileId"])
print("protectionId:", data["protectionId"])
print("version:", data.get("version"))

MusicShield Results Polling

Poll /protections-get/{id} until protection reaches completed or failed. When analysis: true, keep polling after protection completion until protection_metrics.protection_analysis.status is completed or failed.

import time
import requests

base = "https://api.artyshield.ai/functions/v1"
headers = {
    "X-API-Key": "USER_API_KEY",
    "apikey": "ANON_KEY",
    "Authorization": "Bearer ANON_KEY",
}

protection_id = "PUT_PROTECTION_ID_HERE"
analysis_requested = True  # set to False if you queued with analysis=False
while True:
    r = requests.get(f"{base}/protections-get/{protection_id}", headers=headers, timeout=60)
    r.raise_for_status()
    data = r.json()
    status = (data.get("status") or "").lower()
    metrics = data.get("protection_metrics") or {}
    analysis = metrics.get("protection_analysis") or {}
    analysis_status = (analysis.get("status") or "").lower() if isinstance(analysis, dict) else ""

    print("status:", status or "unknown", "| analysis_status:", analysis_status or "n/a")

    if status == "failed":
        break
    if status == "completed":
        if not analysis_requested:
            break
        if analysis_status in {"completed", "failed"}:
            break

    time.sleep(4)

if status == "completed":
    print("version:", data.get("version"))
    print("snr_db:", data.get("snr_db"))
    print("protected_download_url:", data.get("protected_download_url"))
    print("noise_footprint_download_url:", data.get("noise_footprint_download_url"))
    if analysis_requested:
        if analysis:
            print("analysis_status:", analysis.get("status"))
            print("analysis_summary:", analysis.get("summary") or analysis.get("text"))
        else:
            print("analysis_status: not_available_yet")
else:
    print("error:", data.get("error_msg"))

VoiceShield

VoiceShield protects speech and voice clips against unauthorized voice cloning. The API supports a single-pass flow: submit a remote HTTPS audio URL and VoiceShield will ingest it and queue protection in one call.

Supported formats: WAV, MP3, FLAC. File size limits match MusicShield: 25MB on free plans and 100MB on paid plans. Free-plan uploads must be MP3.

Method Path Description
POST /voiceshield-from-url Single-pass endpoint: ingest URL + queue VoiceShield protection + return protectionId and version.
GET /protections-get/{id} Poll one protection and read the signed protected voice download URL.
GET /credits-balance Current credits, plan, and timestamp.

VoiceShield Pricing Logic

  • • VoiceShield protection is billed at 10 credits per minute.
  • • ArtyShield estimates the initial charge when the URL is queued and reconciles final billing from the processed audio duration on completion.
  • • If balance is insufficient, queueing returns or completes with 402 Payment Required.

VoiceShield Quickstart

Submit a remote HTTPS audio URL and queue VoiceShield protection in one request.

import requests

base = "https://api.artyshield.ai/functions/v1"
headers = {
    "X-API-Key": "USER_API_KEY",
    "apikey": "ANON_KEY",
    "Authorization": "Bearer ANON_KEY",
    "Content-Type": "application/json",
}

queue = requests.post(
    f"{base}/voiceshield-from-url",
    headers=headers,
    json={
        "url": "https://cdn.example.com/voice/voice_sample.wav",
        "filename": "voice_sample.wav",
        # Optional request-level callback:
        # "webhookUrl": "https://your-app.example.com/webhooks/artyshield",
        # "webhookSecret": "whsec_your_signing_secret",
    },
    timeout=300,
)
queue.raise_for_status()
data = queue.json()
print("fileId:", data["fileId"])
print("protectionId:", data["protectionId"])
print("version:", data.get("version"))

VoiceShield Results Polling

Poll /protections-get/{id} until the VoiceShield protection reaches completed or failed. Completed VoiceShield results include a signed protected voice download URL; there is no noise footprint or protection analysis output.

import time
import requests

base = "https://api.artyshield.ai/functions/v1"
headers = {
    "X-API-Key": "USER_API_KEY",
    "apikey": "ANON_KEY",
    "Authorization": "Bearer ANON_KEY",
}

protection_id = "PUT_PROTECTION_ID_HERE"
while True:
    response = requests.get(f"{base}/protections-get/{protection_id}", headers=headers, timeout=60)
    response.raise_for_status()
    data = response.json()
    status = (data.get("status") or "").lower()
    print("status:", status or "unknown")

    if status in {"completed", "failed"}:
        break
    time.sleep(4)

if status == "completed":
    print("version:", data.get("version"))
    print("duration_seconds:", data.get("duration_seconds"))
    print("cost_credits:", data.get("cost_credits"))
    print("protected_download_url:", data.get("protected_download_url"))
else:
    print("error:", data.get("error_msg"))

VeriTune

Integrations can POST HTTPS audio URLs directly. ArtyShield fetches each URL in the Edge Function, stores it in your project storage, then queues detection.

Minimum duration requirement: submitted audio must be longer than 10 seconds. Remote file size limit is 100MB.

Method Path Description
POST /veritune-detect-from-url One-call flow: ingest URL + queue detection + return detectionId and version.
GET /detections-get/{id} Poll one detection.
GET /detections-list?status=&limit=&cursor= Paginated history (newest first).
GET /credits-balance Current credits, plan, and timestamp.

VeriTune Pricing Logic

  • • VeriTune detection: 5 credits per clip
  • • Explainability add-on: +15 credits only when:
  • - explainability is requested, and
  • - the clip is detected as AI and explainability is generated.

VeriTune Quickstart

Select Python or JavaScript and run remote URL ingestion + detection.

import requests

base = "https://api.artyshield.ai/functions/v1"
headers = {
    "X-API-Key": "USER_API_KEY",
    "apikey": "ANON_KEY",
    "Authorization": "Bearer ANON_KEY",
    "Content-Type": "application/json",
}

upload = requests.post(
    f"{base}/veritune-detect-from-url",
    headers=headers,
    json={
        "url": "https://cdn.example.com/track.wav",
        "filename": "track.wav",
        "explain": True
    },
    timeout=180,
)
upload.raise_for_status()
result = upload.json()
print("detectionId:", result["detectionId"])
print("version:", result.get("version"))

VeriTune Results Polling

Poll a VeriTune detection until completion, then read detection results and explainability outputs if present.

import time
import requests

base = "https://api.artyshield.ai/functions/v1"
headers = {
    "X-API-Key": "USER_API_KEY",
    "apikey": "ANON_KEY",
    "Authorization": "Bearer ANON_KEY",
}

detection_id = "PUT_DETECTION_ID_HERE"
while True:
    r = requests.get(f"{base}/detections-get/{detection_id}", headers=headers, timeout=60)
    r.raise_for_status()
    data = r.json()
    status = data.get("status")
    print("status:", status)
    if status in {"completed", "failed"}:
        break
    time.sleep(4)

details = data.get("details") or {}
detection = details.get("detection") or {}
print("version:", data.get("version"))
print("ai_probability:", detection.get("ai_probability"))
print("human_probability:", detection.get("human_probability"))

artifacts = detection.get("explainability_artifacts") or {}
analysis = detection.get("analysis") or {}
if artifacts:
    print("slice_mp3_path:", artifacts.get("slice_mp3_path"))
    print("figure_path:", artifacts.get("evidence_areas_png_path"))
if analysis:
    print("analysis_text:", analysis.get("text"))

VeriVoice

Integrations can POST HTTPS audio URLs directly. ArtyShield fetches each URL in the Edge Function, stores it in your project storage, then queues voice-clone detection.

Minimum duration requirement: submitted audio must be longer than 3 seconds. Remote file size limit is 100MB.

Method Path Description
POST /verivoice-detect-from-url One-call flow: ingest URL + queue detection + return detectionId and version.
GET /detections-get/{id} Poll one detection.
GET /detections-list?status=&limit=&cursor= Paginated history (newest first).
GET /credits-balance Current credits, plan, and timestamp.

VeriVoice Pricing Logic

  • • VeriVoice detection: 5 credits per clip
  • • Explainability add-on: +15 credits only when:
  • - explainability is requested, and
  • - the clip is detected as AI and explainability is generated.

VeriVoice Quickstart

Select Python or JavaScript and run remote URL ingestion + queue detection.

import requests

base = "https://api.artyshield.ai/functions/v1"
headers = {
    "X-API-Key": "USER_API_KEY",
    "apikey": "ANON_KEY",
    "Authorization": "Bearer ANON_KEY",
    "Content-Type": "application/json",
}

upload = requests.post(
    f"{base}/verivoice-detect-from-url",
    headers=headers,
    json={
        "url": "https://cdn.example.com/voice.wav",
        "filename": "voice.wav",
        "explain": True
    },
    timeout=180,
)
upload.raise_for_status()
result = upload.json()
print("detectionId:", result["detectionId"])
print("version:", result.get("version"))

VeriVoice Results Polling

Poll a VeriVoice detection until completion, then read detection results and explainability outputs if present.

import time
import requests

base = "https://api.artyshield.ai/functions/v1"
headers = {
    "X-API-Key": "USER_API_KEY",
    "apikey": "ANON_KEY",
    "Authorization": "Bearer ANON_KEY",
}

detection_id = "PUT_VERIVOICE_DETECTION_ID_HERE"
while True:
    r = requests.get(f"{base}/detections-get/{detection_id}", headers=headers, timeout=60)
    r.raise_for_status()
    data = r.json()
    status = data.get("status")
    print("status:", status)
    if status in {"completed", "failed"}:
        break
    time.sleep(4)

details = data.get("details") or {}
detection = details.get("detection") or {}
print("version:", data.get("version"))
print("ai_probability:", detection.get("ai_probability"))
print("human_probability:", detection.get("human_probability"))

artifacts = detection.get("explainability_artifacts") or {}
analysis = detection.get("analysis") or {}
if artifacts:
    print("slice_mp3_path:", artifacts.get("slice_mp3_path"))
    print("figure_path:", artifacts.get("evidence_areas_png_path"))
if analysis:
    print("analysis_text:", analysis.get("text"))

Results Webhooks

Use webhooks for push-based result delivery instead of polling. You can manage webhook subscriptions in DashboardWebhooks and API keys in DashboardAPI Keys.

  • • Quickstart + polling remains the default integration path for all products.
  • • For request-level callbacks, you can pass webhookUrl and webhookSecret in detection, MusicShield, and VoiceShield protection requests.
  • • Request-level callbacks currently deliver: detection.completed, detection.failed, protection.completed, protection.failed.
  • • Dashboard-managed subscriptions currently deliver: protection.completed, protection.failed.

Webhook Headers

Header Description
X-ArtyShield-Event Event name from the supported webhook events currently delivered by ArtyShield.
X-ArtyShield-Delivery-Id Unique delivery id for idempotency tracking.
X-ArtyShield-Timestamp Unix timestamp used for signature calculation.
X-ArtyShield-Signature Format: t=<timestamp>,v1=<hex_hmac_sha256>, generated from <timestamp>.<raw_body>.

Webhook Payload Examples

Payload structure differs by event type. Use the detection shape for detection.* and the protection shape for protection.*.

Detection Event Example

{
  "event": "detection.completed",
  "deliveryId": "f42f2b14-6f59-46fd-95a7-c186f4093fb9",
  "sentAt": "2026-02-25T22:00:00.000Z",
  "data": {
    "id": "result_uuid",
    "status": "completed",
    "product": "veritune",
    "detector_version": "VeriTune",
    "cost_credits": 5,
    "details": {}
  }
}

Protection Event Example

{
  "event": "protection.completed",
  "deliveryId": "f42f2b14-6f59-46fd-95a7-c186f4093fb9",
  "sentAt": "2026-02-25T22:00:00.000Z",
  "data": {
    "id": "protection_uuid",
    "status": "completed",
    "protection_version": "MusicShield or VoiceShield",
    "snr_db": 24.7,
    "duration_seconds": 95,
    "cost_credits": 55,
    "protected_expires_at": "2026-03-27T22:00:00.000Z",
    "protection_metrics": {},
    "input_file": { "id": "input_file_uuid", "filename": "mixdown.wav" },
    "output_file": { "id": "output_file_uuid", "filename": "mixdown_protected.wav" },
    "noise_footprint_file": { "id": "noise_file_uuid", "filename": "mixdown_noise.png" }
  }
}

Signature Verification

import hashlib
import hmac

def is_valid_signature(raw_body: bytes, header: str, secret: str) -> bool:
    # header format: "t=1700000000,v1=abcdef..."
    parts = dict(part.split("=", 1) for part in header.split(",") if "=" in part)
    ts = parts.get("t", "")
    received = parts.get("v1", "")
    if not ts or not received:
        return False
    signed = f"{ts}.".encode("utf-8") + raw_body
    expected = hmac.new(secret.encode("utf-8"), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, received)

Minimal Receiver Endpoint

Return 2xx quickly after verification. Process heavy business logic asynchronously.

import hashlib
import hmac
import os
from flask import Flask, jsonify, request

app = Flask(__name__)
WEBHOOK_SECRET = os.environ.get("ARTYSHIELD_WEBHOOK_SECRET", "")

def is_valid_signature(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(part.split("=", 1) for part in header.split(",") if "=" in part)
    ts = parts.get("t", "")
    received = parts.get("v1", "")
    if not ts or not received:
        return False
    signed = f"{ts}.".encode("utf-8") + raw_body
    expected = hmac.new(secret.encode("utf-8"), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, received)

@app.post("/webhooks/artyshield")
def artyshield_webhook():
    raw_body = request.get_data()  # exact bytes for signature check
    signature = request.headers.get("X-ArtyShield-Signature", "")
    if not is_valid_signature(raw_body, signature, WEBHOOK_SECRET):
        return jsonify({"message": "invalid signature"}), 401

    payload = request.get_json(silent=True) or {}
    event = payload.get("event")
    result = payload.get("data") or {}

    # TODO: enqueue internal processing using deliveryId for idempotency
    _ = event, result
    return jsonify({"ok": True}), 200

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8080)

Error Handling Notes

  • 401 Unauthorized: API key / auth header mismatch.
  • 402 Payment Required: insufficient credits.
  • 415 Unsupported Media Type: only WAV/MP3/FLAC accepted.
  • 400 Bad Request: remote file exceeds 100MB (returns File too large).