sidearm docs

Developer Docs

sidearm is infrastructure for media protection and rights management. It lets creators, publishers, and platforms protect their work from unauthorized AI training, detect when it happens, find stolen content in the wild, and sell usage rights — all through a single API.

What sidearm does

Protect

Media + Presets

Submit any media — images, video, audio, GIFs, text, PDF — and sidearm applies invisible protections from the algorithm catalog. Protections are layered: cryptographic provenance proves origin, invisible poisons make AI training produce garbage, ownership tattoos survive any transformation, and style cloaking prevents mimicry. Choose a preset for the right balance of speed, protection depth, and search capability.

Detect

Detect + Membership Inference

Find out if your content was used without permission. Fingerprint detection checks if suspect media matches your registered corpus — from exact copies to AI-generated derivatives. AI detection determines if media was machine-generated. Membership inference audits a specific model to prove it trained on your protected work, producing cryptographic evidence for legal proceedings.

Search

Search

Find similar content across your registered corpus using tiered similarity search. Exact match finds byte-identical copies. Quick match catches resizes and recompression. Perceptual match catches crops, filters, and screenshots. Compositional match finds collages, partial usage, and AI derivatives that borrow your composition. Search the open web or scope to specific tags and projects.

License

Rights, Deals, Agents

Turn protection into revenue. Create license offers with structured rights policies — usage scope, channels, territory, exclusivity, duration — and pricing. Buyers purchase directly or negotiate custom deals. Autonomous agents can handle negotiation on either side. Every license produces a signed digital certificate that anyone can verify independently.

Algorithm Catalog

At the core of sidearm is a catalog of protection, indexing, fingerprinting, search, and detection algorithms. Each algorithm is a specific technique — invisible poisoning, ownership tattooing, style cloaking, perceptual hashing, neural embedding, and more. Algorithms are combined into presets that control what happens when you ingest media. As algorithms improve, presets abstract the changes — your integration stays the same. Every algorithm supports versioning (-latest or pinned like -v1) so detection stays aligned with the version that protected the content. See the full Algorithm Catalog for details.

Base URLhttps://api.sdrm.io
API Version1.5.1
Catalog Version1.1.0
Machine-readable spec

OpenAPI 3.0.3 — feed this to your agent or SDK generator.

/openapi.json

Quick start

Ingest and protect an image in one call:

curl -X POST https://api.sdrm.io/api/v1/media \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "media_url": "https://cdn.example.com/photo.jpg",
    "preset": "standard",
    "deletes_at": "2027-01-01T00:00:00Z"
  }'

Authentication

All endpoints require an API key:

Option 1: Bearer Token

Authorization: Bearer sk_live_abc123...

Option 2: API Key Header

X-API-Key: sk_live_abc123...

Scopes

Tokens use resource:action scopes. Use wildcards like media:* for all actions on a resource, or * for full access.

ScopeAccess
media:readList and get media records
media:createIngest new media
media:updateUpdate media records
media:deleteDelete media and storage
detect:runRun fingerprint detection
detect:aiRun AI-generated content detection
detect:forensicRun membership inference audits
search:runExecute similarity searches
search:readList and get past searches
rights:readView offers, agreements, deals
rights:createCreate offers, start deals, purchase licenses
rights:manageCounter-offer and accept deals
presets:readList and get presets
presets:createCreate custom presets
presets:deleteDelete custom presets
agents:readView agent configuration and activity
agents:createCreate negotiation agents
agents:updateUpdate agent mandates and parameters
agents:deleteDeactivate agents
jobs:readPoll job status and results
accounts:readView account details
accounts:updateUpdate account info
accounts:deleteDelete account
tokens:readList and view API tokens
tokens:createCreate new API tokens
tokens:updateUpdate token name, scopes, or tags
tokens:deleteRevoke API tokens
billing:readView billing and usage events

Wildcards & Bundles

ScopeExpands to
media:*media:read, media:create, media:update, media:delete
detect:*detect:run, detect:ai, detect:forensic
search:*search:run, search:read
rights:*rights:read, rights:create, rights:manage
presets:*presets:read, presets:create, presets:delete
agents:*agents:read, agents:create, agents:update, agents:delete
accounts:*accounts:read, accounts:update, accounts:delete
tokens:*tokens:read, tokens:create, tokens:update, tokens:delete
*All scopes (full access)

Common use-case bundles

Convenience aliases that expand to a set of scopes for typical integrations:

BundleScopesUse case
creatormedia:*, search:*, detect:*, rights:read, agents:*, jobs:read, billing:readContent creators managing their own assets
agentmedia:read, search:run, rights:read, rights:create, agents:*, jobs:readAI agents discovering and licensing content
auditordetect:*, search:*, jobs:readForensic detection and compliance auditing

Errors

Consistent JSON format:

{
  "error": "not_found",
  "message": "Resource not found"
}
StatusMeaning
400Invalid request body or parameters
401Missing or invalid API key
403Insufficient scope or ownership
404Resource not found
409Conflict (e.g. exclusivity violation)
429Rate limit exceeded
500Internal server error

Algorithms & Run

Browse all available algorithms and run them individually on your media. Includes well-known research algorithms (Nightshade, Glaze, SPECTRA, C2PA) and proprietary sidearm protections. Use POST /api/v1/run for full control, or POST /api/v1/protect for curated bundles.

Quick Examples

Poison an image with Nightshade

{ "algorithms": ["nightshade"], "media_url": "https://example.com/photo.jpg" }

Apply Glaze + PAI hardening

{ "algorithms": ["glaze", "pai"], "media_url": "https://example.com/art.png" }

Watermark text with SPECTRA

{ "algorithms": ["spectra"], "text": "My copyrighted article..." }

Protect audio from voice cloning

{ "algorithms": ["audio-nightshade", "vocal-glaze"], "media_url": "https://example.com/song.mp3" }
GET/api/v1/algorithms

List available algorithms

Returns every algorithm available on sidearm. Open algorithms are individual algorithms by their real name. Proprietary algorithms are sidearm-branded bundles that resolve to open algorithms. Both are runnable via POST /api/v1/run.

Query Parameters

FieldTypeDescription
categorystringopenproprietaryFilter by category.
media_typestringimagevideoaudiogiftextpdfFilter by supported media type.

Response 200Algorithm catalog

{
  "data": [
    {
      "id": "nightshade",
      "name": "Nightshade 2.0",
      "summary": "Feature-space perturbation that poisons AI training data.",
      "category": "open",
      "media_types": [
        "image",
        "video",
        "gif"
      ],
      "technique": "adversarial perturbation",
      "gpu_required": true,
      "paper_url": "https://arxiv.org/abs/2310.13828",
      "run_endpoint": "POST /api/v1/run"
    },
    {
      "id": "harmonic-bait",
      "name": "Harmonic Bait",
      "summary": "AI training poison (sidearm bundle).",
      "category": "proprietary",
      "media_types": [
        "image",
        "video",
        "gif",
        "audio"
      ],
      "technique": "adversarial perturbation",
      "gpu_required": true,
      "paper_url": null,
      "run_endpoint": "POST /api/v1/run"
    }
  ],
  "meta": {
    "total": 21,
    "hint": "Use POST /api/v1/run with { \"algorithms\": [\"nightshade\"], \"media_url\": \"...\" } to run any algorithm."
  }
}
POST/api/v1/run

Run specific algorithms on media

Choose exactly which algorithms to apply to your media. Unlike POST /api/v1/protect (which uses bundled protection levels), this endpoint gives full control. Algorithms execute sequentially in the order provided. By default, output is wrapped in C2PA provenance signing.

Scopes:protectembed*

Request Body

FieldTypeDescription
algorithmsstring[]Algorithm IDs to run, in order. See GET /api/v1/algorithms.
media_urlstring (URL)Public URL of media. Provide one of media_url, media, or text.
mediastring (base64)Base64-encoded file bytes.
textstringRaw text content (for text/PDF algorithms).
mimestringMIME type hint (optional, auto-detected).
c2pa_wrapbooleanWrap output in C2PA provenance signing.
tagsstring[]Tags for billing attribution and access control.
webhook_urlstring (URL)POST callback on completion.

Example Request

{
  "algorithms": [
    "nightshade"
  ],
  "media_url": "https://example.com/photo.jpg"
}

Response 202Job queued

{
  "data": {
    "job_id": "e3b0c442-98fc-1c14-b39f-f32d674f8abc",
    "status": "queued",
    "algorithms": [
      "nightshade"
    ],
    "status_url": "/api/v1/jobs/e3b0c442-98fc-1c14-b39f-f32d674f8abc"
  }
}
POST/api/v1/embed

Extract raw embedding vectors

Extract raw numeric feature vectors from media for use in similarity search, clustering, or ML pipelines. Unlike POST /api/v1/run (which transforms media and returns a modified file), this endpoint returns vectors only. Runs extraction algorithms in parallel and returns all vectors in the completed job result.

Scopes:readembed*

Request Body

FieldTypeDescription
algorithmsstring[]Extractable algorithm IDs. Images: phash, dinov2, clip, scene-graph. Audio: chromaprint, clap, audio-structure. Text/PDF: sentence-transformers.
media_urlstring (URL)Public URL of media. Provide one of media_url, media, or text.
mediastring (base64)Base64-encoded file bytes.
textstringRaw text content (for sentence-transformers).
mimestringMIME type hint (optional, auto-detected).

Example Request

{
  "algorithms": [
    "dinov2",
    "clip"
  ],
  "media_url": "https://example.com/photo.jpg"
}

Response 202Job queued

{
  "data": {
    "job_id": "e3b0c442-98fc-1c14-b39f-f32d674f8abc",
    "status": "queued",
    "algorithms": [
      "dinov2",
      "clip"
    ],
    "status_url": "/api/v1/jobs/e3b0c442-98fc-1c14-b39f-f32d674f8abc"
  }
}
POST/api/v1/protect

Bundled media protection

Apply a curated pipeline of protection algorithms based on protection level. For full control over which algorithms run, use POST /api/v1/run instead. Standard applies core protections; maximum adds style protection, adversarial hardening, and more.

Scopes:protectembed*

Request Body

FieldTypeDescription
media_urlstring (URL)Public URL of media. Provide one of media_url, media, or text.
mediastring (base64)Base64-encoded file bytes.
textstringRaw text content.
mimestringMIME type hint (optional).
levelstringstandardmaximumProtection level.
tagsstring[]Tags for billing and access control.
webhook_urlstring (URL)POST callback on completion.

Example Request

{
  "media_url": "https://example.com/photo.jpg",
  "level": "standard"
}

Response 202Job queued

