Skip to content

Verification API

All verification endpoints are public - no authentication required, free and unlimited on every plan. Base URL: https://auth-api.flowsta.com

Signatures are created in the user's Flowsta Vault and reach the network by gossip, typically within minutes of signing - a just-signed file may briefly return no results. The POST /sign and POST /sign-batch endpoints below are deprecated; see the callouts on each.

GET /api/v1/sign-it/verify

Look up signatures for a file by its SHA-256 hash.

bash
curl "https://auth-api.flowsta.com/api/v1/sign-it/verify?hash=a7f3b9c1e2d4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0"

Parameters

NameInTypeRequiredDescription
hashquerystringYesSHA-256 hex string (exactly 64 characters)

Response

json
{
  "signatures": [
    {
      "file_hash": "a7f3b9c1...",
      "action_hash": "842924c550353c6a...",
      "signer": "uhCAkynvT4p77x...",
      "signer_did": "did:flowsta:z6Mk...",
      "signer_display_name": "Alice",
      "signer_username": "alice",
      "signer_profile_picture": "https://...",
      "signed_at": 1680000000000,
      "intent": "Authorship",
      "ai_generation": "None",
      "content_rights": {
        "license": "CCBY",
        "commercial_licensing": "OpenToLicensing",
        "ai_training": "NotAllowed",
        "contact_preference": "AllowContactRequests"
      },
      "integrity_report": {
        "checks_performed": ["PostEofData", "LsbAnalysis", "UnicodeSteg", "MetadataInspection", "EntropyAnalysis", "AppendedFileDetection"],
        "issues_found": [],
        "checked_at": 1680000000000
      },
      "perceptual_hash": {
        "hash_type": "ImagePHash",
        "hash_value": [/* bytes */],
        "bands": [/* byte arrays */]
      },
      "comment": "Original master file for the 2026 print run.",
      "supersedes": null,
      "superseded_by": null,
      "expires_at": null,
      "tags": null,
      "revoked": false,
      "revoked_at": null,
      "revocation_reason": null,
      "contactable": true
    }
  ],
  "count": 1
}

Field notes:

  • signer_display_name, signer_username, signer_profile_picture - public profile fields; null for signers who haven't granted those scopes yet. Handle nulls gracefully.
  • comment - the signer's Public Note (up to 280 characters), or null.
  • supersedes / superseded_by - hex action hashes linking amended signature chains: supersedes points to the signature this one replaces; superseded_by is set when a newer signature replaces this one. Use them to show only the latest version and walk the edit history.
  • expires_at, tags - optional signature metadata; null on signatures that don't set them.

Rate Limit

30 requests/minute per IP.


POST /api/v1/sign-it/verify-file

Upload a file for server-side perceptual fingerprinting and fuzzy matching. The file is fingerprinted and immediately discarded - not stored.

Supports images (PNG, JPEG, BMP, TIFF, GIF), audio (MP3, WAV, FLAC, OGG), and video (MP4, MKV, AVI, WebM).

bash
curl -X POST "https://auth-api.flowsta.com/api/v1/sign-it/verify-file" \
  --data-binary @photo.jpg

Parameters

NameInTypeRequiredDescription
bodybodybinaryYesRaw file bytes. Max 50 MB

Response

Same format as GET /verify, with additional similarity field (0-100) and match_type: "perceptual". Only returns matches with similarity >= 60%.

Rate Limit

30 requests/minute per IP.


POST /api/v1/sign-it/verify-fuzzy

Fuzzy lookup using pre-computed perceptual hash bands. For when you've already generated the perceptual hash client-side.

bash
curl -X POST "https://auth-api.flowsta.com/api/v1/sign-it/verify-fuzzy" \
  -H "Content-Type: application/json" \
  -d '{"bands": [[1,2],[3,4],[5,6],[7,8]], "hash_value": [/* full hash bytes */]}'

Parameters

NameInTypeRequiredDescription
bandsbodynumber[][]YesLSH band byte arrays
hash_valuebodynumber[]NoFull perceptual hash for Hamming distance
pixelsbodynumber[]No72 grayscale pixels (9x8) - server computes gradient hash

Rate Limit

30 requests/minute per IP.


GET /api/v1/sign-it/quota

Return the authenticated user's (or, with an OAuth token, your org's) current signing quota state. Use it to render a quota meter without attempting a sign. See Signing quotas & sponsored signing.

