ion-full.yaml is the single-file specification of the
Indonesia Open Network. It does not fork Beckn — it
extends it: every Beckn type is referenced, never copied, and
everything ION adds mounts onto a named Beckn slot. This is a tour of
what the file declares, layer by layer, and why each piece exists.
ION is a national commerce network for Indonesia built on the Beckn
protocol. ion-full.yaml is the contract every participant
codes against. Its first design decision is the most important one:
it never embeds Beckn. It points at a local copy of
beckn.yaml and adds only what Indonesia needs on top.
The payoff of this discipline is upgrade safety. Beckn evolves on its own clock; Indonesia's regulatory surface evolves on another. Keeping them in two files means a Beckn point-release never forces an ION rewrite — and an ION policy change never touches the shared protocol core.
The file is organised as a stack. Each layer answers a different question, and each builds on the one beneath it. Beckn core is L1 — the ground the whole thing stands on, living in the other file.
ION never invents a new top-level object. Instead, every extension schema declares two things — where it attaches in Beckn, and that it inherits Beckn's base. Those two lines are the entire extension mechanism.
Beckn objects each expose an open *Attributes slot — a
deliberate hole left for networks to fill. ION fills those holes. Every
L4/L5 schema carries an x-beckn-attaches-to annotation
naming its target slot, and allOf's with Beckn's
Attributes base so it inherits the required
@context / @type machinery.
# an ION pack, in components/schemas IONBusinessIdentity: x-beckn-attaches-to: Provider.providerAttributes allOf: - $ref: beckn.yaml#/components/schemas/Attributes - type: object properties: npwp: # 16-digit tax ID nib: # OSS business number pkpStatus: # PKP | NON_PKP
This is what makes ION a non-forking
extension. A BPP's provider object is still a Beckn provider — it just
carries an IONBusinessIdentity in its attributes bag. Any
Beckn-aware tool that doesn't understand ION simply ignores the extra
fields; any ION-aware validator reads them.
Most packs also carry an x-jsonld block (@context,
@type: ion:…). The spec is OpenAPI-authoritative today,
with a declared transition path toward JSON-LD as the semantic source
of truth — the vocabulary already lives under
schema.ion.id.
x-ion-profile is one big block that every participant must
honour. It sets the cryptography, the currency and timezone, the tax
regime, the settlement rails, and the conformance matrix. These aren't
per-message fields — they are the constants of the network itself.
BLAKE-512=…. Clock-skew tolerance is 30 seconds.id (English supported), timezone Asia/Jakarta.The settlement rails the profile permits:
The profile also names the active sectors — trade, logistics, and
hospitality — with mobility, finance, and tourism reserved
for later. And it carries an experimental x-ion-context-extensions
block: a transaction may declare its pattern (e.g.
parcel, storefront) and a snapshot of policy IRIs,
so ION Central can enforce the exact terms agreed at contract time even if
the registry later changes.
The paths block declares the familiar Beckn action/callback
pairs — /select, /init, /confirm,
/status … — and then adds eight endpoints that are pure ION.
They cover the two things a commerce network needs after the deal is
struck: resolving disputes and settling money.
Every endpoint follows the Beckn idiom — the caller POSTs a signed
request and immediately receives a synchronous Ack; the real
answer arrives later on the matching /on_… callback. In the
summaries the spec writes CN for the consumer-side
participant (the BAP) and PN for the provider-side one
(the BPP).
When two parties can't settle a problem bilaterally through
/update within the policy SLA, either side raises it
to the ION network for adjudication. The dispute itself travels as an
IONTicket object.
After a contract reaches COMPLETE, the BAP initiates
reconciliation of the final settlement amount — with every adjustment
folded in: weight disputes, SLA rebates, COD remittance, FWA rebates, tax.
The BPP answers AGREED, DISPUTED, or
PENDING_ADJUSTMENT.
context.try=true — a dry-run preview that doesn't commit the settlement state.AGREED moves Settlement DRAFT → COMMITTED; DISPUTED keeps it DRAFT, pending exchange or a /raise.
Notice the seam between the two families: /reconcile handles
the money when both sides ultimately agree; /raise is the
escape hatch when they don't. A disputed reconciliation can become a raised
ticket — the two extension families are designed to hand off to each other.
Below the endpoints sit the schemas — the nouns of the network. They divide cleanly: L4 core packs hold the Indonesian fields any sector might use, and L5 sector packs hold the fields that belong to one industry alone. Each pack names the Beckn slot it mounts onto.
Seven schemas, one per Beckn object in a transaction. §07 — field-level reference ↓
The logistics packs carry the operational reality of moving goods —
airway bills, booking references, pickup models, applied cut-offs — plus
the LogisticsParticipantAddendum, which is where a driver's
SIM licence and a customs broker's PPJK number live.
The newest active sector ships a delivery pack and reuses
the trade packs for offer, provider, commitment, consideration, contract,
and performance — full 7-pack coverage for restaurant ordering.
The mounting pattern is uniform: a TradeContract rides in
Contract.contractAttributes, a LogisticsTracking
in Tracking.trackingAttributes, a PaymentDeclaration
in Settlement.settlementAttributes. Learn the slot names once
and the whole schema layer reads predictably.
The trade pack is the L5 schema set for goods commerce. It is seven
schemas under components/schemas, each an
allOf composition over Beckn's Attributes base,
each bound to a Beckn object through x-beckn-attaches-to. All
are open bags — additionalProperties: true and
x-ion-closed-extensions: false.
Constraints arrive in three forms. A flat required array on
the schema (Offer requires 11 fields, Provider 9, Commitment 4,
Consideration 3, Performance 3; Resource and Contract declare none). An
inline allOf / if-then — e.g. TradeContract
requires subscriptionPauseUntil when
subscriptionPauseType = PAUSED_UNTIL_DATE. And cross-schema
rules enforced by ONIX (§08). The figure maps each schema to its Beckn
object and the protocol action that first populates it.
A concrete instance — one TradeCommitment as it sits in a
Beckn Commitment.commitmentAttributes bag after
on_select:
'@type': ion:TradeCommitment lineId: L01 resourceId: ITEM-001 offerId: OFFER-001 quantity: count: 2 price: # written by BPP at on_select value: '89000' currency: IDR # const specialInstructions: Tanpa bawang, tolong pedas ekstra.
TradeConsideration is a per-line model, not a single total.
Each charge, tax, discount and fee is a separate consideration record
tagged with a breakupLineType from a 29-value enum
(ITEM, DELIVERY, TAX,
TAX_PPNBM, PROVIDER_BENEFIT,
COD_FEE, PLATFORM_FEE,
PPH_WITHHOLDING, PPN_REMIT_PMSE, …). The net is
the signed sum of the lines; the per-line tag is what lets a refund or a
/reconcile adjustment target a single line.
ppnRate=0.11 on the TAX line; PROVIDER_BENEFIT carries discountType/discountValue; COD_FEE is the cash-on-delivery surcharge. Bar width ∝ totalAmount; all lines currency: IDR.
Two TradeContract fields are the targets of cross-schema
rules in §08: codAmount is required when
PaymentDeclaration.paymentRail = COD, and
fakturPajakReference when
IONBusinessIdentity.pkpStatus = PKP on a
TAX_INVOICE. The schema carries the fields;
the trigger lives in a different bag, so ONIX enforces the condition at
the network boundary rather than the schema validating it locally.
Some requirements span two schemas: a field becomes mandatory only because
of a value set in a different attribute bag. Standard JSON Schema
if/then can't reach across bags composed at runtime, so ION
declares these in x-ion-conditional-rules — and ONIX, the
network validator, enforces them at every boundary.
Read top to bottom they tell a story about Indonesian commerce: a courier must prove their licence class, a customs broker their PPJK, a cash-on-delivery order must name the cash, and a tax-registered seller must carry a Faktur Pajak number. The protocol encodes the regulation.
Strip away the Beckn scaffolding and what remains is unmistakably Indonesian. The specificity is the point — these are the fields a national network needs to be lawful, deliverable, and payable across an archipelago.
And on the goods themselves: IONProductCertifications carries
Halal status (MUI), BPOM registration for
food and cosmetics, SNI conformance, the SP-PIRT home-food
licence, and age restrictions. Each field in the spec is annotated with the
regulation it answers to — x-ion-regulatory turns a schema into
a citation.
That is the whole of ion-full.yaml: a thin, upgrade-safe
extension on top of Beckn, an L2 profile that fixes the network's
constants, eight endpoints for disputes and settlement, and a library of
attribute packs that encode Indonesian law, geography, and money into the
protocol itself.