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.
curl "https://auth-api.flowsta.com/api/v1/sign-it/verify?hash=a7f3b9c1e2d4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0"Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
hash | query | string | Yes | SHA-256 hex string (exactly 64 characters) |
Response
{
"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;nullfor signers who haven't granted those scopes yet. Handle nulls gracefully.comment- the signer's Public Note (up to 280 characters), ornull.supersedes/superseded_by- hex action hashes linking amended signature chains:supersedespoints to the signature this one replaces;superseded_byis 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;nullon 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).
curl -X POST "https://auth-api.flowsta.com/api/v1/sign-it/verify-file" \
--data-binary @photo.jpgParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| body | body | binary | Yes | Raw 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.
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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
bands | body | number[][] | Yes | LSH band byte arrays |
hash_value | body | number[] | No | Full perceptual hash for Hamming distance |
pixels | body | number[] | No | 72 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.
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
{
"allowed": true,
"scope": "community",
"tier": "premium",
"used": 12,
"limit": 100,
"resets_at": "2026-08-01T00:00:00.000Z"
}| Field | Type | Description |
|---|---|---|
allowed | boolean | Whether another sign would be accepted |
scope | string | community (personal quota) or org (developer pool, when authenticated via OAuth) |
tier | string | Plan name (free, premium, premium_plus - or org tier) |
used | number | Signs used this period |
limit | number | Included signs per period |
resets_at | string | When the period rolls over |
upgrade_tier | string | Present when blocked - the suggested next tier |
overage_signs, overage_cents_per_100 | number | Org 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.
curl "https://auth-api.flowsta.com/api/v1/sign-it/quota/by-agent?agent_pub_key=uhCAk...&client_id=your_client_id"Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
agent_pub_key | query | string | Yes | The signer's agent key (uhCAk... or plain base64) |
client_id | query | string | No | Your 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 -
{
"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.
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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
file_hash | body | string | Yes | SHA-256 hex (64 characters) |
intent | body | string | No | Authorship, Approval, Witness, Receipt, Agreement. Default: Authorship |
ai_generation | body | string | No | none, assisted, generated |
content_rights | body | object | No | License, AI training policy, etc. |
comment | body | string | No | Public Note shown to verifiers. Max 280 characters |
supersedes | body | string | No | Hex action hash of a previous signature this one amends |
perceptual_hash | body | object | No | Pre-computed perceptual hash |
Authentication
- JWT session token (from flowsta.com login)
- OAuth access token with
signscope (from third-party apps)
Response
{
"success": true,
"file_hash": "a7f3b9c1...",
"agent_pub_key": "uhCAkynvT4p77x...",
"signed_at": 1680000000000,
"action_hash": "842924c550353c6a..."
}Over quota:
{
"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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
files | body | array | Yes | Array of { file_hash, intent?, ai_generation?, content_rights?, comment? } |
shared_metadata | body | object | No | Applied to all files unless overridden per-file. May include a shared comment (max 280 characters) |
Authentication
Same as /sign.
Response
{
"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
verifyscope
Response
{
"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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
action_hash | body | string | Yes | Hex-encoded action hash of the signature |
reason | body | string | No | Revocation reason (max 280 characters) |
Authentication
JWT session token or OAuth access token with sign scope.
Response
{
"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 }.
curl -X POST "https://auth-api.flowsta.com/api/v1/sign-it/fingerprint" \
-H "Authorization: Bearer <jwt_or_oauth_token>" \
--data-binary @photo.jpgParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| body | body | binary | Yes | Raw file bytes. Max 50 MB |
Authentication
JWT session token or OAuth access token with sign scope.
Response
{
"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.
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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
action_hash | body | string | Yes | Hex-encoded action hash of the signature |
thumbnail | body | string | Yes | data:image/... URI, max 15 KB |
Authentication
JWT session token or OAuth access token with sign scope. Must be the original signer.
Response
{
"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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
signer_agent_key | body | string | Yes | Agent key of the signer |
file_hash | body | string | Yes | File hash the contact is about |
sender_name | body | string | Yes | Your name (max 100 characters) |
sender_email | body | string | Yes | Your email (max 254 characters) |
message | body | string | Yes | Your message (max 500 characters) |
purpose | body | string | No | licensing, ai_training, collaboration, other |
Rate Limit
3 requests/hour per IP.
Response
Always returns success to prevent user enumeration:
{
"success": true,
"message": "Your message has been sent to the signer."
}