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.

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 (Remote URL Ingestion)

MusicShield API supports a single-pass flow with one request. Submit a remote HTTPS audio URL and MusicShield will ingest it and queue protection in one call. Completed jobs provide a protected track download URL, noise footprint URL, and protection metrics.

Supported formats: WAV, MP3, FLAC. Remote file size limit is 100MB.

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

  • • Protection cost is computed as ceil(durationSeconds / 60) * 25 credits.
  • • If analysis: true, add 15 credits per track for protection analysis.
  • • MusicShield protection is billed at 25 credits per minute.
  • • If balance is insufficient, queueing returns 402 Payment Required.

MusicShield Quickstart

Select Python or JavaScript and run URL ingestion + protection queue in one script.

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"))

VeriTune (Remote URL Ingestion)

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 track
  • • Explainability add-on: +15 credits only when:
  • - explainability is requested, and
  • - the track 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 (Remote URL Ingestion)

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

  • • Detection is queued through /verivoice-detect-from-url.
  • • Credit policy is controlled by your deployment configuration and backend env vars.
  • • Recommended: verify your current credits behavior in staging before production rollout.

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 and MusicShield 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",
    "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).