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.
| Package | Use Case | How It Signs |
|---|---|---|
@flowsta/auth | Web apps using OAuth | Detects the user's local Vault and signs through it (user approves each signature) |
@flowsta/holochain | Desktop Holochain apps | Direct IPC call to Flowsta Vault - Vault signs locally, user approves |
@flowsta/auth - Web App Signing
For web apps that use "Sign in with Flowsta" (OAuth). Requires the sign scope.
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 the signature in a Vault dialog - your
clientIdis included, so sponsored signing applies and the dialog shows whose quota pays. The key never leaves their device. - 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.
const result = await flowsta.signFile({
fileHash: hash,
intent: 'Authorship',
aiGeneration: 'none',
contentRights: {
license: 'cc-by',
aiTraining: 'not_allowed',
contactPreference: 'allow_contact_requests',
},
});
console.log('Signed:', result.action_hash);| 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 | object | No | See Content Rights for all fields |
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 | Unix timestamp in milliseconds |
action_hash | string | null | DHT action hash (hex) |
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 |
FlowstaAuthError | timeout | The user didn't respond to the Vault dialog in time (~60s) |
Requires: sign scope
Signatures made via the Vault publish to Flowsta's tamper-proof network, built on Holochain, over the next few minutes via gossip - a just-signed file may briefly return no verification results.
signBatch(options)
One approval per signature
The Vault approves one document at a time, so signBatch() throws FlowstaAuthError with code batch_requires_individual_approval. Call signFile() per file instead so the user can approve each signature.
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 and unlimited on every plan.
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 signers |
rights | array | Array of { signer, license?, aiTraining?, commercialLicensing?, contactPreference?, aiGeneration? }. 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.
signDocument(options)
Request Vault to sign a file hash. Shows an approval dialog in Vault with your app name, the file label, the metadata, and whose signing quota pays (sponsored signing).
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',
},
});| Parameter | Type | Required | Description |
|---|---|---|---|
clientId | string | Yes | Your app's client ID from dev.flowsta.com |
appName | string | Yes | Shown in Vault approval dialog |
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 |
ipcUrl | string | No | Default: http://127.0.0.1:27777 |
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 | DHT action hash |
Errors:
| Error Class | When |
|---|---|
VaultNotFoundError | Vault is not running |
VaultLockedError | Vault is locked |
UserDeniedError | User rejected the signing request |
SigningDnaNotInstalledError | Signing DNA not available in Vault |
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
Used by both @flowsta/auth and @flowsta/holochain:
| 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.