{
  "data": {
    "job_id": "e3b0c442-98fc-1c14-b39f-f32d674f8abc",
    "status": "queued",
    "status_url": "/api/v1/jobs/e3b0c442-98fc-1c14-b39f-f32d674f8abc"
  }
}

Media

The core resource. Ingest media to register, fingerprint, and protect it in a single call. Accepts images (JPEG, PNG, WebP, TIFF), video (MP4, MOV, WebM), audio (MP3, WAV, FLAC, AAC), GIFs, raw text, and PDF. The preset parameter controls which algorithms are applied.

System Presets

Built-in presets available to all accounts. Create custom presets via the Presets API.

registerRegister

Proof of existence. Content hash + Digital Trust provenance. No active protections, no indexing/search/detection. Lightweight — use when you just need a timestamp and origin proof.

Algorithms: digital_trust-latest, content_hash-latest

search_readySearch Ready

Everything in register plus full fingerprint indexes for detection and similarity search. Media is not modified — no active protections are applied.

Algorithms: digital_trust-latest, content_hash-latest, perceptual_hash-latest, neural_embed-latest, scene_graph-latest

standardStandard

Core protections + indexing, search & detection. Invisible poison, ownership tattoo, and provenance. Enables perceptual search and fingerprint detection.

Algorithms: digital_trust-latest, id_embed-latest, harmonic_bait-latest, content_hash-latest, perceptual_hash-latest, neural_embed-latest

maximumMaximum

Full protection suite. Everything in standard plus style protection, tamper shielding, and full indexing, fingerprinting, search & detection. Enables all search tiers and forensic membership inference.

Algorithms: digital_trust-latest, id_embed-latest, harmonic_bait-latest, style_cloak-latest, tamper_shield-latest, content_hash-latest, perceptual_hash-latest, neural_embed-latest, scene_graph-latest

POST/api/v1/media

Ingest & protect media

The primary endpoint. Submit media to register it, build fingerprint indexes, and apply active protections — all in one call. The preset parameter controls which algorithms run. Accepts images, video, audio, GIFs, raw text, and PDF. Returns 202 with a job ID for async processing.

Scopes:media:create

Request Body

FieldTypeDescription
media_urlstring (uri)Public URL of the file. Accepts images (JPEG, PNG, WebP, TIFF), video (MP4, MOV, WebM), audio (MP3, WAV, FLAC, AAC), GIFs, raw text, and PDF. Provide one of media_url, media, or text.
mediastring (base64)Base64-encoded file bytes. Type is auto-detected from content.
textstringRaw text content (articles, manuscripts, books).
presetstringregistersearch_readystandardmaximumPreset ID or system preset name. Determines which algorithms are applied.
deletes_atstring (datetime)ISO 8601 timestamp. The record and all associated storage are permanently deleted at this time.
tagsstring[]Labels for grouping, search scope, and cost attribution.
metadataobjectArbitrary key-value pairs stored with the record. Use for internal IDs, project codes, or any context you want to retrieve later.
webhook_urlstring (uri)URL to receive a POST callback when processing completes.

Example Request

{
  "media_url": "https://cdn.example.com/photo.jpg",
  "preset": "standard",
  "deletes_at": "2027-02-17T00:00:00Z",
  "tags": [
    "portfolio",
    "client-acme"
  ],
  "webhook_url": "https://example.com/hooks/sidearm"
}

Response 202Accepted

