API Reference
Invitations & Affiliations

Invitations & Affiliations API

An invite link is a private key; the AE behind it sits in the AffiliationTreasury contract until someone redeems it. This API is the indexed view: which invites an address has issued and which have been claimed. Creating or revoking an invite is a contract call, not a REST call.

All paths are relative to https://api.superhero.com. Reads need no authentication.

Endpoints

Method & pathQueryReturns
GET /api/invitationsorder_by, order_direction, inviter, limit, pagePaginated invitation rows
GET /api/affiliations/invites/{code}NoneOne invite-code pool, by its public code
POST /api/affiliationsNoneCreate a pool of invite codes
POST /api/affiliations/invites/{code}/{provider}/{access_code}NoneExchange a GitHub, Google or X access code for one private code
GET /api/affiliations/previewNoneAn HTML dashboard, not JSON
GET /api/bcl-affiliation/analyticsstart_date, end_dateReferral programme metrics
GET /api/bcl-affiliation/analytics/top-invitersstart_date, end_date, limitLeaderboard of inviters
GET /api/bcl-affiliation/analytics/x-verificationstart_date, end_dateX-verification funnel metrics
GET /api/bcl-affiliation/analytics/x-invitestart_date, end_dateX-invite usage metrics
GET /api/bcl-affiliation/treeNoneThe whole referral graph, one node per inviter

