A guided reading · OpenAPI 3.1.1 · extends Beckn v2.0

One file,
a whole network.

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.

3.1.1
OpenAPI dialect
0.6.0
Profile · draft
8
New endpoints
L2–L5
Spec layers
01 What it is

An extension, not a fork.

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.

ion.yaml · THIS FILE the extension L2 · x-ion-profile L3 · 8 endpoints (/raise · /reconcile) L4 · core attribute packs L5 · sector attribute packs version 0.6.0-draft beckn.yaml · UPSTREAM core v2.0.0 30 core endpoints Context · Attributes · base objects transport & signing model untouched · swap to upgrade $ref: beckn.yaml#/… reference, never copy fig.01 two files, run together, merged never
fig. 01 Run the two files together through any OpenAPI 3.1.1 validator. When Beckn ships a new version, only beckn.yaml changes — ion.yaml is untouched.

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.

02 Four layers

Read it top to bottom.

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.

L5 · SECTOR ATTRIBUTE PACKS trade · logistics · hospitality fields only one industry needs L4 · CORE ATTRIBUTE PACKS identity · payment · tax · address · rating … cross-sector Indonesian fields L3 · PROTOCOL ENDPOINT EXTENSIONS /raise family + /reconcile 8 verbs Beckn doesn’t define L2 · NETWORK PROFILE x-ion-profile signing · rails · compliance · conformance L1 · BECKN CORE v2.0.0 beckn.yaml — the shared protocol referenced, in the other file ION BUILDS UPWARD
fig. 02 L1 is Beckn, in the other file. Everything above it — L2 through L5 — is what ion-full.yaml contributes.
L2 · profile
x-ion-profile
Network-wide settings: the signing algorithm, payment rails, tax rules, conformance matrix, and policy registry that bind every Indonesian participant.
where one block near the top
L3 · endpoints
paths:
Eight new HTTP verbs — the /raise dispute family and /reconcile settlement pair — that sit alongside the Beckn action endpoints.
where the paths block
L4 · core packs
~11 schemas
Cross-sector attribute packs — identity, payment, tax, address, rating, support — that any transaction in any sector may carry.
where components/schemas
L5 · sector packs
~16 schemas
Trade (7) and logistics (9) packs — plus a hospitality delivery pack — for fields only that industry needs.
where components/schemas
03 The contract

How a pack mounts.

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.

04 L2 · the profile

The rules of the network.

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.

x-ion-security
ed25519 · BLAKE2b-512
Signatures are mandatory and use Ed25519; the body digest is BLAKE2b-512, carried on the wire as BLAKE-512=…. Clock-skew tolerance is 30 seconds.
enforces message authenticity
x-ion-localization
ID · IDR · Asia/Jakarta
Country Indonesia, default currency IDR, default language id (English supported), timezone Asia/Jakarta.
enforces money & time defaults
x-ion-compliance
PPN 11% · NPWP · NIB · PKP
Standard tax is PPN at 11% (luxury via PPnBM). Providers must carry NPWP and NIB; PKP status is required. Data residency is ID.
enforces who may sell & how taxed
x-ion-settlement
AFTER_DELIVERY · ≤48h
Default settlement is after delivery, within a 48-hour window. Funds may be collected by the BAP, BPP, or LSP.
enforces when money moves
x-ion-conformance
schema-pack matrix
The exact pack versions a participant must implement, per sector — core, trade, logistics, hospitality — pinned to Beckn core 2.0.0.
enforces what you must support
x-ion-policy-registry
16 policy categories
Returns, cancellation, warranty, dispute, SLA, penalty, insurance… Offers cite policy IRIs; ION Central resolves & enforces them at every boundary.
enforces the fine print

The settlement rails the profile permits:

BI_FAST QRIS VIRTUAL_ACCOUNT COD EWALLET BANK_TRANSFER BNPL

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.

05 L3 · the new verbs

Eight verbs Beckn doesn't have.

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).

The /raise family — dispute escalation

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.

raise → on_raise
open the dispute
A BAP or BPP submits a commercial dispute; the gateway returns a ticket ID and an expected resolution timeline.
body RaiseAction · OnRaiseAction
raise_status → on_raise_status
poll the dispute
Query a ticket by ID; the callback returns its current status and stage in the resolution process.
body RaiseStatusAction · …
raise_details → on_raise_details
read the thread
Request the full dispute thread; the callback returns every message and piece of evidence on the ticket.
body RaiseDetailsAction · …
why it exists
bilateral → network
Beckn handles the happy path. ION adds the unhappy path: a neutral, auditable escalation lane when the two parties are stuck.
actor BAP or BPP → gateway

The /reconcile pair — financial settlement

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.

