REST API

Endpoints

All paths below are rooted at https://attestiaprotocol.xyz/api/v1. Private routes require request signing. Rate limits are per access key (see Limits & errors).

Endpoint index

MethodPathAuthDescription
GET/pingNoneConnectivity test.
GET/timeNoneServer time (ms).
GET/healthNoneService metadata.
GET/attester/mediaSigned + stakeList open assets.
GET/attester/media/:idSigned + stakeSingle open asset.
GET/attester/media/streamSigned + stakeSSE stream of open assets.
POST/attester/media/:id/scoresSigned + stakeSubmit a score for an open asset.
POST/submitter/mediaSigned + stakeRegister media asset.
GET

https://attestiaprotocol.xyz/api/v1/attester/media

List open media (attester)

Counts toward your per-key rate limit (Limits & errors).

NameTypeMandatoryDescription
includeMyScoreINTNOIf 1, adds myScore for the key wallet when you already submitted a score for that asset.

Request headers

Attestia-Access-Key, Attestia-Timestamp, Attestia-Signature — signing string uses GET and path+query exactly as sent, e.g. /api/v1/attester/media?includeMyScore=1, empty body hash.

Response 200

{
  "assets": [
    {
      "id": "…",
      "title": "…",
      "mediaContext": "…",
      "contentHash": "0x…",
      "uri": "ipfs://…",
      "status": "open",
      "mediaType": "image",
      "turnaround": "standard",
      "scoreCount": 3,
      "myScore": { "submitted": true, "deepfakeRiskScore": 42, "…": "…" }
    }
  ],
  "stake": { "stakedWei": "…", "minStakeWei": "…" }
}
GET

https://attestiaprotocol.xyz/api/v1/attester/media/:id

Get open media by id (attester)

NameTypeMandatoryDescription
idSTRINGYESPath segment: asset id.
includeMyScoreINTNOSame as list endpoint.

404 if the asset is not in status open.

GET

https://attestiaprotocol.xyz/api/v1/attester/media/stream

Open media stream (attester, SSE)

Response Content-Type: text/event-stream. Same signing headers as GET; use the exact path /api/v1/attester/media/stream (no query) unless you add one.

Attester workers can use the new-open-assets delta event as the trigger that newly registered media is available to review. The open-assets snapshot is also sent whenever the open set changes (use it for reconciliation or initial state). If your deployment exposes the websocket gateway, it carries the same authenticated feed over WebSocket.

Events

eventdata (JSON)
ready{ stake: { stakedWei, minStakeWei } }
new-open-assets{ ids: string[], assets: MediaAsset[] } delta when the open set expands
open-assets{ ids: string[], assets: MediaAsset[] } snapshot
ping{ t: number } heartbeat
error{ message: string }
POST

https://attestiaprotocol.xyz/api/v1/attester/media/:id/scores

Submit score (attester)

Submit a verification score for an open asset. The request must be signed like other v1 routes; the body JSON must be the exact string you sign.

Off-chain EAS attestations are generated and signed by the Attestia server using the attester’s linked Privy wallet. API clients should not construct or submit an off-chain attestation payload.

Request headers

Attestia-Access-Key, Attestia-Timestamp, Attestia-Signature — signing string uses POST and path exactly as sent, e.g. /api/v1/attester/media/asset_123/scores, and SHA-256 of the JSON body.

FieldTypeMandatoryDescription
deepfakeRiskScoreNUMBERYES0..100 (percentage)
algorithmSTRINGYESShort algorithm/model label
notesSTRINGNOOptional notes
signatureSTRINGNOReserved for future use (ignored today).

The server derives attesterAddress from the API key wallet and will reject attempts to submit scores “as” another address.

Requirements: your attester must have a Privy wallet linked in the dashboard (so the server can sign), and meet the stake gate for attesters.

Response 200

{
  "score": { "id": "…", "assetId": "…", "attesterAddress": "0x…", "deepfakeRiskScore": 42, "…" : "…" },
  "aggregate": { "…" : "…" }
}
POST

https://attestiaprotocol.xyz/api/v1/submitter/media

Register media (submitter)

Body JSON must be the string you sign. ownerEmail and ownerAddress are always taken from the access key (ignored if present in JSON).

FieldTypeMandatoryDescription
titleSTRINGYES
mediaContextSTRINGYESShort description hint (≤ 200 chars; may be empty).
contentTypeSTRINGNODetector content hint: auto | face | landscape | … (default auto).
contentHashSTRINGYESbytes32 hex 0x + 64 hex.
uriSTRINGYESTypically ipfs://… from /api/media/upload.
contributorAttestationUidSTRINGYESbytes32 uid.
verificationDeadlineSTRINGYESISO-8601, must be in the future.
mediaTypeENUMNOimage | audio | video_short | video_long | text
turnaroundENUMNOstandard | priority_5min
Signed POST: submitter/media
import { createHash, createHmac } from "node:crypto";

const ORIGIN = "https://attestiaprotocol.xyz";
const ACCESS_KEY = process.env.ATTESTIA_ACCESS_KEY ?? "";
const SIGNING_SECRET = process.env.ATTESTIA_SIGNING_SECRET ?? "";

function sha256HexUtf8(s: string) {
  return createHash("sha256").update(s, "utf8").digest("hex");
}

function signRequest(method: string, pathWithQuery: string, body: string) {
  const ts = String(Date.now());
  const bodyHash = sha256HexUtf8(body);
  const payload = [ts, method.toUpperCase(), pathWithQuery, bodyHash].join(String.fromCharCode(10));
  const signature = createHmac("sha256", SIGNING_SECRET).update(payload, "utf8").digest("hex");
  return {
    headers: {
      "Content-Type": "application/json",
      "Attestia-Access-Key": ACCESS_KEY,
      "Attestia-Timestamp": ts,
      "Attestia-Signature": signature,
    },
  };
}

const path = "/api/v1/submitter/media";
const body = JSON.stringify({
  title: "My asset",
  mediaContext: "Context here",
  contentHash: "0x" + "ab".repeat(32),
  uri: "ipfs://…",
  contributorAttestationUid: "0x" + "cd".repeat(32),
  verificationDeadline: new Date(Date.now() + 12 * 3600 * 1000).toISOString(),
  mediaType: "image",
  turnaround: "standard",
});
const { headers } = signRequest('POST', path, body);
const res = await fetch(ORIGIN + path, { method: 'POST', headers, body });
console.log(res.status, await res.text());

WebSocket API