Skip to Content
APIVesting API

Vesting API

Manage token vesting contracts.

Get Vesting Status

GET /vesting/:contractId

Retrieve detailed vesting contract information.

Response

{ "contractId": "VEST123...", "launchId": "launch_abc123", "beneficiary": "8vP2...", "mint": "ABC123...", "total": 1000000000, "claimed": 250000000, "unlocked": 500000000, "claimable": 250000000, "locked": 500000000, "schedule": { "start": 1700000000, "cliff": 1710000000, "end": 1735000000, "cliffMonths": 6, "durationMonths": 24 }, "status": "active", "createdAt": "2026-02-15T12:00:00Z" }

schedule.cliff (and the underlying Streamflow stream.cliff) is an absolute unix timestamp — the moment the cliff ends — not an offset from start. unlocked / claimable are computed against that absolute value: they are 0 before the cliff and grow linearly from the cliff to end.

Status Values

StatusDescription
activeVesting in progress
cliffIn cliff period
completedAll tokens vested
cancelledContract cancelled

Claim Tokens

POST /vesting/:contractId/claim

Claim unlocked but unclaimed tokens.

Ownership. The claim is signed by Etch; the unlocked tokens are always paid to the stream recipient (the launch beneficiary), never to the caller. The authenticated wallet must be that recipient (or the wallet that created the launch), otherwise the API returns 403 { "error": "not_owner" }.

Request Body

{ "amount": "all" // Or specific amount in base units }

Response

{ "signature": "5abc...", "claimed": 250000000, "newClaimable": 0, "newTotalClaimed": 500000000 }

Build a Claim Transaction (non-custodial)

POST /vesting/:contractId/tx

Preferred over the custodial claim above. Etch builds an unsigned Streamflow withdraw with the stream recipient as authority and fee payer; the recipient’s wallet signs it and the client sends it. See the Transactions API for the response object and signing flow.

The authenticated wallet must be the stream’s recipient (the address the stream pays out to) — otherwise the API returns 403 { "error": "not_recipient" }. If the recipient’s token account does not exist yet, its creation is included in the same transaction (paid by the recipient).

Request Body

{ "action": "claim", "amount": 12.5 }

amount is in whole tokens (9 decimals). Omit it to claim everything currently claimable.

Response

{ "transaction": "<base64 legacy Transaction, unsigned>", "version": "legacy", "feePayer": "<recipient wallet>", "blockhash": "…", "lastValidBlockHeight": 123, "signers": ["<recipient wallet>"], "summary": "Claim all claimable vested tokens from VEST…1234" }

Errors

StatuserrorMeaning
401unauthorizedMissing / invalid JWT
400invalid_requestBody failed validation
403not_recipientWallet is not the stream recipient
404vesting_not_foundStream could not be read
501unsupportedThe Streamflow SDK cannot build an unsigned withdraw — fall back to the custodial POST /vesting/:contractId
500build_failedRPC / SDK error while building (message)

List Vesting Contracts

GET /launches/:launchId/vesting

Get all vesting contracts for a launch.

Response

{ "contracts": [ { "contractId": "VEST123...", "beneficiary": "8vP2...", "total": 1000000000, "status": "active" } ] }

Create Manual Vesting (Admin)

POST /admin/vesting

Create a vesting contract manually (requires admin auth).

Request Body

{ "mint": "ABC123...", "beneficiary": "8vP2...", "amount": 1000000000, "cliffMonths": 6, "durationMonths": 24 }

Response

{ "contractId": "VEST456...", "transaction": "5def...", "status": "created" }

Cancel Vesting (Admin)

POST /vesting/:contractId/cancel

Cancel a vesting contract and return remaining tokens (requires admin auth).

Response

{ "signature": "5ghi...", "returned": 500000000, "status": "cancelled" }

Webhook Events

Vesting emits these webhook events:

EventDescription
vesting.createdContract created
vesting.cliff_reachedCliff period ended
vesting.claimedTokens claimed
vesting.completedAll tokens vested
vesting.cancelledContract cancelled

Examples

Check and Claim

async function checkAndClaim(authToken: string, contractId: string) { // Check status const status = await fetch( `https://etch.film.fun/api/vesting/${contractId}`, { headers: { Authorization: `Bearer ${authToken}` } } ).then(r => r.json()); console.log(`Claimable: ${status.claimable}`); if (status.claimable > 0) { // Claim tokens const claim = await fetch( `https://etch.film.fun/api/vesting/${contractId}/claim`, { method: "POST", headers: { Authorization: `Bearer ${authToken}`, "Content-Type": "application/json" }, body: JSON.stringify({ amount: "all" }) } ).then(r => r.json()); console.log(`Claimed! Tx: ${claim.signature}`); return claim; } return null; }

Error Codes

CodeDescription
vesting_not_foundContract doesn’t exist
nothing_to_claimNo tokens available
vesting_completeAlready fully claimed
unauthorized_claimWrong beneficiary
contract_cancelledContract was cancelled
Last updated on