SDK Reference
Two SDK packages provide Sign It functionality. Both sign through the user's Flowsta Vault: the user approves each signature in a Vault dialog and the keys never leave their device. They differ in whether the signature reaches the network.
| Package | Use Case | What You Get |
|---|---|---|
@flowsta/auth (2.6.0) | Web apps using OAuth | Detects the user's local Vault and signs through it. The signature is returned to your app and is not published - nobody else can verify the file by it |
@flowsta/holochain (3.6.0) | Desktop Holochain apps | Direct IPC call to Flowsta Vault. A linked app publishes the signature to the Sign It network from the person's device by default; anyone can then verify the file |
@flowsta/auth - Web App Signing
For web apps that use "Sign in with Flowsta" (OAuth). Request the sign scope at login so the consent screen covers signing; the Vault path itself only needs a signed-in session.
hashFile(file)
Hash a file using SHA-256 in the browser via SubtleCrypto. The file is never uploaded.
import { hashFile } from '@flowsta/auth';
const file = document.querySelector('input[type=file]').files[0];
const hash = await hashFile(file);
// hash = "a7f3b9c1e2d4..." (64 hex characters)| Parameter | Type | Required | Description |
|---|---|---|---|
file | File | Yes | File object from file input or drag-and-drop |
Returns: Promise<string> - hex-encoded SHA-256 hash (64 characters)
signFile(options)
Request a signature for a file hash. The file is never uploaded - only the hash leaves your app.
signFile() probes for a running Flowsta Vault on every call:
- Vault running (the normal case): signs via the Vault. The user approves in a Vault dialog that names your page by its origin and shows the first characters of the hash; the key never leaves their device. The Vault returns the signature to your app without publishing it - a web app cannot ask the Vault to publish, so
action_hashisnull, the file does not show as signed on the verify page, and no one else can verify it by this signature. People publish from their own Vault's Sign It page. - No Vault running: throws
VaultRequiredError- signing keys live only in the user's Vault, so there's nothing else that can sign. Prompt the user to open or install Flowsta Vault. When the browser blocked the page from reaching the Vault, throwsVaultBlockedErrorinstead (2.5.0).
const result = await flowsta.signFile({
fileHash: hash,
intent: 'Authorship',
aiGeneration: 'none',
contentRights: {
license: 'cc-by',
aiTraining: 'not_allowed',
contactPreference: 'allow_contact_requests',
},
});
console.log('Approved by:', result.agent_pub_key, 'at', result.signed_at);| Parameter | Type | Required | Description |
|---|---|---|---|
fileHash | string | Yes | SHA-256 hex string (64 characters) |
intent | string | No | Authorship, Approval, Witness, Receipt, Agreement. Default: Authorship |
aiGeneration | string | No | none, assisted, generated |
contentRights | Record<string, string> | No | See Content Rights Object. camelCase keys are translated on the wire (2.6.0); snake_case keys are accepted as-is |
Returns:
| Field | Type | Description |
|---|---|---|
success | boolean | true on success |
file_hash | string | The hash that was signed |
agent_pub_key | string | Signer's Holochain agent key (uhCAk... format) |
signed_at | number | string | ISO 8601 string via the Vault; Unix milliseconds via the legacy API path |
action_hash | string | null | null via the Vault - the signature is not published. Set only by the legacy API path |
Errors:
| Error Class | Code | When |
|---|---|---|
UserDeniedError | user_denied | The user declined the request in the Vault dialog |
VaultRequiredError | vault_required | No Vault is reachable - nothing else holds the user's signing keys. Also thrown (with a specific message) when the Vault is locked |
VaultBlockedError (2.5.0) | vault_blocked | The browser refused the loopback request (Chrome 142+ local network access permission, Brave's localhost block). The Vault may be running - point the user at the browser's site settings |
VaultIdentityMismatchError (2.4.0) | vault_identity_mismatch | The local Vault holds a different identity than the signed-in user - refused before any signature; tell the user to switch Vaults, never fall back to remote signing |
FlowstaAuthError | timeout | The user didn't respond to the Vault dialog in time (~60 s) |
FlowstaAuthError | invalid_file_hash, reserved_prefix, identity_mismatch, conductor_not_ready | Passed through from the Vault: malformed hash, hash bytes beginning with flowsta-, the Vault refused under a different identity, the Vault is still starting |
Requires: a signed-in session; request the sign scope at login
signBatch(options)
One approval per signature
The Vault approves one document at a time, so when a Vault is detected signBatch() throws FlowstaAuthError with code batch_requires_individual_approval. Call signFile() per file instead so the user can approve each signature. Without a Vault the call goes to the deprecated REST POST /sign-batch, which only works for legacy server-hosted identities.
const result = await flowsta.signBatch({
files: [
{ fileHash: hash1 },
{ fileHash: hash2, intent: 'Approval' },
],
sharedMetadata: {
intent: 'Authorship',
aiGeneration: 'none',
contentRights: { license: 'all-rights-reserved' },
},
});
console.log(`Signed ${result.signed}, failed ${result.failed}`);| Parameter | Type | Required | Description |
|---|---|---|---|
files | array | Yes | Array of { fileHash, intent?, aiGeneration?, contentRights? }. Max 100 files |
sharedMetadata | object | No | Applied to all files unless overridden per-file |
Returns:
| Field | Type | Description |
|---|---|---|
results | array | Per-file results: { file_hash, action_hash, success, error? } |
signed | number | Count of successfully signed files |
failed | number | Count of failed files |
Requires: sign scope
verifyFile(fileHash)
Check if a file has been signed. Public endpoint - no authentication required. Verification is free on every plan (30 requests per minute per IP).
const result = await flowsta.verifyFile(hash);
if (result.count > 0) {
result.signatures.forEach(sig => {
console.log(`Signed by ${sig.signer} on ${new Date(sig.signed_at)}`);
console.log(`License: ${sig.content_rights?.license}`);
});
}| Parameter | Type | Required | Description |
|---|---|---|---|
fileHash | string | Yes | SHA-256 hex string (64 characters) |
Returns:
| Field | Type | Description |
|---|---|---|
signatures | array | Array of signature objects (see Verification API) |
count | number | Total number of signatures |
getContentRights(fileHash)
Convenience wrapper over verifyFile() that returns just the content rights info from non-revoked signatures.
const rights = await flowsta.getContentRights(hash);
if (rights.signed) {
rights.rights.forEach(r => {
console.log(`${r.signer}: ${r.license}, AI training: ${r.aiTraining}`);
});
}Returns:
| Field | Type | Description |
|---|---|---|
signed | boolean | Whether the file has any signatures |
signerCount | number | Total number of signatures, revoked ones included |
rights | array | One entry per non-revoked signature that declared rights: { signer, license?, aiTraining?, commercialLicensing?, contactPreference?, aiGeneration? }. signer is the DID when known, else the agent key. Values are the network's enum forms (e.g. CCBY, NotAllowed) |
@flowsta/holochain - Desktop App Signing
For Holochain apps that run alongside Flowsta Vault. Signs via IPC - the user approves in Vault. An app that is linked to the person's identity (linkFlowstaIdentity, see the Holochain SDK) publishes to the Sign It network from their device.
signDocument(options)
Request Vault to sign a file hash. Shows an approval dialog in Vault with your app name, the file label and the first characters of the hash; when the signature will be published, also whose signing quota pays and a note that it publishes from this device. The dialog does not show the license, AI disclosure or intent you supplied.
import { signDocument } from '@flowsta/holochain';
const result = await signDocument({
clientId: 'flowsta_app_abc123...',
appName: 'ArtStudio',
fileHash: 'a7f3b9c1e2d4...',
label: 'Illustration.png',
intent: 'authorship',
contentRights: {
license: 'cc-by',
aiTraining: 'not_allowed',
contactPreference: 'allow_contact_requests',
},
});
if (result.published) {
console.log('On the network:', result.actionHash);
} else {
console.log('Signature returned to the app only:', result.signature);
}| Parameter | Type | Required | Description |
|---|---|---|---|
clientId | string | Yes | Your app's client ID from dev.flowsta.com |
appName | string | Yes | Shown in Vault approval dialog (for a linked app, the Vault shows the name approved at link time instead) |
fileHash | string | Yes | SHA-256 hex string (64 characters) |
label | string | No | Human-readable file name for the dialog |
intent | string | No | authorship, approval, witness, receipt, agreement (Vault normalizes to the network's capitalized form, e.g. Authorship) |
aiGeneration | string | No | none, assisted, generated |
contentRights | object | No | See Content Rights Object |
publish (3.6.0) | boolean | No | Publish the signature to the Sign It network from the person's device. Default: true when this app is bound to an identity (it linked through linkFlowstaIdentity), false otherwise. Only linked apps may publish (PublishForbiddenError); every publish draws on a signing quota (QuotaExceededError) |
ipcUrl | string | No | Default: sweeps 127.0.0.1:27777-27779 for the Vault (SDK v3 / auth 2.4) |
Returns:
| Field | Type | Description |
|---|---|---|
success | boolean | true if signed |
fileHash | string | The hash that was signed |
signature | string | Base64 Ed25519 signature |
agentPubKey | string | Signer's agent key (uhCAk... format) |
signedAt | string | ISO 8601 timestamp |
actionHash | string | null | Hex action hash of the published record; null when the signature was not published |
published (3.6.0) | boolean | Whether the signature was published to the Sign It network |
Errors:
| Error Class | Code | When |
|---|---|---|
VaultNotFoundError | vault_not_found | Vault is not running |
VaultBlockedError (3.1.0) | vault_blocked | The browser refused the loopback request - the Vault may be running |
VaultLockedError | vault_locked | Vault is locked |
UserDeniedError | user_denied | User rejected the signing request |
PublishForbiddenError (3.6.0) | tier_forbidden | Publishing was asked for, but this app is not linked - only Flowsta pages and linked apps may publish. Link the app or sign with publish: false |
QuotaExceededError (3.6.0) | quota_exceeded | The signing quota for this period is used up (the person's plan, and the sponsor pool when your app sponsors). description says which |
IdentityMismatchError (v3) | identity_mismatch | The Vault's active identity differs from the one this app is bound to - stop and tell the user |
FlowstaHolochainError | timeout | The user didn't respond to the Vault dialog in time (~60 s) |
FlowstaHolochainError | invalid_file_hash, reserved_prefix, conductor_not_ready, commit_failed | Malformed hash; hash bytes beginning with flowsta-; the Vault is still starting; the publish could not be committed |
SigningDnaNotInstalledError is deprecated: the Vault never emits its code, so it was never thrown. It is removed in 4.0.
getSigningStatus(ipcUrl?)
Check if Vault is available for document signing.
import { getSigningStatus } from '@flowsta/holochain';
const status = await getSigningStatus();
if (status.available) {
// Show "Sign with Flowsta" button
}Returns:
| Field | Type | Description |
|---|---|---|
available | boolean | true if Vault is running, unlocked, and ready to sign |
vaultRunning | boolean | true if Vault IPC server is reachable |
vaultUnlocked | boolean | true if Vault is unlocked |
Content Rights Object
Both @flowsta/auth (2.6.0+) and @flowsta/holochain accept these keys and translate them to the snake_case the Vault and the API read:
| Field | Type | Values |
|---|---|---|
license | string | all-rights-reserved, cc0, cc-by, cc-by-sa, cc-by-nc, cc-by-nc-sa, mit, apache-2, gpl-3 |
commercialLicensing | string | not_available, open_to_licensing |
aiTraining | string | allowed, allowed_with_attribution, requires_license, not_allowed |
contactPreference | string | no_contact, allow_contact_requests |
All fields are optional. Only declared fields are recorded in the signature. On the network (and in verification responses) these are stored in their enum forms - e.g. cc-by → CCBY, not_allowed → NotAllowed; the full mapping is on the Content Rights page. Send only the values listed: unknown strings are passed through unchanged, and the network's signing cell only accepts these values, so a publish with one fails.