reconcile
propose the net amount
BAP sends the final figure with all adjustments. Supports context.try=true — a dry-run preview that doesn't commit the settlement state.
body ReconcileAction
on_reconcile
agree or dispute
BPP returns the verdict and net amount. AGREED moves Settlement DRAFT → COMMITTED; DISPUTED keeps it DRAFT, pending exchange or a /raise.
body OnReconcileAction

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.

06 L4 / L5 · the packs

The vocabulary, in packs.

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.

L4 Core attribute packs cross-sector · used everywhere
IONParticipant
Who is acting — role, subscriberId, NPWP/NIB/NIK, KTP, bank & insurer licences, plus the full Indonesian address hierarchy.
▸ Participant.participantAttributes
IONBusinessIdentity
A provider’s legal proof — NPWP, NIB (OSS), PKP status, legal entity type (PT/CV/UD/Koperasi…).
▸ Provider.providerAttributes
PaymentDeclaration
The authoritative payment rail per transaction — method, collectedBy, timing, gateway, and a oneOf of instrument detail.
▸ Settlement.settlementAttributes
IONReconciliation
Settlement basis, withholding, and the signed adjustment ledger (penalties, rebates, COD remittance).
▸ Settlement.settlementAttributes
IONTaxDetail
Tax regime (PPN / PPnBM / PPh), category (BKP/JKP), rate, and the eFaktur reference.
▸ Consideration (nested)
IONAddressSubdivisions
BPS province→kelurahan codes, RT/RW, landmark (patokan), 3T remote classification, last-mile accessibility flags.
▸ address / location detail
IONProductCertifications
Halal (MUI), BPOM registration, SNI, SP-PIRT food licence, age restrictions.
▸ product attributes
IONRating
Verified-purchase reviews — 1–5 scale, dimension sub-categories, media, seller response.
▸ RatingInput.target.targetAttributes
IONSupportTicket
Consumer complaint triage — category, severity, SLA clocks, escalation ladder.
▸ Support.channels[*]
IONTicket
Network-level dispute object carried in the /raise body — type, thread, resolution, penalty.
▸ /raise message body
IONCatalogLocalization
Multilingual catalog text keyed by language (id / en / ms / zh …).
▸ catalog / item attributes
L5 Trade pack retail, B2B, procurement
TradeProviderTradeOfferTradeResourceTradeCommitmentTradeConsiderationTradeContractTradePerformance

Seven schemas, one per Beckn object in a transaction. §07 — field-level reference ↓

L5 Logistics pack parcel, freight, hyperlocal, roro
LogisticsProviderLogisticsOfferLogisticsShipmentLogisticsCommitmentLogisticsConsiderationLogisticsContractLogisticsPerformanceLogisticsTrackingLogisticsParticipantAddendum

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.

L5 Hospitality pack restaurant ordering

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.

07 The trade pack, up close

The seven trade schemas.

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.

