Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3937ea0ead | ||
|
|
492722073d |
@@ -137,6 +137,27 @@ func describeBilling() {
|
||||
"rather than a silently blank list. It is a catalog read, not an entitlement read — "+
|
||||
"it says what may be bought, never what this caller has.")
|
||||
|
||||
openapi.Describe("/v1/billing/tier", http.MethodGet,
|
||||
"The subject's plan tier and the balance a metered call is admitted on",
|
||||
"Answers one subject's resolved tier — name, display name, agent ceiling and allowed "+
|
||||
"models — with the balance that admits their next metered call: prepaidAvailable, "+
|
||||
"creditsRemaining, dailyRemaining and the effectiveAvailable those fold into. The ai "+
|
||||
"router reads it per request to pick that caller's rate-limit tier. It sits on the "+
|
||||
"org-resolving chain because a tier is org state, and the subject keys are pinned to "+
|
||||
"the validated caller before the handler runs, so a browser read is always the "+
|
||||
"caller's own; user is required, which only a service-to-service caller can omit and "+
|
||||
"be refused 400 for. The tier is an upstream tier claim, or an explicit tier "+
|
||||
"override, when either is present — that is the service-to-service contract — and is "+
|
||||
"otherwise DERIVED from the org's active and trialing subscriptions, the highest one "+
|
||||
"winning, its paid-ness read from the plan catalog by slug rather than from the "+
|
||||
"subscription's own stored copy. The rule to get right is effectiveAvailable and not "+
|
||||
"prepaidAvailable: granted credits spend too, credits first, so an account funded "+
|
||||
"only by a grant reads zero prepaid while holding real spendable credit — and with "+
|
||||
"the daily term zero on every tier there is no free allowance behind it, so a "+
|
||||
"zero-balance account is gated. A subscription-store error answers 500 rather than "+
|
||||
"downgrading to free, so a transient failure never reports a paid subscriber as "+
|
||||
"unsubscribed.")
|
||||
|
||||
openapi.Describe("/v1/billing/alerts", http.MethodGet,
|
||||
"List your org's spend caps and rate limits",
|
||||
"Returns the caps and alerts keyed to the caller's own billing subject, each with its "+
|
||||
|
||||
@@ -37,9 +37,6 @@ func init() {
|
||||
zip.Describe("POST /finance/scope-rules", zip.Doc{
|
||||
Description: "Lists the caller org's per-scope request-rate ceilings, so the edge limiter in\nanother process can enforce a budget whose rows it cannot open.\n\nIt takes NO input: the org rides the caller and there is nothing else to name,\nso one org can never read another's ceilings. Only rows that SET a ceiling are\nreturned — a spend-alert row with no rate limit is a spend cap, a different\npolicy answered by a different op, and shipping it here would make the limiter\nweigh rules that bind nothing.\n\nThe row scan is the org's WHOLE policy set — the same query commerce's own cap\nverdict runs (loadOrgScopes), bounded the same way. The retired HTTP read went\nthrough the per-subject list instead, so the rate ceiling and the spend cap\ncould in principle bind on different rows; one query is what makes that\nimpossible rather than merely unlikely.\n\nA named handler, not a closure, so zipdoc can lift this prose into the registry.",
|
||||
})
|
||||
zip.Describe("POST /finance/starter", zip.Doc{
|
||||
Description: "Grants a new account its opening welcome credit and answers the amount granted.\n\nGRANTED ONCE, whoever asks. The idempotency key is the ACCOUNT and nothing\nelse, so asking twice — from two processes, after a restart, or concurrently —\ngrants exactly once; that property lives in the ledger, which dedups inside the\nsame transaction as the insert, and this op only carries the question across.\n\nThe org is the CALLER'S and can never be named in the input; an empty subject\ngrants to the org's own account. It is published because the grant runs as\nmiddleware on EVERY app's chain while the ledger has one writer and lives here\n— a grant that could not reach it left new orgs unfunded and correctly\npaywalled for a reason nobody had chosen.\n\nA named handler, not a closure, so zipdoc can lift this prose into the registry.",
|
||||
})
|
||||
zip.Describe("POST /finance/txns", zip.Doc{
|
||||
Description: "Lists the caller org's ledger entries — id, kind, ref, memo, amount and\ntimestamp — most recent first and bounded to one page. It is the movement list\nbehind the customer-facing transactions, credits and receipts pages, all three\nof which read this one list.\n\nIt takes NO input: the org comes from the caller and there is nothing else to\nname, so one org can never read another's entries. Unlike the usage read the\namount crosses as a DECIMAL STRING with its currency rather than a bare\nquantity, because an entry is a movement a customer reads rather than a number\na gate does arithmetic on. A ledger implementation that cannot list entries is\nan error, not an empty page.\n\nA named handler, not a closure, so zipdoc can lift this prose into the registry.",
|
||||
})
|
||||
|
||||
@@ -172,6 +172,12 @@ func init() {
|
||||
"projectsUpdate.visibility": "Visibility flips an existing project between \"public\" and \"private\". Same\nONE rule as at create: public is free, private needs a paid plan.",
|
||||
},
|
||||
})
|
||||
zip.Describe("POST /sites/resolve", zip.Doc{
|
||||
Description: "Answers the multi-tenant product URL (<slug>.hanzo.app) and\nbound custom domains. Not-found is `Found:false`, never an error: the edge\nturns that into an honest 404, and an error into a 503. Collapsing the two\nwould serve 404s for real live sites during a transient failure.",
|
||||
})
|
||||
zip.Describe("POST /sites/resolve-org", zip.Doc{
|
||||
Description: "Is the first-party path: it NEVER falls back to\nunique-across-orgs, so an internal host is served only by our own project and\nnever a customer's same-named one.",
|
||||
})
|
||||
zip.Describe("POST /v1/platform/sites", zip.Doc{
|
||||
Description: "Creates a project — the handle a site is deployed and served\nunder — and answers 201 with it in `draft`.\n\n`name` is required; `slug` is derived from the name when omitted and is the\nidentifier that matters — it becomes the S3 key segment, the public host\n`<slug>.hanzo.app`, and the handle every later call addresses, so it must\nmatch `^[a-z0-9]([a-z0-9-]{0,38}[a-z0-9])?$` and may not be a reserved label\nsuch as `api` or `admin`. `framework` is a build hint from a closed set,\ndefaulting to `static`; it never gates a deploy, it only tells CI how to build\na linked repo.\n\nTwo defaults are worth knowing: the analytics beacon is ON unless `analytics`\nis explicitly false, and `visibility` is `public` unless asked otherwise.\nPublishing publicly is free; PRIVATE is the paid feature, and an unfunded org\nasking for it is refused rather than quietly published as public. Creation\nalso provisions the project's data space and a canonical git repo, both\nbest-effort — neither can fail the create.\n\nScope: a validated principal is required (403 without one) and the project is\ncreated in THAT principal's org. The slug is unique per org, so a slug already\nused in the caller's own org is a 409 while the same slug in another org is\nirrelevant.",
|
||||
Fields: map[string]string{
|
||||
|
||||
+96
-132
@@ -4580,28 +4580,22 @@ components:
|
||||
instrument:
|
||||
description: |-
|
||||
Instrument identifies the payment method. It is the anti-farming key: one
|
||||
redemption per instrument, fleet-wide.
|
||||
redemption per instrument, fleet-wide, and it is REQUIRED — an absent
|
||||
instrument is refused, never waved through.
|
||||
type: string
|
||||
plan:
|
||||
description: 'Plan is the plan being redeemed against: pro, max or team.'
|
||||
type: string
|
||||
seats:
|
||||
description: |-
|
||||
Seats is the Team seat count; 0 means 1. Seats beyond the promo's
|
||||
teamSeatCap bill at list.
|
||||
type: integer
|
||||
type: object
|
||||
RedeemResult:
|
||||
properties:
|
||||
alreadyRedeemed:
|
||||
description: |-
|
||||
AlreadyRedeemed is true when this org had already taken the promo and the
|
||||
call was an idempotent replay — nothing was credited a second time.
|
||||
call was an idempotent replay.
|
||||
type: boolean
|
||||
chargeCents:
|
||||
description: |-
|
||||
ChargeCents is what month one costs after the discount, DiscountCents the
|
||||
credit that produced it.
|
||||
discount that produced it. Both are quoted figures against the org's
|
||||
derived plan — NOTHING WAS CREDITED and no wallet moved.
|
||||
type: integer
|
||||
discountCents:
|
||||
type: integer
|
||||
@@ -4613,17 +4607,18 @@ components:
|
||||
code:
|
||||
description: Code is the promo redeemed.
|
||||
type: string
|
||||
creditCents:
|
||||
discountCents:
|
||||
description: |-
|
||||
CreditCents is the discount value credited to the org's wallet — the promo
|
||||
is realized as a NON-CASH credit, not a subscription coupon.
|
||||
DiscountCents is the month-one discount this redemption CLAIMS, in USD
|
||||
cents. It is a recorded figure, NOT a balance: nothing was credited and no
|
||||
wallet moved. An admin granting against this claim is what would make it
|
||||
money, and that decision happens on the admin surface, not here.
|
||||
type: integer
|
||||
creditEntryId:
|
||||
description: CreditEntryID is the finance ledger entry that credit landed
|
||||
in.
|
||||
type: string
|
||||
plan:
|
||||
description: Plan and Seats are what was redeemed against.
|
||||
description: |-
|
||||
Plan and Seats are what was redeemed against. Both are DERIVED server-side
|
||||
— Plan from the org's live paid subscription, Seats from claimSeats — and
|
||||
neither is ever read from the request.
|
||||
type: string
|
||||
redeemedAt:
|
||||
description: RedeemedAt is unix seconds.
|
||||
@@ -7586,7 +7581,7 @@ components:
|
||||
adminBonusDirectory:
|
||||
properties:
|
||||
referrals:
|
||||
description: Referrals is every referral in the ledger, both orgs exposed.
|
||||
description: Referrals is every referral in the directory, both orgs exposed.
|
||||
items:
|
||||
$ref: '#/components/schemas/adminReferralView'
|
||||
type: array
|
||||
@@ -7721,11 +7716,6 @@ components:
|
||||
createdAt:
|
||||
description: CreatedAt is when the referral was recorded, as a Unix timestamp.
|
||||
type: integer
|
||||
creditedAt:
|
||||
description: |-
|
||||
CreditedAt is when the bonuses were latched and granted, as a Unix
|
||||
timestamp; 0 until they are.
|
||||
type: integer
|
||||
id:
|
||||
description: ID is the referral's handle.
|
||||
type: string
|
||||
@@ -7734,36 +7724,14 @@ components:
|
||||
QualifiedAt is when the referee first made metered spend, as a Unix
|
||||
timestamp; 0 while still pending.
|
||||
type: integer
|
||||
refereeGrantCents:
|
||||
description: |-
|
||||
RefereeGrantCents is what the referee was granted, in USD cents; 0 until the
|
||||
referral is credited.
|
||||
type: integer
|
||||
refereeOrg:
|
||||
description: RefereeOrg is the org that signed up with it.
|
||||
type: string
|
||||
refereeTxn:
|
||||
description: |-
|
||||
RefereeTxn is the commerce ledger transaction that carried the referee's
|
||||
grant, omitted until one exists.
|
||||
type: string
|
||||
referrerGrantCents:
|
||||
description: |-
|
||||
ReferrerGrantCents is what the referrer was granted, in USD cents; 0 until
|
||||
the referral is credited.
|
||||
type: integer
|
||||
referrerOrg:
|
||||
description: ReferrerOrg is the org whose code was used.
|
||||
type: string
|
||||
referrerTxn:
|
||||
description: |-
|
||||
ReferrerTxn is the commerce ledger transaction that carried the referrer's
|
||||
grant, omitted until one exists.
|
||||
type: string
|
||||
status:
|
||||
description: |-
|
||||
Status is the referral's lifecycle state: "signup", "qualified" or
|
||||
"credited".
|
||||
description: 'Status is the referral''s lifecycle state: "signup" or "qualified".'
|
||||
type: string
|
||||
type: object
|
||||
adminReportData:
|
||||
@@ -7796,22 +7764,14 @@ components:
|
||||
type: object
|
||||
adminSummary:
|
||||
properties:
|
||||
credited:
|
||||
description: Credited is how many have paid both bonuses.
|
||||
type: integer
|
||||
grantedCents:
|
||||
description: |-
|
||||
GrantedCents is the promo credit granted across BOTH sides of every
|
||||
referral, in USD cents — the program's total liability to date.
|
||||
type: integer
|
||||
qualified:
|
||||
description: Qualified is how many have qualified but are not yet credited.
|
||||
description: Qualified is how many referees have made metered spend.
|
||||
type: integer
|
||||
signup:
|
||||
description: Signup is how many are recorded but not yet qualified.
|
||||
type: integer
|
||||
total:
|
||||
description: Total is every referral in the ledger.
|
||||
description: Total is every referral in the directory.
|
||||
type: integer
|
||||
type: object
|
||||
advanceIn:
|
||||
@@ -18385,16 +18345,6 @@ components:
|
||||
createdAt:
|
||||
description: CreatedAt is when the referral was recorded, as a Unix timestamp.
|
||||
type: integer
|
||||
creditedAt:
|
||||
description: |-
|
||||
CreditedAt is when the bonuses were latched and granted, as a Unix
|
||||
timestamp; 0 until they are. It is the at-most-once latch.
|
||||
type: integer
|
||||
creditsCents:
|
||||
description: |-
|
||||
CreditsCents is what I earned from this referral, in USD cents. It is 0
|
||||
until the referee qualifies.
|
||||
type: integer
|
||||
id:
|
||||
description: ID is the referral's handle.
|
||||
type: string
|
||||
@@ -18408,8 +18358,8 @@ components:
|
||||
type: string
|
||||
status:
|
||||
description: |-
|
||||
Status is the referral's lifecycle state: "signup" until the referee
|
||||
makes metered spend, then "qualified", then "credited".
|
||||
Status is the referral's lifecycle state: "signup" until the referee makes
|
||||
metered spend, then "qualified".
|
||||
type: string
|
||||
type: object
|
||||
myReferrals:
|
||||
@@ -18422,29 +18372,15 @@ components:
|
||||
counts:
|
||||
$ref: '#/components/schemas/statusCounts'
|
||||
description: Counts tallies this org's referrals by status.
|
||||
creditsEarnedCents:
|
||||
description: |-
|
||||
CreditsEarnedCents is the total promo credit this org has earned as the
|
||||
REFERRER, in USD cents.
|
||||
type: integer
|
||||
link:
|
||||
description: Link is the shareable signup link carrying the code, on the
|
||||
brand's own host.
|
||||
type: string
|
||||
refereeBonusCents:
|
||||
description: RefereeBonusCents is what a referee is granted on qualification,
|
||||
in USD cents.
|
||||
type: integer
|
||||
referrals:
|
||||
description: Referrals is one row per org that signed up with this code.
|
||||
items:
|
||||
$ref: '#/components/schemas/myReferralView'
|
||||
type: array
|
||||
referrerBonusCents:
|
||||
description: |-
|
||||
ReferrerBonusCents is what the referrer is granted when a referee qualifies,
|
||||
in USD cents.
|
||||
type: integer
|
||||
type: object
|
||||
namespaceCreateIn:
|
||||
properties:
|
||||
@@ -35841,11 +35777,8 @@ components:
|
||||
type: object
|
||||
statusCounts:
|
||||
properties:
|
||||
credited:
|
||||
description: Credited is how many referrals have paid both bonuses.
|
||||
type: integer
|
||||
qualified:
|
||||
description: Qualified is how many referees have spent but are not yet credited.
|
||||
description: Qualified is how many referees have made metered spend.
|
||||
type: integer
|
||||
signup:
|
||||
description: Signup is how many referees have signed up but not yet spent.
|
||||
@@ -36411,10 +36344,9 @@ components:
|
||||
type: object
|
||||
sweepResult:
|
||||
properties:
|
||||
credited:
|
||||
description: |-
|
||||
Credited is how many of those referrals qualified on this pass and were
|
||||
granted their bonuses.
|
||||
qualified:
|
||||
description: Qualified is how many of those referrals qualified on this
|
||||
pass.
|
||||
type: integer
|
||||
swept:
|
||||
description: Swept is how many pending referrals were checked.
|
||||
@@ -41097,13 +41029,13 @@ paths:
|
||||
/v1/admin/referrals/bonuses:
|
||||
get:
|
||||
description: |-
|
||||
Returns every one-time referral bonus in the ledger with a fleet summary.
|
||||
Returns every referral edge in the directory with a fleet summary.
|
||||
|
||||
SuperAdmin only, fail-closed. This is the ONE-TIME BONUS ledger — who referred
|
||||
whom, what each side was granted and which ledger transactions carried it. The
|
||||
cross-tenant referral ANALYTICS board (top referrers, conversion, multi-level
|
||||
accrual liability) is a different surface, GET /v1/admin/referrals, owned by the
|
||||
affiliates subsystem over the shared attribution spine.
|
||||
SuperAdmin only, fail-closed. This is the ATTRIBUTION directory — who referred
|
||||
whom and whether that referee became a customer. It carries no amounts because
|
||||
this package issues none. The cross-tenant referral ANALYTICS board (top
|
||||
referrers, conversion) is a different surface, GET /v1/admin/referrals, owned by
|
||||
the affiliates subsystem over the shared attribution spine.
|
||||
operationId: get_v1_admin_referrals_bonuses
|
||||
parameters:
|
||||
- description: |-
|
||||
@@ -41124,25 +41056,24 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/adminBonusesEnvelope'
|
||||
description: ok
|
||||
summary: Returns every one-time referral bonus in the ledger with a fleet summary.
|
||||
summary: Returns every referral edge in the directory with a fleet summary.
|
||||
tags:
|
||||
- admin
|
||||
x-app: referrals
|
||||
/v1/admin/referrals/sweep:
|
||||
post:
|
||||
description: |-
|
||||
Qualify-checks every pending referral and grants the ones that now qualify.
|
||||
Qualify-checks every pending referral and advances the ones that now qualify.
|
||||
|
||||
SuperAdmin only, fail-closed. This is the cron path: a referee QUALIFIES once
|
||||
they have made metered spend — the honest signal that they actually used the
|
||||
product rather than merely claiming a welcome grant — and qualifying grants the
|
||||
referrer and the referee their bonuses in one latched step.
|
||||
SuperAdmin only, fail-closed. This is the cron path, and the ONLY path that
|
||||
advances a referral: a referee QUALIFIES once they have made metered spend — the
|
||||
honest signal that they actually used the product rather than merely signing up.
|
||||
|
||||
The grant is backed against the platform reserve fund before it is latched, so
|
||||
an empty fund leaves the referral honestly pending rather than minting unbacked
|
||||
credit, and the latch makes it at-most-once: this sweep, a concurrent sweep and
|
||||
the lazy check on GET /v1/referrals can never double-pay. One pass is bounded,
|
||||
so a large backlog drains over several runs instead of wedging one request.
|
||||
Qualifying moves NO money. It records that an attribution became a real customer;
|
||||
what is owed for that is an affiliate payable in commerce, settled by wire or to a
|
||||
connected wallet. One pass is bounded, so a large backlog drains over several runs
|
||||
instead of wedging one request, and the latch makes the transition at-most-once
|
||||
under a concurrent sweep.
|
||||
|
||||
It reads nothing from the caller — the counters it returns are the whole result.
|
||||
operationId: post_v1_admin_referrals_sweep
|
||||
@@ -41153,7 +41084,7 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/sweepEnvelope'
|
||||
description: ok
|
||||
summary: Qualify-checks every pending referral and grants the ones that now
|
||||
summary: Qualify-checks every pending referral and advances the ones that now
|
||||
qualify.
|
||||
tags:
|
||||
- admin
|
||||
@@ -47955,6 +47886,32 @@ paths:
|
||||
tags:
|
||||
- billing
|
||||
x-app: commerce
|
||||
/v1/billing/tier:
|
||||
get:
|
||||
description: 'Answers one subject''s resolved tier — name, display name, agent
|
||||
ceiling and allowed models — with the balance that admits their next metered
|
||||
call: prepaidAvailable, creditsRemaining, dailyRemaining and the effectiveAvailable
|
||||
those fold into. The ai router reads it per request to pick that caller''s
|
||||
rate-limit tier. It sits on the org-resolving chain because a tier is org
|
||||
state, and the subject keys are pinned to the validated caller before the
|
||||
handler runs, so a browser read is always the caller''s own; user is required,
|
||||
which only a service-to-service caller can omit and be refused 400 for. The
|
||||
tier is an upstream tier claim, or an explicit tier override, when either
|
||||
is present — that is the service-to-service contract — and is otherwise DERIVED
|
||||
from the org''s active and trialing subscriptions, the highest one winning,
|
||||
its paid-ness read from the plan catalog by slug rather than from the subscription''s
|
||||
own stored copy. The rule to get right is effectiveAvailable and not prepaidAvailable:
|
||||
granted credits spend too, credits first, so an account funded only by a grant
|
||||
reads zero prepaid while holding real spendable credit — and with the daily
|
||||
term zero on every tier there is no free allowance behind it, so a zero-balance
|
||||
account is gated. A subscription-store error answers 500 rather than downgrading
|
||||
to free, so a transient failure never reports a paid subscriber as unsubscribed.'
|
||||
operationId: get_v1_billing_tier
|
||||
summary: The subject's plan tier and the balance a metered call is admitted
|
||||
on
|
||||
tags:
|
||||
- billing
|
||||
x-app: commerce
|
||||
/v1/billing/topup/token:
|
||||
post:
|
||||
description: 'Charges the single-use card token for the given amount and credits
|
||||
@@ -69649,13 +69606,22 @@ paths:
|
||||
/v1/marketing/promos/{code}/redeem:
|
||||
post:
|
||||
description: |-
|
||||
Redeems the promo for the caller's org, crediting the discount
|
||||
value to its wallet through the finance ledger. Three guards run under one
|
||||
lock so the cap cannot be raced past: the fleet-wide redemption cap, one
|
||||
redemption per org, and one per payment instrument.
|
||||
Records the caller org's claim on a promo. NOTHING IS CREDITED:
|
||||
the redemption is a row, and credit into an org is an admin decision made on
|
||||
the admin surface against an auditable ledger.
|
||||
|
||||
The plan is DERIVED from the org's live ACTIVE/TRIALING paid subscription and
|
||||
can never be named by the caller — an org with no qualifying subscription is
|
||||
refused, and so is one whose subscription cannot be read. The seat count is
|
||||
the single-seat floor (claimSeats), so the recorded figure has no input that
|
||||
can inflate it.
|
||||
|
||||
Guards run under one lock so the cap cannot be raced past: the fleet-wide
|
||||
redemption cap, one redemption per org, one per payment instrument (REQUIRED),
|
||||
and the per-redemption ceiling.
|
||||
|
||||
It is IDEMPOTENT: an org that already redeemed gets its original redemption
|
||||
back with alreadyRedeemed true and is not credited twice.
|
||||
back with alreadyRedeemed true.
|
||||
operationId: post_v1_marketing_promos_code_redeem
|
||||
parameters:
|
||||
- description: Code is the promo code from the path.
|
||||
@@ -69671,8 +69637,6 @@ paths:
|
||||
example:
|
||||
code: first1000
|
||||
instrument: pm_1QxYz2AbCdEf
|
||||
plan: pro
|
||||
seats: 1
|
||||
schema:
|
||||
$ref: '#/components/schemas/RedeemInput'
|
||||
required: true
|
||||
@@ -69683,8 +69647,7 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RedeemResult'
|
||||
description: ok
|
||||
summary: Redeems the promo for the caller's org, crediting the discount value
|
||||
to its wallet through the finance ledger.
|
||||
summary: Records the caller org's claim on a promo.
|
||||
tags:
|
||||
- marketing
|
||||
x-app: marketing
|
||||
@@ -86777,15 +86740,15 @@ paths:
|
||||
Returns the caller's referral code, share link and the referrals they have made.
|
||||
|
||||
The code is a stable, deterministic function of the org, so the link in this
|
||||
response is the same one every time. Each row carries the referee, its status and
|
||||
the credit this org earned from it; creditsEarnedCents is their sum.
|
||||
response is the same one every time. Each row carries the referee and the status
|
||||
of that attribution.
|
||||
|
||||
The read is self-updating: before listing, it runs the qualify check over this
|
||||
org's still-pending referees, so a referee who has since made metered spend is
|
||||
credited by the act of the referrer loading their page. That check is
|
||||
best-effort and bounded — a commerce hiccup leaves the referral pending for the
|
||||
next check rather than failing the page — and the grant is latched at-most-once,
|
||||
so this path and the admin sweep can never double-pay.
|
||||
IT IS A PURE READ. It advances no referral, grants nothing and deposits nothing
|
||||
— a GET reports state, it never changes it. Qualification is the admin sweep's
|
||||
job (POST /v1/admin/referrals/sweep). The one row this handler can write is the
|
||||
caller's OWN code-directory entry (EnsureCode), which materialises a value
|
||||
deriveCode already computes deterministically from the org id so the code has an
|
||||
O(1) reverse lookup; it carries no money, no referral state and no other tenant.
|
||||
operationId: get_v1_referrals
|
||||
responses:
|
||||
"200":
|
||||
@@ -86812,9 +86775,10 @@ paths:
|
||||
call returns the referral already on file with created=false and 200, where the
|
||||
first call answers 201.
|
||||
|
||||
Recording a referral grants nothing. Both bonuses are granted later, when the
|
||||
referee actually makes metered spend — see GET /v1/referrals and
|
||||
POST /v1/admin/referrals/sweep.
|
||||
Recording a referral grants nothing, and neither does anything downstream of it:
|
||||
the edge later advances to qualified when the referee makes metered spend
|
||||
(POST /v1/admin/referrals/sweep), and that is the end of it. No credit is ever
|
||||
issued from this package.
|
||||
operationId: post_v1_referrals_claim
|
||||
requestBody:
|
||||
content:
|
||||
@@ -96503,8 +96467,8 @@ tags:
|
||||
a cloud binary with the money, ingest and telemetry callbacks cloud BUILDS but
|
||||
cannot INSTALL.
|
||||
name: rag
|
||||
- description: Package referrals is credit for both sides when someone you refer actually
|
||||
spends.
|
||||
- description: 'Package referrals is referral ATTRIBUTION: who referred whom, and
|
||||
whether that referee ever became a real customer.'
|
||||
name: referrals
|
||||
- description: 'Package registry is your container and package registry: push images,
|
||||
pull them back, see what you store.'
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"paths": 1696,
|
||||
"operations": 2351,
|
||||
"paths": 1697,
|
||||
"operations": 2352,
|
||||
"products": {
|
||||
"admin": 87,
|
||||
"ads": 7,
|
||||
@@ -20,7 +20,7 @@
|
||||
"balancers": 4,
|
||||
"base": 1,
|
||||
"benchmark": 6,
|
||||
"billing": 26,
|
||||
"billing": 27,
|
||||
"blueprint": 3,
|
||||
"books": 25,
|
||||
"bot": 11,
|
||||
|
||||
@@ -265,6 +265,16 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/billing/tier": {
|
||||
"get": {
|
||||
"operationId": "get_v1_billing_tier",
|
||||
"summary": "The subject's plan tier and the balance a metered call is admitted on",
|
||||
"description": "Answers one subject's resolved tier — name, display name, agent ceiling and allowed models — with the balance that admits their next metered call: prepaidAvailable, creditsRemaining, dailyRemaining and the effectiveAvailable those fold into. The ai router reads it per request to pick that caller's rate-limit tier. It sits on the org-resolving chain because a tier is org state, and the subject keys are pinned to the validated caller before the handler runs, so a browser read is always the caller's own; user is required, which only a service-to-service caller can omit and be refused 400 for. The tier is an upstream tier claim, or an explicit tier override, when either is present — that is the service-to-service contract — and is otherwise DERIVED from the org's active and trialing subscriptions, the highest one winning, its paid-ness read from the plan catalog by slug rather than from the subscription's own stored copy. The rule to get right is effectiveAvailable and not prepaidAvailable: granted credits spend too, credits first, so an account funded only by a grant reads zero prepaid while holding real spendable credit — and with the daily term zero on every tier there is no free allowance behind it, so a zero-balance account is gated. A subscription-store error answers 500 rather than downgrading to free, so a transient failure never reports a paid subscriber as unsubscribed.",
|
||||
"tags": [
|
||||
"billing"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/billing/topup/token": {
|
||||
"post": {
|
||||
"operationId": "post_v1_billing_topup_token",
|
||||
|
||||
@@ -749,8 +749,8 @@
|
||||
"/v1/marketing/promos/{code}/redeem": {
|
||||
"post": {
|
||||
"operationId": "post_v1_marketing_promos_code_redeem",
|
||||
"summary": "Redeems the promo for the caller's org, crediting the discount value to its wallet through the finance ledger.",
|
||||
"description": "Redeems the promo for the caller's org, crediting the discount\nvalue to its wallet through the finance ledger. Three guards run under one\nlock so the cap cannot be raced past: the fleet-wide redemption cap, one\nredemption per org, and one per payment instrument.\n\nIt is IDEMPOTENT: an org that already redeemed gets its original redemption\nback with alreadyRedeemed true and is not credited twice.",
|
||||
"summary": "Records the caller org's claim on a promo.",
|
||||
"description": "Records the caller org's claim on a promo. NOTHING IS CREDITED:\nthe redemption is a row, and credit into an org is an admin decision made on\nthe admin surface against an auditable ledger.\n\nThe plan is DERIVED from the org's live ACTIVE/TRIALING paid subscription and\ncan never be named by the caller — an org with no qualifying subscription is\nrefused, and so is one whose subscription cannot be read. The seat count is\nthe single-seat floor (claimSeats), so the recorded figure has no input that\ncan inflate it.\n\nGuards run under one lock so the cap cannot be raced past: the fleet-wide\nredemption cap, one redemption per org, one per payment instrument (REQUIRED),\nand the per-redemption ceiling.\n\nIt is IDEMPOTENT: an org that already redeemed gets its original redemption\nback with alreadyRedeemed true.",
|
||||
"tags": [
|
||||
"marketing"
|
||||
],
|
||||
@@ -771,9 +771,7 @@
|
||||
"application/json": {
|
||||
"example": {
|
||||
"code": "first1000",
|
||||
"instrument": "pm_1QxYz2AbCdEf",
|
||||
"plan": "pro",
|
||||
"seats": 1
|
||||
"instrument": "pm_1QxYz2AbCdEf"
|
||||
},
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/RedeemInput"
|
||||
@@ -1798,16 +1796,8 @@
|
||||
"type": "string"
|
||||
},
|
||||
"instrument": {
|
||||
"description": "Instrument identifies the payment method. It is the anti-farming key: one\nredemption per instrument, fleet-wide.",
|
||||
"description": "Instrument identifies the payment method. It is the anti-farming key: one\nredemption per instrument, fleet-wide, and it is REQUIRED — an absent\ninstrument is refused, never waved through.",
|
||||
"type": "string"
|
||||
},
|
||||
"plan": {
|
||||
"description": "Plan is the plan being redeemed against: pro, max or team.",
|
||||
"type": "string"
|
||||
},
|
||||
"seats": {
|
||||
"description": "Seats is the Team seat count; 0 means 1. Seats beyond the promo's\nteamSeatCap bill at list.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -1815,11 +1805,11 @@
|
||||
"RedeemResult": {
|
||||
"properties": {
|
||||
"alreadyRedeemed": {
|
||||
"description": "AlreadyRedeemed is true when this org had already taken the promo and the\ncall was an idempotent replay — nothing was credited a second time.",
|
||||
"description": "AlreadyRedeemed is true when this org had already taken the promo and the\ncall was an idempotent replay.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"chargeCents": {
|
||||
"description": "ChargeCents is what month one costs after the discount, DiscountCents the\ncredit that produced it.",
|
||||
"description": "ChargeCents is what month one costs after the discount, DiscountCents the\ndiscount that produced it. Both are quoted figures against the org's\nderived plan — NOTHING WAS CREDITED and no wallet moved.",
|
||||
"type": "integer"
|
||||
},
|
||||
"discountCents": {
|
||||
@@ -1837,16 +1827,12 @@
|
||||
"description": "Code is the promo redeemed.",
|
||||
"type": "string"
|
||||
},
|
||||
"creditCents": {
|
||||
"description": "CreditCents is the discount value credited to the org's wallet — the promo\nis realized as a NON-CASH credit, not a subscription coupon.",
|
||||
"discountCents": {
|
||||
"description": "DiscountCents is the month-one discount this redemption CLAIMS, in USD\ncents. It is a recorded figure, NOT a balance: nothing was credited and no\nwallet moved. An admin granting against this claim is what would make it\nmoney, and that decision happens on the admin surface, not here.",
|
||||
"type": "integer"
|
||||
},
|
||||
"creditEntryId": {
|
||||
"description": "CreditEntryID is the finance ledger entry that credit landed in.",
|
||||
"type": "string"
|
||||
},
|
||||
"plan": {
|
||||
"description": "Plan and Seats are what was redeemed against.",
|
||||
"description": "Plan and Seats are what was redeemed against. Both are DERIVED server-side\n— Plan from the org's live paid subscription, Seats from claimSeats — and\nneither is ever read from the request.",
|
||||
"type": "string"
|
||||
},
|
||||
"redeemedAt": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"openapi": "3.1.0",
|
||||
"info": {
|
||||
"title": "Hanzo Cloud API",
|
||||
"description": "Package referrals is credit for both sides when someone you refer actually spends.",
|
||||
"description": "Package referrals is referral ATTRIBUTION: who referred whom, and whether that referee ever became a real customer.",
|
||||
"version": "v1"
|
||||
},
|
||||
"servers": [
|
||||
@@ -22,8 +22,8 @@
|
||||
"/v1/admin/referrals/bonuses": {
|
||||
"get": {
|
||||
"operationId": "get_v1_admin_referrals_bonuses",
|
||||
"summary": "Returns every one-time referral bonus in the ledger with a fleet summary.",
|
||||
"description": "Returns every one-time referral bonus in the ledger with a fleet summary.\n\nSuperAdmin only, fail-closed. This is the ONE-TIME BONUS ledger — who referred\nwhom, what each side was granted and which ledger transactions carried it. The\ncross-tenant referral ANALYTICS board (top referrers, conversion, multi-level\naccrual liability) is a different surface, GET /v1/admin/referrals, owned by the\naffiliates subsystem over the shared attribution spine.",
|
||||
"summary": "Returns every referral edge in the directory with a fleet summary.",
|
||||
"description": "Returns every referral edge in the directory with a fleet summary.\n\nSuperAdmin only, fail-closed. This is the ATTRIBUTION directory — who referred\nwhom and whether that referee became a customer. It carries no amounts because\nthis package issues none. The cross-tenant referral ANALYTICS board (top\nreferrers, conversion) is a different surface, GET /v1/admin/referrals, owned by\nthe affiliates subsystem over the shared attribution spine.",
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
@@ -55,8 +55,8 @@
|
||||
"/v1/admin/referrals/sweep": {
|
||||
"post": {
|
||||
"operationId": "post_v1_admin_referrals_sweep",
|
||||
"summary": "Qualify-checks every pending referral and grants the ones that now qualify.",
|
||||
"description": "Qualify-checks every pending referral and grants the ones that now qualify.\n\nSuperAdmin only, fail-closed. This is the cron path: a referee QUALIFIES once\nthey have made metered spend — the honest signal that they actually used the\nproduct rather than merely claiming a welcome grant — and qualifying grants the\nreferrer and the referee their bonuses in one latched step.\n\nThe grant is backed against the platform reserve fund before it is latched, so\nan empty fund leaves the referral honestly pending rather than minting unbacked\ncredit, and the latch makes it at-most-once: this sweep, a concurrent sweep and\nthe lazy check on GET /v1/referrals can never double-pay. One pass is bounded,\nso a large backlog drains over several runs instead of wedging one request.\n\nIt reads nothing from the caller — the counters it returns are the whole result.",
|
||||
"summary": "Qualify-checks every pending referral and advances the ones that now qualify.",
|
||||
"description": "Qualify-checks every pending referral and advances the ones that now qualify.\n\nSuperAdmin only, fail-closed. This is the cron path, and the ONLY path that\nadvances a referral: a referee QUALIFIES once they have made metered spend — the\nhonest signal that they actually used the product rather than merely signing up.\n\nQualifying moves NO money. It records that an attribution became a real customer;\nwhat is owed for that is an affiliate payable in commerce, settled by wire or to a\nconnected wallet. One pass is bounded, so a large backlog drains over several runs\ninstead of wedging one request, and the latch makes the transition at-most-once\nunder a concurrent sweep.\n\nIt reads nothing from the caller — the counters it returns are the whole result.",
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
@@ -78,7 +78,7 @@
|
||||
"get": {
|
||||
"operationId": "get_v1_referrals",
|
||||
"summary": "Returns the caller's referral code, share link and the referrals they have made.",
|
||||
"description": "Returns the caller's referral code, share link and the referrals they have made.\n\nThe code is a stable, deterministic function of the org, so the link in this\nresponse is the same one every time. Each row carries the referee, its status and\nthe credit this org earned from it; creditsEarnedCents is their sum.\n\nThe read is self-updating: before listing, it runs the qualify check over this\norg's still-pending referees, so a referee who has since made metered spend is\ncredited by the act of the referrer loading their page. That check is\nbest-effort and bounded — a commerce hiccup leaves the referral pending for the\nnext check rather than failing the page — and the grant is latched at-most-once,\nso this path and the admin sweep can never double-pay.",
|
||||
"description": "Returns the caller's referral code, share link and the referrals they have made.\n\nThe code is a stable, deterministic function of the org, so the link in this\nresponse is the same one every time. Each row carries the referee and the status\nof that attribution.\n\nIT IS A PURE READ. It advances no referral, grants nothing and deposits nothing\n— a GET reports state, it never changes it. Qualification is the admin sweep's\njob (POST /v1/admin/referrals/sweep). The one row this handler can write is the\ncaller's OWN code-directory entry (EnsureCode), which materialises a value\nderiveCode already computes deterministically from the org id so the code has an\nO(1) reverse lookup; it carries no money, no referral state and no other tenant.",
|
||||
"tags": [
|
||||
"referrals"
|
||||
],
|
||||
@@ -100,7 +100,7 @@
|
||||
"post": {
|
||||
"operationId": "post_v1_referrals_claim",
|
||||
"summary": "Records that the caller's org signed up through a referral code.",
|
||||
"description": "Records that the caller's org signed up through a referral code.\n\nThe REFEREE is the validated caller, never a client field, and the referrer is\nresolved from the code — so a caller can only ever attach THEMSELVES to someone\nelse's code. Referring yourself is 400 and an unknown code is 404.\n\nIt is idempotent and first-touch: an org can be referred once, ever. A repeat\ncall returns the referral already on file with created=false and 200, where the\nfirst call answers 201.\n\nRecording a referral grants nothing. Both bonuses are granted later, when the\nreferee actually makes metered spend — see GET /v1/referrals and\nPOST /v1/admin/referrals/sweep.",
|
||||
"description": "Records that the caller's org signed up through a referral code.\n\nThe REFEREE is the validated caller, never a client field, and the referrer is\nresolved from the code — so a caller can only ever attach THEMSELVES to someone\nelse's code. Referring yourself is 400 and an unknown code is 404.\n\nIt is idempotent and first-touch: an org can be referred once, ever. A repeat\ncall returns the referral already on file with created=false and 200, where the\nfirst call answers 201.\n\nRecording a referral grants nothing, and neither does anything downstream of it:\nthe edge later advances to qualified when the referee makes metered spend\n(POST /v1/admin/referrals/sweep), and that is the end of it. No credit is ever\nissued from this package.",
|
||||
"tags": [
|
||||
"referrals"
|
||||
],
|
||||
@@ -137,7 +137,7 @@
|
||||
"adminBonusDirectory": {
|
||||
"properties": {
|
||||
"referrals": {
|
||||
"description": "Referrals is every referral in the ledger, both orgs exposed.",
|
||||
"description": "Referrals is every referral in the directory, both orgs exposed.",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/adminReferralView"
|
||||
},
|
||||
@@ -177,10 +177,6 @@
|
||||
"description": "CreatedAt is when the referral was recorded, as a Unix timestamp.",
|
||||
"type": "integer"
|
||||
},
|
||||
"creditedAt": {
|
||||
"description": "CreditedAt is when the bonuses were latched and granted, as a Unix\ntimestamp; 0 until they are.",
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID is the referral's handle.",
|
||||
"type": "string"
|
||||
@@ -189,32 +185,16 @@
|
||||
"description": "QualifiedAt is when the referee first made metered spend, as a Unix\ntimestamp; 0 while still pending.",
|
||||
"type": "integer"
|
||||
},
|
||||
"refereeGrantCents": {
|
||||
"description": "RefereeGrantCents is what the referee was granted, in USD cents; 0 until the\nreferral is credited.",
|
||||
"type": "integer"
|
||||
},
|
||||
"refereeOrg": {
|
||||
"description": "RefereeOrg is the org that signed up with it.",
|
||||
"type": "string"
|
||||
},
|
||||
"refereeTxn": {
|
||||
"description": "RefereeTxn is the commerce ledger transaction that carried the referee's\ngrant, omitted until one exists.",
|
||||
"type": "string"
|
||||
},
|
||||
"referrerGrantCents": {
|
||||
"description": "ReferrerGrantCents is what the referrer was granted, in USD cents; 0 until\nthe referral is credited.",
|
||||
"type": "integer"
|
||||
},
|
||||
"referrerOrg": {
|
||||
"description": "ReferrerOrg is the org whose code was used.",
|
||||
"type": "string"
|
||||
},
|
||||
"referrerTxn": {
|
||||
"description": "ReferrerTxn is the commerce ledger transaction that carried the referrer's\ngrant, omitted until one exists.",
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"description": "Status is the referral's lifecycle state: \"signup\", \"qualified\" or\n\"credited\".",
|
||||
"description": "Status is the referral's lifecycle state: \"signup\" or \"qualified\".",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -222,16 +202,8 @@
|
||||
},
|
||||
"adminSummary": {
|
||||
"properties": {
|
||||
"credited": {
|
||||
"description": "Credited is how many have paid both bonuses.",
|
||||
"type": "integer"
|
||||
},
|
||||
"grantedCents": {
|
||||
"description": "GrantedCents is the promo credit granted across BOTH sides of every\nreferral, in USD cents — the program's total liability to date.",
|
||||
"type": "integer"
|
||||
},
|
||||
"qualified": {
|
||||
"description": "Qualified is how many have qualified but are not yet credited.",
|
||||
"description": "Qualified is how many referees have made metered spend.",
|
||||
"type": "integer"
|
||||
},
|
||||
"signup": {
|
||||
@@ -239,7 +211,7 @@
|
||||
"type": "integer"
|
||||
},
|
||||
"total": {
|
||||
"description": "Total is every referral in the ledger.",
|
||||
"description": "Total is every referral in the directory.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
@@ -285,14 +257,6 @@
|
||||
"description": "CreatedAt is when the referral was recorded, as a Unix timestamp.",
|
||||
"type": "integer"
|
||||
},
|
||||
"creditedAt": {
|
||||
"description": "CreditedAt is when the bonuses were latched and granted, as a Unix\ntimestamp; 0 until they are. It is the at-most-once latch.",
|
||||
"type": "integer"
|
||||
},
|
||||
"creditsCents": {
|
||||
"description": "CreditsCents is what I earned from this referral, in USD cents. It is 0\nuntil the referee qualifies.",
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID is the referral's handle.",
|
||||
"type": "string"
|
||||
@@ -306,7 +270,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"description": "Status is the referral's lifecycle state: \"signup\" until the referee\nmakes metered spend, then \"qualified\", then \"credited\".",
|
||||
"description": "Status is the referral's lifecycle state: \"signup\" until the referee makes\nmetered spend, then \"qualified\".",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -322,40 +286,24 @@
|
||||
"$ref": "#/components/schemas/statusCounts",
|
||||
"description": "Counts tallies this org's referrals by status."
|
||||
},
|
||||
"creditsEarnedCents": {
|
||||
"description": "CreditsEarnedCents is the total promo credit this org has earned as the\nREFERRER, in USD cents.",
|
||||
"type": "integer"
|
||||
},
|
||||
"link": {
|
||||
"description": "Link is the shareable signup link carrying the code, on the brand's own host.",
|
||||
"type": "string"
|
||||
},
|
||||
"refereeBonusCents": {
|
||||
"description": "RefereeBonusCents is what a referee is granted on qualification, in USD cents.",
|
||||
"type": "integer"
|
||||
},
|
||||
"referrals": {
|
||||
"description": "Referrals is one row per org that signed up with this code.",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/myReferralView"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"referrerBonusCents": {
|
||||
"description": "ReferrerBonusCents is what the referrer is granted when a referee qualifies,\nin USD cents.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"statusCounts": {
|
||||
"properties": {
|
||||
"credited": {
|
||||
"description": "Credited is how many referrals have paid both bonuses.",
|
||||
"type": "integer"
|
||||
},
|
||||
"qualified": {
|
||||
"description": "Qualified is how many referees have spent but are not yet credited.",
|
||||
"description": "Qualified is how many referees have made metered spend.",
|
||||
"type": "integer"
|
||||
},
|
||||
"signup": {
|
||||
@@ -388,8 +336,8 @@
|
||||
},
|
||||
"sweepResult": {
|
||||
"properties": {
|
||||
"credited": {
|
||||
"description": "Credited is how many of those referrals qualified on this pass and were\ngranted their bonuses.",
|
||||
"qualified": {
|
||||
"description": "Qualified is how many of those referrals qualified on this pass.",
|
||||
"type": "integer"
|
||||
},
|
||||
"swept": {
|
||||
|
||||
Reference in New Issue
Block a user