Documentation
¶
Overview ¶
Package api defines the go-ledger HTTP API using huma, which generates the OpenAPI 3.1 spec directly from the typed operation handlers. The spec is a byproduct of the same handlers that serve traffic, so the published docs and the API cannot drift. huma runs on a chi router via the humachi adapter, so chi owns routing and middleware while huma owns operations, validation, and problem+json errors.
Index ¶
- Constants
- func New(router chi.Router, deps Deps) huma.API
- func RegisterPlayground(router chi.Router)
- func SpecYAML() ([]byte, error)
- type AccountAuditInput
- type AccountAuditOutput
- type AccountBalanceBody
- type AccountBody
- type AccountOutput
- type AccountTreeNode
- type AccountTreeOutput
- type AccountsOutput
- type ApprovePendingOutput
- type AuditAnchorBody
- type AuditEntryBody
- type AuditHeadBody
- type AuditListOutput
- type AuditLogInput
- type AuditLogOutput
- type BalanceOutput
- type ConvertInput
- type ConvertOutput
- type ConvertResponseBody
- type CreateAccountInput
- type CreateFXRateInput
- type CreateTenantInput
- type CreateTransactionInput
- type CreateTransactionOutput
- type CreateWebhookSubscriptionInput
- type CreateWebhookSubscriptionOutput
- type CurrencyBalanceBody
- type Deps
- type DisputeBody
- type DisputeOutput
- type EmptyOutput
- type FXDetailBody
- type FXMarkupBody
- type FXRateBody
- type FXRateOutput
- type GetFXMarkupInput
- type GetFXMarkupOutput
- type HealthOutput
- type IssueKeyInput
- type IssueKeyOutput
- type IssuedKeyBody
- type IssuedWebhookSubscriptionBody
- type KeyBody
- type ListAccountsInput
- type ListDisputesInput
- type ListDisputesOutput
- type ListFXRatesInput
- type ListFXRatesOutput
- type ListKeysInput
- type ListKeysOutput
- type ListTenantsOutput
- type ListWebhookSubscriptionsInput
- type ListWebhookSubscriptionsOutput
- type OpenDisputeInput
- type PendingBody
- type PendingListInput
- type PendingListOutput
- type PendingOutput
- type PostingBody
- type PostingInput
- type RejectPendingInput
- type ResolveDisputeInput
- type ReverseTransactionOutput
- type SetAccountParentInput
- type SetAccountStatusInput
- type SetFXMarkupInput
- type SetFXMarkupOutput
- type SetTenantPolicyInput
- type SetTenantStatusInput
- type ShredTenantPIIInput
- type StatementEntryBody
- type StatementExportInput
- type StatementExportOutput
- type StatementInput
- type StatementOutput
- type TenantBody
- type TenantOutput
- type TransactionBody
- type TransactionExportInput
- type TransactionExportOutput
- type TransactionListInput
- type TransactionListOutput
- type TransactionOutput
- type TrialBalanceOutput
- type VerifyAuditInput
- type VerifyAuditOutput
- type WebhookSubscriptionBody
Constants ¶
const APIVersion = "0.2.0"
APIVersion is the OpenAPI info.version. It is a deliberately stable semantic version, never a build SHA or timestamp, so the committed api/openapi.yaml snapshot stays reproducible across machines and CI.
const MaxRequestBodyBytes int64 = 64 * 1024
MaxRequestBodyBytes bounds every request body, so one request can no longer exhaust memory before validation runs (see ADR-012, "Input hardening"). cmd/server applies the same limit as a router-level maxBodyBytes middleware that wraps huma, so it runs first and enforces the hard cutoff. The write operations below also set MaxBodyBytes as their own huma option, so huma's own accounting returns a clean 413 for the same limit when it is the one reading the body, such as in tests that exercise the huma router directly without the wrapping middleware.
Variables ¶
This section is empty.
Functions ¶
func New ¶
New builds the huma API on the given chi router, registers every operation, and returns the API. huma also serves /openapi.json, /openapi.yaml, and /schemas/ from the same router. The interactive playground is registered separately by RegisterPlayground.
func RegisterPlayground ¶
RegisterPlayground serves the interactive Scalar playground at /playground and its embedded JS asset. The spec it renders comes from huma at /openapi.json, so the playground always reflects the live API.
Types ¶
type AccountAuditInput ¶
type AccountAuditInput struct {
ID string `path:"id" format:"uuid" doc:"Account id"`
Limit int `query:"limit" default:"50" minimum:"1" maximum:"200" doc:"Max entries per page"`
Cursor string `query:"cursor" doc:"Opaque cursor from a previous page's next_cursor"`
}
AccountAuditInput is the account audit request: a path id plus keyset paging.
type AccountAuditOutput ¶
type AccountAuditOutput struct {
Body struct {
Entries []AuditEntryBody `json:"entries"`
NextCursor *string `json:"next_cursor" doc:"Cursor for the next page, or null if this is the last page"`
}
}
AccountAuditOutput is one page of an account's audit log.
type AccountBalanceBody ¶
type AccountBalanceBody struct {
AccountID string `json:"account_id"`
Name string `json:"name"`
Type string `json:"type" doc:"One of: asset, liability, equity, income, expense"`
Currency string `json:"currency"`
IsSystem bool `` /* 159-byte string literal not displayed */
Balance int64 `json:"balance" doc:"Signed derived balance in minor units"`
// RolledUpBalance is Balance summed over this account and every
// descendant in its hierarchy (ADR-023). It is additive display only:
// the balance proof (the currency net/imbalance fields above) is
// computed from own balances alone, since a rollup double-counts a
// parent and its descendants by design.
RolledUpBalance int64 `json:"rolled_up_balance" doc:"Balance including all descendants, in minor units"`
}
AccountBalanceBody is one account's identity plus its derived balance in the trial balance report. IsSystem accounts (FX clearing accounts, ADR-014) are included, clearly marked, since they hold the FX position and are part of the balance proof.
type AccountBody ¶
type AccountBody struct {
ID string `json:"id" doc:"Account id (UUID)"`
Name string `json:"name"`
Type string `json:"type" doc:"One of: asset, liability, equity, income, expense"`
Currency string `json:"currency" doc:"ISO 4217 code, e.g. USD"`
// Status is the account's lifecycle gate (Task 5.5, audit A1.5): active,
// frozen, or closed. See POST /v1/accounts/{id}/status to change it.
Status string `json:"status" doc:"One of: active, frozen, closed"`
// MinBalance is the optional floor enforced on this account's derived
// balance, in minor units (Task 5.5, audit A1.5). null means no floor.
MinBalance *int64 `json:"min_balance,omitempty" doc:"Optional minimum balance floor, in minor units. Omitted when unset."`
// PartyReference and PartyType are optional linkage metadata for an
// external KYC/party system (Task 6.1, audit A9.1). Both are omitted
// when unset; this package does not validate them beyond a length cap,
// the actual party/KYC system is external.
PartyReference *string `` /* 134-byte string literal not displayed */
PartyType *string `json:"party_type,omitempty" doc:"Optional free-text party classification, e.g. individual or business. Omitted when unset."`
// ParentID is this account's parent in the hierarchy (ADR-023), null for a root.
ParentID *string `json:"parent_id" nullable:"true" doc:"Parent account id, null for a root"`
}
AccountBody is the JSON shape of an account in responses.
type AccountOutput ¶
type AccountOutput struct {
Body AccountBody
}
AccountOutput wraps an account in a response.
type AccountTreeNode ¶ added in v1.0.0
type AccountTreeNode struct {
AccountBody
Depth int `json:"depth"`
OwnBalance int64 `json:"own_balance" doc:"This account's own balance, in minor units"`
RolledUpBalance int64 `json:"rolled_up_balance" doc:"Balance including all descendants, in minor units"`
}
AccountTreeNode is one row of the account hierarchy: the account, its depth from a root, its own balance, and the balance rolled up over its whole subtree (ADR-023).
type AccountTreeOutput ¶ added in v1.0.0
type AccountTreeOutput struct {
Body struct {
Accounts []AccountTreeNode `json:"accounts"`
}
}
AccountTreeOutput is the list-account-tree response (ADR-023).
type AccountsOutput ¶
type AccountsOutput struct {
Body struct {
Accounts []AccountBody `json:"accounts"`
}
}
AccountsOutput is the list-accounts response.
type ApprovePendingOutput ¶ added in v1.0.0
type ApprovePendingOutput struct {
Body TransactionBody
}
ApprovePendingOutput is the approve response: the transaction the approval posted. Approving an already-approved pending is idempotent (ApprovalService. Approve's own doc comment) and returns the SAME transaction rather than posting a second one, still with status 200.
type AuditAnchorBody ¶
type AuditAnchorBody struct {
ChainSeq int64 `json:"chain_seq"`
RowHash string `json:"row_hash"`
CreatedAt time.Time `json:"created_at"`
}
AuditAnchorBody is the tenant's most recently recorded off-box anchor (Task 5.3, migration 0025): the chain_seq and row_hash the anchor job read and logged at CreatedAt.
type AuditEntryBody ¶
type AuditEntryBody struct {
ID string `json:"id"`
Action string `json:"action"`
TransactionID string `json:"transaction_id"`
Actor string `json:"actor"`
Before any `json:"before,omitempty"`
After any `json:"after"`
CreatedAt time.Time `json:"created_at"`
}
AuditEntryBody is one audit row in a response. Before and After are raw JSON (Before is omitted for a create). Amounts inside After are minor units.
type AuditHeadBody ¶
AuditHeadBody is the tenant's current chain head (Task 5.3): the chain_seq and row_hash of its latest audit_log row.
type AuditListOutput ¶
type AuditListOutput struct {
Body struct {
Entries []AuditEntryBody `json:"entries"`
}
}
AuditListOutput is a list of audit entries.
type AuditLogInput ¶ added in v1.0.0
type AuditLogInput struct {
Limit int `query:"limit" default:"50" minimum:"1" maximum:"200" doc:"Max entries per page"`
Cursor string `query:"cursor" doc:"Opaque cursor from a previous page's next_cursor"`
}
AuditLogInput is the tenant-wide audit log request: keyset paging only.
type AuditLogOutput ¶ added in v1.0.0
type AuditLogOutput struct {
Body struct {
Entries []AuditEntryBody `json:"entries"`
NextCursor *string `json:"next_cursor" doc:"Cursor for the next page, or null if this is the last page"`
}
}
AuditLogOutput is one page of the tenant's whole audit log.
type BalanceOutput ¶
type BalanceOutput struct {
Body struct {
AccountID string `json:"account_id"`
Amount int64 `json:"amount" doc:"Signed balance in minor units (e.g. cents)"`
Currency string `json:"currency"`
}
}
BalanceOutput is the account balance response.
type ConvertInput ¶
type ConvertInput struct {
// See CreateTransactionInput's IdempotencyKey field for why this is not
// required:"true": a missing header should fail our explicit 400 check
// below with a clear message, not huma's generic 422.
IdempotencyKey string `` /* 405-byte string literal not displayed */
Body struct {
FromAccount string `json:"from_account" format:"uuid" doc:"Account to debit, in its own currency"`
ToAccount string `json:"to_account" format:"uuid" doc:"Account to credit; its currency is the conversion's destination currency"`
SourceAmount int64 `json:"source_amount" doc:"Amount to convert, in minor units of the from account's currency; must be positive"`
}
}
ConvertInput is the convert request body: the account to debit, the account to credit, and the amount to convert, in the from account's own currency. The destination currency always comes from the to account (ADR-014 decision 3): a client cannot supply a rate or target currency directly, since a client-controlled rate would be a money-theft vector.
type ConvertOutput ¶
type ConvertOutput struct {
Status int
Replayed bool `header:"Idempotent-Replayed"`
Body ConvertResponseBody
}
ConvertOutput is the convert response. Replayed mirrors CreateTransactionOutput: true when an Idempotency-Key matched an earlier convert request and the original conversion was returned unchanged. Status is 201 (converted) or 202 (ADR-025: held for approval); see CreateTransactionOutput's Status doc comment for the mechanism.
type ConvertResponseBody ¶
type ConvertResponseBody struct {
Transaction TransactionBody `json:"transaction"`
FX FXDetailBody `json:"fx"`
Pending *PendingBody `` /* 180-byte string literal not displayed */
}
ConvertResponseBody is the JSON shape of a successful convert: the posted transaction (source account, source clearing, destination clearing, destination account) plus the FX rate detail actually applied. Pending is set instead (ADR-025) when the conversion exceeded the approval threshold and was held rather than posted; Transaction and FX are left zero in that case.
type CreateAccountInput ¶
type CreateAccountInput struct {
Body struct {
Name string `json:"name" minLength:"1" maxLength:"200" doc:"Human-readable account name"`
Type string `json:"type" enum:"asset,liability,equity,income,expense" doc:"Fundamental account class"`
Currency string `` /* 134-byte string literal not displayed */
MinBalance *int64 `json:"min_balance,omitempty" doc:"Optional minimum balance floor, in minor units. Omit for no floor."`
// PartyReference and PartyType (Task 6.1, audit A9.1) are optional
// linkage metadata for an external KYC/party system: an id and a
// free-text classification (e.g. "individual", "business"). Neither
// is validated beyond a length cap; the party/KYC system itself is
// external and out of scope for this service.
PartyReference *string `` /* 153-byte string literal not displayed */
PartyType *string `json:"party_type,omitempty" maxLength:"256" doc:"Optional free-text party classification, e.g. individual or business."`
// ParentID (ADR-023) optionally nests the new account under an
// existing one at creation time. The parent must be the same tenant
// and currency; an unknown parent or a hierarchy violation is
// rejected with 422 (see toHumaErr).
ParentID *string `json:"parent_id,omitempty" format:"uuid" doc:"Optional parent account id. The parent must be the same tenant and currency."`
}
}
CreateAccountInput is the create-account request body. Currency is optional (omitempty): when the caller omits it, the server stamps the deployment's configured DEFAULT_CURRENCY instead (ADR-014). MinBalance is also optional (Task 5.5, audit A1.5): every account is created active, so there is no status field here (see POST /v1/accounts/{id}/status).
type CreateFXRateInput ¶ added in v1.0.0
type CreateFXRateInput struct {
Body struct {
TenantID string `json:"tenant_id,omitempty" doc:"Tenant to scope this rate to. Omit or empty for the global default."`
Base string `json:"base" pattern:"^[A-Z]{3}$" doc:"Base currency, 3 uppercase letters"`
Quote string `json:"quote" pattern:"^[A-Z]{3}$" doc:"Quote currency, 3 uppercase letters"`
MidRateE8 int64 `json:"mid_rate_e8" minimum:"1" doc:"Mid rate, quote units per base unit, scaled by 1e8"`
SpreadBps *int32 `` /* 145-byte string literal not displayed */
}
}
CreateFXRateInput is the create-fx-rate request body.
type CreateTenantInput ¶
type CreateTenantInput struct {
Body struct {
Name string `json:"name" minLength:"1" maxLength:"200" doc:"Human-readable tenant name"`
}
}
CreateTenantInput is the create-tenant request body.
type CreateTransactionInput ¶
type CreateTransactionInput struct {
// The schema field is left optional (not required:"true") so a missing
// header fails our explicit 400 check below with a clear message,
// rather than huma's generic schema-validation 422.
IdempotencyKey string `` /* 407-byte string literal not displayed */
Body struct {
Currency string `json:"currency" pattern:"^[A-Z]{3}$" doc:"ISO 4217 code shared by every posting"`
Postings []PostingInput `json:"postings" minItems:"2" maxItems:"100" doc:"Two or more legs that must sum to zero"`
// Reference is an optional external id for reconciliation (Task 4.3,
// audit A1.3), for example an upstream payment processor's charge id
// or a bank statement line. Must be unique for this tenant when
// supplied: reusing one already in use returns 409 Conflict.
Reference *string `` /* 155-byte string literal not displayed */
// EffectiveAt is an optional value date (Task 4.3, audit A1.3),
// distinct from when the row is actually written. Omitted, it
// defaults to the post time.
EffectiveAt *time.Time `` /* 143-byte string literal not displayed */
}
}
CreateTransactionInput is the post-transaction request body.
type CreateTransactionOutput ¶
type CreateTransactionOutput struct {
Status int
Replayed bool `header:"Idempotent-Replayed"`
Body struct {
TransactionBody
Pending *PendingBody `` /* 226-byte string literal not displayed */
}
}
CreateTransactionOutput is the post-transaction response. Replayed is surfaced as the Idempotent-Replayed header: true when an Idempotency-Key matched an earlier request and the original transaction was returned unchanged.
Status overrides the operation's DefaultStatus (201) at runtime: huma reads a field literally named Status on an output struct and uses it as the actual response code instead (see huma's processOutputType), so every return path through create-transaction sets it explicitly, either 201 (the transaction posted) or 202 (ADR-025: postings exceeded the tenant's approval threshold and were held as a pending instead; Body.Pending is set in that case and the TransactionBody fields are left zero).
type CreateWebhookSubscriptionInput ¶
type CreateWebhookSubscriptionInput struct {
Body struct {
TenantID string `json:"tenant_id" format:"uuid" doc:"Tenant this subscription belongs to"`
URL string `json:"url" doc:"Callback URL; must be an absolute http or https URL"`
EventTypes []string `` /* 149-byte string literal not displayed */
}
}
CreateWebhookSubscriptionInput is the create-subscription request body.
type CreateWebhookSubscriptionOutput ¶
type CreateWebhookSubscriptionOutput struct {
Body IssuedWebhookSubscriptionBody
}
CreateWebhookSubscriptionOutput wraps a newly created subscription.
type CurrencyBalanceBody ¶
type CurrencyBalanceBody struct {
Currency string `json:"currency"`
Net int64 `` /* 136-byte string literal not displayed */
Imbalance bool `json:"imbalance" doc:"true if net is nonzero, which should never happen"`
}
CurrencyBalanceBody is one currency's net posted total in the trial balance report (Task 6.3, audit A9.2): the double-entry balance proof. In a correct ledger Imbalance is always false; a true value names a genuine invariant violation.
type Deps ¶
type Deps struct {
Accounts *ledger.AccountService
Transactions *ledger.TransactionService
Audit *ledger.AuditService
// Admin backs the /v1/admin operations (Task 2.2b): onboarding a tenant
// and issuing/rotating/revoking its api keys. Every /v1/admin/ path
// already requires domain.ScopeAdmin via auth.HumaMiddleware
// (RequiredHTTPScope), so the operations themselves add no further auth.
Admin *admin.Service
// Reports backs GET /v1/reports/trial-balance (Task 6.3, audit A9.2).
Reports *ledger.ReportService
// Disputes backs the /v1/disputes operations (Task 6.3, audit A9.2): a
// dispute/chargeback data model built on the reversal primitive.
Disputes *ledger.DisputeService
// Approvals backs the /v1/pending operations (Task 9, ADR-025): listing,
// reading, approving, rejecting, and cancelling a held (over-threshold)
// transaction. Optional like the other services; a zero Deps leaves it
// nil, and the approve/reject/cancel/list/get operations are still
// registered (they are always part of the spec), but call a nil
// *ledger.ApprovalService if ever invoked without one configured, the same
// tradeoff every other optional Deps field already makes.
Approvals *ledger.ApprovalService
// FX backs the /v1/admin/fx operations (ADR-020): live rate and markup
// config. Optional like the other services; a zero Deps leaves it nil.
FX *fx.AdminService
// DemoMode, when true, makes create-tenant prefill the new tenant with the
// demo's starter FX rates and 1 percent markup (fx.PrefillDemoRates). It is
// demo-only convenience; a zero Deps (production) leaves it false and no
// prefill happens.
DemoMode bool
Auth *auth.Resolver
// RateLimiter, if set, is registered immediately after the auth middleware
// (see New). It is optional: a zero Deps (spec generation, and tests that
// only exercise unauthenticated routes) leaves it nil and no rate-limit
// middleware is registered at all, rather than one that always fails open.
RateLimiter *auth.Limiter
// NegativeThrottle, if set, is passed straight through to
// auth.HumaMiddleware (Task 5.2, audit A2.5/A6.4): it caps failed-auth
// attempts per client IP BEFORE the resolver's database lookup runs, so a
// garbage-API-key flood cannot exhaust the connection pool. Optional for
// the same reason RateLimiter is: a nil value skips the gate entirely
// rather than failing closed.
NegativeThrottle *auth.NegativeThrottle
// Revision is the running binary's build revision (git short SHA; "dev"
// outside a real build), surfaced additively on GET /healthz (Task
// 5.6a). A zero value (spec generation, and tests that never care) just
// serializes as an empty string; it never affects the "ok" status the
// deploy health check depends on.
Revision string
}
Deps are the services the operations call, plus the auth resolver every /v1 request goes through to derive its tenant, and the per-key rate limiter applied after auth. The tenant is never a request field: HumaMiddleware resolves it from the bearer key and handlers read it back with auth.TenantFromContext. Spec generation passes a zero Deps because the handler bodies (and the middleware, which only runs on a served request) are never invoked while serializing the schema.
type DisputeBody ¶
type DisputeBody struct {
ID string `json:"id"`
TransactionID string `json:"transaction_id"`
Status string `json:"status" doc:"One of: open, resolved_reversed, resolved_rejected"`
Reason string `json:"reason"`
ResolutionTransactionID *string `` /* 134-byte string literal not displayed */
CreatedAt time.Time `json:"created_at"`
ResolvedAt *time.Time `json:"resolved_at,omitempty" doc:"When the dispute was resolved. Unset while status is open."`
}
DisputeBody is the JSON shape of a dispute in responses.
type DisputeOutput ¶
type DisputeOutput struct {
Body DisputeBody
}
DisputeOutput wraps a dispute in a response.
type EmptyOutput ¶
type EmptyOutput struct{}
EmptyOutput is the huma output for admin operations that report success with no response body, just a 204 No Content status.
type FXDetailBody ¶
type FXDetailBody struct {
SourceAmount int64 `json:"source_amount" doc:"Amount converted, in minor units of the source account's currency"`
ConvertedAmount int64 `json:"converted_amount" doc:"Amount credited, in minor units of the destination account's currency"`
MidRateE8 int64 `json:"mid_rate_e8" doc:"Mid rate (destination units per source unit), scaled by 1e8, before the spread"`
AppliedE8 int64 `json:"applied_e8" doc:"Informational, spread-adjusted rate actually reflected in converted_amount"`
SpreadBps int32 `json:"spread_bps" doc:"Spread widened against the customer, in basis points"`
RateSource string `json:"rate_source" doc:"Where the mid rate came from"`
EffectiveAt time.Time `json:"effective_at" doc:"When the applied fx_rates row went live"`
RateID int64 `json:"rate_id" doc:"The fx_rates row id the mid rate and spread were read from"`
}
FXDetailBody is the applied-rate detail for a cross-currency convert: the requested and converted amounts, the mid rate and spread applied, and where that rate came from (ADR-014 decision 7). It only appears on the convert response: GET /v1/transactions/{id} reports per-posting currency only, not the fx_* snapshot (ADR-014, "Out of scope for v1").
type FXMarkupBody ¶ added in v1.0.0
type FXMarkupBody struct {
DefaultSpreadBps *int32 `` /* 137-byte string literal not displayed */
EffectiveAt time.Time `json:"effective_at"`
}
FXMarkupBody is the JSON shape of a markup default in the set-fx-markup and get-fx-markup responses. DefaultSpreadBps is null when the row is a CLEAR (see SetFXMarkupInput).
type FXRateBody ¶ added in v1.0.0
type FXRateBody struct {
TenantID string `json:"tenant_id" doc:"Tenant id, empty for the global default"`
Base string `json:"base"`
Quote string `json:"quote"`
MidRateE8 int64 `json:"mid_rate_e8"`
SpreadBps *int32 `json:"spread_bps" doc:"Per-pair override, null when the row uses the markup default"`
EffectiveSpreadBps int32 `json:"effective_spread_bps" doc:"Spread a conversion would actually apply: override, else default, else 0"`
Source string `json:"source"`
EffectiveAt time.Time `json:"effective_at"`
}
FXRateBody is the JSON shape of an FX rate in the create-fx-rate and list-fx-rates responses.
type FXRateOutput ¶ added in v1.0.0
type FXRateOutput struct{ Body FXRateBody }
FXRateOutput wraps a rate in a create-fx-rate response.
type GetFXMarkupInput ¶ added in v1.0.0
type GetFXMarkupInput struct {
TenantID string `query:"tenant_id" doc:"Tenant whose override to return alongside the global default. Omit for global only."`
}
GetFXMarkupInput is the get-fx-markup request: an optional tenant filter.
type GetFXMarkupOutput ¶ added in v1.0.0
type GetFXMarkupOutput struct {
Body struct {
Global nullableFXMarkupBody `json:"global"`
Tenant nullableFXMarkupBody `json:"tenant"`
}
}
GetFXMarkupOutput is the get-fx-markup response.
type HealthOutput ¶
type HealthOutput struct {
Body struct {
Status string `json:"status" example:"ok" doc:"Liveness status, \"ok\" while the service is serving"`
Revision string `` /* 134-byte string literal not displayed */
}
}
HealthOutput is the response for the liveness check. huma derives the response schema from the Body field's type. Revision is additive (Task 5.6a): status stays exactly "ok" so the deploy health check's grep for it keeps working unchanged; revision only adds a field callers can read to see which build is actually serving.
type IssueKeyInput ¶
type IssueKeyInput struct {
Body struct {
TenantID string `json:"tenant_id" format:"uuid" doc:"Tenant this key acts as"`
Name string `json:"name" minLength:"1" maxLength:"200" doc:"Human-readable label for this key"`
Scopes []string `json:"scopes" minItems:"1" doc:"One or more of: read, post, admin"`
ExpiresAt *time.Time `json:"expires_at,omitempty" doc:"Optional expiry. Omit for a key that never expires."`
}
}
IssueKeyInput is the issue-key request body.
type IssueKeyOutput ¶
type IssueKeyOutput struct {
Body IssuedKeyBody
}
IssueKeyOutput wraps a newly issued or rotated key.
type IssuedKeyBody ¶
type IssuedKeyBody struct {
ID string `json:"id"`
TenantID string `json:"tenant_id"`
Name string `json:"name"`
Scopes []string `json:"scopes"`
ExpiresAt *time.Time `json:"expires_at" doc:"Null if the key never expires"`
Plaintext string `` /* 145-byte string literal not displayed */
}
IssuedKeyBody is the JSON shape of a just-issued or just-rotated key: its metadata plus the plaintext credential itself, shown exactly once. Store it now: only its hash is ever persisted, so it cannot be recovered again.
type IssuedWebhookSubscriptionBody ¶
type IssuedWebhookSubscriptionBody struct {
ID string `json:"id"`
TenantID string `json:"tenant_id"`
URL string `json:"url"`
EventTypes []string `json:"event_types"`
Secret string `` /* 198-byte string literal not displayed */
}
IssuedWebhookSubscriptionBody is the JSON shape of a just-created webhook subscription: its metadata plus the signing secret, shown exactly once. Store it now: only the secret's own value is ever persisted (Task 4.1, unlike an api key it is not hashed, since the delivery worker must read it back to sign every outbound payload), but it is still never returned again by any read after this one response.
type KeyBody ¶
type KeyBody struct {
ID string `json:"id" doc:"Key id (UUID)"`
TenantID string `json:"tenant_id"`
Name string `json:"name"`
Scopes []string `json:"scopes"`
ExpiresAt *time.Time `json:"expires_at" doc:"Null if the key never expires"`
LastUsedAt *time.Time `json:"last_used_at" doc:"Null if the key has never been used"`
RevokedAt *time.Time `json:"revoked_at" doc:"Null if the key is still live"`
CreatedAt time.Time `json:"created_at"`
}
KeyBody is the JSON shape of an api key in the list-keys response. It never carries the plaintext: that is never stored anywhere to return, only shown once, at issue or rotate time, in IssuedKeyBody below.
type ListAccountsInput ¶
type ListAccountsInput struct {
Limit int `query:"limit" default:"100" minimum:"1" maximum:"500" doc:"Max accounts to return"`
}
ListAccountsInput is the list-accounts request: a capped limit, no cursor.
type ListDisputesInput ¶
type ListDisputesInput struct {
Status string `query:"status" doc:"Optional filter: one of open, resolved_reversed, resolved_rejected. Omitted returns every status."`
Limit int `query:"limit" default:"50" minimum:"1" maximum:"200" doc:"Max disputes per page"`
Cursor string `query:"cursor" doc:"Opaque cursor from a previous page's next_cursor"`
}
ListDisputesInput is the list-disputes request: an optional status filter plus keyset paging (Task 6.3, audit A9.2). Status is validated by hand in the handler (an empty string means "no filter"), the same convention TransactionListInput's Reference field uses, rather than an enum schema tag, which cannot express "valid value OR empty".
type ListDisputesOutput ¶
type ListDisputesOutput struct {
Body struct {
Disputes []DisputeBody `json:"disputes"`
NextCursor *string `json:"next_cursor" doc:"Cursor for the next page, or null if this is the last page"`
}
}
ListDisputesOutput is one page of a tenant's disputes.
type ListFXRatesInput ¶ added in v1.0.0
type ListFXRatesInput struct {
TenantID string `query:"tenant_id" doc:"Tenant whose overrides to include alongside the global defaults. Omit for globals only."`
}
ListFXRatesInput is the list-fx-rates request: an optional tenant filter.
type ListFXRatesOutput ¶ added in v1.0.0
type ListFXRatesOutput struct {
Body struct {
Rates []FXRateBody `json:"rates"`
}
}
ListFXRatesOutput is the list-fx-rates response.
type ListKeysInput ¶
type ListKeysInput struct {
TenantID string `query:"tenant_id" format:"uuid" required:"true" doc:"Tenant to list keys for"`
}
ListKeysInput is the list-keys request: a required tenant filter.
type ListKeysOutput ¶
type ListKeysOutput struct {
Body struct {
Keys []KeyBody `json:"keys"`
}
}
ListKeysOutput is the list-keys response.
type ListTenantsOutput ¶
type ListTenantsOutput struct {
Body struct {
Tenants []TenantBody `json:"tenants"`
}
}
ListTenantsOutput is the list-tenants response.
type ListWebhookSubscriptionsInput ¶
type ListWebhookSubscriptionsInput struct {
TenantID string `query:"tenant_id" format:"uuid" required:"true" doc:"Tenant to list webhook subscriptions for"`
}
ListWebhookSubscriptionsInput is the list-webhook-subscriptions request: a required tenant filter.
type ListWebhookSubscriptionsOutput ¶
type ListWebhookSubscriptionsOutput struct {
Body struct {
Subscriptions []WebhookSubscriptionBody `json:"subscriptions"`
}
}
ListWebhookSubscriptionsOutput is the list-webhook-subscriptions response.
type OpenDisputeInput ¶
type OpenDisputeInput struct {
Body struct {
TransactionID string `json:"transaction_id" format:"uuid" doc:"The transaction being disputed; must belong to the caller's tenant"`
Reason string `json:"reason" minLength:"1" maxLength:"256" doc:"Free-text reason for the dispute"`
}
}
OpenDisputeInput is the open-dispute request body (Task 6.3, audit A9.2).
type PendingBody ¶ added in v1.0.0
type PendingBody struct {
ID string `json:"id"`
Kind string `json:"kind" doc:"Which write path this pending will replay on approval: post, convert, or reverse"`
// Status is the pending's own lifecycle state, not to be confused with
// the output struct's Status field elsewhere in this package (which sets
// the HTTP response code): this is a JSON body field.
Status string `json:"status" doc:"One of: pending, approved, rejected, cancelled, expired"`
ThresholdCurrency string `json:"threshold_currency" doc:"The currency whose configured threshold this transaction exceeded"`
ThresholdAmount int64 `json:"threshold_amount" doc:"The configured threshold amount, in minor units, this transaction exceeded"`
CreatedBy string `json:"created_by"`
CreatedAt time.Time `json:"created_at"`
DecidedBy *string `json:"decided_by,omitempty" doc:"Set once a decision (approve, reject, cancel, or the TTL sweep) has been made"`
DecidedAt *time.Time `json:"decided_at,omitempty"`
Reason *string `json:"reason,omitempty" doc:"Set when status is rejected"`
TransactionID *string `json:"transaction_id,omitempty" doc:"Set when status is approved: the transaction the approval posted"`
}
PendingBody is the JSON shape of a pending (over-threshold, held) transaction in responses (ADR-025). Payload itself is never exposed: it is an internal replay format (postPayloadBody/convertPayloadBody/ reversePayloadBody in internal/ledger), not a stable public contract.
type PendingListInput ¶ added in v1.0.0
type PendingListInput struct {
Status string `query:"status" doc:"Optional filter: one of pending, approved, rejected, cancelled, expired. Omitted returns every status."`
Limit int `query:"limit" default:"50" minimum:"1" maximum:"200" doc:"Max pending transactions per page"`
Cursor string `query:"cursor" doc:"Opaque cursor from a previous page's next_cursor"`
}
PendingListInput is the list-pending request: an optional status filter, validated by hand the same way ListDisputesInput's Status is (an empty string means "no filter", which an enum schema tag cannot express), plus keyset paging.
type PendingListOutput ¶ added in v1.0.0
type PendingListOutput struct {
Body struct {
Pending []PendingBody `json:"pending"`
NextCursor *string `json:"next_cursor" doc:"Cursor for the next page, or null if this is the last page"`
}
}
PendingListOutput is one page of the tenant's pending transactions.
type PendingOutput ¶ added in v1.0.0
type PendingOutput struct {
Body PendingBody
}
PendingOutput wraps a single pending transaction in a response.
type PostingBody ¶
type PostingBody struct {
AccountID string `json:"account_id"`
Amount int64 `json:"amount"`
Currency string `json:"currency"`
Description string `json:"description"`
}
PostingBody is one leg of a transaction in responses. Currency is the posting's own currency (ADR-014): a cross-currency (FX) transaction spans two currencies, so each leg is labeled with its own rather than inheriting one shared transaction-level currency.
type PostingInput ¶
type PostingInput struct {
AccountID string `json:"account_id" format:"uuid" doc:"Account this leg posts to"`
Amount int64 `json:"amount" doc:"Signed amount in minor units: positive debit, negative credit"`
Description string `json:"description,omitempty" maxLength:"256" doc:"Optional narration for this line"`
}
PostingInput is one leg of a transaction in the request body.
type RejectPendingInput ¶ added in v1.0.0
type RejectPendingInput struct {
ID string `path:"id" format:"uuid" doc:"Pending transaction id"`
Body struct {
Reason *string `json:"reason,omitempty" maxLength:"256" doc:"Optional free-text reason recorded on the pending"`
}
}
RejectPendingInput is the reject request: a path id plus an optional free-text reason recorded on the pending.
type ResolveDisputeInput ¶
type ResolveDisputeInput struct {
ID string `path:"id" format:"uuid" doc:"Dispute id"`
Body struct {
Action string `json:"action" enum:"reverse,reject" doc:"reverse: post a real reversal of the disputed transaction. reject: no money moves."`
}
}
ResolveDisputeInput is the resolve-dispute request: a path id plus the action to take.
type ReverseTransactionOutput ¶
type ReverseTransactionOutput struct {
Status int
AlreadyReversed bool `header:"Already-Reversed"`
Body struct {
TransactionBody
Pending *PendingBody `` /* 186-byte string literal not displayed */
}
}
ReverseTransactionOutput is the reverse-transaction response. AlreadyReversed mirrors CreateTransactionOutput's Replayed / ConvertOutput's Replayed: true when the original already had a reversal and the existing one was returned unchanged instead of a new one being posted. The response status is 201 whenever a reversal exists (new or already-reversed), or 202 (ADR-025) when the reversal itself exceeded the approval threshold and was held instead; see CreateTransactionOutput's Status doc comment for the mechanism.
type SetAccountParentInput ¶ added in v1.0.0
type SetAccountParentInput struct {
ID string `path:"id" format:"uuid" doc:"Account id"`
Body struct {
ParentID *string `json:"parent_id,omitempty" format:"uuid" doc:"New parent account id. Omit or send null to clear (make it a root)."`
}
}
SetAccountParentInput is the set-account-parent request: a path id plus the new (or cleared) parent id (ADR-023).
type SetAccountStatusInput ¶
type SetAccountStatusInput struct {
ID string `path:"id" format:"uuid" doc:"Account id"`
Body struct {
Status string `json:"status" enum:"active,frozen,closed" doc:"New account status"`
}
}
SetAccountStatusInput is the set-account-status request: a path id plus the new status (Task 5.5, audit A1.5).
type SetFXMarkupInput ¶ added in v1.0.0
type SetFXMarkupInput struct {
Body struct {
TenantID string `json:"tenant_id,omitempty" doc:"Tenant to scope this default to. Omit or empty for the global default."`
// DefaultSpreadBps is optional: omit it, or send it explicitly as
// null, to append a CLEARED row. For a tenant scope, a cleared row
// means the tenant drops its own override and follows the global
// default again; for the global scope it means no markup at all.
DefaultSpreadBps *int32 `` /* 234-byte string literal not displayed */
}
}
SetFXMarkupInput is the set-fx-markup request body.
type SetFXMarkupOutput ¶ added in v1.0.0
type SetFXMarkupOutput struct{ Body FXMarkupBody }
SetFXMarkupOutput wraps a markup default in a set-fx-markup response.
type SetTenantPolicyInput ¶
type SetTenantPolicyInput struct {
ID string `path:"id" format:"uuid" doc:"Tenant id"`
Body struct {
MaxTransactionAmount int64 `` /* 148-byte string literal not displayed */
DailyVolumeLimit int64 `` /* 150-byte string literal not displayed */
AllowedCurrencies []string `` /* 129-byte string literal not displayed */
}
}
SetTenantPolicyInput is the set-tenant-policy request (Task 2.4b, audit A3.4): a path id plus the full policy in the body. Every field is optional; an omitted field is 0/empty, meaning "no limit" for that guardrail (see domain.TenantPolicy). This call always writes the FULL policy the body describes, not a partial merge: omitting a field clears (unlimits) that guardrail rather than leaving a previous value in place.
type SetTenantStatusInput ¶
type SetTenantStatusInput struct {
ID string `path:"id" format:"uuid" doc:"Tenant id"`
Body struct {
Status string `json:"status" enum:"active,suspended,closed" doc:"New tenant status"`
}
}
SetTenantStatusInput is the set-tenant-status request: a path id plus the new status in the body.
type ShredTenantPIIInput ¶
type ShredTenantPIIInput struct {
ID string `path:"id" format:"uuid" doc:"Tenant id"`
Body struct {
Confirm bool `` /* 209-byte string literal not displayed */
}
}
ShredTenantPIIInput is the shred-pii request: a path id plus a required confirmation flag (Task 6.2, audit A9.3). Confirm must be exactly true: omitting it (the zero value, false) fails closed with a 400 rather than silently proceeding, since this call is irreversible (see admin.Service.ShredTenantPII's own doc comment). There is deliberately no way to pass this as a query parameter or header instead: a body field forces a caller to construct the request deliberately, not accidentally via a copy-pasted URL.
type StatementEntryBody ¶
type StatementEntryBody struct {
ID string `json:"id" doc:"Posting id"`
TransactionID string `json:"transaction_id"`
Amount int64 `json:"amount" doc:"Signed posting amount in minor units"`
RunningBalance int64 `json:"running_balance" doc:"Account balance as of this posting, in minor units"`
Description string `json:"description"`
CreatedAt time.Time `json:"created_at"`
}
StatementEntryBody is one line of a statement: a posting affecting the account, with the running balance as of that posting.
type StatementExportInput ¶
type StatementExportInput struct {
ID string `path:"id" format:"uuid" doc:"Account id"`
From string `query:"from" doc:"RFC3339 timestamp. Only postings created at or after this time."`
To string `query:"to" doc:"RFC3339 timestamp. Only postings created strictly before this time."`
Format string `` /* 142-byte string literal not displayed */
}
StatementExportInput is the export-account-statement request: a path id, an optional from/to created_at window (RFC3339, half-open like TransactionExportInput's), plus format (Task 6.3, audit A9.2). Unlike GET /v1/accounts/{id}/statement this is not keyset paged: it returns every matching entry up to ledger.MaxExportRows in a single response, the same bounded-export shape GET /v1/transactions/export uses.
type StatementExportOutput ¶
type StatementExportOutput struct {
ContentType string `header:"Content-Type"`
ContentDisposition string `header:"Content-Disposition" doc:"Set to attachment for csv; unset for json"`
Truncated bool `` /* 160-byte string literal not displayed */
Body []byte
}
StatementExportOutput is a raw csv or json export body, the same shape TransactionExportOutput uses (Task 6.3, audit A9.2): Body is a []byte, which huma writes out verbatim instead of JSON-encoding it.
type StatementInput ¶
type StatementInput struct {
ID string `path:"id" format:"uuid" doc:"Account id"`
Limit int `query:"limit" default:"50" minimum:"1" maximum:"200" doc:"Max entries per page"`
Cursor string `query:"cursor" doc:"Opaque cursor from a previous page's next_cursor"`
}
StatementInput is the account statement request: a path id plus keyset paging.
type StatementOutput ¶
type StatementOutput struct {
Body struct {
AccountID string `json:"account_id"`
Currency string `json:"currency"`
Entries []StatementEntryBody `json:"entries"`
NextCursor *string `json:"next_cursor" doc:"Cursor for the next page, or null if this is the last page"`
}
}
StatementOutput is one page of an account statement.
type TenantBody ¶
type TenantBody struct {
ID string `json:"id" doc:"Tenant id (UUID)"`
Name string `json:"name"`
Status string `json:"status" doc:"One of: active, suspended, closed"`
}
TenantBody is the JSON shape of a tenant in admin responses.
type TenantOutput ¶
type TenantOutput struct {
Body TenantBody
}
TenantOutput wraps a tenant in a response.
type TransactionBody ¶
type TransactionBody struct {
ID string `json:"id"`
Postings []PostingBody `json:"postings"`
// ReversesTransactionID is set only when this transaction is itself a
// reversal (Task 4.2, audit A1.2): the id of the transaction it reverses.
// Omitted for an ordinary post or convert.
ReversesTransactionID *string `` /* 130-byte string literal not displayed */
// Reference is the caller's optional external id for reconciliation
// (Task 4.3, audit A1.3), unique per tenant when present. Omitted when
// the transaction was posted without one.
Reference *string `json:"reference,omitempty" doc:"Optional client-supplied external reference, unique per tenant"`
// EffectiveAt is the value date (Task 4.3, audit A1.3): when the
// transaction is considered to have happened economically. Always
// present in a response, even when the caller supplied none: it then
// falls back to when the transaction was actually posted.
EffectiveAt time.Time `json:"effective_at" doc:"Value date; defaults to when the transaction was posted if not supplied"`
}
TransactionBody is the JSON shape of a transaction in responses. There is deliberately no transaction-level currency field (ADR-014 decision 1): a convert transaction spans two currencies, so a single top-level currency could never label every posting correctly. Each PostingBody carries its own currency instead.
type TransactionExportInput ¶
type TransactionExportInput struct {
From string `query:"from" doc:"RFC3339 timestamp. Only transactions created at or after this time."`
To string `query:"to" doc:"RFC3339 timestamp. Only transactions created strictly before this time."`
EffectiveFrom string `` /* 162-byte string literal not displayed */
EffectiveTo string `` /* 164-byte string literal not displayed */
Reference string `query:"reference" doc:"Exact match on the transaction's reference."`
Format string `` /* 181-byte string literal not displayed */
}
TransactionExportInput is the export-transactions request: the same from/to/reference filters as TransactionListInput, plus format (Task 4.4, audit A7.2). Unlike the list endpoint this is not paged: it returns every matching transaction up to ledger.MaxExportRows in a single response.
type TransactionExportOutput ¶
type TransactionExportOutput struct {
ContentType string `header:"Content-Type"`
ContentDisposition string `header:"Content-Disposition" doc:"Set to attachment for csv; unset for json"`
Truncated bool `` /* 151-byte string literal not displayed */
Body []byte
}
TransactionExportOutput is a raw csv or json export body (Task 4.4, audit A7.2). Body is a []byte, which huma writes out verbatim instead of JSON-encoding it (see huma's ContentTypeFilter / raw-body handling), so ContentType and, for csv, ContentDisposition are set explicitly here rather than inferred from a struct tag.
type TransactionListInput ¶
type TransactionListInput struct {
From string `query:"from" doc:"RFC3339 timestamp. Only transactions created at or after this time."`
To string `query:"to" doc:"RFC3339 timestamp. Only transactions created strictly before this time."`
EffectiveFrom string `` /* 162-byte string literal not displayed */
EffectiveTo string `` /* 164-byte string literal not displayed */
Reference string `query:"reference" doc:"Exact match on the transaction's reference."`
Limit int `query:"limit" default:"50" minimum:"1" maximum:"200" doc:"Max transactions per page"`
Cursor string `query:"cursor" doc:"Opaque cursor from a previous page's next_cursor"`
}
TransactionListInput is the list-transactions request: optional date-range and reference filters, plus keyset paging (Task 4.4, audit A7.2). From and To follow the same half-open convention as domain.TransactionFilter: From is inclusive, To is exclusive.
type TransactionListOutput ¶
type TransactionListOutput struct {
Body struct {
Transactions []TransactionBody `json:"transactions"`
NextCursor *string `json:"next_cursor" doc:"Cursor for the next page, or null if this is the last page"`
}
}
TransactionListOutput is one page of a tenant's transactions.
type TransactionOutput ¶
type TransactionOutput struct {
Body TransactionBody
}
TransactionOutput wraps a transaction in a response.
type TrialBalanceOutput ¶
type TrialBalanceOutput struct {
Body struct {
Currencies []CurrencyBalanceBody `json:"currencies"`
Accounts []AccountBalanceBody `json:"accounts"`
}
}
TrialBalanceOutput is the GET /v1/reports/trial-balance response.
type VerifyAuditInput ¶
type VerifyAuditInput struct {
Fast bool `` /* 375-byte string literal not displayed */
}
VerifyAuditInput is the verify-audit-chain request: no path parameters, and one optional query flag choosing which of AuditService's two walks runs.
type VerifyAuditOutput ¶
type VerifyAuditOutput struct {
Body struct {
Valid bool `json:"valid"`
Checked int `json:"checked"`
FirstBreakID *string `json:"first_break_id" doc:"Id of the first audit row that failed to verify, or null if the chain is intact"`
Pending int `` /* 147-byte string literal not displayed */
Head *AuditHeadBody `json:"head" doc:"The tenant's current chain head (chain_seq + row_hash), or null for an empty chain"`
Anchor *AuditAnchorBody `json:"anchor" doc:"The tenant's most recently recorded off-box anchor, or null if none has been recorded yet"`
}
}
VerifyAuditOutput is the result of walking the caller's tenant audit chain (ADR-012, "A per-tenant, tamper-evident audit chain"). FirstBreakID is null when Valid is true. Pending is the number of posted events not yet reflected in the chain (ADR-017): the chain is built asynchronously by a single background chainer, so a just-posted transaction can briefly show up here before its audit-chain link exists.
Head and Anchor (Task 5.3, audit A2.4) are independent of which walk (fast=true or false) actually ran: Head is always the tenant's current live chain head, and Anchor is always the latest off-box-logged checkpoint, or null if the tenant has none yet. A caller (or an automated external monitor comparing Head against the same anchor line its own log shipper captured, per Task 5.6) can tell a rewrite of already-anchored history apart from ordinary chain growth: if Head.ChainSeq equals Anchor.ChainSeq but Head.RowHash does not equal Anchor.RowHash, the anchored row itself was altered after being anchored, something a self-consistent in-database rewrite would not otherwise reveal (see migration 0025's own doc comment).
type WebhookSubscriptionBody ¶
type WebhookSubscriptionBody struct {
ID string `json:"id" doc:"Subscription id (UUID)"`
TenantID string `json:"tenant_id"`
URL string `json:"url"`
EventTypes []string `json:"event_types" doc:"Actions this subscription receives; empty means every action"`
Active bool `json:"active"`
CreatedAt time.Time `json:"created_at"`
}
WebhookSubscriptionBody is the JSON shape of a webhook subscription in the list-webhook-subscriptions response (Task 4.1, audit A7.1). It never carries the signing secret: that is shown once, at creation time, in IssuedWebhookSubscriptionBody below.