CATALOG · published by BPP ORDER · select → confirm FULFIL PROVIDER RESOURCE OFFER COMMITMENT CONSIDERATION CONTRACT PERFORMANCE TradeProvider TradeResource TradeOffer TradeCommitment TradeConsideration TradeContract TradePerformance ·providerAttributes ·resourceAttributes ·offerAttributes ·commitmentAttributes ·considerationAttributes ·contractAttributes ·performanceAttributes required · 9 required · 0 required · 11 required · 4 required · 3 required · 0 * required · 3 set · publish set · publish set · publish set · select set · on_select set · confirm set · on_status publish_catalog select · init · confirm status · track fig.03 the trade pack, mapped onto the Beckn object model
fig. 03 Each L5 trade schema attaches to one Beckn object via x-beckn-attaches-to and composes its Attributes base. required = count of schema-level required fields; Contract (*) declares none, deferring to inline if/then and cross-schema rules. set = 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.
CATALOG Catalog-time written by BPP · returned in on_discover
TradeProvider
▸ Provider.providerAttributes
set · publish_catalog  ·  required · 9
providerCategory — RESTAURANT | GROCERY | PHARMACY | FASHION | … (16+)
invoicingModel — CENTRAL | PER_FULFILMENT_CENTRE
kycLevel — BASIC | STANDARD | ENHANCED | ENTERPRISE
storeStatus, operatingHours, holidayCalendar, kycValidUntil
nibRegistered: boolean
TradeResource
▸ Resource.resourceAttributes
set · publish_catalog  ·  required · none (open bag)
resourceStructure — PLAIN | VARIANT | WITH_EXTRAS | COMPOSED | BUNDLE
resourceTangibility — PHYSICAL | DIGITAL_VOUCHER | DIGITAL_TOP_UP | DIGITAL_SUBSCRIPTION
variantMatrix, customisationGroups, menuCategory, preparationTime
identity, physical, packaged, regulatory (sub-objects)
TradeOffer
▸ Offer.offerAttributes
set · publish_catalog  ·  required · 11
offerType — LIST_PRICE | DISCOUNT | BUNDLE | BUY_X_GET_Y | CASHBACK | FREE_DELIVERY
priceModel — FIXED | DYNAMIC | TIERED
returnable, availableOnCod: boolean · timeToShip · displayLanguage
policy IRIs: cancellation, return, warranty, dispute, grievanceSla, paymentTerms
ORDER Order-time negotiated across select → init → confirm
TradeCommitment
▸ Commitment.commitmentAttributes
set · select  ·  required · 4
lineId, resourceId, offerId: string
quantity — { count: int≥1 } | { measure: { unit: GRAM|KILOGRAM|LITRE|…, value: ≥0 } }
price — { value: string, currency: const IDR } · set by BPP at on_select
customisationSelections (req for COMPOSED | WITH_EXTRAS) · specialInstructions: maxLength 500
TradeConsideration
▸ Consideration.considerationAttributes
set · on_select / on_init  ·  required · 3
breakupLineType — 29-value enum: ITEM, DELIVERY, TAX, TAX_PPNBM, …, PPH_WITHHOLDING, PPN_REMIT_PMSE
ppnRate — number 0..1, required on TAX lines (PMK 131/2024)
ppnbmRate — number 0..1, luxury goods
discountType — PERCENT | FIXED · totalAmount: number · currency: const IDR
TradeContract
▸ Contract.contractAttributes
set · init / confirm  ·  required · none — if/then + cross-schema
invoicePreferences.invoiceType — RECEIPT | TAX_INVOICE | EINVOICE | SURAT_TAGIHAN | KOMERSIAL_INVOICE
cancellationReasonCode — BUYER_REQUESTED | ITEM_UNAVAILABLE | … | FORCE_CANCEL_TAT_BREACH (8)
codAmount (cross-schema: rail=COD) · fakturPajakReference (cross-schema: pkpStatus=PKP)
inline: subscriptionPauseType=PAUSED_UNTIL_DATE ⇒ require subscriptionPauseUntil
FULFIL Fulfilment-time updated post-confirm via on_status / on_track
TradePerformance
▸ Performance.performanceAttributes
set · on_status / on_track  ·  required · 3
performanceMode — DELIVERY | SELF_PICKUP | DINE_IN | CURBSIDE
supportedPerformanceModes: array · sla (required)
awbNumber, trackingUrl, realTimeGps, deliveryOtp, deliveryProofUrl
ageVerificationRequired: boolean (+ method, evidence for restricted goods)

TradeConsideration — the breakup model

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.

TradeConsideration · breakup one record per breakupLineType · charges (+) in rust · seller-funded PROVIDER_BENEFIT (−) in teal ITEM + Rp 89.000 DELIVERY + Rp 12.000 TAX · PPN 11% + Rp 9.790 PROVIDER_BENEFIT − Rp 10.000 COD_FEE + Rp 2.500 NET PAYABLE Rp 103.290
fig. 04 A worked TradeConsideration. 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.

08 Cross-schema rules

Five rules JSON Schema can't say.

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.

when participant.role = DRIVER
require driverLicenceCategory
a courier must declare which SIM class authorises their vehicle.
on LogisticsParticipantAddendum
when participant.role = DRIVER
require driverLicenceNumber
identifies the human carrying the parcel (roro, freight, hyperlocal).
on LogisticsParticipantAddendum
when participant.role = CUSTOMS_BROKER
require ppjkLicenceNumber
cross-border clearance requires a registered PPJK.
on LogisticsParticipantAddendum
when payment.paymentRail = COD
require codAmount
cash-on-delivery must state the sum the courier collects.
on TradeContract
when identity.pkpStatus = PKP
require fakturPajakReference
a PKP seller issuing a TAX_INVOICE owes a tax-invoice number.
on TradeContract
enforced by
ONIX · at the edge
These run at every API boundary, not inside the schema — the validator is the network's gatekeeper, so the rules live with it.
version rules 1.0.0

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.

09 Indonesia, encoded

Why it could only be here.

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.

Identitywho
Regulatory proofs baked into participant & provider objects, each tied to a specific statute.
NPWPNIBNIK / KTPPKP statusSIM classPPJK
Addresswhere
A formal hierarchy for a place where street numbering often fails — plus last-mile reality.
BPS codesRT / RWpatokan3T remotekelurahan
Moneyhow
The full spread of Indonesian rails and tax, with eFaktur for the tax-registered.
QRISBI-FASTVirtual AcctCODBNPLPPN / PPnBM

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.