bash
curl "https://auth-api.flowsta.com/api/v1/sign-it/quota" \
  -H "Authorization: Bearer <jwt_or_oauth_token>"

Authentication

JWT session token or OAuth access token with verify scope.

Response

json
{
  "allowed": true,
  "scope": "community",
  "tier": "premium",
  "used": 12,
  "limit": 100,
  "resets_at": "2026-08-01T00:00:00.000Z"
}
FieldTypeDescription
allowedbooleanWhether another sign would be accepted
scopestringcommunity (personal quota) or org (developer pool, when authenticated via OAuth)
tierstringPlan name (free, premium, premium_plus - or org tier)
usednumberSigns used this period
limitnumberIncluded signs per period
resets_atstringWhen the period rolls over
upgrade_tierstringPresent when blocked - the suggested next tier
overage_signs, overage_cents_per_100numberOrg scope, paid tiers only - metered overage state and price

Responses are Cache-Control: no-store.


GET /api/v1/sign-it/quota/by-agent

Public quota lookup by agent key (agent keys are public information - they're on the network). Used by Flowsta Vault to fetch quota state without minting a token; useful for any client that holds the user's agent key.

bash
curl "https://auth-api.flowsta.com/api/v1/sign-it/quota/by-agent?agent_pub_key=uhCAk...&client_id=your_client_id"

Parameters

NameInTypeRequiredDescription
agent_pub_keyquerystringYesThe signer's agent key (uhCAk... or plain base64)
client_idquerystringNoYour app's client ID - resolves the sponsored signing pool

Response

Without client_id: the same shape as GET /quota (the user's personal quota).

With client_id (and an active app with an organization): the sponsoring org's pool while it has room, plus a sponsor object -

json
{
  "allowed": true,
  "scope": "org",
  "tier": "starter",
  "used": 480,
  "limit": 2500,
  "resets_at": "2026-08-01T00:00:00.000Z",
  "overage_signs": 0,
  "overage_cents_per_100": 100,
  "sponsor": { "app_name": "ArtStudio", "exhausted": false }
}

If the org pool is exhausted, the response is the user's personal quota with sponsor: { "app_name": "...", "exhausted": true } - this is how the Vault approval dialog stays honest about whose quota a signature draws from.

Returns 404 agent_not_found if no user matches the agent key.


POST /api/v1/sign-it/sign

Deprecated - sign through the Vault

Signing happens in the user's Flowsta Vault, where they approve each signature and the keys never leave their device. Use signFile() (web) or signDocument() (desktop). This server-side endpoint is not available to Flowsta accounts and is documented only for completeness.

Sign a file hash. Requires authentication. Quota-gated (not rate-limited): over-quota requests return 402 with error quota_exceeded - see quotas.

bash
curl -X POST "https://auth-api.flowsta.com/api/v1/sign-it/sign" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <jwt_or_oauth_token>" \
  -d '{
    "file_hash": "a7f3b9c1e2d4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0",
    "intent": "Authorship",
    "comment": "Original master file for the 2026 print run.",
    "content_rights": {
      "license": "cc-by",
      "ai_training": "not_allowed"
    }
  }'

Parameters

NameInTypeRequiredDescription
file_hashbodystringYesSHA-256 hex (64 characters)
intentbodystringNoAuthorship, Approval, Witness, Receipt, Agreement. Default: Authorship
ai_generationbodystringNonone, assisted, generated
content_rightsbodyobjectNoLicense, AI training policy, etc.
commentbodystringNoPublic Note shown to verifiers. Max 280 characters
supersedesbodystringNoHex action hash of a previous signature this one amends
perceptual_hashbodyobjectNoPre-computed perceptual hash

Authentication

  • JWT session token (from flowsta.com login)
  • OAuth access token with sign scope (from third-party apps)

Response

json
{
  "success": true,
  "file_hash": "a7f3b9c1...",
  "agent_pub_key": "uhCAkynvT4p77x...",
  "signed_at": 1680000000000,
  "action_hash": "842924c550353c6a..."
}

Over quota:

json
{
  "error": "quota_exceeded",
  "message": "You have used all 2 of your monthly signs on the free tier.",
  "scope": "community",
  "tier": "free",
  "used": 2,
  "limit": 2,
  "resets_at": "2026-08-01T00:00:00.000Z",
  "upgrade_tier": "premium"
}

POST /api/v1/sign-it/sign-batch

Deprecated - no batch path

Same as POST /sign. There is no batch path through the Vault - it approves one document at a time by design. Users batch-sign in the Vault's own Sign It page; apps call signFile() per file.

Sign multiple file hashes in one request. Maximum 100 files per batch. Quota-gated: the batch must fit within the remaining quota or the whole request returns 402 quota_exceeded.

Parameters

NameInTypeRequiredDescription
filesbodyarrayYesArray of { file_hash, intent?, ai_generation?, content_rights?, comment? }
shared_metadatabodyobjectNoApplied to all files unless overridden per-file. May include a shared comment (max 280 characters)

Authentication

Same as /sign.

Response

json
{
  "results": [
    { "file_hash": "a7f3...", "action_hash": "8429...", "success": true },
    { "file_hash": "b8c4...", "action_hash": null, "success": false, "error": "..." }
  ],
  "signed": 1,
  "failed": 1
}

GET /api/v1/sign-it/my-signatures

Get all signatures created by the authenticated user and their linked agents.

Authentication

  • JWT session token, or
  • OAuth access token with verify scope

Response

json
{
  "signatures": [
    {
      "file_hash": "a7f3...",
      "signed_at": 1680000000000,
      "intent": "Authorship",
      "action_hash": "8429...",
      "revoked": false,
      "revoked_at": null,
      "revoked_reason": null
    }
  ]
}

POST /api/v1/sign-it/revoke

Revoke a signature. Only the original signer can revoke.

Parameters

NameInTypeRequiredDescription
action_hashbodystringYesHex-encoded action hash of the signature
reasonbodystringNoRevocation reason (max 280 characters)

Authentication

JWT session token or OAuth access token with sign scope.

Response

json
{
  "success": true
}

POST /api/v1/sign-it/fingerprint

Upload a file so the server computes its perceptual hash with the same algorithm Vault uses - guaranteed matching hashes. Useful when your web app wants to pre-compute a perceptual hash before calling /sign with a perceptual_hash field. The file is immediately discarded after fingerprinting.

Supports images (PNG, JPEG, BMP, TIFF, GIF), audio (MP3, WAV, FLAC, OGG), and video (MP4, MKV, AVI, WebM). Unsupported file types return { "fingerprint": null }.

bash
curl -X POST "https://auth-api.flowsta.com/api/v1/sign-it/fingerprint" \
  -H "Authorization: Bearer <jwt_or_oauth_token>" \
  --data-binary @photo.jpg

Parameters

NameInTypeRequiredDescription
bodybodybinaryYesRaw file bytes. Max 50 MB

Authentication

JWT session token or OAuth access token with sign scope.

Response

json
{
  "fingerprint": {
    "hash_value": [/* bytes */],
    "bands": [[1,2],[3,4],[5,6],[7,8]]
  }
}

fingerprint is null if the file type is unsupported. The fingerprinter has a 60-second timeout.


POST /api/v1/sign-it/set-thumbnail

Attach a thumbnail to a previously signed entry on the network. Only the original signer can set the thumbnail.

bash
curl -X POST "https://auth-api.flowsta.com/api/v1/sign-it/set-thumbnail" \
  -H "Authorization: Bearer <jwt_or_oauth_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "action_hash": "842924c550353c6a...",
    "thumbnail": "data:image/webp;base64,..."
  }'

Parameters

NameInTypeRequiredDescription
action_hashbodystringYesHex-encoded action hash of the signature
thumbnailbodystringYesdata:image/... URI, max 15 KB

Authentication

JWT session token or OAuth access token with sign scope. Must be the original signer.

Response

json
{
  "success": true
}

POST /api/v1/sign-it/contact

Send a message to a signer via blind email relay. The signer's email is never exposed.

Parameters

NameInTypeRequiredDescription
signer_agent_keybodystringYesAgent key of the signer
file_hashbodystringYesFile hash the contact is about
sender_namebodystringYesYour name (max 100 characters)
sender_emailbodystringYesYour email (max 254 characters)
messagebodystringYesYour message (max 500 characters)
purposebodystringNolicensing, ai_training, collaboration, other

Rate Limit

3 requests/hour per IP.

Response

Always returns success to prevent user enumeration:

json
{
  "success": true,
  "message": "Your message has been sent to the signer."
}

Documentation licensed under CC BY-SA 4.0.