Skip to main content

API Reference

The SignedShot API provides cryptographic proof of authenticity for photos and videos.

Base URL

https://dev-api.signedshot.io
Staging Environment

During the MVP launch, use dev-api.signedshot.io for development and testing.

Production API (api.signedshot.io) will be available post-launch with proper account registration and authentication.

Authentication

Most endpoints require authentication via the Authorization header:

Authorization: Bearer <token>
Token TypeUsed ForObtained From
Device TokenCapture endpointsPOST /devices response

Endpoints Overview

MethodEndpointDescriptionAuth
POST/publishersCreate a publisherNone
GET/publishers/{id}Get publisher detailsNone
PATCH/publishers/{id}Update publisherNone
POST/devicesRegister a deviceHeader
POST/capture/sessionStart capture sessionBearer
POST/capture/trustExchange nonce for JWTBearer
POST/validateValidate media + sidecarNone
GET/.well-known/jwks.jsonPublic keys for JWT verificationNone

Publishers

Publishers represent apps or organizations that capture signed media.

Create Publisher

POST /publishers

Request Body:

{
"name": "My Camera App",
"sandbox": true,
"attestation_provider": "NONE",
"firebase_project_id": null,
"attestation_bundle_id": null,
"track_devices": false
}
FieldTypeRequiredDescription
namestringYesDisplay name (1-255 chars)
sandboxbooleanNoSandbox mode (default: true)
attestation_providerstringNo"NONE" or "FIREBASE_APP_CHECK"
firebase_project_idstringNoFirebase project ID (required for App Check)
attestation_bundle_idstringNoApp bundle ID for attestation
track_devicesbooleanNoEnable device tracking (default: false)

Response (201):

{
"publisher_id": "9a5b1062-a8fe-4871-bdc1-fe54e96cbf1c",
"name": "My Camera App",
"sandbox": true,
"attestation_provider": "NONE",
"firebase_project_id": null,
"attestation_bundle_id": null,
"track_devices": false,
"created_at": "2025-01-15T10:30:00Z"
}

Get Publisher

GET /publishers/{publisher_id}

Response (200): Same as create response.

Errors:

  • 404 — Publisher not found

Update Publisher

PATCH /publishers/{publisher_id}

Only provided fields are updated.

Request Body:

{
"sandbox": false,
"attestation_provider": "FIREBASE_APP_CHECK",
"firebase_project_id": "my-project-123",
"attestation_bundle_id": "io.signedshot.capture"
}

Response (200): Updated publisher object.

Errors:

  • 404 — Publisher not found

Devices

Devices are registered once per app installation and receive a token for authentication.

Register Device

POST /devices

Headers:

HeaderRequiredDescription
X-Publisher-IDYesPublisher UUID
X-Attestation-TokenConditionalFirebase App Check token (required if publisher has attestation enabled)

Request Body:

{
"external_id": "device-abc-123"
}
FieldTypeRequiredDescription
external_idstringYesUnique device identifier (1-255 chars)

Response (201):

{
"device_id": "ea5c9bfe-6bbc-4ee2-b82d-0bcfcc185ef1",
"publisher_id": "9a5b1062-a8fe-4871-bdc1-fe54e96cbf1c",
"external_id": "device-abc-123",
"device_token": "eyJhbGciOiJIUzI1NiIs...",
"created_at": "2025-01-15T10:30:00Z"
}

Important: Store device_token securely. It's only returned once.

Errors:

  • 400 — Invalid publisher ID format
  • 401 — Attestation verification failed
  • 404 — Publisher not found
  • 409 — Device already registered
  • 500 — Attestation not configured for non-sandbox publisher

Capture

The capture flow creates a session before capturing and exchanges a nonce for a trust token after.

Create Session

POST /capture/session

Headers:

Authorization: Bearer <device_token>

Response (201):

{
"capture_id": "550e8400-e29b-41d4-a716-446655440000",
"nonce": "a1b2c3d4e5f6...",
"expires_at": "2025-01-15T10:35:00Z"
}
FieldDescription
capture_idUUID for this capture (include in sidecar)
nonceOne-time token to exchange for trust token
expires_atSession expiration (complete capture before this)

Errors:

  • 401 — Invalid or missing device token

Exchange Trust Token

POST /capture/trust

Headers:

Authorization: Bearer <device_token>

Request Body:

{
"nonce": "a1b2c3d4e5f6..."
}

Response (200):

{
"trust_token": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtleS0xIn0..."
}

The trust_token is an ES256-signed JWT containing:

{
"iss": "https://dev-api.signedshot.io",
"aud": "signedshot",
"sub": "capture-service",
"iat": 1705312200,
"capture_id": "550e8400-e29b-41d4-a716-446655440000",
"publisher_id": "9a5b1062-a8fe-4871-bdc1-fe54e96cbf1c",
"device_id": "ea5c9bfe-6bbc-4ee2-b82d-0bcfcc185ef1",
"attestation": {
"method": "app_check",
"app_id": "io.signedshot.capture"
}
}

Errors:

  • 400 — Invalid or expired nonce
  • 401 — Invalid device token

Validate

Verify a media file against its sidecar.

Validate Media

POST /validate
Content-Type: multipart/form-data

Form Fields:

FieldTypeDescription
mediafileThe media file (photo/video)
sidecarfileThe sidecar JSON file

Response (200):

{
"valid": true,
"version": "1.0",
"capture_trust": {
"signature_valid": true,
"issuer": "https://dev-api.signedshot.io",
"publisher_id": "9a5b1062-a8fe-4871-bdc1-fe54e96cbf1c",
"device_id": "ea5c9bfe-6bbc-4ee2-b82d-0bcfcc185ef1",
"capture_id": "550e8400-e29b-41d4-a716-446655440000",
"method": "app_check",
"app_id": "io.signedshot.capture",
"issued_at": 1705312200,
"key_id": "key-1"
},
"media_integrity": {
"content_hash_valid": true,
"signature_valid": true,
"capture_id_match": true,
"content_hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"capture_id": "550e8400-e29b-41d4-a716-446655440000",
"captured_at": "2025-01-15T10:30:00Z"
},
"error": null
}

Errors:

  • 400 — Invalid sidecar format or validation error

JWKS

Public keys for verifying JWT signatures.

Get JWKS

GET /.well-known/jwks.json

Response (200):

{
"keys": [
{
"kty": "EC",
"crv": "P-256",
"kid": "key-1",
"x": "...",
"y": "...",
"use": "sig",
"alg": "ES256"
}
]
}

Use the kid from the JWT header to find the matching key.


Error Responses

All errors follow this format:

{
"detail": "Error message describing the issue"
}

Common Status Codes

CodeMeaning
400Bad request (invalid input)
401Unauthorized (missing/invalid token)
404Resource not found
409Conflict (duplicate resource)
500Server error

Rate Limits

The API currently has no rate limits. This may change in the future.


Next Steps