Introduction

The Sparker Verify API provides programmatic access to cryptographic media verification. All endpoints are served over HTTPS from https://verify.sparker.io/api/v1.

The API follows REST conventions with JSON request and response bodies. Multipart form data is used for file uploads.

Base URL: https://verify.sparker.io/api/v1

Authentication

Sparker has two auth modes. Dashboard management uses Google OAuth and JWT bearer tokens. SDK traffic should use dashboard-generated API keys in the Authorization header.

Public verification endpoints can be called without authentication, but those requests are much more heavily rate-limited than requests that include an API key.

OAuth Flow

  1. Redirect user to GET /api/v1/auth/google
  2. User authenticates with Google
  3. Callback redirects to your app with access_token
  4. Include token in subsequent requests

GET /api/v1/auth/me

Get the current authenticated user.

curl https://verify.sparker.io/api/v1/auth/me \
  -H "Authorization: Bearer your-jwt-access-token"
Response
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "email": "user@example.com",
  "name": "Jane Doe",
  "avatar_url": "https://lh3.googleusercontent.com/...",
  "oauth_provider": "google",
  "email_verified": true,
  "created_at": "2026-01-15T10:30:00Z"
}

POST /api/v1/auth/refresh

Refresh an expired access token using the refresh token cookie.

POST /api/v1/auth/logout

Invalidate the current session and clear refresh token.

API Keys

Create API keys from the dashboard, then initialize the SDK with that key and reuse the client across your app.

POST /api/v1/api-keys

Create a new API key. The full key value is only returned once.

curl -X POST https://verify.sparker.io/api/v1/api-keys \
  -H "Authorization: Bearer your-jwt-access-token" \
  -H "Content-Type: application/json" \
  -d '{"name": "Production SDK"}'
Response
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "Production SDK",
  "key_prefix": "spk_live_abc123",
  "key": "spk_live_abc123def456ghi789...",
  "is_active": true,
  "created_at": "2026-01-15T10:30:00Z",
  "last_used_at": null,
  "revoked_at": null
}

GET /api/v1/api-keys

List active API keys for the authenticated dashboard user.

DELETE /api/v1/api-keys/:id

Revoke an API key. Returns 204 No Content.

Browser Device Registration

Devices must be registered with hardware attestation before they can sign media. Each device generates an ECDSA P-256 key pair via WebAuthn. This production flow is web/browser only today; native iOS and Android SDKs are not supported yet.

POST /api/v1/devices/challenge

Generate a challenge for device registration. The client signs this challenge with the device's private key during WebAuthn registration. Accepts either a Bearer API key or a 3P verification link token in X-Token.

ParameterTypeDescription
AuthorizationstringBearer API key or dashboard JWT
X-Tokenstring3P verification link token
Response
{
  "challenge": "dGhpcyBpcyBhIGNoYWxsZW5nZQ..."
}

POST /api/v1/devices/register

Register a device with attestation proof and public key. Accepts either a Bearer API key or a 3P verification link token in X-Token.

curl -X POST https://verify.sparker.io/api/v1/devices/register \
  -H "Authorization: Bearer spk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "platform": "web",
    "device_id": "credential-id-base64",
    "public_key": {
      "algorithm": "ES256",
      "x": "base64-x-coordinate",
      "y": "base64-y-coordinate"
    },
    "attestation": {
      "format": "webauthn",
      "data": "attestation-object-base64",
      "challenge_response": "client-data-json-base64"
    },
    "device_info": {
      "name": "Chrome on macOS",
      "model": "Chrome",
      "os_version": "macOS 15.3"
    }
  }'
Response
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "device_id": "credential-id-base64",
  "name": "Chrome on macOS",
  "model": "Chrome",
  "platform": "web",
  "registered_at": "2026-01-15T10:30:00Z"
}

GET /api/v1/devices

List all registered devices for the authenticated user.

DELETE /api/v1/devices/:id

Remove a registered device.

Verifications

The verification flow is a two-step process: create a verification request to get a nonce, then submit signed media to complete the verification.

POST /api/v1/verifications

Create a verification request. Returns a nonce that must be signed with the media. The nonce expires after 5 minutes.

ParameterTypeDescription
AuthorizationstringBearer API key or dashboard JWT
X-Tokenstring3P verification link token
curl -X POST https://verify.sparker.io/api/v1/verifications \
  -H "Authorization: Bearer spk_live_your_api_key"
Response
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "nonce": "random-nonce-base64",
  "expires_at": "2026-01-15T10:35:00Z"
}

POST /api/v1/verifications/verify

Submit signed media to complete verification. Uses multipart form data. The signature must be Sign(Hash(file + nonce)) using the device's private key.

ParameterTypeDescription
id*UUIDVerification ID from creation step
file*fileMedia file (image, video, or audio)
device_id*stringRegistered device identifier
signature*stringBase64 signature of Hash(file + nonce)
signature_metadataJSON stringSigning algorithm metadata
metadataJSON stringGPS coordinates and other metadata
authenticator_datastringWebAuthn authenticatorData (base64url)
client_data_jsonstringWebAuthn clientDataJSON (base64url)
curl -X POST https://verify.sparker.io/api/v1/verifications/verify \
  -H "Authorization: Bearer spk_live_your_api_key" \
  -F "id=550e8400-e29b-41d4-a716-446655440000" \
  -F "file=@photo.jpg" \
  -F "device_id=credential-id-base64" \
  -F "signature=signature-base64" \
  -F 'metadata={"latitude":37.7749,"longitude":-122.4194}'
