Quickstart
Workspace to first commission
1. Create a workspace
A workspace is one isolated tenant. An operator creates it from the command line with a database role that is not the runtime role. The command prints the new workspace record and its first service key. The key appears once and never again.
npm run workspace:create -- \ --workspace acme \ --name "Acme" \ --key-label "acme-runtime"
2. Create a program
A program is one partner offering inside the workspace. It takes an id, a name and a config object.
POST /v1/invoke
Authorization: Bearer trk_...
{
"capability": "ticket4:createProgram",
"program": "partner-2027",
"payload": {
"id": "partner-2027",
"name": "Acme partner program",
"config": {}
}
}3. Draft and activate a rulebook
A rulebook version holds the commission terms of one program from a stated instant. An agent drafts it. A person activates it.
{
"capability": "ticket6:draftRulebookVersion",
"program": "partner-2027",
"payload": {
"version": "v1",
"effectiveFrom": "2027-01-01T00:00:00.000Z",
"maturityDays": 30,
"approvalMode": "manual",
"recurringDays": "lifetime",
"rounding": "half-up",
"currency": "USD",
"rules": [{ "id": "rate", "kind": "rate", "rateBps": 3000 }]
}
}The draft does nothing until a person activates that exact version. Activation is immutable, and the effective instant must be later than every payment the program has already ledgered.
{
"capability": "ticket6:activateRulebookVersion",
"program": "partner-2027",
"payload": {
"version": "v1",
"activatedAt": "2027-01-01T00:00:00.000Z"
}
}4. Invite a partner
An invitation creates a pending partner relationship bound to one workspace, one program and one creator record. The relationship id is derived from the program and the creator record.
{
"capability": "ticket4:invitePartner",
"program": "partner-2027",
"payload": {
"programId": "partner-2027",
"creatorRecordId": "creator-1",
"partnerActorIdentity": "partner-1"
}
}Then call ticket4:completeOnboarding with the relationship id, and the contact and payout details you hold. The partner accepts the terms through ticket4:acceptStandardTerms, which is a human gate.
5. Issue a referral link
A referral link belongs to one partner relationship and one product. The call takes the product id and reads the rest from the request scope.
{
"capability": "ticket5:issueReferralLink",
"program": "partner-2027",
"relationship": "relationship:partner-2027:creator-1",
"payload": { "productId": "prod_acme_pro" }
}Use ticket5:issueCoupon for a coupon code instead, and ticket15:publishReferralLink to publish a link that resolves and records clicks.
6. Take the first commission
The runtime polls Stripe events and ingests each collected payment. Parka resolves the partner from the subscription referral code, applies the rulebook and writes one commission entry. An event it cannot resolve is held, and nothing is posted.
ticket6:queryPendingAccrualslists what has accrued and not yet matured.ticket6:readLedgerreturns the entries, the holds and the balance.ticket7:explainEntriesreturns the payment and the terms behind each line.
{
"capability": "ticket6:approveCommission",
"program": "partner-2027",
"relationship": "relationship:partner-2027:creator-1",
"payload": {
"ledgerId": "commission:evt_1",
"approvedAt": "2027-02-01T00:00:00.000Z"
}
}7. Pay and record the receipt
Parka never moves money. You pay the partner with your own rails. Then a person records the receipt, and the ledger shows the entry as paid.
{
"capability": "ticket7:recordPayoutReceipt",
"program": "partner-2027",
"relationship": "relationship:partner-2027:creator-1",
"payload": {
"receiptId": "receipt-1",
"commissionId": "commission:evt_1",
"amountMinor": 3000,
"currency": "USD",
"externalReference": "bank-transfer-8841",
"receivedAt": "2027-02-02T00:00:00.000Z",
"operatorEvidence": "paid from the Acme account"
}
}