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 + PresetsSubmit 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 InferenceFind 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
SearchFind 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, AgentsTurn 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.
https://api.sdrm.io1.5.11.1.0OpenAPI 3.0.3 — feed this to your agent or SDK generator.
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.
| Scope | Access |
|---|---|
| media:read | List and get media records |
| media:create | Ingest new media |
| media:update | Update media records |
| media:delete | Delete media and storage |
| detect:run | Run fingerprint detection |
| detect:ai | Run AI-generated content detection |
| detect:forensic | Run membership inference audits |
| search:run | Execute similarity searches |
| search:read | List and get past searches |
| rights:read | View offers, agreements, deals |
| rights:create | Create offers, start deals, purchase licenses |
| rights:manage | Counter-offer and accept deals |
| presets:read | List and get presets |
| presets:create | Create custom presets |
| presets:delete | Delete custom presets |
| agents:read | View agent configuration and activity |
| agents:create | Create negotiation agents |
| agents:update | Update agent mandates and parameters |
| agents:delete | Deactivate agents |
| jobs:read | Poll job status and results |
| accounts:read | View account details |
| accounts:update | Update account info |
| accounts:delete | Delete account |
| tokens:read | List and view API tokens |
| tokens:create | Create new API tokens |
| tokens:update | Update token name, scopes, or tags |
| tokens:delete | Revoke API tokens |
| billing:read | View billing and usage events |
Wildcards & Bundles
| Scope | Expands 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:
| Bundle | Scopes | Use case |
|---|---|---|
| creator | media:*, search:*, detect:*, rights:read, agents:*, jobs:read, billing:read | Content creators managing their own assets |
| agent | media:read, search:run, rights:read, rights:create, agents:*, jobs:read | AI agents discovering and licensing content |
| auditor | detect:*, search:*, jobs:read | Forensic detection and compliance auditing |
Errors
Consistent JSON format:
{
"error": "not_found",
"message": "Resource not found"
}| Status | Meaning |
|---|---|
| 400 | Invalid request body or parameters |
| 401 | Missing or invalid API key |
| 403 | Insufficient scope or ownership |
| 404 | Resource not found |
| 409 | Conflict (e.g. exclusivity violation) |
| 429 | Rate limit exceeded |
| 500 | Internal 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" }/api/v1/algorithmsList 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
| Field | Type | Description |
|---|---|---|
| category | stringopenproprietary | Filter by category. |
| media_type | stringimagevideoaudiogiftextpdf | Filter 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."
}
}/api/v1/runRun 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.
Request Body
| Field | Type | Description |
|---|---|---|
| algorithms | string[] | Algorithm IDs to run, in order. See GET /api/v1/algorithms. |
| media_url | string (URL) | Public URL of media. Provide one of media_url, media, or text. |
| media | string (base64) | Base64-encoded file bytes. |
| text | string | Raw text content (for text/PDF algorithms). |
| mime | string | MIME type hint (optional, auto-detected). |
| c2pa_wrap | boolean | Wrap output in C2PA provenance signing. |
| tags | string[] | Tags for billing attribution and access control. |
| webhook_url | string (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"
}
}/api/v1/embedExtract 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.
Request Body
| Field | Type | Description |
|---|---|---|
| algorithms | string[] | Extractable algorithm IDs. Images: phash, dinov2, clip, scene-graph. Audio: chromaprint, clap, audio-structure. Text/PDF: sentence-transformers. |
| media_url | string (URL) | Public URL of media. Provide one of media_url, media, or text. |
| media | string (base64) | Base64-encoded file bytes. |
| text | string | Raw text content (for sentence-transformers). |
| mime | string | MIME 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"
}
}/api/v1/protectBundled 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.
Request Body
| Field | Type | Description |
|---|---|---|
| media_url | string (URL) | Public URL of media. Provide one of media_url, media, or text. |
| media | string (base64) | Base64-encoded file bytes. |
| text | string | Raw text content. |
| mime | string | MIME type hint (optional). |
| level | stringstandardmaximum | Protection level. |
| tags | string[] | Tags for billing and access control. |
| webhook_url | string (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.
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
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
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
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
/api/v1/mediaIngest & 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.
Request Body
| Field | Type | Description |
|---|---|---|
| media_url | string (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. |
| media | string (base64) | Base64-encoded file bytes. Type is auto-detected from content. |
| text | string | Raw text content (articles, manuscripts, books). |
| preset | stringregistersearch_readystandardmaximum | Preset ID or system preset name. Determines which algorithms are applied. |
| deletes_at | string (datetime) | ISO 8601 timestamp. The record and all associated storage are permanently deleted at this time. |
| tags | string[] | Labels for grouping, search scope, and cost attribution. |
| metadata | object | Arbitrary key-value pairs stored with the record. Use for internal IDs, project codes, or any context you want to retrieve later. |
| webhook_url | string (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"
}
}/api/v1/mediaList media
List registered media for the authenticated account. Cursor-based pagination.
Query Parameters
| Field | Type | Description |
|---|---|---|
| cursor | string | Pagination cursor from a previous response. |
| limit | integer | Results 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"
}
}/api/v1/media/{id}Get media
Retrieve a single media record by ID.
Path Parameters
| Field | Type | Description |
|---|---|---|
| id | string (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"
}
}/api/v1/media/{id}Update media
Update the original media URL when the source asset has moved.
Path Parameters
| Field | Type | Description |
|---|---|---|
| id | string (uuid) | Media ID. |
Request Body
| Field | Type | Description |
|---|---|---|
| original_media_url | string (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"
}
}/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.
Path Parameters
| Field | Type | Description |
|---|---|---|
| id | string (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"
}
}/api/v1/media/deletionsList 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.
Query Parameters
| Field | Type | Description |
|---|---|---|
| cursor | string | Pagination cursor. |
| limit | integer | Results 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
}
}/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.
Path Parameters
| Field | Type | Description |
|---|---|---|
| id | string | Deletion 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-..."
}
}/api/v1/media/deletions/{id}/certificateDownload 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.
Path Parameters
| Field | Type | Description |
|---|---|---|
| id | string | Deletion record ID. |
Query Parameters
| Field | Type | Description |
|---|---|---|
| format | stringjwtpdf | Certificate 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 soonPresets 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.
/api/v1/presetsList 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.
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"
]
}
]
}/api/v1/presets/{id}Get preset
Retrieve details of a preset including its full algorithm list and compatible media types.
Path Parameters
| Field | Type | Description |
|---|---|---|
| id | string | Preset 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."
}
]
}
}/api/v1/presetsCreate custom preset
Create a custom preset by selecting algorithms from the catalog. Useful for clients with specific protection requirements.
Request Body
| Field | Type | Description |
|---|---|---|
| name | string | Display name for the preset. |
| description | string | What this preset is for. |
| algorithms | string[] | Algorithm codes to include. See Algorithm Catalog. |
| media_types | string[]imagevideoaudiogiftextpdf | Restrict 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"
}
}/api/v1/presets/{id}Delete custom preset
Delete a custom preset. System presets cannot be deleted. Media already processed with this preset is unaffected.
Path Parameters
| Field | Type | Description |
|---|---|---|
| id | string | Custom 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.
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.
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.
Runs both methods and cross-validates. Produces the strongest cryptographic proof. Required for legal-grade evidence.
/api/v1/detectDetect 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.
Request Body
| Field | Type | Description |
|---|---|---|
| media_url | string (uri) | Public URL of media to analyze. |
| media | string (base64) | Base64-encoded media bytes. |
| tags | string[] | Limit detection to corpus matching these tags. |
| tier | stringexactquickperceptualcompositionalfull | Forensic 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
}
]
}
}/api/v1/detect/aiDetect 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.
Request Body
| Field | Type | Description |
|---|---|---|
| media_url | string (uri) | Public URL of media to analyze. Type is auto-detected. |
| media | string (base64) | Base64-encoded media bytes. |
| text | string | Raw text to analyze. |
| tags | string[] | 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-..."
}
}/api/v1/detect/membershipMembership 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.
Request Body
| Field | Type | Description |
|---|---|---|
| content_ids | string[] (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_model | string | Identifier of the model to audit (e.g. 'stable-diffusion-xl', 'midjourney-v6'). |
| method | stringpatternstatisticalcombined | Inference method. See method reference below. |
| tags | string[] | 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-..."
}
}Search
Find similar registered media using perceptual and neural similarity. The type parameter controls which search tier is used. Higher tiers find more subtle matches but take longer.
Search Tiers
Both search and detection use the same tier system. Higher tiers find more subtle similarities but take longer. Available tiers depend on the preset used during media ingestion.
Byte-identical copies — mirrors, reposts, unmodified downloads
Algorithms: Content Hash
Requires: All presets (register and above)
Resized, recompressed, minor color shifts, format conversions
Algorithms: Perceptual Hash (image) / Chromaprint (audio)
Requires: search_ready, standard, maximum
Crops, filters, overlays, screenshots, watermark removal, noise addition
Algorithms: Neural Embedding (image) / CLAP (audio)
Requires: search_ready, standard, maximum
Collages, partial usage, scene recreation, AI-generated derivatives that borrow composition
Algorithms: Scene Graph (image) / Audio Structure (audio)
Requires: search_ready, maximum
Runs all methods — most thorough, finds everything above in a single pass
Algorithms: All algorithms
Requires: search_ready, maximum
/api/v1/searchSearch by media
Find similar registered media using a query file. Returns ranked matches with similarity scores. Available tiers depend on the preset used during ingestion.
Query Parameters
| Field | Type | Description |
|---|---|---|
| limit | integer | Max results (1–100). |
Request Body
| Field | Type | Description |
|---|---|---|
| media_url | string (uri) | Public URL of the query media. |
| media | string (base64) | Base64-encoded query media. |
| scope | object | Filter scope. Contains optional tags array. |
| type | stringexactquickperceptualcompositionalfull | Search tier. |
Example Request
{
"media_url": "https://cdn.example.com/query.jpg",
"type": "perceptual",
"scope": {
"tags": [
"project-a"
]
}
}Response 200OK
{
"data": {
"search_id": "b2c3d4e5-...",
"matches": [
{
"media_id": "d4e5f6a7-...",
"score": 0.97,
"storage_url": "https://storage.example.com/asset.jpg",
"tags": [
"project-a"
]
}
]
}
}/api/v1/searchList searches
List past searches for the authenticated account.
Query Parameters
| Field | Type | Description |
|---|---|---|
| cursor | string | Pagination cursor. |
| limit | integer | Results per page. |
Response 200OK
{
"data": [
{
"id": "b2c3d4e5-...",
"type": "perceptual",
"created_at": "2026-02-17T12:00:00Z"
}
],
"meta": {
"next_cursor": null
}
}/api/v1/search/{id}Get search by ID
Retrieve a single search with its ranked results.
Path Parameters
| Field | Type | Description |
|---|---|---|
| id | string (uuid) | Search ID. |
Response 200OK
{
"data": {
"id": "b2c3d4e5-...",
"type": "perceptual",
"results": [
{
"media_id": "d4e5f6a7-...",
"score": 0.97,
"rank": 1
}
],
"created_at": "2026-02-17T12:00:00Z"
}
}Rights & Licensing
Coming soonSell, 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.
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.
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.
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.
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
GET /media/{id}/offers→purchases with POST /orders→Agreement createdBest for standard licensing where the terms and price are pre-set. Like buying from a catalog.
Path B: Negotiation
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.
/api/v1/media/{id}/offersBrowse 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.
Path Parameters
| Field | Type | Description |
|---|---|---|
| id | string (uuid) | Media ID. |
Query Parameters
| Field | Type | Description |
|---|---|---|
| territory | string | ISO 3166-1 alpha-2 country code. Filters to offers that include this territory. |
| usage_scope | stringcommercialeditorialpersonalai_training | Filter 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"
}
]
}/api/v1/offersCreate 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.
Request Body
| Field | Type | Description |
|---|---|---|
| asset_id | string (uuid) | Media ID this offer applies to. Must be owned by your account. |
| policy | object | The rights being granted. See Policy fields below. |
| policy.name | string | Human-readable name for this license tier (e.g. 'Editorial Web Use', 'Global Exclusive'). |
| policy.permissions.usage_scope | stringcommercialeditorialpersonalai_training | Allowed use. |
| policy.permissions.channels | string[]websocial_organicsocial_paidbroadcastprintebookappooh | Where the media may be used. |
| policy.permissions.modifications_allowed | boolean | Can the buyer crop, filter, or composite the media? |
| policy.permissions.sublicensable | boolean | Can the buyer grant their clients usage rights? Critical for agencies. |
| policy.permissions.attribution_required | boolean | Must the buyer credit the licensor? |
| policy.constraints.territory | object | Geographic restriction. { type: 'include' | 'exclude', codes: ['US', 'CA', ...] } using ISO 3166-1 alpha-2. Omit for worldwide. |
| policy.constraints.exclusivity | object | Exclusivity terms. { exclusive: true, scope: 'global' | 'territory' | 'industry', industry_code?: 'automotive' }. Exclusive offers block conflicting sales. |
| policy.constraints.duration | string | License duration as ISO 8601 period. E.g. 'P1Y' (1 year), 'P6M' (6 months), 'P999Y' (perpetual). |
| pricing | object | What it costs. See Pricing fields below. |
| pricing.amount | integer | Price in the smallest currency unit (e.g. cents). 50000 = $500.00. |
| pricing.currency | stringUSDEURGBP | ISO 4217 currency code. |
| pricing.model | stringflat_feeroyalty | Pricing model. |
| quantity_limit | integer | Max 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"
}
}/api/v1/ordersPurchase 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.
Request Body
| Field | Type | Description |
|---|---|---|
| offer_id | string | The 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_method | string | Stripe 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"
}
}/api/v1/agreementsList 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.
Query Parameters
| Field | Type | Description |
|---|---|---|
| role | stringbuyerlicensor | Filter by your role in the agreement. |
| asset_id | string (uuid) | Filter to a specific media asset. |
| status | stringactiveexpiredrevoked | Filter by status. |
| cursor | string | Pagination 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
}
}/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.
Path Parameters
| Field | Type | Description |
|---|---|---|
| id | string | Agreement 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"
}
}/api/v1/dealsPropose 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.
Request Body
| Field | Type | Description |
|---|---|---|
| asset_id | string (uuid) | Media ID you want to license. |
| proposed_policy | object | Your proposed rights policy — same structure as the policy object in offers. Specify the permissions and constraints you want. |
| offer_amount | integer | Your proposed price in the smallest currency unit (cents). E.g. 150000 = $1,500.00. |
| currency | string | ISO 4217 currency code. |
| message | string | Opening 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"
}
}/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.
Path Parameters
| Field | Type | Description |
|---|---|---|
| id | string | Deal 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"
}
}/api/v1/deals/{id}/counterCounter-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.
Path Parameters
| Field | Type | Description |
|---|---|---|
| id | string | Deal ID. |
Request Body
| Field | Type | Description |
|---|---|---|
| proposed_policy | object | Revised rights policy. Omit to keep the current terms and only change the price. |
| offer_amount | integer | Revised price in cents. Omit to keep the current price and only change terms. |
| message | string | Message 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
}
}/api/v1/deals/{id}/acceptAccept 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.
Path Parameters
| Field | Type | Description |
|---|---|---|
| id | string | Deal ID. |
Request Body
| Field | Type | Description |
|---|---|---|
| payment_method | string | Stripe 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.
C2PA manifest assertions embedded during protection: c2pa.training-mining (notAllowed) + sidearm.licensing (acquire URL) + schema_org.CreativeWork. Readable by Adobe, Google, Microsoft C2PA tools.
IPTC/XMP metadata written to image headers: Licensor URL, WebStatement, CopyrightOwnerID. Readable by Google Images, Bing, CMS platforms, and digital asset managers.
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.
The rights discovery endpoint returns TDM-Reservation: 1 and TDM-Policy headers. W3C TDM-aware crawlers check these before any content processing.
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.
GET /rights/{id}→GET /rights/{id}/acquire→POST /rsl/token→POST /orders→Agreement + CAP tokenAll discovery protocols converge on the same canonical URL: GET /api/v1/rights/{media_id}
/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
| Field | Type | Description |
|---|---|---|
| media_id | string (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-..."
}
}
}
}/api/v1/rights/{media_id}/acquireAcquire 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
| Field | Type | Description |
|---|---|---|
| media_id | string (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"
}
}/api/v1/rights/{media_id}/embedPublisher 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.
Path Parameters
| Field | Type | Description |
|---|---|---|
| media_id | string (uuid) | Media ID. |
Query Parameters
| Field | Type | Description |
|---|---|---|
| format | stringhtmljson | Output 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"
}
}/api/v1/rights/tdm-policyTDM 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"
}
}/api/v1/rsl/tokenOLP 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
| Field | Type | Description |
|---|---|---|
| grant_type | stringclient_credentialslicense_token | OAuth 2.0 grant type. |
| client_id | string | API token ID or account ID. |
| client_secret | string | API token secret. |
| scope.media_ids | string[] | Specific media assets this token should cover. |
| scope.usage | stringcrawlindexai_trainingdisplaycommercial | Intended 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\""
}/api/v1/rsl/introspectIntrospect 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
| Field | Type | Description |
|---|---|---|
| token | string | The CAP token to validate. |
| media_id | string (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 soonAutonomous 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
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.
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.
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.
/api/v1/agentsCreate 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.
Request Body
| Field | Type | Description |
|---|---|---|
| role | stringbuyerlicensor | Which side this agent represents. |
| name | string | Display name for this agent (e.g. 'Stock Photo Buyer', 'Portfolio Licensor'). |
| scope | object | What the agent covers. For licensors: which of your assets. For buyers: what kind of media to seek. |
| scope.asset_ids | string[] | Specific media IDs (licensor). Omit to use tags instead. |
| scope.tags | string[] | Match assets with these tags. Applies to both roles. |
| scope.media_types | string[]imagevideoaudiogiftextpdf | Restrict to specific media types. |
| mandate | object | The agent's authority — price range, acceptable terms, and negotiation limits. |
| mandate.min_price | integer | Licensor: floor price in cents. Will not accept below this. |
| mandate.max_price | integer | Buyer: ceiling price in cents. Will not offer above this. |
| mandate.ideal_price | integer | Target price the agent aims for. Opens near this and concedes toward min/max. |
| mandate.acceptable_terms | object | Policy constraints the agent will accept. Same structure as a rights policy. The agent rejects deals outside these bounds. |
| mandate.dealbreakers | string[] | Hard limits that cause immediate rejection. E.g. ['exclusive', 'sublicensable', 'ai_training']. |
| mandate.max_rounds | integer | Walk away after this many counter-offers. |
| mandate.auto_accept | boolean | If true, the agent closes deals within mandate without human approval. If false, it negotiates but pauses before accepting for you to review. |
| strategy | stringaggressivebalancedaccommodating | Negotiation style. |
| payment_method | string | Buyer agents only. Stripe payment method ID to use when auto-accepting deals. |
| active | boolean | Whether 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"
}
}/api/v1/agentsList agents
List all negotiation agents for the authenticated account.
Query Parameters
| Field | Type | Description |
|---|---|---|
| role | stringbuyerlicensor | Filter by role. |
| active | boolean | Filter 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"
}
]
}/api/v1/agents/{id}Get agent
Retrieve agent configuration, current mandate, and performance stats.
Path Parameters
| Field | Type | Description |
|---|---|---|
| id | string | Agent 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"
}
}/api/v1/agents/{id}/activityGet 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.
Path Parameters
| Field | Type | Description |
|---|---|---|
| id | string | Agent ID. |
Query Parameters
| Field | Type | Description |
|---|---|---|
| cursor | string | Pagination cursor. |
| limit | integer | Results 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
}
}/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.
Path Parameters
| Field | Type | Description |
|---|---|---|
| id | string | Agent ID. |
Request Body
| Field | Type | Description |
|---|---|---|
| name | string | New display name. |
| scope | object | Updated scope. |
| mandate | object | Updated mandate. Merged with existing — pass only the fields you want to change. |
| strategy | stringaggressivebalancedaccommodating | Updated negotiation style. |
| active | boolean | Set 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"
}
}/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.
Path Parameters
| Field | Type | Description |
|---|---|---|
| id | string | Agent 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).
/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.
Path Parameters
| Field | Type | Description |
|---|---|---|
| id | string (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.
/api/v1/accounts/{id}Get account
Retrieve account details. Callers may only access their own account.
Path Parameters
| Field | Type | Description |
|---|---|---|
| id | string (uuid) | Account ID. |
Response 200OK
{
"data": {
"id": "e5f6a7b8-...",
"email": "team@example.com",
"name": "Acme Corp"
}
}/api/v1/accounts/{id}Update account
Update email or display name.
Path Parameters
| Field | Type | Description |
|---|---|---|
| id | string (uuid) | Account ID. |
Request Body
| Field | Type | Description |
|---|---|---|
| string (email) | New contact email. | |
| name | string | New display name. |
Example Request
{
"name": "Acme Corp (Renamed)"
}Response 200OK
{
"data": {
"id": "e5f6a7b8-...",
"name": "Acme Corp (Renamed)",
"updated_at": "2026-02-17T13:00:00Z"
}
}/api/v1/accounts/{id}Delete account
Permanently delete an account. Callers may only delete their own.
Path Parameters
| Field | Type | Description |
|---|---|---|
| id | string (uuid) | Account ID. |
Response 200OK
{
"data": {
"deleted": true
}
}API Tokens
Coming soonCreate 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.
/api/v1/tokensCreate 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.
Request Body
| Field | Type | Description |
|---|---|---|
| name | string | A label for this token (e.g. 'Production', 'CI/CD Pipeline', 'Partner Integration'). |
| scopes | string[] | 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. |
| tags | string[] | Labels for organization and billing attribution. Usage by this token is tagged automatically for cost tracking. |
| expires_at | string (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"
}
}/api/v1/tokensList tokens
List all API tokens for the authenticated account. Token secrets are never included — only metadata, scopes, and usage stats.
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"
}
]
}/api/v1/tokens/{id}Get token
Retrieve token details including scopes, tags, and last-used timestamp. The token secret is never returned.
Path Parameters
| Field | Type | Description |
|---|---|---|
| id | string | Token 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"
}
}/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.
Path Parameters
| Field | Type | Description |
|---|---|---|
| id | string | Token ID. |
Request Body
| Field | Type | Description |
|---|---|---|
| name | string | New label. |
| scopes | string[] | Replacement scopes. Cannot exceed the scopes of the token making this request. |
| tags | string[] | Replacement tags. |
| expires_at | string (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"
}
}/api/v1/tokens/{id}Revoke token
Permanently revoke a token. Any requests using this token will immediately receive 401. This cannot be undone.
Path Parameters
| Field | Type | Description |
|---|---|---|
| id | string | Token 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.
/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.
Path Parameters
| Field | Type | Description |
|---|---|---|
| account_id | string (uuid) | Account ID. |
Query Parameters
| Field | Type | Description |
|---|---|---|
| start_date | string (date) | Events on or after (YYYY-MM-DD). |
| end_date | string (date) | Events on or before. |
| type | string | Filter by event type (e.g. protect:harmonic-bait:v1, storage:daily). |
| tags | string | Comma-separated tag filter. |
| token_id | string (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-baitv1Invisible poison that confuses AI models. If they train on your work, the AI produces garbled, unusable output for your concepts.
style-cloakv1Protects your unique artistic style. Prevents AI from mimicking your look and feel or cloning your vocal timbre.
id-embedv1An invisible, unbreakable digital tattoo that proves this file belongs to you. Survives compression, cropping, reformatting, and noise.
tamper-shieldv1Hardens other protections against removal. If someone tries to strip your protections, we can prove it.
digital-trustv1Cryptographic provenance manifest. Applied twice — before and after protection — to create a tamper-evident chain of custody.
timbre-shieldv1Locks your voice. AI models will fail to capture your unique vocal tone.
text-poisonv1Invisible linguistic traps that prove an AI model consumed your writing. Detectable even at 0.001% of training corpus.
doc-tattoov1An invisible fingerprint embedded in your text that survives copy-paste, OCR, and reformatting.
pdf-shieldv1Deep-embedded protection inside PDF files. Invisible to viewers, survives print-to-PDF round-trips.
Open — Active Protections (Visual)
nightshadev1Feature-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.
glazev1Style-transfer perturbation that prevents AI from learning and reproducing an artist's visual style.
hmarkv1Steganographic 64-bit owner ID watermark using spread-spectrum techniques. Survives JPEG compression, cropping, rescaling, and noise.
paiv1Adversarial hardening that makes preceding watermarks and perturbations resistant to removal attacks.
iptc-rightsv1Machine-readable licensing metadata in EXIF/XMP fields. Recognized by Google Images, Bing, and DAMs.
Open — Active Protections (Audio)
audio-nightshadev1Spectral perturbation that disrupts audio-generative model training. Extension of Nightshade to audio.
vocal-glazev1Formant shift that prevents effective voice cloning. Extension of Glaze to the audio domain.
hmark-audiov1Spread-spectrum 64-bit audio watermark. Robust against MP3/AAC compression, speed changes, and noise.
Open — Active Protections (Text & Documents)
spectrav1Paraphrase-guided watermark detectable at 0.001% of training corpus. Enables membership inference.
textmarkv1Unicode steganographic owner ID. Survives copy-paste, OCR, and reformatting.
pdfstegov1PDF stream operator steganography. Invisible to viewers, survives print-to-PDF round-trips.
Open — Provenance & Signing
c2pav1Cryptographic 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
phashv1Perceptual and difference hashing for near-duplicate image detection. Industry-standard for reverse image search.
dinov2v1Meta's self-supervised vision transformer. Semantic embeddings that find similar content by meaning, not pixels.
clipv1OpenAI CLIP / Google SigLIP multimodal embeddings. Enables text-to-image search and cross-modal matching.
scene-graphv1Structural scene analysis — maps objects, relationships, and spatial layout for compositional search.
chromaprintv1Audio fingerprinting that powers MusicBrainz. Identifies tracks across format conversions and edits.
clapv1Contrastive Language-Audio Pretraining. Semantic audio search — find audio by meaning, not waveform.
audio-structurev1Essentia structural embeddings. Compositional audio search — find audio with similar arrangement, rhythm, and tonal structure.
sentence-transformersv1Dense 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-nodeQuick 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 handleclient.protect(opts)Apply a curated preset (standard / maximum) — returns a Job handleclient.algorithms.list()List all available algorithmsclient.jobs.get(id)Fetch a job by IDclient.media.*register · list · get · update · deleteclient.detect.ai(opts)Detect AI-generated contentclient.detect.fingerprint(opts)Check for embedded watermark or ownership fingerprintclient.detect.membership(opts)Test if content was used in AI model trainingclient.search.run(opts)Find similar or stolen contentclient.rights.get(mediaId)Rights and licensing informationclient.billing.get(accountId)Credit balance, usage breakdown, billing eventsJob 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 mediaError 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 sidearmdrmQuick 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 handleclient.protect(**kwargs)Apply a curated preset (standard / maximum) — returns a Job handleclient.algorithms.list()List all available algorithmsclient.jobs.get(id)Fetch a job by IDclient.media.*register · list · get · update · deleteclient.detect.ai(**kwargs)Detect AI-generated contentclient.detect.fingerprint(**kwargs)Check for embedded watermark or ownership fingerprintclient.detect.membership(**kwargs)Test if content was used in AI model trainingclient.search.run(**kwargs)Find similar or stolen contentclient.rights.get(media_id)Rights and licensing informationclient.billing.get(account_id)Credit balance, usage breakdown, billing eventsContext 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 typesrun_algorithmRun specific named algorithms on media — returns a job_idprotect_mediaApply standard or maximum protection preset — returns a job_idcheck_jobPoll job status and retrieve output_url on completionsearch_mediaFind similar or stolen content; returns matches with similarity scoreslist_searchesList past search operationsdetect_aiDetect whether content is AI-generateddetect_fingerprintCheck for embedded watermark or ownership fingerprintdetect_membershipTest if content was used in AI model trainingregister_mediaRegister a media asset in the librarylist_mediaList registered media assetsget_mediaFetch a single media asset by IDupdate_mediaUpdate tags or metadata on a media assetdelete_mediaPermanently delete a media assetget_rightsRetrieve licensing and rights information for a media assetget_billingGet credit balance, usage breakdown, and billing eventsget_provenanceFull provenance chain — protections applied, search matches, membership inference resultsTypical agent workflow
list_algorithmsDiscover what's available and pick the right algorithm for the taskprotect_mediaQueue a protection job with the desired level (standard / maximum)check_jobPoll until status = completed, then retrieve the output_urlget_provenanceVerify the protection chain was applied correctly