{
  "data": {
    "job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "status": "queued",
    "preset": "standard",
    "status_url": "/api/v1/jobs/a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  }
}
GET/api/v1/media

List media

List registered media for the authenticated account. Cursor-based pagination.

Scopes:media:read

Query Parameters

FieldTypeDescription
cursorstringPagination cursor from a previous response.
limitintegerResults per page (1–100).

Response 200OK

{
  "data": [
    {
      "id": "d4e5f6a7-...",
      "tags": [
        "portfolio"
      ],
      "preset": "standard",
      "status": "active",
      "created_at": "2026-02-17T12:00:00Z"
    }
  ],
  "meta": {
    "next_cursor": "eyJpZCI6Ii4uLiJ9"
  }
}
GET/api/v1/media/{id}

Get media

Retrieve a single media record by ID.

Scopes:media:read

Path Parameters

FieldTypeDescription
idstring (uuid)Media ID.

Response 200OK

{
  "data": {
    "id": "d4e5f6a7-b8c9-0123-4567-890abcdef012",
    "account_id": "...",
    "manifest": "a1b2c3d4e5f6...",
    "storage_url": "https://storage.example.com/embedded/d4e5f6a7.jpg",
    "original_storage_key": "originals/d4e5f6a7.jpg",
    "preset": "standard",
    "algorithms_applied": [
      "digital_trust-v1",
      "id_embed-v1",
      "harmonic_bait-v1",
      "content_hash-v1",
      "perceptual_hash-v1",
      "neural_embed-v1"
    ],
    "deletes_at": "2027-02-17T00:00:00Z",
    "tags": [
      "portfolio"
    ],
    "metadata": {
      "internal_id": "img-42"
    },
    "created_at": "2026-02-17T12:00:00Z"
  }
}
PATCH/api/v1/media/{id}

Update media

Update the original media URL when the source asset has moved.

Scopes:media:update

Path Parameters

FieldTypeDescription
idstring (uuid)Media ID.

Request Body

FieldTypeDescription
original_media_urlstring (uri)New URL for the original media.

Example Request

{
  "original_media_url": "https://cdn.example.com/new-location/original.jpg"
}

Response 200OK

{
  "data": {
    "id": "d4e5f6a7-...",
    "updated_at": "2026-02-17T13:00:00Z"
  }
}
DELETE/api/v1/media/{id}

Delete media

Delete a media record and all associated storage. Returns a deletion record ID that serves as a permanent, auditable proof of deletion. Irreversible.

Scopes:media:delete

Path Parameters

FieldTypeDescription
idstring (uuid)Media ID.

Response 200OK

{
  "data": {
    "deleted": true,
    "deletion_id": "del_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "media_id": "d4e5f6a7-...",
    "deleted_at": "2026-02-17T14:00:00Z"
  }
}
GET/api/v1/media/deletions

List deletion records

List all deletion records for the authenticated account. Each record is immutable proof that a media asset and all its associated storage were permanently destroyed.

Scopes:media:read

Query Parameters

FieldTypeDescription
cursorstringPagination cursor.
limitintegerResults per page (1–100).

Response 200OK

{
  "data": [
    {
      "id": "del_a1b2c3d4-...",
      "media_id": "d4e5f6a7-...",
      "media_type": "image",
      "tags": [
        "portfolio"
      ],
      "algorithms_applied": [
        "digital_trust-v1",
        "id_embed-v1",
        "harmonic_bait-v1"
      ],
      "storage_purged": true,
      "deleted_at": "2026-02-17T14:00:00Z"
    }
  ],
  "meta": {
    "next_cursor": null
  }
}
GET/api/v1/media/deletions/{id}

Get deletion record

Retrieve a single deletion record with full details of what was destroyed — original asset metadata, algorithms that were applied, storage locations purged, and timestamps.

Scopes:media:read

Path Parameters

FieldTypeDescription
idstringDeletion record ID.

Response 200OK

{
  "data": {
    "id": "del_a1b2c3d4-...",
    "media_id": "d4e5f6a7-b8c9-0123-4567-890abcdef012",
    "account_id": "...",
    "media_type": "image",
    "original_hash": "sha256:e3b0c44298fc1c14...",
    "preset": "standard",
    "algorithms_applied": [
      "digital_trust-v1",
      "id_embed-v1",
      "harmonic_bait-v1"
    ],
    "tags": [
      "portfolio",
      "client-acme"
    ],
    "storage_locations_purged": [
      "originals/d4e5f6a7.jpg",
      "embedded/d4e5f6a7.jpg",
      "indexes/d4e5f6a7/*"
    ],
    "storage_purged": true,
    "deleted_at": "2026-02-17T14:00:00Z",
    "deleted_by": "acc_e5f6a7b8-..."
  }
}
GET/api/v1/media/deletions/{id}/certificate

Download deletion certificate

Download a signed legal certificate proving that the specified media asset was permanently deleted and all copies purged from storage. The certificate is a signed JWT that can be independently verified. Share this with your legal team or include in compliance reports.

Scopes:media:read

Path Parameters

FieldTypeDescription
idstringDeletion record ID.

Query Parameters

FieldTypeDescription
formatstringjwtpdfCertificate format.

Response 200OK

{
  "data": {
    "deletion_id": "del_a1b2c3d4-...",
    "format": "jwt",
    "certificate": "eyJhbGciOiJFZDI1NTE5IiwidHlwIjoiSldUIn0...",
    "verify_url": "https://api.sdrm.io/verify/del_a1b2c3d4-...",
    "issued_at": "2026-02-17T14:00:01Z"
  }
}

Presets

Coming soon

Presets are named combinations of algorithms. System presets cover common use cases; create custom presets for specific client or project needs. As algorithms evolve, presets abstract the changes.

These endpoints are part of the planned API surface and are not yet available. The schemas and behavior documented here represent the target design.

GET/api/v1/presets

List presets

List all available presets — both system presets and custom presets you've created. System presets are always available; custom presets are scoped to your account.

Scopes:presets:read

Response 200OK

{
  "data": [
    {
      "id": "register",
      "type": "system",
      "name": "Register",
      "description": "Proof of existence only",
      "algorithms": [
        "digital_trust-latest",
        "content_hash-latest"
      ],
      "media_types": [
        "image",
        "video",
        "audio",
        "gif",
        "text",
        "pdf"
      ]
    },
    {
      "id": "search_ready",
      "type": "system",
      "name": "Search Ready",
      "description": "Register + full search indexes, no active protections",
      "algorithms": [
        "digital_trust-latest",
        "content_hash-latest",
        "perceptual_hash-latest",
        "neural_embed-latest",
        "scene_graph-latest"
      ],
      "media_types": [
        "image",
        "video",
        "audio",
        "gif",
        "text",
        "pdf"
      ]
    },
    {
      "id": "standard",
      "type": "system",
      "name": "Standard",
      "description": "Core protections + indexing, search & detection",
      "algorithms": [
        "digital_trust-latest",
        "id_embed-latest",
        "harmonic_bait-latest",
        "content_hash-latest",
        "perceptual_hash-latest",
        "neural_embed-latest"
      ],
      "media_types": [
        "image",
        "video",
        "audio",
        "gif",
        "text",
        "pdf"
      ]
    },
    {
      "id": "maximum",
      "type": "system",
      "name": "Maximum",
      "description": "Full protection suite + indexing, search & detection",
      "algorithms": [
        "digital_trust-latest",
        "id_embed-latest",
        "harmonic_bait-latest",
        "style_cloak-latest",
        "tamper_shield-latest",
        "content_hash-latest",
        "perceptual_hash-latest",
        "neural_embed-latest",
        "scene_graph-latest"
      ],
      "media_types": [
        "image",
        "video",
        "audio",
        "gif",
        "text",
        "pdf"
      ]
    },
    {
      "id": "preset_abc123",
      "type": "custom",
      "name": "Client Portfolio",
      "description": "Standard + style protection",
      "algorithms": [
        "digital_trust-latest",
        "id_embed-latest",
        "harmonic_bait-latest",
        "style_cloak-v1"
      ],
      "media_types": [
        "image",
        "gif"
      ]
    }
  ]
}
GET/api/v1/presets/{id}

Get preset

Retrieve details of a preset including its full algorithm list and compatible media types.

Scopes:presets:read

Path Parameters

FieldTypeDescription
idstringPreset ID or system preset name.

Response 200OK

{
  "data": {
    "id": "standard",
    "type": "system",
    "name": "Standard",
    "description": "Core protections + indexing, search & detection",
    "algorithms": [
      "digital_trust-latest",
      "id_embed-latest",
      "harmonic_bait-latest",
      "content_hash-latest",
      "perceptual_hash-latest",
      "neural_embed-latest"
    ],
    "media_types": [
      "image",
      "video",
      "audio",
      "gif",
      "text",
      "pdf"
    ],
    "algorithm_details": [
      {
        "code": "digital_trust-latest",
        "resolved": "digital_trust-v1",
        "name": "Digital Trust",
        "description": "Cryptographic provenance manifest — verify origin with a click."
      },
      {
        "code": "id_embed-latest",
        "resolved": "id_embed-v1",
        "name": "ID Embedding",
        "description": "Invisible digital tattoo that proves ownership. Survives compression, cropping, and noise."
      },
      {
        "code": "harmonic_bait-latest",
        "resolved": "harmonic_bait-v1",
        "name": "Harmonic Bait",
        "description": "Invisible poison that confuses AI models. Training on protected work produces garbled, unusable output."
      }
    ]
  }
}
POST/api/v1/presets

Create custom preset

Create a custom preset by selecting algorithms from the catalog. Useful for clients with specific protection requirements.

Scopes:presets:create

Request Body

FieldTypeDescription
namestringDisplay name for the preset.
descriptionstringWhat this preset is for.
algorithmsstring[]Algorithm codes to include. See Algorithm Catalog.
media_typesstring[]imagevideoaudiogiftextpdfRestrict to specific media types.

Example Request

{
  "name": "Agency Portfolio",
  "description": "Standard protection + style cloaking for visual assets",
  "algorithms": [
    "digital_trust-latest",
    "id_embed-latest",
    "harmonic_bait-latest",
    "style_cloak-latest"
  ],
  "media_types": [
    "image",
    "gif",
    "video"
  ]
}

Response 201Created

{
  "data": {
    "id": "preset_xyz789",
    "type": "custom",
    "name": "Agency Portfolio",
    "algorithms": [
      "digital_trust-latest",
      "id_embed-latest",
      "harmonic_bait-latest",
      "style_cloak-latest"
    ],
    "media_types": [
      "image",
      "gif",
      "video"
    ],
    "created_at": "2026-02-17T12:00:00Z"
  }
}
DELETE/api/v1/presets/{id}

Delete custom preset

Delete a custom preset. System presets cannot be deleted. Media already processed with this preset is unaffected.

Scopes:presets:delete

Path Parameters

FieldTypeDescription
idstringCustom preset ID.

Response 200OK

{
  "data": {
    "deleted": true
  }
}

Detect

Detect fingerprinted content in the wild, identify AI-generated media, and audit models for unauthorized training on your corpus. Detection and search share the same tier system — see the Search Tiers reference below.

Membership Inference Methods

Used with POST /detect/membership to prove a model trained on your content.

patternPattern Analysis2–5 min

Detects structural fingerprints left in model weights by protected content. Effective against Harmonic Bait and Text Poison — if the model ingested poisoned work, pattern analysis surfaces the signal.

statisticalStatistical Testing5–15 min

Hypothesis testing on model loss distributions. Catches models that memorized your content even without active protections — works with any registered media, but strongest when ID Embedding or Doc Tattoo was applied.

combinedCombined (recommended)10–20 min

Runs both methods and cross-validates. Produces the strongest cryptographic proof. Required for legal-grade evidence.

POST/api/v1/detect

Detect fingerprinted media

Check whether submitted media matches your registered corpus. The tier parameter controls forensic depth — higher tiers catch more sophisticated copies but take longer. Which tiers are available depends on the preset used when the media was ingested.

Scopes:detect:run

Request Body

FieldTypeDescription
media_urlstring (uri)Public URL of media to analyze.
mediastring (base64)Base64-encoded media bytes.
tagsstring[]Limit detection to corpus matching these tags.
tierstringexactquickperceptualcompositionalfullForensic depth. See tier reference below.

Example Request

{
  "media_url": "https://cdn.example.com/suspect-image.png",
  "tier": "perceptual"
}

Response 200OK

{
  "data": {
    "has_signal": true,
    "confidence": 0.94,
    "matched_manifests": [
      "mfst_abc123"
    ],
    "detection_method": "neural",
    "tier": "perceptual",
    "corpus_similarity": [
      {
        "media_id": "d4e5f6a7-...",
        "score": 0.91
      }
    ]
  }
}
POST/api/v1/detect/ai

Detect AI-generated content

Determine whether media was generated by an AI model. Routes to specialized detectors by media type. Returns 202 with a job ID.

Scopes:detect:ai

Request Body

FieldTypeDescription
media_urlstring (uri)Public URL of media to analyze. Type is auto-detected.
mediastring (base64)Base64-encoded media bytes.
textstringRaw text to analyze.
tagsstring[]Tags for filtering and attribution.

Example Request

{
  "media_url": "https://cdn.example.com/generated-image.png"
}

Response 202Accepted

{
  "data": {
    "job_id": "f0e1d2c3-...",
    "status": "queued",
    "media_type": "image",
    "status_url": "/api/v1/jobs/f0e1d2c3-..."
  }
}
POST/api/v1/detect/membership

Membership inference

Determine if a suspect model was trained on your protected content. Submit one or many registered media IDs to check in a single audit. Produces cryptographic proof of training exposure. Returns 202 — inference can take several minutes depending on corpus size and method.

Scopes:detect:forensic

Request Body

FieldTypeDescription
content_idsstring[] (uuid)IDs of your registered media to check against. Pass a single ID or an array to audit training exposure across multiple works at once.
suspect_modelstringIdentifier of the model to audit (e.g. 'stable-diffusion-xl', 'midjourney-v6').
methodstringpatternstatisticalcombinedInference method. See method reference below.
tagsstring[]Tags for attribution.

Example Request

{
  "content_ids": [
    "d4e5f6a7-b8c9-0123-4567-890abcdef012",
    "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  ],
  "suspect_model": "stable-diffusion-xl",
  "method": "combined"
}

Response 202Accepted

{
  "data": {
    "job_id": "c3d4e5f6-...",
    "status": "queued",
    "method": "combined",
    "content_ids_count": 2,
    "suspect_model": "stable-diffusion-xl",
    "status_url": "/api/v1/jobs/c3d4e5f6-..."
  }
}

Rights & Licensing

Coming soon

Sell, license, and negotiate usage rights for your protected media. Built on ODRL-aligned terminology so the same concepts work across legal, technical, and business teams.

These endpoints are part of the planned API surface and are not yet available. The schemas and behavior documented here represent the target design.

Key Concepts

Four objects, each building on the last.

1Offer

A price tag. The licensor attaches an Offer to a media asset — it bundles a Policy (the rights) with Pricing (the cost). A single asset can have multiple offers for different tiers of usage (e.g. 'Editorial Web $250' vs 'Global Exclusive $5,000'). Think of offers as items on a shelf.

Policy

The terms inside an Offer. Specifies exactly what the buyer can do: usage scope (commercial, editorial, personal, AI training), channels (web, social, print, broadcast), territory, exclusivity, duration, and whether modifications or sublicensing are allowed. The policy is the contract language in structured form.

2Agreement

The license. Created automatically when a purchase completes — either from buying an Offer directly or from a Deal negotiation. The Agreement is immutable: it snapshots the exact policy and price at the moment of purchase, so if the licensor later changes the offer terms, existing buyers aren't affected. Includes a signed digital certificate (JWT) the buyer can host publicly for third-party verification.

3Deal

A negotiation thread. When existing offers don't fit, a buyer proposes custom terms and a price. The licensor can accept, counter, or reject. Either party can counter-offer as many times as needed. When someone accepts, the system charges payment and creates an Agreement. Deals are for high-value or custom licensing that requires back-and-forth.

Two Paths to a License

Path A: Direct Purchase

Licensorcreates Offer on their mediaBuyerbrowses GET /media/{id}/offerspurchases with POST /ordersAgreement created

Best for standard licensing where the terms and price are pre-set. Like buying from a catalog.

Path B: Negotiation

Buyerproposes a Deal with custom termscounter-offers exchangeda party acceptspayment chargedAgreement created

Best for high-value or custom licensing. "I want global rights for 2 years with paid social" — terms that don't match any existing offer.

Verification

Every Agreement includes a signed digital certificate (Ed25519 JWT) and a public verify_url.

Buyers can host the certificate on their own domain. Anyone — legal teams, compliance bots, AI agents — can verify the license is valid by hitting the verify URL or validating the JWT signature directly, without contacting sidearm.

GET/api/v1/media/{id}/offers

Browse offers for media

List all active license offers attached to a media asset. Any authenticated user can browse — this is how buyers discover what's available and on what terms.

Scopes:rights:read

Path Parameters

FieldTypeDescription
idstring (uuid)Media ID.

Query Parameters

FieldTypeDescription
territorystringISO 3166-1 alpha-2 country code. Filters to offers that include this territory.
usage_scopestringcommercialeditorialpersonalai_trainingFilter by usage scope.

Response 200OK

{
  "data": [
    {
      "id": "off_888",
      "asset_id": "d4e5f6a7-...",
      "licensor_id": "acc_owner",
      "policy": {
        "name": "Standard Web Commercial",
        "permissions": {
          "usage_scope": "commercial",
          "channels": [
            "web",
            "social_organic"
          ],
          "modifications_allowed": true,
          "sublicensable": false,
          "attribution_required": true
        },
        "constraints": {
          "territory": {
            "type": "include",
            "codes": [
              "US",
              "CA",
              "GB"
            ]
          },
          "exclusivity": {
            "exclusive": false
          },
          "duration": "P1Y"
        }
      },
      "pricing": {
        "amount": 50000,
        "currency": "USD",
        "model": "flat_fee"
      },
      "quantity_limit": null,
      "status": "active",
      "created_at": "2026-01-15T10:00:00Z"
    }
  ]
}
POST/api/v1/offers

Create an offer

As a licensor, create a license offer for one of your media assets. An offer bundles a rights policy (what the buyer can do) with pricing (what it costs). You can create multiple offers per asset — for example, a cheap editorial-only offer and an expensive exclusive commercial offer.

Scopes:rights:create

Request Body

FieldTypeDescription
asset_idstring (uuid)Media ID this offer applies to. Must be owned by your account.
policyobjectThe rights being granted. See Policy fields below.
policy.namestringHuman-readable name for this license tier (e.g. 'Editorial Web Use', 'Global Exclusive').
policy.permissions.usage_scopestringcommercialeditorialpersonalai_trainingAllowed use.
policy.permissions.channelsstring[]websocial_organicsocial_paidbroadcastprintebookappoohWhere the media may be used.
policy.permissions.modifications_allowedbooleanCan the buyer crop, filter, or composite the media?
policy.permissions.sublicensablebooleanCan the buyer grant their clients usage rights? Critical for agencies.
policy.permissions.attribution_requiredbooleanMust the buyer credit the licensor?
policy.constraints.territoryobjectGeographic restriction. { type: 'include' | 'exclude', codes: ['US', 'CA', ...] } using ISO 3166-1 alpha-2. Omit for worldwide.
policy.constraints.exclusivityobjectExclusivity terms. { exclusive: true, scope: 'global' | 'territory' | 'industry', industry_code?: 'automotive' }. Exclusive offers block conflicting sales.
policy.constraints.durationstringLicense duration as ISO 8601 period. E.g. 'P1Y' (1 year), 'P6M' (6 months), 'P999Y' (perpetual).
pricingobjectWhat it costs. See Pricing fields below.
pricing.amountintegerPrice in the smallest currency unit (e.g. cents). 50000 = $500.00.
pricing.currencystringUSDEURGBPISO 4217 currency code.
pricing.modelstringflat_feeroyaltyPricing model.
quantity_limitintegerMax times this offer can be purchased. Null = unlimited. Set to 1 for exclusive offers (auto-archives after sale).

Example Request

{
  "asset_id": "d4e5f6a7-b8c9-0123-4567-890abcdef012",
  "policy": {
    "name": "Editorial Web Use",
    "permissions": {
      "usage_scope": "editorial",
      "channels": [
        "web"
      ],
      "modifications_allowed": false,
      "sublicensable": false,
      "attribution_required": true
    },
    "constraints": {
      "territory": {
        "type": "include",
        "codes": [
          "US"
        ]
      },
      "duration": "P1Y"
    }
  },
  "pricing": {
    "amount": 25000,
    "currency": "USD",
    "model": "flat_fee"
  }
}

Response 201Created

{
  "data": {
    "id": "off_999",
    "asset_id": "d4e5f6a7-...",
    "policy": {
      "name": "Editorial Web Use"
    },
    "pricing": {
      "amount": 25000,
      "currency": "USD"
    },
    "status": "active",
    "created_at": "2026-02-17T12:00:00Z"
  }
}
POST/api/v1/orders

Purchase a license

Buy a license for a specific media asset by referencing an existing offer. The offer already specifies the asset, the rights, and the price — you're confirming the purchase and providing payment. The system checks exclusivity conflicts, charges your payment method, and generates an immutable Agreement with a signed digital certificate.

Scopes:rights:create

Request Body

FieldTypeDescription
offer_idstringThe offer you're purchasing. This determines which media asset, what rights you receive, and the price. Browse available offers with GET /media/{id}/offers.
payment_methodstringStripe payment method ID (e.g. pm_card_...).

Example Request

{
  "offer_id": "off_888",
  "payment_method": "pm_card_visa_1234"
}

Response 201Created — returns the newly created Agreement

{
  "data": {
    "agreement_id": "agr_999",
    "offer_id": "off_888",
    "asset_id": "d4e5f6a7-...",
    "licensor_id": "acc_owner",
    "licensee_id": "acc_buyer",
    "rights_snapshot": {
      "name": "Standard Web Commercial",
      "permissions": {
        "usage_scope": "commercial",
        "channels": [
          "web",
          "social_organic"
        ],
        "modifications_allowed": true
      },
      "constraints": {
        "territory": {
          "type": "include",
          "codes": [
            "US",
            "CA",
            "GB"
          ]
        },
        "duration": "P1Y"
      }
    },
    "valid_from": "2026-02-17T00:00:00Z",
    "valid_until": "2027-02-17T00:00:00Z",
    "status": "active",
    "payment_ref": "pi_stripe_abc123",
    "digital_certificate": "eyJhbGciOiJFZDI1NTE5...",
    "verify_url": "https://api.sdrm.io/verify/agr_999"
  }
}
GET/api/v1/agreements

List agreements

List license agreements. As a buyer you see licenses you've purchased. As a licensor you see licenses you've sold. Each agreement is an immutable record of the exact terms, price, and asset at the time of purchase.

Scopes:rights:read

Query Parameters

FieldTypeDescription
rolestringbuyerlicensorFilter by your role in the agreement.
asset_idstring (uuid)Filter to a specific media asset.
statusstringactiveexpiredrevokedFilter by status.
cursorstringPagination cursor.

Response 200OK

{
  "data": [
    {
      "id": "agr_999",
      "asset_id": "d4e5f6a7-...",
      "role": "buyer",
      "rights_snapshot": {
        "name": "Standard Web Commercial"
      },
      "status": "active",
      "valid_until": "2027-02-17T00:00:00Z"
    }
  ],
  "meta": {
    "next_cursor": null
  }
}
GET/api/v1/agreements/{id}

Get agreement

Retrieve the full agreement including the rights snapshot (exact terms at time of purchase), asset details (checksum for integrity), and the signed digital certificate. The certificate is a JWT that can be hosted publicly — anyone can verify the license without contacting sidearm.

Scopes:rights:read

Path Parameters

FieldTypeDescription
idstringAgreement ID.

Response 200OK

{
  "data": {
    "id": "agr_999",
    "offer_id": "off_888",
    "licensor_id": "acc_owner",
    "licensee_id": "acc_buyer",
    "asset_details": {
      "id": "d4e5f6a7-...",
      "media_type": "image",
      "checksum": "sha256:a1b2c3...",
      "preset": "standard"
    },
    "rights_snapshot": {
      "name": "Standard Web Commercial",
      "permissions": {
        "usage_scope": "commercial",
        "channels": [
          "web",
          "social_organic"
        ],
        "modifications_allowed": true,
        "sublicensable": false,
        "attribution_required": true
      },
      "constraints": {
        "territory": {
          "type": "include",
          "codes": [
            "US",
            "CA",
            "GB"
          ]
        },
        "exclusivity": {
          "exclusive": false
        },
        "duration": "P1Y"
      }
    },
    "valid_from": "2026-02-17T00:00:00Z",
    "valid_until": "2027-02-17T00:00:00Z",
    "status": "active",
    "payment_ref": "pi_stripe_abc123",
    "digital_certificate": "eyJhbGciOiJFZDI1NTE5...",
    "verify_url": "https://api.sdrm.io/verify/agr_999"
  }
}
POST/api/v1/deals

Propose a deal

Start a negotiation for a media asset when the existing offers don't fit your needs. You propose your own terms (policy) and price. This opens a thread between you and the licensor where either party can counter-offer until you reach agreement or walk away.

Scopes:rights:create

Request Body

FieldTypeDescription
asset_idstring (uuid)Media ID you want to license.
proposed_policyobjectYour proposed rights policy — same structure as the policy object in offers. Specify the permissions and constraints you want.
offer_amountintegerYour proposed price in the smallest currency unit (cents). E.g. 150000 = $1,500.00.
currencystringISO 4217 currency code.
messagestringOpening message to the licensor explaining what you need and why.

Example Request

{
  "asset_id": "d4e5f6a7-b8c9-0123-4567-890abcdef012",
  "proposed_policy": {
    "name": "Global English Commercial",
    "permissions": {
      "usage_scope": "commercial",
      "channels": [
        "web",
        "social_organic",
        "social_paid"
      ],
      "modifications_allowed": true,
      "sublicensable": false
    },
    "constraints": {
      "territory": {
        "type": "include",
        "codes": [
          "US",
          "CA",
          "GB",
          "AU"
        ]
      },
      "duration": "P2Y"
    }
  },
  "offer_amount": 150000,
  "message": "We'd like English-speaking territory rights for 2 years with paid social included."
}

Response 201Created

{
  "data": {
    "id": "deal_456",
    "asset_id": "d4e5f6a7-...",
    "buyer_id": "acc_buyer",
    "licensor_id": "acc_owner",
    "status": "open",
    "current_proposal": {
      "policy": {
        "name": "Global English Commercial"
      },
      "amount": 150000,
      "currency": "USD",
      "proposed_by": "buyer"
    },
    "created_at": "2026-02-17T12:00:00Z"
  }
}
GET/api/v1/deals/{id}

Get deal

Get the current state of a negotiation — the latest proposal on the table, full revision history, and status. Both the buyer and licensor can view the deal.

Scopes:rights:read

Path Parameters

FieldTypeDescription
idstringDeal ID.

Response 200OK

{
  "data": {
    "id": "deal_456",
    "asset_id": "d4e5f6a7-...",
    "buyer_id": "acc_buyer",
    "licensor_id": "acc_owner",
    "status": "open",
    "current_proposal": {
      "policy": {
        "name": "Global English Commercial",
        "permissions": {
          "usage_scope": "commercial",
          "channels": [
            "web",
            "social_organic",
            "social_paid"
          ]
        }
      },
      "amount": 175000,
      "currency": "USD",
      "proposed_by": "licensor"
    },
    "history": [
      {
        "round": 1,
        "from": "buyer",
        "amount": 150000,
        "policy": {
          "name": "Global English Commercial"
        },
        "message": "We'd like English-speaking territory rights for 2 years.",
        "timestamp": "2026-02-17T12:00:00Z"
      },
      {
        "round": 2,
        "from": "licensor",
        "amount": 175000,
        "message": "We can do $1,750 for those territories.",
        "timestamp": "2026-02-17T14:30:00Z"
      }
    ],
    "created_at": "2026-02-17T12:00:00Z",
    "updated_at": "2026-02-17T14:30:00Z"
  }
}
POST/api/v1/deals/{id}/counter

Counter-offer

Send a counter-proposal with revised terms and/or price. Either party (buyer or licensor) can counter. Each counter replaces the current proposal on the table.

Scopes:rights:manage

Path Parameters

FieldTypeDescription
idstringDeal ID.

Request Body

FieldTypeDescription
proposed_policyobjectRevised rights policy. Omit to keep the current terms and only change the price.
offer_amountintegerRevised price in cents. Omit to keep the current price and only change terms.
messagestringMessage to the other party explaining the revision.

Example Request

{
  "offer_amount": 160000,
  "message": "Can we meet in the middle at $1,600?"
}

Response 200OK

{
  "data": {
    "id": "deal_456",
    "status": "open",
    "current_proposal": {
      "amount": 160000,
      "proposed_by": "buyer"
    },
    "round": 3
  }
}
POST/api/v1/deals/{id}/accept

Accept deal

Accept the current proposal on the table. The system charges the buyer's payment method, creates an immutable Agreement with the negotiated terms, and moves the deal to 'accepted'. Both parties can accept — whoever agrees to the other's last counter-offer closes the deal.

Scopes:rights:manage

Path Parameters

FieldTypeDescription
idstringDeal ID.

Request Body

FieldTypeDescription
payment_methodstringStripe payment method ID. Required when the buyer accepts. The licensor accepting means the buyer's pre-authorized method is charged.

Example Request

{
  "payment_method": "pm_card_visa_1234"
}

Response 201Created — returns the deal status and the new Agreement

{
  "data": {
    "deal_id": "deal_456",
    "status": "accepted",
    "agreement_id": "agr_1001",
    "payment_ref": "pi_stripe_xyz",
    "verify_url": "https://api.sdrm.io/verify/agr_1001"
  }
}

Rights Discovery

Machine-readable rights discovery for crawlers, AI agents, and compliance bots. These endpoints enable automated license discovery and acquisition — from C2PA manifests embedded in files to Schema.org markup on web pages to the OLP token flow for programmatic purchases.

Multi-Layer Discovery

Five protocols ensure your content is discoverable regardless of how agents find it.

1In-file (C2PA)

C2PA manifest assertions embedded during protection: c2pa.training-mining (notAllowed) + sidearm.licensing (acquire URL) + schema_org.CreativeWork. Readable by Adobe, Google, Microsoft C2PA tools.

2In-file (IPTC)

IPTC/XMP metadata written to image headers: Licensor URL, WebStatement, CopyrightOwnerID. Readable by Google Images, Bing, CMS platforms, and digital asset managers.

3On-page (Schema.org + TDM)

Publisher pastes the embed snippet (GET /rights/{id}/embed) into their HTML. Includes Schema.org JSON-LD with acquireLicensePage, TDM-AI meta tags, and link rel='license'. Readable by search engines and AI crawlers.

4HTTP headers

The rights discovery endpoint returns TDM-Reservation: 1 and TDM-Policy headers. W3C TDM-aware crawlers check these before any content processing.

5API (RSL/OLP)

Automated agents obtain a CAP token via POST /rsl/token, then present it on subsequent requests. This OAuth 2.0-based flow bridges to the existing offers/deals model for programmatic license acquisition.

Discovery Chain

How an AI agent discovers and acquires a license.

Agent finds mediaReads C2PA / IPTC / Schema.orgGET /rights/{id}GET /rights/{id}/acquirePOST /rsl/tokenPOST /ordersAgreement + CAP token

All discovery protocols converge on the same canonical URL: GET /api/v1/rights/{media_id}

GET/api/v1/rights/{media_id}

Get rights info

Public rights discovery endpoint. Returns machine-readable licensing information for a media asset. No authentication required — this is the canonical URL embedded in C2PA manifests, IPTC metadata, and Schema.org markup. Supports content negotiation: application/json (default), application/ld+json (Schema.org JSON-LD), or application/xml (RSL document). Returns TDM-Reservation and TDM-Policy headers.

Path Parameters

FieldTypeDescription
media_idstring (uuid)Media ID.

Response 200OK — rights info with protocol-specific discovery URLs

{
  "data": {
    "media_id": "d4e5f6a7-...",
    "media_type": "image",
    "owner_id": "acc_owner",
    "rights": {
      "ai_training_allowed": false,
      "acquire_license_url": "https://api.sdrm.io/api/v1/rights/d4e5f6a7-.../acquire",
      "tdm_policy_url": "https://api.sdrm.io/api/v1/rights/tdm-policy",
      "verify_url": "https://api.sdrm.io/verify/d4e5f6a7-..."
    },
    "protocols": {
      "c2pa": {
        "training_mining": "notAllowed",
        "constraint_info": "https://api.sdrm.io/api/v1/rights/d4e5f6a7-..."
      },
      "schema_org": {
        "type": "CreativeWork",
        "acquireLicensePage": "https://api.sdrm.io/api/v1/rights/d4e5f6a7-.../acquire"
      },
      "rsl": {
        "document_url": "https://api.sdrm.io/api/v1/rights/d4e5f6a7-...",
        "olp_token_endpoint": "https://api.sdrm.io/api/v1/rsl/token"
      },
      "tdm": {
        "policy_url": "https://api.sdrm.io/api/v1/rights/tdm-policy",
        "reservation": 1
      },
      "iptc": {
        "licensor_url": "https://api.sdrm.io/api/v1/rights/d4e5f6a7-.../acquire",
        "web_statement": "https://api.sdrm.io/api/v1/rights/d4e5f6a7-..."
      }
    }
  }
}
GET/api/v1/rights/{media_id}/acquire

Acquire license

License acquisition entry point. No authentication required. For human users (browser Accept header), redirects to the web UI licensing page. For machines (Accept: application/json), returns structured data showing how to purchase a license: available offers, the purchase endpoint, negotiation endpoint, and the OLP token endpoint for automated acquisition.

Path Parameters

FieldTypeDescription
media_idstring (uuid)Media ID.

Response 200OK — acquisition instructions and endpoints

{
  "data": {
    "media_id": "d4e5f6a7-...",
    "media_type": "image",
    "ai_training_allowed": false,
    "acquisition": {
      "browse_offers": "/api/v1/media/{id}/offers",
      "purchase": "/api/v1/orders",
      "negotiate": "/api/v1/deals",
      "olp_token_endpoint": "/api/v1/rsl/token"
    },
    "human_url": "https://sdrm.io/media/d4e5f6a7-.../license"
  }
}
GET/api/v1/rights/{media_id}/embed

Publisher embed snippet

Returns ready-to-paste HTML markup for publishers to embed alongside their protected media. The snippet includes Schema.org JSON-LD, TDM-AI meta tags, and link rel='license'. Requires authentication — the media must belong to your account. Use ?format=json to get the structured components separately instead of the HTML block.

Scopes:readrights:read

Path Parameters

FieldTypeDescription
media_idstring (uuid)Media ID.

Query Parameters

FieldTypeDescription
formatstringhtmljsonOutput format.

Response 200OK — HTML snippet or JSON components

{
  "data": {
    "media_id": "d4e5f6a7-...",
    "schema_org": {
      "@context": "https://schema.org",
      "@type": "CreativeWork",
      "acquireLicensePage": "https://api.sdrm.io/api/v1/rights/d4e5f6a7-.../acquire"
    },
    "tdm": {
      "reservation": 1,
      "policy_url": "https://api.sdrm.io/api/v1/rights/tdm-policy"
    },
    "link_rel_license": "https://api.sdrm.io/api/v1/rights/d4e5f6a7-.../acquire"
  }
}
GET/api/v1/rights/tdm-policy

TDM policy document

W3C TDM Repertoire Protocol policy document. Declares that AI training is not permitted by default for sidearm-managed content. Per-asset licenses can be acquired programmatically. Supports content negotiation: JSON (default) or XML. This URL is referenced by TDM-Policy HTTP headers and <meta name='tdm-policy'> tags.

Response 200OK — TDM policy

{
  "version": "1.0",
  "publisher": "sidearm",
  "policy": {
    "tdm": "not-allowed",
    "ai_training": "not-allowed",
    "ai_inference": "allowed-with-license"
  },
  "licensing": {
    "acquire_per_asset": "/api/v1/rights/{media_id}/acquire",
    "olp_token_endpoint": "/api/v1/rsl/token"
  }
}
POST/api/v1/rsl/token

OLP token

Open License Protocol (OLP) token endpoint. AI crawlers and agents authenticate here to obtain a CAP (Crawler Authorization Protocol) token. The agent provides client credentials and desired scope (media IDs, usage type). The endpoint validates against existing offers or agreements and returns a short-lived token the agent presents via the Authorization header when accessing protected content.

Request Body

FieldTypeDescription
grant_typestringclient_credentialslicense_tokenOAuth 2.0 grant type.
client_idstringAPI token ID or account ID.
client_secretstringAPI token secret.
scope.media_idsstring[]Specific media assets this token should cover.
scope.usagestringcrawlindexai_trainingdisplaycommercialIntended usage.

Example Request

{
  "grant_type": "client_credentials",
  "client_id": "tok_abc123",
  "client_secret": "sk_live_...",
  "scope": {
    "media_ids": [
      "d4e5f6a7-..."
    ],
    "usage": "crawl"
  }
}

Response 200OK — CAP token

{
  "access_token": "cap_tok_abc123_1708200000",
  "token_type": "CAP",
  "expires_in": 3600,
  "scope": {
    "media_ids": [
      "d4e5f6a7-..."
    ],
    "usage": "crawl",
    "permissions": [
      "read",
      "index"
    ]
  },
  "cap_header": "CAP token=\"cap_tok_abc123_1708200000\""
}
POST/api/v1/rsl/introspect

Introspect CAP token

Validate a Crawler Authorization Protocol (CAP) token. Content servers hosting sidearm-protected media can call this to verify that a crawler's token is valid and covers the requested asset. Follows RFC 7662 (OAuth 2.0 Token Introspection) semantics.

Request Body

FieldTypeDescription
tokenstringThe CAP token to validate.
media_idstring (uuid)Check if the token covers this specific asset.

Example Request

{
  "token": "cap_tok_abc123_1708200000",
  "media_id": "d4e5f6a7-..."
}

Response 200OK — introspection result

{
  "active": true,
  "token_type": "CAP",
  "scope": {
    "usage": "crawl",
    "permissions": [
      "read",
      "index"
    ]
  },
  "sub": "tok_abc123",
  "iss": "sidearm",
  "media_id_covered": true
}

Agents

Coming soon

Autonomous negotiation agents that buy or sell licenses on your behalf. Create an agent with a mandate (price range, acceptable terms, dealbreakers) and it handles the back-and-forth automatically.

These endpoints are part of the planned API surface and are not yet available. The schemas and behavior documented here represent the target design.

How Agents Work

MMandate

The agent's authority. Defines price range (floor/ceiling/ideal), which terms are acceptable, hard dealbreakers, and how many rounds to negotiate before walking away. The agent never exceeds its mandate.

SStrategy

How the agent negotiates. 'Aggressive' opens near your ideal and concedes slowly. 'Balanced' splits the difference. 'Accommodating' concedes quickly to close faster. All strategies respect the mandate bounds.

AAuto-accept

When enabled, the agent closes deals that fall within mandate without waiting for human approval. When disabled, the agent negotiates but pauses before accepting — you review and confirm.

Agent Scenarios

Licensor agent only

A buyer sends POST /deals manually. Your agent automatically counter-offers or accepts based on its mandate. You don't have to be online.

Buyer agent only

Your buyer agent scans for media matching its scope and proposes deals. The licensor responds manually (or via their own agent).

Agent vs. Agent

Both sides have agents. The buyer agent proposes a deal, the licensor agent counters, and they negotiate autonomously — potentially closing in seconds. Both mandates are respected, so neither side goes outside their bounds.

POST/api/v1/agents

Create an agent

Create an autonomous negotiation agent that acts on your behalf. A buyer agent automatically proposes deals for media matching your criteria. A licensor agent automatically responds to incoming deals on your assets. When both sides have agents, they negotiate with each other — counter-offers, concessions, and acceptance all happen without human intervention.

Scopes:agents:create

Request Body

FieldTypeDescription
rolestringbuyerlicensorWhich side this agent represents.
namestringDisplay name for this agent (e.g. 'Stock Photo Buyer', 'Portfolio Licensor').
scopeobjectWhat the agent covers. For licensors: which of your assets. For buyers: what kind of media to seek.
scope.asset_idsstring[]Specific media IDs (licensor). Omit to use tags instead.
scope.tagsstring[]Match assets with these tags. Applies to both roles.
scope.media_typesstring[]imagevideoaudiogiftextpdfRestrict to specific media types.
mandateobjectThe agent's authority — price range, acceptable terms, and negotiation limits.
mandate.min_priceintegerLicensor: floor price in cents. Will not accept below this.
mandate.max_priceintegerBuyer: ceiling price in cents. Will not offer above this.
mandate.ideal_priceintegerTarget price the agent aims for. Opens near this and concedes toward min/max.
mandate.acceptable_termsobjectPolicy constraints the agent will accept. Same structure as a rights policy. The agent rejects deals outside these bounds.
mandate.dealbreakersstring[]Hard limits that cause immediate rejection. E.g. ['exclusive', 'sublicensable', 'ai_training'].
mandate.max_roundsintegerWalk away after this many counter-offers.
mandate.auto_acceptbooleanIf true, the agent closes deals within mandate without human approval. If false, it negotiates but pauses before accepting for you to review.
strategystringaggressivebalancedaccommodatingNegotiation style.
payment_methodstringBuyer agents only. Stripe payment method ID to use when auto-accepting deals.
activebooleanWhether the agent is active.

Example Request

{
  "role": "licensor",
  "name": "Portfolio Auto-Licensor",
  "scope": {
    "tags": [
      "portfolio"
    ],
    "media_types": [
      "image",
      "gif"
    ]
  },
  "mandate": {
    "min_price": 25000,
    "ideal_price": 50000,
    "acceptable_terms": {
      "permissions": {
        "usage_scope": "commercial",
        "channels": [
          "web",
          "social_organic"
        ]
      },
      "constraints": {
        "duration": "P1Y"
      }
    },
    "dealbreakers": [
      "exclusive",
      "sublicensable"
    ],
    "max_rounds": 4,
    "auto_accept": true
  },
  "strategy": "balanced"
}

Response 201Created

{
  "data": {
    "id": "agt_abc123",
    "role": "licensor",
    "name": "Portfolio Auto-Licensor",
    "status": "active",
    "scope": {
      "tags": [
        "portfolio"
      ],
      "media_types": [
        "image",
        "gif"
      ]
    },
    "deals_opened": 0,
    "deals_closed": 0,
    "created_at": "2026-02-17T12:00:00Z"
  }
}
GET/api/v1/agents

List agents

List all negotiation agents for the authenticated account.

Scopes:agents:read

Query Parameters

FieldTypeDescription
rolestringbuyerlicensorFilter by role.
activebooleanFilter by active status.

Response 200OK

{
  "data": [
    {
      "id": "agt_abc123",
      "role": "licensor",
      "name": "Portfolio Auto-Licensor",
      "status": "active",
      "deals_opened": 12,
      "deals_closed": 8,
      "revenue_cents": 340000,
      "created_at": "2026-02-17T12:00:00Z"
    }
  ]
}
GET/api/v1/agents/{id}

Get agent

Retrieve agent configuration, current mandate, and performance stats.

Scopes:agents:read

Path Parameters

FieldTypeDescription
idstringAgent ID.

Response 200OK

{
  "data": {
    "id": "agt_abc123",
    "role": "licensor",
    "name": "Portfolio Auto-Licensor",
    "status": "active",
    "scope": {
      "tags": [
        "portfolio"
      ],
      "media_types": [
        "image",
        "gif"
      ]
    },
    "mandate": {
      "min_price": 25000,
      "ideal_price": 50000,
      "acceptable_terms": {
        "permissions": {
          "usage_scope": "commercial"
        }
      },
      "dealbreakers": [
        "exclusive",
        "sublicensable"
      ],
      "max_rounds": 4,
      "auto_accept": true
    },
    "strategy": "balanced",
    "stats": {
      "deals_opened": 12,
      "deals_closed": 8,
      "deals_rejected": 3,
      "deals_expired": 1,
      "revenue_cents": 340000,
      "avg_close_price": 42500,
      "avg_rounds": 2.4
    },
    "created_at": "2026-02-17T12:00:00Z",
    "updated_at": "2026-02-17T18:30:00Z"
  }
}
GET/api/v1/agents/{id}/activity

Get agent activity

View the agent's recent activity — deals it opened, counter-offers it made, deals it closed or rejected, and why. Useful for auditing agent behavior and tuning mandates.

Scopes:agents:read

Path Parameters

FieldTypeDescription
idstringAgent ID.

Query Parameters

FieldTypeDescription
cursorstringPagination cursor.
limitintegerResults per page (1–100).

Response 200OK

{
  "data": [
    {
      "type": "deal_opened",
      "deal_id": "deal_789",
      "asset_id": "d4e5f6a7-...",
      "counterparty": "acc_buyer_x",
      "amount": 35000,
      "timestamp": "2026-02-17T14:00:00Z"
    },
    {
      "type": "counter_sent",
      "deal_id": "deal_789",
      "round": 2,
      "amount": 45000,
      "message": "Counter-offering at $450 based on mandate floor.",
      "timestamp": "2026-02-17T14:05:00Z"
    },
    {
      "type": "deal_accepted",
      "deal_id": "deal_789",
      "agreement_id": "agr_1002",
      "final_amount": 42000,
      "rounds": 3,
      "timestamp": "2026-02-17T14:12:00Z"
    },
    {
      "type": "deal_rejected",
      "deal_id": "deal_790",
      "reason": "dealbreaker:exclusive",
      "timestamp": "2026-02-17T15:00:00Z"
    }
  ],
  "meta": {
    "next_cursor": null
  }
}
PATCH/api/v1/agents/{id}

Update agent

Update an agent's mandate, strategy, scope, or active status. Changes apply to new deals only — deals already in progress continue under the mandate they started with.

Scopes:agents:update

Path Parameters

FieldTypeDescription
idstringAgent ID.

Request Body

FieldTypeDescription
namestringNew display name.
scopeobjectUpdated scope.
mandateobjectUpdated mandate. Merged with existing — pass only the fields you want to change.
strategystringaggressivebalancedaccommodatingUpdated negotiation style.
activebooleanSet to false to pause the agent.

Example Request

{
  "mandate": {
    "min_price": 30000,
    "auto_accept": false
  },
  "active": true
}

Response 200OK

{
  "data": {
    "id": "agt_abc123",
    "status": "active",
    "updated_at": "2026-02-17T19:00:00Z"
  }
}
DELETE/api/v1/agents/{id}

Delete agent

Permanently deactivate and delete an agent. Any deals the agent currently has in progress are left open for you to manage manually or will expire per your account's deal timeout.

Scopes:agents:delete

Path Parameters

FieldTypeDescription
idstringAgent ID.

Response 200OK

{
  "data": {
    "deleted": true,
    "open_deals_remaining": 2
  }
}

Jobs

Poll the status and results of async jobs (media ingestion, AI detection, membership inference).

GET/api/v1/jobs/{id}

Poll job status

Check status and progress of an async job. When complete, the result field contains output data including download URLs for protected media.

Scopes:jobs:read

Path Parameters

FieldTypeDescription
idstring (uuid)Job ID.

Response 200OK

{
  "data": {
    "id": "a1b2c3d4-...",
    "type": "media_ingest",
    "status": "completed",
    "preset": "standard",
    "progress": {
      "completed": 6,
      "total": 6
    },
    "result": {
      "media_id": "d4e5f6a7-...",
      "download_url": "https://storage.example.com/protected/d4e5f6a7.jpg",
      "algorithms_applied": [
        "digital_trust-v1",
        "id_embed-v1",
        "harmonic_bait-v1",
        "content_hash-v1",
        "perceptual_hash-v1",
        "neural_embed-v1"
      ]
    },
    "error": null,
    "created_at": "2026-02-17T12:00:00Z",
    "updated_at": "2026-02-17T12:01:30Z"
  }
}

Accounts

Manage accounts for programmatic onboarding and team management.

GET/api/v1/accounts/{id}

Get account

Retrieve account details. Callers may only access their own account.

Scopes:accounts:read

Path Parameters

FieldTypeDescription
idstring (uuid)Account ID.

Response 200OK

{
  "data": {
    "id": "e5f6a7b8-...",
    "email": "team@example.com",
    "name": "Acme Corp"
  }
}
PATCH/api/v1/accounts/{id}

Update account

Update email or display name.

Scopes:accounts:update

Path Parameters

FieldTypeDescription
idstring (uuid)Account ID.

Request Body

FieldTypeDescription
emailstring (email)New contact email.
namestringNew display name.

Example Request

{
  "name": "Acme Corp (Renamed)"
}

Response 200OK

{
  "data": {
    "id": "e5f6a7b8-...",
    "name": "Acme Corp (Renamed)",
    "updated_at": "2026-02-17T13:00:00Z"
  }
}
DELETE/api/v1/accounts/{id}

Delete account

Permanently delete an account. Callers may only delete their own.

Scopes:accounts:delete

Path Parameters

FieldTypeDescription
idstring (uuid)Account ID.

Response 200OK

{
  "data": {
    "deleted": true
  }
}

API Tokens

Coming soon

Create and manage API tokens programmatically. Each token has its own scopes and tags for fine-grained access control and billing attribution.

These endpoints are part of the planned API surface and are not yet available. The schemas and behavior documented here represent the target design.

Token Security

Secret shown once: The full token is only returned when you create it. Store it in a secrets manager immediately — you cannot retrieve it again.

No privilege escalation: A token can only create child tokens with equal or fewer scopes. You cannot grant a new token permissions that the creating token doesn't have.

Tags for attribution: All API usage made with a tagged token is automatically attributed to those tags in billing. Use this for per-project, per-environment, or per-partner cost tracking.

POST/api/v1/tokens

Create API token

Create a new API token with specific scopes. The token secret is returned only once in this response — store it securely. You cannot retrieve it again.

Scopes:tokens:create

Request Body

FieldTypeDescription
namestringA label for this token (e.g. 'Production', 'CI/CD Pipeline', 'Partner Integration').
scopesstring[]Permissions this token grants. Use resource:action format. The new token can only have scopes that your current token also has — you cannot escalate privileges.
tagsstring[]Labels for organization and billing attribution. Usage by this token is tagged automatically for cost tracking.
expires_atstring (datetime)Optional expiration. ISO 8601 timestamp. Omit for non-expiring tokens.

Example Request

{
  "name": "Production Backend",
  "scopes": [
    "media:*",
    "detect:*",
    "search:*",
    "jobs:read"
  ],
  "tags": [
    "production",
    "backend"
  ],
  "expires_at": "2027-02-17T00:00:00Z"
}

Response 201Created — token secret is only shown here

{
  "data": {
    "id": "tok_a1b2c3d4",
    "name": "Production Backend",
    "token": "sk_live_a1b2c3d4e5f6g7h8i9j0...",
    "scopes": [
      "media:*",
      "detect:*",
      "search:*",
      "jobs:read"
    ],
    "tags": [
      "production",
      "backend"
    ],
    "expires_at": "2027-02-17T00:00:00Z",
    "created_at": "2026-02-17T12:00:00Z"
  }
}
GET/api/v1/tokens

List tokens

List all API tokens for the authenticated account. Token secrets are never included — only metadata, scopes, and usage stats.

Scopes:tokens:read

Response 200OK

{
  "data": [
    {
      "id": "tok_a1b2c3d4",
      "name": "Production Backend",
      "prefix": "sk_live_a1b2...j0",
      "scopes": [
        "media:*",
        "detect:*",
        "search:*",
        "jobs:read"
      ],
      "tags": [
        "production",
        "backend"
      ],
      "last_used_at": "2026-02-17T18:45:00Z",
      "expires_at": "2027-02-17T00:00:00Z",
      "created_at": "2026-02-17T12:00:00Z"
    }
  ]
}
GET/api/v1/tokens/{id}

Get token

Retrieve token details including scopes, tags, and last-used timestamp. The token secret is never returned.

Scopes:tokens:read

Path Parameters

FieldTypeDescription
idstringToken ID.

Response 200OK

{
  "data": {
    "id": "tok_a1b2c3d4",
    "name": "Production Backend",
    "prefix": "sk_live_a1b2...j0",
    "scopes": [
      "media:*",
      "detect:*",
      "search:*",
      "jobs:read"
    ],
    "tags": [
      "production",
      "backend"
    ],
    "last_used_at": "2026-02-17T18:45:00Z",
    "expires_at": "2027-02-17T00:00:00Z",
    "created_at": "2026-02-17T12:00:00Z"
  }
}
PATCH/api/v1/tokens/{id}

Update token

Update a token's name, scopes, or tags. Scope changes take effect immediately on the next request made with this token. You cannot add scopes that your current token doesn't have.

Scopes:tokens:update

Path Parameters

FieldTypeDescription
idstringToken ID.

Request Body

FieldTypeDescription
namestringNew label.
scopesstring[]Replacement scopes. Cannot exceed the scopes of the token making this request.
tagsstring[]Replacement tags.
expires_atstring (datetime)New expiration. Set to null to remove expiration.

Example Request

{
  "scopes": [
    "media:read",
    "search:*",
    "jobs:read"
  ],
  "tags": [
    "production",
    "backend",
    "read-only"
  ]
}

Response 200OK

{
  "data": {
    "id": "tok_a1b2c3d4",
    "scopes": [
      "media:read",
      "search:*",
      "jobs:read"
    ],
    "updated_at": "2026-02-17T19:00:00Z"
  }
}
DELETE/api/v1/tokens/{id}

Revoke token

Permanently revoke a token. Any requests using this token will immediately receive 401. This cannot be undone.

Scopes:tokens:delete

Path Parameters

FieldTypeDescription
idstringToken ID.

Response 200OK

{
  "data": {
    "revoked": true,
    "id": "tok_a1b2c3d4"
  }
}

Billing

Credit balance, storage costs, per-algorithm usage breakdown, billing events, and Stripe portal access. Filter by date, type, tags, or API token.

GET/api/v1/billing/{account_id}

Get billing summary, storage, algorithm breakdown, and events

Returns credit balance, protection and storage cost breakdown, per-algorithm usage stats, billing events, and a Stripe portal link. Filter by date, type, tags, or API token for attribution.

Scopes:billing:read

Path Parameters

FieldTypeDescription
account_idstring (uuid)Account ID.

Query Parameters

FieldTypeDescription
start_datestring (date)Events on or after (YYYY-MM-DD).
end_datestring (date)Events on or before.
typestringFilter by event type (e.g. protect:harmonic-bait:v1, storage:daily).
tagsstringComma-separated tag filter.
token_idstring (uuid)Filter events to a specific API token.

Response 200OK

{
  "data": {
    "summary": {
      "credit_balance": 4200,
      "total_credits_used": 800,
      "protection_credits": 750,
      "storage_credits": 50
    },
    "storage": {
      "total_bytes": 524288000,
      "file_count": 42,
      "daily_cost": 1,
      "weekly_cost": 7,
      "monthly_cost": 15,
      "rate_per_mb_per_day": 0.001
    },
    "by_algorithm": [
      {
        "algorithm": "harmonic-bait",
        "display_name": "Harmonic Bait",
        "operations": 12,
        "credits": 480
      },
      {
        "algorithm": "id-embed",
        "display_name": "ID Embedding",
        "operations": 8,
        "credits": 270
      }
    ],
    "events": [
      {
        "id": "evt_001",
        "type": "protect:harmonic-bait:v1",
        "quantity": 40,
        "unit": "credits",
        "tag": "project-alpha",
        "api_token_id": null,
        "metadata": null,
        "created_at": "2026-02-17T12:00:00Z"
      }
    ],
    "portal_url": "https://billing.stripe.com/session/..."
  }
}

Algorithm Catalog

Open algorithms are individual algorithms by their real name. Proprietary algorithms are sidearm-branded bundles that resolve to open algorithms. Both are runnable via POST /api/v1/run. Use GET /api/v1/algorithms to fetch this catalog as JSON.

Versioning

Every algorithm code supports a -latest suffix that always resolves to the current version. You can also pin to a specific version (e.g. harmonic_bait-v1).

Why pin? If content was protected with v1 and a model trained on it, detection should target v1. When we release a new version, -latest moves forward but your existing records still reference the version they were processed with. System presets always use -latest. Pin specific versions in custom presets when you need consistency.

Proprietary Algorithms

harmonic-baitv1
image, gif, video, audio
Harmonic Bait

Invisible poison that confuses AI models. If they train on your work, the AI produces garbled, unusable output for your concepts.

style-cloakv1
image, gif, video, audio
Style Cloaking

Protects your unique artistic style. Prevents AI from mimicking your look and feel or cloning your vocal timbre.

id-embedv1
all
ID Embedding

An invisible, unbreakable digital tattoo that proves this file belongs to you. Survives compression, cropping, reformatting, and noise.

tamper-shieldv1
image, gif, video
Tamper Shield

Hardens other protections against removal. If someone tries to strip your protections, we can prove it.

digital-trustv1
all
Digital Trust

Cryptographic provenance manifest. Applied twice — before and after protection — to create a tamper-evident chain of custody.

timbre-shieldv1
audio
Timbre Shield

Locks your voice. AI models will fail to capture your unique vocal tone.

text-poisonv1
text, pdf
Text Poison

Invisible linguistic traps that prove an AI model consumed your writing. Detectable even at 0.001% of training corpus.

doc-tattoov1
text, pdf
Doc Tattoo

An invisible fingerprint embedded in your text that survives copy-paste, OCR, and reformatting.

pdf-shieldv1
pdf
PDF Shield

Deep-embedded protection inside PDF files. Invisible to viewers, survives print-to-PDF round-trips.

Open — Active Protections (Visual)

nightshadev1
image, gif, video
Nightshade 2.0

Feature-space perturbation that poisons AI training data. If they train on your work, the AI produces garbled output. Based on research from the University of Chicago.

glazev1
image, gif, video
Glaze

Style-transfer perturbation that prevents AI from learning and reproducing an artist's visual style.

hmarkv1
image, gif, video
HMark

Steganographic 64-bit owner ID watermark using spread-spectrum techniques. Survives JPEG compression, cropping, rescaling, and noise.

paiv1
image, gif, video
PAI Hardening

Adversarial hardening that makes preceding watermarks and perturbations resistant to removal attacks.

iptc-rightsv1
image
IPTC/XMP Rights

Machine-readable licensing metadata in EXIF/XMP fields. Recognized by Google Images, Bing, and DAMs.

Open — Active Protections (Audio)

audio-nightshadev1
audio
AudioNightshade

Spectral perturbation that disrupts audio-generative model training. Extension of Nightshade to audio.

vocal-glazev1
audio
VocalGlaze

Formant shift that prevents effective voice cloning. Extension of Glaze to the audio domain.

hmark-audiov1
audio
HMarkAudio

Spread-spectrum 64-bit audio watermark. Robust against MP3/AAC compression, speed changes, and noise.

Open — Active Protections (Text & Documents)

spectrav1
text, pdf
SPECTRA

Paraphrase-guided watermark detectable at 0.001% of training corpus. Enables membership inference.

textmarkv1
text, pdf
TextMark

Unicode steganographic owner ID. Survives copy-paste, OCR, and reformatting.

pdfstegov1
pdf
PDFStego

PDF stream operator steganography. Invisible to viewers, survives print-to-PDF round-trips.

Open — Provenance & Signing

c2pav1
all
C2PA Content Credentials

Cryptographic provenance manifest (Content Authenticity Initiative standard). Two-pass signing: pre-sign captures the original, post-sign captures the protected output.

Open — Indexing, Fingerprinting, Search & Detection

phashv1
image, gif, video
pHash / dHash

Perceptual and difference hashing for near-duplicate image detection. Industry-standard for reverse image search.

dinov2v1
image, gif, video
DINOv2

Meta's self-supervised vision transformer. Semantic embeddings that find similar content by meaning, not pixels.

clipv1
image, gif, video
CLIP / SigLIP

OpenAI CLIP / Google SigLIP multimodal embeddings. Enables text-to-image search and cross-modal matching.

scene-graphv1
image, gif, video
Scene Graph (PSGFormer / SAM2)

Structural scene analysis — maps objects, relationships, and spatial layout for compositional search.

chromaprintv1
audio
Chromaprint / AcoustID

Audio fingerprinting that powers MusicBrainz. Identifies tracks across format conversions and edits.

clapv1
audio
CLAP

Contrastive Language-Audio Pretraining. Semantic audio search — find audio by meaning, not waveform.

audio-structurev1
audio
Audio Structure (Essentia)

Essentia structural embeddings. Compositional audio search — find audio with similar arrangement, rhythm, and tonal structure.

sentence-transformersv1
text, pdf
Sentence Transformers

Dense sentence embeddings (384-dim). Semantic text search — find documents by meaning, not keywords.

Schemas

Core data models returned by the API.

Media

{
  "id": "uuid",
  "account_id": "uuid",
  "media_type": "image | video | audio | gif | text | pdf",
  "manifest": "string",
  "storage_url": "uri",
  "original_storage_key": "string",
  "preset": "string",
  "algorithms_applied": "string[]",
  "deletes_at": "datetime",
  "tags": "string[]",
  "metadata": "object",
  "status": "active | processing",
  "created_at": "datetime",
  "updated_at": "datetime"
}

Deletion

{
  "id": "string",
  "media_id": "uuid",
  "account_id": "uuid",
  "media_type": "string",
  "original_hash": "string (sha256)",
  "preset": "string",
  "algorithms_applied": "string[]",
  "tags": "string[]",
  "storage_locations_purged": "string[]",
  "storage_purged": "boolean",
  "deleted_at": "datetime",
  "deleted_by": "uuid"
}

Job

{
  "id": "uuid",
  "type": "media_ingest | ai_detect | membership_inference",
  "status": "queued | running | completed | failed",
  "preset": "string | null",
  "progress": "{ completed, total } | null",
  "result": "object | null",
  "error": "string | null",
  "created_at": "datetime",
  "updated_at": "datetime"
}

Preset

{
  "id": "string",
  "type": "system | custom",
  "name": "string",
  "description": "string",
  "algorithms": "string[]",
  "media_types": "string[]",
  "created_at": "datetime"
}

Policy

{
  "name": "string",
  "permissions.usage_scope": "commercial | editorial | personal | ai_training",
  "permissions.channels": "string[] (web, social_organic, social_paid, broadcast, print, ...)",
  "permissions.modifications_allowed": "boolean",
  "permissions.sublicensable": "boolean",
  "permissions.attribution_required": "boolean",
  "constraints.territory": "{ type: include | exclude, codes: string[] } | null",
  "constraints.exclusivity": "{ exclusive: boolean, scope?: string, industry_code?: string } | null",
  "constraints.duration": "string (ISO 8601 period, e.g. P1Y)"
}

Offer

{
  "id": "string",
  "asset_id": "uuid",
  "licensor_id": "uuid",
  "policy": "Policy",
  "pricing.amount": "integer (smallest currency unit)",
  "pricing.currency": "string (ISO 4217)",
  "pricing.model": "flat_fee | royalty",
  "status": "active | paused | archived",
  "quantity_limit": "integer | null",
  "created_at": "datetime"
}

Agreement

{
  "id": "string",
  "offer_id": "string | null",
  "deal_id": "string | null",
  "licensor_id": "uuid",
  "licensee_id": "uuid",
  "asset_details": "{ id, media_type, checksum, preset }",
  "rights_snapshot": "Policy (immutable copy at time of purchase)",
  "valid_from": "datetime",
  "valid_until": "datetime",
  "status": "active | expired | revoked",
  "payment_ref": "string",
  "digital_certificate": "string (Ed25519 JWT)",
  "verify_url": "uri"
}

Deal

{
  "id": "string",
  "asset_id": "uuid",
  "buyer_id": "uuid",
  "licensor_id": "uuid",
  "status": "open | accepted | rejected | cancelled",
  "current_proposal": "{ policy: Policy, amount: integer, currency: string, proposed_by: string }",
  "history": "{ round, from, amount, policy?, message?, timestamp }[]",
  "agreement_id": "string | null",
  "created_at": "datetime",
  "updated_at": "datetime"
}

Agent

{
  "id": "string",
  "account_id": "uuid",
  "role": "buyer | licensor",
  "name": "string",
  "status": "active | paused | deleted",
  "scope": "{ asset_ids?, tags?, media_types? }",
  "mandate.min_price": "integer | null",
  "mandate.max_price": "integer | null",
  "mandate.ideal_price": "integer | null",
  "mandate.acceptable_terms": "Policy | null",
  "mandate.dealbreakers": "string[]",
  "mandate.max_rounds": "integer",
  "mandate.auto_accept": "boolean",
  "strategy": "aggressive | balanced | accommodating",
  "stats": "{ deals_opened, deals_closed, deals_rejected, revenue_cents, avg_close_price, avg_rounds }",
  "created_at": "datetime",
  "updated_at": "datetime"
}

Account

{
  "id": "uuid",
  "email": "string | null",
  "name": "string | null",
  "stripe_customer_id": "string | null",
  "created_at": "datetime",
  "updated_at": "datetime"
}

Token

{
  "id": "string",
  "account_id": "uuid",
  "name": "string",
  "prefix": "string (first 8 chars)",
  "scopes": "string[]",
  "tags": "string[]",
  "last_used_at": "datetime | null",
  "expires_at": "datetime | null",
  "created_at": "datetime"
}

Node.js SDK

TypeScript-first client for Node ≥ 18. Wraps every API resource and provides a Job helper for polling async operations.

Install

Package: @sidearmdrm/sdk-node · v0.6.0

npm install @sidearmdrm/sdk-node

Quick start

import { Sidearm } from '@sidearmdrm/sdk-node';

const client = new Sidearm({ apiKey: 'sk_live_...' });

// Protect an image — returns a Job handle
const job = await client.protect({ media_url: 'https://example.com/photo.jpg', level: 'maximum' });

// Wait for completion (polls every 2s, 2 min timeout)
const result = await job.wait();
console.log(result.result?.output_url);

Resources

client.run(opts)Run specific named algorithms — returns a Job handle
client.protect(opts)Apply a curated preset (standard / maximum) — returns a Job handle
client.algorithms.list()List all available algorithms
client.jobs.get(id)Fetch a job by ID
client.media.*register · list · get · update · delete
client.detect.ai(opts)Detect AI-generated content
client.detect.fingerprint(opts)Check for embedded watermark or ownership fingerprint
client.detect.membership(opts)Test if content was used in AI model training
client.search.run(opts)Find similar or stolen content
client.rights.get(mediaId)Rights and licensing information
client.billing.get(accountId)Credit balance, usage breakdown, billing events

Job polling

const job = await client.protect({ media_url: '...' });

await job.poll();                                        // single fetch
const result = await job.wait({ timeoutMs: 300_000 });  // wait until done

result.status               // 'completed' | 'failed'
result.result?.output_url   // download URL for protected media

Error handling

import { Sidearm, SidearmError } from '@sidearmdrm/sdk-node';

try {
  const job = await client.protect({ media_url: '...' });
} catch (err) {
  if (err instanceof SidearmError) {
    console.error(err.status, err.message);
  }
}

Python SDK

Synchronous Python client for Python ≥ 3.9. Built on httpx, fully typed, supports context managers for automatic connection cleanup.

Install

Package: sidearmdrm · v0.6.0

pip install sidearmdrm

Quick start

from sidearm import Sidearm

client = Sidearm(api_key="sk_live_...")

# Protect media — returns a Job handle
job = client.protect(media_url="https://example.com/photo.jpg", level="maximum")

# Wait for completion
result = job.wait()
print(result["result"]["output_url"])

Resources

client.run(**kwargs)Run specific named algorithms — returns a Job handle
client.protect(**kwargs)Apply a curated preset (standard / maximum) — returns a Job handle
client.algorithms.list()List all available algorithms
client.jobs.get(id)Fetch a job by ID
client.media.*register · list · get · update · delete
client.detect.ai(**kwargs)Detect AI-generated content
client.detect.fingerprint(**kwargs)Check for embedded watermark or ownership fingerprint
client.detect.membership(**kwargs)Test if content was used in AI model training
client.search.run(**kwargs)Find similar or stolen content
client.rights.get(media_id)Rights and licensing information
client.billing.get(account_id)Credit balance, usage breakdown, billing events

Context manager

from sidearm import Sidearm, SidearmError

with Sidearm(api_key="sk_live_...") as client:
    try:
        job = client.protect(media_url="https://example.com/photo.jpg")
        result = job.wait(timeout=300, interval=5)
    except SidearmError as e:
        print(e.status, str(e))

MCP Server

Model Context Protocol server — exposes every sidearm capability as a tool for AI agents (Claude Desktop, Cursor, and any MCP-compatible host). No code required.

Setup — Claude Desktop

Package: @sidearmdrm/mcp · v0.6.0 · Requires Node ≥ 18

Add to ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "sdrm": {
      "command": "npx",
      "args": [
        "-y",
        "@sidearmdrm/mcp"
      ],
      "env": {
        "SDRM_API_KEY": "sk_live_..."
      }
    }
  }
}

Available tools

list_algorithmsList all protection algorithms with descriptions and supported media types
run_algorithmRun specific named algorithms on media — returns a job_id
protect_mediaApply standard or maximum protection preset — returns a job_id
check_jobPoll job status and retrieve output_url on completion
search_mediaFind similar or stolen content; returns matches with similarity scores
list_searchesList past search operations
detect_aiDetect whether content is AI-generated
detect_fingerprintCheck for embedded watermark or ownership fingerprint
detect_membershipTest if content was used in AI model training
register_mediaRegister a media asset in the library
list_mediaList registered media assets
get_mediaFetch a single media asset by ID
update_mediaUpdate tags or metadata on a media asset
delete_mediaPermanently delete a media asset
get_rightsRetrieve licensing and rights information for a media asset
get_billingGet credit balance, usage breakdown, and billing events
get_provenanceFull provenance chain — protections applied, search matches, membership inference results

Typical agent workflow

1list_algorithmsDiscover what's available and pick the right algorithm for the task
2protect_mediaQueue a protection job with the desired level (standard / maximum)
3check_jobPoll until status = completed, then retrieve the output_url
4get_provenanceVerify the protection chain was applied correctly