Skip to content
Core Conventions

Core Conventions

Every domain package (registration, balanceinfo, transactionhistory, transfercredit, transferdebit) follows the same small set of rules below. Learn them once and every endpoint becomes predictable — you’re just reading a field table, not learning a new pattern each time.

One package per ASPI portal category

Each domain package maps 1:1 onto one of the ASPI SNAP Developer Site’s top-level “API Services” categories, so “which package does this endpoint belong in” is never a judgment call. The root snap package holds only what every domain package needs: signing, the token lifecycle, inbound-request verification, header assembly, response-code parsing, and Money.

One shape per endpoint

Every calling function has the same signature:

func Endpoint(ctx context.Context, t *snap.Transport, hb snap.HeaderBuilder, req XRequest) (XResponse, error)

and does the same four things, in order:

  1. Marshal req into JSON.
  2. Set hb.Body to those exact bytes, then sign and send via t.Do(ctx, hb) — the same bytes are used for both the signature and the wire body, so nothing can drift between them.
  3. Check the response with snap.CheckResponseStatusHTTP status is authoritative over the SNAP responseCode body, never the other way around. A non-2xx HTTP status is never treated as success even if an embedded responseCode claims otherwise.
  4. Unmarshal into XResponse and return it.

A handful of read-only, GET, or URL-path-parameter endpoints (GetOAuthURL, CardRegistrationInquiry) deviate from the JSON-body shape because the standard itself defines them as a query string or path parameter — each is called out explicitly in its reference page.

Field presence: Mandatory / Optional / Conditional

The SNAP standard marks every field Mandatory, Optional, or Conditional (optional, but required together with some other condition). This package expresses that as Go’s omitempty:

  • A field the standard marks Mandatory has no omitempty — it always appears on the wire, even as a zero value.
  • Every other field carries omitempty.
Optional and Conditional both use the same omitempty tag, so the reference pages’ “Presence” column shows both as Optional — Go can’t tell them apart. If a field is actually Conditional, the paragraph above the table says so directly (e.g. “OriginatorInfos is Conditional”). Read that paragraph before assuming an Optional field is always safe to skip.

This package validates wire shape only, never the standard’s business rules (e.g. “exactly one of A or B must be set”) — the server does that.

json.RawMessage means “ambiguous or unspecified shape”

Most fields are string, bool, a nested struct, or *snap.Money. A field is typed json.RawMessage instead when the Guides tab documents its shape ambiguously (e.g. a value shown as a bare JSON number in one worked example and a quoted string in another) — decoding it as a concrete Go type would either reject a legitimate server response or silently coerce data that wasn’t actually a string. additionalInfo is the most common case: the standard leaves it fully open-ended per integration.

The shared Money type

type Money struct {
	Value    string `json:"value"`
	Currency string `json:"currency"`
}

Money is the {value, currency} shape used across every request or response that carries a monetary amount. Value is a decimal string (e.g. "200000.00", always two decimal places) — never a float, to avoid binary floating-point rounding on money. Currency is ISO 4217 (e.g. "IDR").

Next