Response
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "authentic": true,
  "signature_hash": "abc123def4567890abc123def4567890abc123def4567890abc123def4567890",
  "verified_at": "2026-01-15T10:31:00Z",
  "device": {
    "name": "Chrome on macOS",
    "model": "Chrome",
    "platform": "web"
  },
  "gps": {
    "latitude": 37.7749,
    "longitude": -122.4194,
    "accuracy": 10.0
  }
}

GET /api/v1/verifications/:id

Get verification details by ID.

3P Verification Links

3P verification links let an account holder delegate capture flows to third parties. Verifications created via these links count against the creator's quota.

POST /api/v1/verifications/external

Create a new 3P verification link. Requires a Pro or Enterprise plan.

ParameterTypeDescription
max_usesintegerMaximum number of uses (null for unlimited)
expires_atISO 8601Expiration timestamp (null for no expiry)
curl -X POST https://verify.sparker.io/api/v1/verifications/external \
  -H "Authorization: Bearer spk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"max_uses": 10, "expires_at": "2026-02-01T00:00:00Z"}'
Response
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "token": "spk_abc123def456...",
  "url": "https://verify.sparker.io/capture?token=spk_abc123def456...",
  "max_uses": 10,
  "expires_at": "2026-02-01T00:00:00Z",
  "created_at": "2026-01-15T10:30:00Z"
}

GET /api/v1/verifications/external

List all 3P verification links for the authenticated account.

ParameterTypeDescription
include_inactivebooleanInclude revoked/expired tokens (default: false)

DELETE /api/v1/verifications/external/:id

Revoke an active token. Returns 204 No Content.

GET /api/v1/verifications/external/validate/:token

Validate a 3P verification link token. Public endpoint (no authentication required).

Response
{
  "valid": true,
  "creator_name": "Jane Doe",
  "remaining_uses": 8,
  "expires_at": "2026-06-15T10:30:00Z"
}

Public Verification

Anyone can verify media authenticity using these public endpoints. The SDK extracts signature hashes locally so files never need to be uploaded for verification. These endpoints can be called without authentication, but adding a Bearer API key unlocks much higher request limits. The backend accepts either the registered device-signature hash or the stored file hash as the public lookup key.

POST /api/v1/verify

Check if a public lookup hash is registered.

curl -X POST https://verify.sparker.io/api/v1/verify \
  -H "Content-Type: application/json" \
  -d '{"signature_hash": "abc123def4567890abc123def4567890abc123def4567890abc123def4567890"}'
Response
{
  "authentic": true,
  "verification_id": "550e8400-e29b-41d4-a716-446655440000",
  "verified_at": "2026-01-15T10:31:00Z"
}

GET /api/v1/verify/:id

Get full verification details including GPS, device info, and C2PA manifest for a public or 3P verification record.

POST /api/v1/verify/batch

Verify up to 100 signature hashes in a single request.

curl -X POST https://verify.sparker.io/api/v1/verify/batch \
  -H "Content-Type: application/json" \
  -d '{"signatures": ["abc123def456...", "7890fedcba12..."]}'

Quota

Each user has a monthly verification quota based on their tier. Quotas reset on the 1st of each month at midnight UTC.

TierMonthly VerificationsDevicesActive Tokens
Free5025
Pro2,0001050
EnterpriseUnlimitedUnlimitedUnlimited

GET /api/v1/quota

Get current quota status for the authenticated user.

curl https://verify.sparker.io/api/v1/quota \
  -H "Authorization: Bearer your-jwt-access-token"
Response
{
  "quota_tier": "free",
  "monthly_quota": 50,
  "quota_used_this_month": 42,
  "quota_reset_at": "2026-02-01T00:00:00Z",
  "device_limit": 2,
  "device_count": 1,
  "token_limit": 5,
  "active_token_count": 2
}

Rate Limits

Rate limits vary based on authentication status. When rate limited, the API returns 429 Too Many Requests with a Retry-After header.

User TypeBase RateVerification CreationSignature Verification
Public (no API key)10/minn/a3/min
3P link token10/min10/minn/a
API key / dashboard session10/min10/min100/min120/min

Rate limit headers

X-RateLimit-Limit: 60

X-RateLimit-Remaining: 58

X-RateLimit-Reset: 1706356200

Retry-After: 30

Errors

The API uses standard HTTP status codes. Error responses include a detail field with a human-readable description.

StatusDescription
400Bad Request - Invalid parameters or malformed request
401Unauthorized - Missing or invalid authentication
403Forbidden - Insufficient permissions for this resource
404Not Found - Resource does not exist
409Conflict - Resource already exists (e.g., duplicate device)
422Unprocessable Entity - Valid JSON but invalid data
429Too Many Requests - Rate limit or quota exceeded
500Internal Server Error - Something went wrong on our end

Error response format

Response
{
  "detail": "Verification not found"
}