Each /api/bcl-affiliation/* endpoint has a /preview sibling that returns a server-rendered HTML dashboard, not JSON.

Account-level invite counters live on the accounts endpoint: GET /api/accounts?order_by=total_invitation_count also accepts total_claimed_invitation_count and total_revoked_invitation_count. See Accounts.

GET /api/invitations

ParameterNotes
inviterFilter to invitations sent by this ak_.... A malformed address is a 400
order_byamount (default) or created_at. Anything else is a 400
order_directionASC or DESC, default DESC
limit, pagelimit defaults to 100 and must be 1–100
curl -s "https://api.superhero.com/api/invitations?inviter=ak_YOUR_ADDRESS&limit=100&page=1"
FieldTypeMeaning
idstringRow id
tx_hashstringth_... of the register_invitation_code call that created it
block_heightnumberHeight of that call
amountstringThe gift behind the invite, in AE as a decimal string. Do not divide by 1e18
sender_addressstringThe inviter's ak_.... This is the field inviter filters on
invitee_addressstring or nullThe wallet that redeemed it
receiver_addressstringThe ephemeral invitee keypair address encoded in the link. Unique
statusstringpending, claimed or revoked
claim_tx_hashstring or nullth_... of the claiming transaction
revoke_tx_hashstring or nullth_... of the revoking transaction
status_updated_atstring or nullWhen status last changed
created_atstringISO 8601
senderobject or nullThe inviter's full account row, joined in
inviteeobject or nullThe redeemer's full account row, joined in
claimedbooleanstatus === 'claimed'
claimer_addressstring or nullAlias of invitee_address
claimed_atstring or nullstatus_updated_at when claimed, else null

receiver_address is the throwaway keypair in the link; invitee_address is the wallet that redeemed it. An unclaimed invite has a receiver_address and no invitee_address. Each item carries its claim status alongside the creation call's tx_hash, block_height and amount, so one call covers an inviter's invites without a per-invitee middleware lookup.

/api/affiliations/*: the OAuth invite-code pool

A separate mechanism from /api/invitations, unrelated to the on-chain treasury: a pool of pre-shared invite codes handed out one per social account, gated by an OAuth login. An Affiliation holds a public code and an account_address; each of its codes holds one private_code and the claimed_by identity that took it.

POST /api/affiliations

Takes no credential.

curl -s -X POST "https://api.superhero.com/api/affiliations" \
  -H "Content-Type: application/json" \
  -d '{"sender_address":"ak_YOUR_ADDRESS","codes":["code1","code2","code3"]}'

Stores codes as the pool's private codes, generates a random ten-character public code from [a-z0-9], and returns it:

{ "code": "k3f9xq2m1p" }

sender_address is stored as the pool's account_address.

GET /api/affiliations/invites/{code}

{code} is the ten-character public code that POST /api/affiliations returned. It is validated against /^[a-z0-9]{1,64}$/i, so anything with punctuation or a ct_/ak_ prefix is a 400.

Returns the Affiliation row (id, account_address, code, created_at) or null (a 200 with a null body, not a 404) when no such pool exists. The child codes are not included.

POST /api/affiliations/invites/{code}/{provider}/{access_code}

provider is github, google or x (anything else is a 400; the segment is validated against /^[a-z][a-z0-9_-]{0,31}$/i). access_code is an OAuth authorization code or access token, which the server exchanges with that provider for a stable user id.

The server enforces one code per social identity: if provider@userId has already claimed a code, the call is a 409 User already claimed a code. Otherwise it picks one of the pool's unclaimed codes at random, marks it claimed_by, and returns:

{
  "affiliation": { "code": "k3f9xq2m1p", "available_codes": 12 },
  "invitationCode": { "private_code": "code2" }
}

404 Affiliation not found when the pool does not exist; 409 No available codes for this affiliation when it is exhausted.

The on-chain treasury

Invites are created and revoked on chain. The AffiliationTreasury contract is reached through the community factory, and its address is also affiliation_address on GET /api/factory:

const factory = await initCommunityFactory(sdk, factorySchema.address);
const treasury = await factory.affiliationTreasury();
EntrypointWhat it does
register_invitation_code(addresses, redemption_fee, amount)Funds a batch of invite keypairs. Payable: send (amount + redemption_fee) * count
revoke_invitation_code(address)Reclaims an unused invite
get_invitation_code(address)Returns [sender, amount, claimed] for one invite

To generate invites: create count throwaway keypairs, call register_invitation_code with their addresses, and build each link as the keypair's secret key in a URL fragment:

https://superhero.com#invite_code=<secret key>

Redemption gas is covered by a fixed 0.001 AE per invite (10n ** 15n aettos) on top of the gift amount, so the total sent is (amount + 0.001) * count plus the call's own gas. Revoking takes the invite's address, not its secret key.

The creation side can also be read from the middleware:

GET {middlewareUrl}/v3/transactions?contract=ct_2GG42rs2FDPTXuUCWHMn98bu5Ab6mgNxY7KdGAKUNsrLqutNxZ&caller_id=ak_...

Filter to tx.function === 'register_invitation_code', read the invitee addresses from argument 0 and the per-invite amount from argument 2 (in aettos; divide by 1e18), and follow response.next to page. Use it only for an invite the indexer has not caught; GET /api/invitations?inviter=... answers the same question in one call.

Referral analytics

The /api/bcl-affiliation/* family aggregates the same invitations table. start_date and end_date must be exactly YYYY-MM-DD; any other format is a 400.

GET /api/bcl-affiliation/analytics

Returns series, summary, xVerification and a queryMs timing. series is one point per day, zero-filled across the range:

date · registered · redeemed · revoked · amount_ae_registered

summary carries total_registered, total_redeemed, total_revoked, total_outstanding, unique_inviters, unique_invitees, unique_redeemers, total_amount_ae_registered, avg_amount_ae_per_registered, avg_amount_ae_per_inviter, redeemed_rate and revoked_rate. The two rates are fractions of total_registered. All amounts are in AE.

xVerification nests its own series (date, daily_new_verified, cumulative_verified) and summary (total_verified_users), counted from link calls against the AddressLink contract.

GET /api/bcl-affiliation/analytics/top-inviters

{ items, queryMs }, limit default 10. Each item:

inviter · registered_count · redeemed_count · revoked_count · pending_count · total_amount_ae

Ordered by registered_count descending, then total_amount_ae. All amounts in AE.

curl -s "https://api.superhero.com/api/bcl-affiliation/analytics/top-inviters?limit=10"

GET /api/bcl-affiliation/analytics/x-verification and /x-invite

Each returns series plus summary. X-verification points are date, daily_new_verified, cumulative_verified. X-invite points are date, created_codes, invited_users, with a summary of total_created_codes, total_invited_users and invite_bind_rate.

GET /api/bcl-affiliation/tree

The full referral graph as one payload, with no pagination and no parameters. { items, meta }, where items is one node per inviter:

FieldTypeMeaning
sender_addressstringThe inviter's ak_...
senderobjectThat inviter's own stats (see below)
inviteesarrayOne stats object per distinct address they invited
amountnumberTotal AE this inviter sent to invitees, in AE

Each stats object, inviter and invitee alike, has the same fields:

address
total_invitation_count            registered as inviter
total_claimed_invitation_count    redeemed as inviter
total_revoked_invitation_count    revoked as inviter
total_pending_invitation_count    registered minus redeemed minus revoked
total_amount_ae                   sum of amount as inviter, in AE
total_received_invitation_count   registered as invitee

An invitee node carries only total_received_invitation_count; its other counters are 0 even when that address is itself an inviter. To walk deeper, look the invitee's address up as a sender_address in the same items array. The structure is a flat list of one-level fans.

meta carries unique_inviters, unique_invitees, total_registered, total_redeemed, total_revoked and total_amount_ae.

Only invitations with both a sender_address and an invitee_address are counted as edges, so pending and revoked invites that were never redeemed contribute to meta.total_revoked but not to any node's invitee list.

Related