Skip to content
NFH Fabric Support home
NFH Fabric Support home

API reference

Authoritative reference for every CATALG endpoint, request and response shape, field semantics, error codes, and shared terminology.

Authoritative reference for every CATALG endpoint, request and response shape, field semantics, error codes, and shared terminology. Use this page as a lookup. For the concepts behind these endpoints, see Catalog Concepts.

Endpoints at a glance

Flow

Caller → Receiver

Endpoint

Type

Subscribe

DS → CATALG

POST /catalog/subscription

Synchronous

List subscriptions

DS → CATALG

GET /catalog/subscription

Synchronous

Deactivate subscription

DS → CATALG

DELETE /catalog/subscription?subscriptionId=…

Synchronous

Publish catalog

PN → CATALG

POST /catalog/publish

ACK + async on_publish

Publish result

CATALG → PN

POST /catalog/on_publish

Asynchronous callback

Push catalog

CATALG → DS

POST /catalog/push

Synchronous (immediate ACK / NACK)

Pull catalogs

DS → CATALG

POST /catalog/pull

ACK + async on_pull

Pull result

CATALG → DS

POST /catalog/on_pull

Asynchronous callback

Search catalogs

Caller → CATALG

POST /catalog/search

Synchronous (response action: catalog/on_search)

Discover

CN → DS

POST /discover

Discovery Service endpoint — included here for completeness

Discovery result

DS → CN

POST /on_discover

Asynchronous callback


Authentication

All requests require a Beckn HTTP Signature in the Authorization header. The auth scheme name is Signature. The header is a single line — the only whitespace permitted is the single space after Signature and the spaces that separate the three tokens inside the headers="(created) (expires) digest" value. All other separators are commas with no surrounding whitespace.

Template:

Authorization: Signature keyId="{subscriberId}|{recordId}|{algorithm}",algorithm="ed25519",created="{unix_timestamp}",expires="{unix_timestamp}",headers="(created) (expires) digest",signature="{base64-ed25519-signature}"

Concrete example:

Authorization: Signature keyId="local-retail.net/grocery-net|bpp-key-001|ed25519",algorithm="ed25519",created="1748332800",expires="1748333400",headers="(created) (expires) digest",signature="BASE64SIGNATURE=="

The keyId value is composed of three pipe-separated segments:

Segment

Meaning

subscriberId

The participant's identity in the DeDi registry, in the form namespace_id/registry_id — namespace_id is the network namespace (e.g. local-retail.net) and registry_id is the participant's record within it (e.g. grocery-net)

recordId

Identifier of the specific signing-key record registered against that subscriber

algorithm

Signing algorithm — ed25519

The receiving service resolves the sender's public key from the DeDi registry using subscriberId + recordId and verifies the signature against the canonical signing string. The digest header carries the BLAKE2b-512 hash of the request body, Base64-encoded and prefixed with the algorithm tag — BLAKE-512={Base64} — matching the wire pattern ^BLAKE-512=[A-Za-z0-9+/]+=*$.


The catalog object

Every catalog submitted to POST /catalog/publish follows this shape:

Field

Type

Required

Description

id

string

yes

Stable catalog identifier. Updates to the same id are merged or replaced by updateMode

descriptor

object

yes

Human / agent-readable description of this catalog

provider

object

yes

Publishing entity. Carries id, descriptor, optional availableAt[], optional providerAttributes

resources

array

at least one

Resources offered by the provider. At least one of resources or offers must be present

offers

array

at least one

Commercial terms attached to resources. At least one of resources or offers must be present

bppId

string

no

PN identifier that publishes this catalog

bppUri

string (uri)

no

Beckn Protocol API base URL of the PN

isActive

boolean

no

Whether the catalog is active. Defaults to true

validity

object

no

TimePeriod (startDate / endDate) during which this catalog is valid

At least one of resources or offers must be present. Both is common.


Resource fields

Field

Required

Description

id

yes

Globally unique identifier of the resource

descriptor

no

Human / agent-readable description of the resource

resourceAttributes

no

Domain-specific fields. When present MUST carry @context (URI) and @type

Provider-level locations are declared on provider.availableAt[] (an array of Location objects). The Resource schema itself does not carry location fields — geo-availability is inherited from the publishing provider.


Offer fields

Field

Required

Description

id

yes

Unique identifier of the offer

descriptor

no

Human / agent-readable description of the offer

provider

no

Provider associated with the offer

resourceIds

no

References (IDs) to resources covered by this offer. If omitted, the offer applies to all resources from the provider

addOns

no

List of add-ons that may be included with this offer

considerations

no

Proposed values to be exchanged under this offer (e.g. price)

validity

no

TimePeriod (startDate / endDate) constraining the offer

offerAttributes

no

Domain-specific fields. When present MUST carry @context (URI) and @type


publishDirectives

publishDirectives is an optional array carried at the message level (not inside any catalog). Each entry, when supplied, controls how CATALG processes one catalog and is keyed by catalogId. When publishDirectives is omitted entirely, CATALG applies its own defaults to every catalog in the request.

"publishDirectives": [ { "catalogId": "CAT-FRESHMART-2026", "catalogType": "REGULAR", "updateMode": "MERGE", "visibleTo": ["local-retail.net/grocery-net"] } ]

Field

Required

Default

Description

catalogId

yes

Must match a catalog id in message.catalogs[] of this request

catalogType

yes (within an entry)

Declares the type of this catalog — MASTER (canonical resource definitions reusable across the network) or REGULAR (provider-specific catalog that can optionally extend master resources). Required only when a publishDirectives entry is supplied

updateMode

no

MERGE

MERGE upserts resources by id — existing resources for this catalogId not in this request are retained. FULL replaces the entire catalog with this request

visibleTo

no

nfh.global/beckn-nodes

Restricts delivery to the listed network participant identifiers (registry-registered). When omitted, the catalog is delivered to all subscribers of the default global network nfh.global/beckn-nodes

resourceDirectives

no

Per-resource directives linking individual resources to master resources they inherit from. Applies only when catalogType is REGULAR

Fields inside each resourceDirectives entry:

Field

Required

Description

resourceId

yes

Must match a resource id in catalogs[].resources[] of this request

extends

yes (within an entry)

Object linking this resource to a master resource it inherits from. Required whenever a resourceDirectives entry is supplied

extends.masterResourceId

yes (within extends)

The id of the master resource to inherit from. Must refer to a resource previously published in a master catalog within the same network

variant

no

Object that classifies this resource as a variant of another (size, colour, etc.)

variant.type

yes (within variant)

Category that groups related variants together (any string)

variant.id

yes (within variant)

Identifier for this specific variant within the given type. The combination of type + id must be unique per resource

variant.label

no

Human-readable name for this variant

Default network behaviour. When publishDirectives is omitted entirely, or when an entry has no visibleTo, the catalog is distributed via the default global network nfh.global/beckn-nodes. Every Discovery Service subscribed to that network receives the catalog. This is the catch-all path that lets a Provider Node publish without explicit visibility configuration — keeping the catalog broadly discoverable across the Fabric.

Defaults at a glance. publishDirectives itself is optional — omit it and CATALG applies its own defaults to every catalog in the request. The moment you provide an entry, catalogType is mandatory. updateMode defaults to MERGE.



POST /catalog/subscription

Create a subscription. The DS declares which networkIds and/or schemaTypes it wants to receive. The response is synchronous — a successful request returns 200 OK with a CatalogSubscriptionResponse carrying the assigned subscriptionId and status: ACTIVE. The subscription is bound to the caller's identity (derived from the Authorization header) — only that identity can later list, view, update, or deactivate it.

Request

POST /catalog/subscription Authorization: Signature keyId="local-retail.net/grocery-net|ds-key-001|ed25519",algorithm="ed25519",created="...",expires="...",headers="(created) (expires) digest",signature="..."
{ "context": { "version": "2.0.0", "action": "catalog/subscription", "messageId": "a1b2c3d4-0000-0000-0000-000000000001", "transactionId": "a1b2c3d4-0000-0000-0000-000000000002", "timestamp": "2026-05-27T08:00:00.000Z", "bapId": "ds.local-retail.net", "bapUri": "https://ds.local-retail.net" }, "message": { "subscription": { "networkIds": ["local-retail.net/grocery-net"], "schemaTypes": ["https://schema.beckn.io/RetailResource/2.1/context.jsonld"] } } }

At least one of networkIds or schemaTypes must be present. Empty schemaTypes matches all schema types on the subscribed networks.

Response (200 OK)

{ "context": { "action": "catalog/on_subscription", "version": "2.0.0", "messageId": "a1b2c3d4-0000-0000-0000-000000000001", "transactionId": "a1b2c3d4-0000-0000-0000-000000000002", "timestamp": "2026-05-27T08:00:00.000Z" }, "message": { "subscriptions": [ { "subscriptionId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "status": "ACTIVE", "networkIds": ["local-retail.net/grocery-net"], "schemaTypes": ["https://schema.beckn.io/RetailResource/2.1/context.jsonld"], "createdAt": "2026-05-27T08:00:00.000Z", "updatedAt": "2026-05-27T08:00:00.000Z" } ] } }

Shared response envelope. POST /catalog/subscription, GET /catalog/subscription, and DELETE /catalog/subscription all return the same CatalogSubscriptionResponse envelope — message.subscriptions[] is always an array, even when a create or deactivate operation affects a single subscription. List operations may return one or many entries depending on filters.


List, deactivate

Operation

Request

Notes

List

GET /catalog/subscription

Optional ?status=ACTIVE|INACTIVE, ?subscriptionId=<id>, ?limit=, ?offset=. Omitting ?status returns all statuses

Deactivate

DELETE /catalog/subscription?subscriptionId=<id>

Marks subscription INACTIVE (no body)

The caller MUST be the subscription owner. CATALG derives the caller's identity from the Authorization header on the request and compares it to the identity that originally created the subscription — calls from any other identity return 403 NackForbidden carrying error.code: AUT_UNAUTHORIZED_ACTION.


List response

{ "context": { "action": "catalog/on_subscription", "version": "2.0.0", "messageId": "a1b2c3d4-0000-0000-0000-000000000010", "timestamp": "2026-05-27T08:05:00.000Z" }, "message": { "subscriptions": [ { "subscriptionId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "networkIds": ["local-retail.net/grocery-net"], "schemaTypes": ["https://schema.beckn.io/RetailResource/2.1/context.jsonld"], "status": "ACTIVE", "createdAt": "2026-05-27T08:00:00.000Z", "updatedAt": "2026-05-27T08:00:00.000Z" } ], "pagination": { "total": 1, "limit": 20, "offset": 0 } } }

Deactivate response

{ "context": { "action": "catalog/on_subscription", "version": "2.0.0", "messageId": "a1b2c3d4-0000-0000-0000-000000000020", "timestamp": "2026-05-27T08:30:00.000Z" }, "message": { "subscriptions": [ { "subscriptionId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "networkIds": ["local-retail.net/grocery-net"], "schemaTypes": ["https://schema.beckn.io/RetailResource/2.1/context.jsonld"], "status": "INACTIVE", "createdAt": "2026-05-27T08:00:00.000Z", "updatedAt": "2026-05-27T08:30:00.000Z" } ] } }

NACK example (403 — caller does not own the subscription)

{ "message": { "status": "NACK", "messageId": "a1b2c3d4-0000-0000-0000-000000000001", "error": { "code": "AUT_UNAUTHORIZED_ACTION", "message": "Caller is not authorized to act on this subscription" } } }

Error code values are drawn from the canonical Fabric ErrorCode enum (see Errors).



POST /catalog/publish

A Provider Node submits one or more Catalog objects to CATALG for validation and indexing so that Consumer Nodes may discover them via /discover. The message MUST contain a non-empty catalogs array; each Catalog MUST include id, descriptor, and provider. A 200 Ack confirms the request was accepted for processing — per-catalog results are delivered asynchronously on the /catalog/on_publish callback. The publisher correlates the callback by context.messageId.

Payload size limit — 5 MB. The publish request body may be up to 5 MB (configurable via BODY_SIZE_LIMIT, default 5mb — raised from the earlier 2 MB so large catalogs of 500+ items publish in a single request). A request that exceeds the limit is rejected with HTTP 413 and a NACK carrying error.code: SCH_SCHEMA_VALIDATION_FAILED ("request entity too large"). Split oversized submissions across multiple publish requests (e.g. by catalog or resource batch).

Request — grocery store

POST /catalog/publish Authorization: Signature keyId="local-retail.net/grocery-net|bpp-key-001|ed25519",algorithm="ed25519",created="...",expires="...",headers="(created) (expires) digest",signature="..."
{ "context": { "version": "2.0.0", "action": "catalog/publish", "messageId": "b2c3d4e5-0000-0000-0000-000000000003", "transactionId": "b2c3d4e5-0000-0000-0000-000000000004", "timestamp": "2026-05-27T09:00:00.000Z", "bppId": "bpp.freshmart.local-retail.net", "bppUri": "https://bpp.freshmart.local-retail.net" }, "message": { "publishDirectives": [ { "catalogId": "CAT-FRESHMART-2026", "catalogType": "REGULAR", "updateMode": "MERGE", "visibleTo": ["local-retail.net/grocery-net"] } ], "catalogs": [ { "id": "CAT-FRESHMART-2026", "descriptor": { "name": "FreshMart Daily Catalog", "shortDesc": "Fresh groceries — Bangalore South" }, "provider": { "id": "freshmart-blr-001", "descriptor": { "name": "FreshMart Koramangala" }, "availableAt": [ { "geo": { "type": "Point", "coordinates": [77.6278, 12.9352] }, "address": { "streetAddress": "42 80-Feet Road, Koramangala 4th Block", "addressLocality": "Bengaluru", "addressRegion": "Karnataka", "postalCode": "560034", "addressCountry": "IN" } } ] }, "resources": [ { "id": "ITEM-BASMATI-RICE-1KG", "descriptor": { "name": "India Gate Basmati Rice", "shortDesc": "Aged basmati, 1 kg pack" }, "resourceAttributes": { "@context": "https://schema.beckn.io/RetailResource/2.1/context.jsonld", "@type": "RetailResource", "brand": "India Gate", "sku": "BSMTI-1KG-IG" } } ], "offers": [ { "id": "OFFER-BASMATI-MAY26", "descriptor": { "name": "May Staples Deal" }, "resourceIds": ["ITEM-BASMATI-RICE-1KG"], "validity": { "startDate": "2026-05-01T00:00:00Z", "endDate": "2026-05-31T23:59:59Z" }, "offerAttributes": { "@context": "https://schema.beckn.io/RetailOffer/2.1/context.jsonld", "@type": "RetailOffer", "isActive": true }, "considerations": [ { "id": "PRICE-BASMATI", "considerationAttributes": { "@context": "https://schema.beckn.io/PriceSpecification/2.0/context.jsonld", "@type": "PriceSpecification", "value": 95, "currency": "INR" } } ] } ] } ] } }

Response — ACK (200 OK)

{ "message": { "status": "ACK", "messageId": "b2c3d4e5-0000-0000-0000-000000000003" } }

Response — NACK (4xx)

{ "message": { "status": "NACK", "messageId": "b2c3d4e5-0000-0000-0000-000000000003", "error": { "code": "SCH_SCHEMA_VALIDATION_FAILED", "message": "publishDirectives[0].catalogId does not match any entry in catalogs[]" } } }


POST /catalog/on_publish

CATALG delivers one on_publish callback per /catalog/publish request — not one per catalog. After it finishes indexing all the catalogs in a publish request, CATALG sends a single callback whose results array carries one entry per catalog submitted, each with the originating catalogId and a status of ACCEPTED, REJECTED, or PARTIAL along with any per-resource errors. The publisher MUST correlate this callback with the originating publish request using context.messageId, and MUST handle REJECTED or PARTIAL results by correcting and resubmitting the affected catalogs. The Context carries a requestDigest binding this callback to the originating request.

{ "context": { "version": "2.0.0", "action": "catalog/on_publish", "messageId": "b2c3d4e5-0000-0000-0000-000000000003", "transactionId": "b2c3d4e5-0000-0000-0000-000000000004", "timestamp": "2026-05-27T09:00:02.000Z", "requestDigest": { "digest": "BLAKE-512=UWgRZ2xFP8Eaq7L0r4y5N9XGsTbm6Q8h2dXFv5z3HrBlnAa7Cw1F8gJoVtKjP9rE2mDQzhR0vXc8jL7aT5sP1Q==" } }, "message": { "results": [ { "catalogId": "CAT-FRESHMART-2026", "status": "ACCEPTED", "stats": { "itemCount": 2, "providerCount": 1, "categoryCount": 1 } } ] } }

Status

Meaning

ACCEPTED

Catalog processed and distributed to matching subscribers

REJECTED

Catalog rejected entirely — errors array explains the cause

PARTIAL

Some resources accepted, some rejected — see errors

When status is REJECTED or PARTIAL, each result carries an errors[] array of canonical Error objects (code, message, optional details.path):

{ "catalogId": "CAT-FRESHMART-2026", "status": "REJECTED", "errors": [ { "code": "SCH_SCHEMA_VALIDATION_FAILED", "message": "resources[0].resourceAttributes does not conform to the declared @context", "details": { "path": "$.message.catalogs[0].resources[0].resourceAttributes" } } ] }

POST /catalog/push

CATALG calls this endpoint on a Discovery Service to deliver catalog updates that match an active subscription. An active subscription matching the catalog's networkId and schema types MUST exist for the DS, and the DS MUST have a registered endpoint in the network. The body mirrors /catalog/publish — same CatalogPublishAction message schema with publishDirectives[] and catalogs[]. The DS applies MERGE or FULL per the directive.

Because CATALG initiates the push autonomously when a subscription matches — there is no prior DS request whose signature could be chained — /catalog/push uses the standard Authorization header, not the callback-chain CallbackAuthorizationHeader.

Note on bppId / bppUri. On /catalog/push, CATALG is the caller. The context.bppId and context.bppUri identify CATALG as the provider node-side sender of the push, not the Provider Node whose catalog is being delivered. Each catalog inside message.catalogs[] still carries its own bppId / bppUri identifying the originating Provider Node.

{ "context": { "version": "2.0.0", "action": "catalog/push", "messageId": "c3d4e5f6-0000-0000-0000-000000000007", "transactionId": "c3d4e5f6-0000-0000-0000-000000000008", "timestamp": "2026-05-27T09:00:02.500Z", "bppId": "catalg.example.net", "bppUri": "https://catalg.example.net" }, "message": { "publishDirectives": [ { "catalogId": "CAT-FRESHMART-2026", "catalogType": "REGULAR", "updateMode": "MERGE" } ], "catalogs": [ /* fully assembled catalogs */ ] } }

catalog/push is synchronous — the DS responds in the same HTTP response. There is no separate on_catalog_push callback.

HTTP

Body

When

200

Ack

Push received and accepted for processing

202

AckNoCallback

Push authenticated and received, but no further async processing will follow (e.g. no matching local subscription). Body MUST carry an error block describing why

400

NackBadRequest

Malformed body or failed schema validation

401

NackUnauthorized

Authorization signature missing or invalid

429

NackTooManyRequests

DS is rate-limiting the caller

500

ServerError

Unexpected failure at the DS

Response — ACK (200)

{ "message": { "status": "ACK", "messageId": "c3d4e5f6-0000-0000-0000-000000000007" } }

Response — AckNoCallback (202)

{ "message": { "status": "ACK", "messageId": "c3d4e5f6-0000-0000-0000-000000000007", "error": { "code": "BIZ_NO_RESULTS_FOUND", "message": "No active subscription on this DS matches the pushed catalog" } } }

Response — NACK (4xx)

{ "message": { "status": "NACK", "messageId": "c3d4e5f6-0000-0000-0000-000000000007", "error": { "code": "SCH_SCHEMA_VALIDATION_FAILED", "message": "publishDirectives[0].catalogType missing" } } }

A 202 AckNoCallback is not a failure — the DS has authenticated and received the push but will not process it further (e.g. no matching local subscription). The body always carries an error block explaining why no further processing or callback will follow. Only 4xx / 5xx NACKs indicate the push itself was rejected.



POST /catalog/pull

A Discovery Service submits an asynchronous bulk catalog retrieval request to CATALG, scoped to an active subscription owned by the caller. The subscription's networkIds and schemaTypes determine which catalogs are returned — they are not specified on this request. mode selects between FULL (the latest version of each matching catalog) and INCREMENTAL (all catalog versions published between fromDate and toDate inclusive). A 200 Ack confirms the request was accepted for processing; results are delivered asynchronously on the /catalog/on_pull callback and correlated by context.messageId.

INCREMENTAL mode is not supported yet. It is defined by the spec but currently disabled. A POST /catalog/pull with mode: "INCREMENTAL" is rejected up front with an HTTP 400 NACK carrying error.code: NET_NOT_IMPLEMENTED. Use mode: "FULL" (the default). INCREMENTAL — and the fromDate / toDate bounds — will be enabled in a future release.

Request

{ "context": { "version": "2.0.0", "action": "catalog/pull", "messageId": "e1f2a3b4-0000-0000-0000-000000000099", "transactionId": "e1f2a3b4-0000-0000-0000-000000000100", "timestamp": "2026-05-27T10:00:00.000Z", "bapId": "ds.local-retail.net", "bapUri": "https://ds.local-retail.net" }, "message": { "subscriptionId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "mode": "FULL", "limit": 20, "offset": 0 } }

Field

Required

Description

subscriptionId

yes

An active subscription owned by the caller

mode

no

FULL (default — latest state of each matching catalog). INCREMENTAL is not yet supported — see the note above; a request with mode: "INCREMENTAL" returns 400 NET_NOT_IMPLEMENTED

fromDate

no

Inclusive lower bound for INCREMENTAL mode (ISO 8601), default 7 days before now. Ignored while INCREMENTAL is disabled.

toDate

no

Inclusive upper bound for INCREMENTAL mode (ISO 8601), default current time. Ignored while INCREMENTAL is disabled.

limit

no

[1, 100], default 20

offset

no

≥ 0, default 0

Response — ACK (200 OK)

{ "message": { "status": "ACK", "messageId": "e1f2a3b4-0000-0000-0000-000000000099" } }

Response — NACK (4xx)

The synchronous NACK covers failures detectable at request time — malformed JSON, schema violations, an invalid subscriptionId (400), an unauthorized signature (401), a subscription owned by another DS (403), or a subscription that does not exist (404). Failures that surface only while assembling the result (catalog source unreadable, subscription deactivated mid-flight) are delivered on the catalog/on_pull callback with status: FAILED.

{ "message": { "status": "NACK", "messageId": "e1f2a3b4-0000-0000-0000-000000000099", "error": { "code": "CTX_INVALID_FIELD", "message": "message.subscriptionId is not a valid UUID" } } }



POST /catalog/on_pull

CATALG calls this endpoint on the Discovery Service to deliver the results of a previously accepted /catalog/pull request. The DS MUST correlate the callback with the originating request using context.messageId. When status = COMPLETED, exactly one of catalogs[] (inline) or downloadManifest (large payload, downloaded out-of-band) is present — never both. When status = FAILED, neither is present and the DS MUST read the error field instead of attempting to process any result. The Context carries a requestDigest binding the callback to the originating request.

Inline payload

{ "context": { "version": "2.0.0", "action": "catalog/on_pull", "messageId": "e1f2a3b4-0000-0000-0000-000000000099", "transactionId": "e1f2a3b4-0000-0000-0000-000000000100", "timestamp": "2026-05-27T10:01:05.000Z", "requestDigest": { "digest": "BLAKE-512=Tg5Hf9PuQ3kRn7M0XaZsBd2YcVwLpKjEi8oRmTbU6xN4qJvWy1HrCs7tFkAaEgD8vNcBqLpZmRsT3xUwYpKjAg==" } }, "message": { "status": "COMPLETED", "catalogs": [ /* fully assembled catalog objects */ ], "pagination": { "total": 12, "limit": 20, "offset": 0 } } }

Download manifest (large payloads)

{ "context": { "version": "2.0.0", "action": "catalog/on_pull", "messageId": "e1f2a3b4-0000-0000-0000-000000000099", "transactionId": "e1f2a3b4-0000-0000-0000-000000000100", "timestamp": "2026-05-27T10:01:05.000Z", "requestDigest": { "digest": "BLAKE-512=Tg5Hf9PuQ3kRn7M0XaZsBd2YcVwLpKjEi8oRmTbU6xN4qJvWy1HrCs7tFkAaEgD8vNcBqLpZmRsT3xUwYpKjAg==" } }, "message": { "status": "COMPLETED", "downloadManifest": { "url": "https://storage.example.com/catalog-pull-results/e1f2a3b4.json.gz?token=...", "expiresAt": "2026-05-27T11:01:05.000Z", "format": "json.gz", "sizeBytes": 4823104, "checksum": "sha256:a3f1e9b7c2d04..." }, "pagination": { "total": 1450, "limit": 20, "offset": 0 } } }

The DS MUST download the object at url, verify the sha256:-prefixed checksum against the downloaded content before processing, and MUST NOT process the content if verification fails. The DS MUST NOT attempt the download after expiresAt; if expired, the DS MUST re-issue POST /catalog/pull. Payloads with format: json.gz MUST be decompressed before parsing.

FAILED callback

When CATALG could not assemble the pull result — for example, the catalog source was unreadable or the subscription was deactivated mid-flight — it delivers a status: FAILED callback with an error block. The DS MUST read the error field and MUST NOT attempt to process catalogs[] or downloadManifest. The code is a value from the canonical Fabric ErrorCode enum.

{ "context": { "version": "2.0.0", "action": "catalog/on_pull", "messageId": "e1f2a3b4-0000-0000-0000-000000000099", "transactionId": "e1f2a3b4-0000-0000-0000-000000000100", "timestamp": "2026-05-27T10:01:05.000Z", "requestDigest": { "digest": "BLAKE-512=Tg5Hf9PuQ3kRn7M0XaZsBd2YcVwLpKjEi8oRmTbU6xN4qJvWy1HrCs7tFkAaEgD8vNcBqLpZmRsT3xUwYpKjAg==" } }, "message": { "status": "FAILED", "error": { "code": "NET_CATALOG_SOURCE_UNAVAILABLE", "message": "Catalog source could not be read while assembling the pull result" } } }



POST /catalog/search

A caller queries CATALG for indexed catalogs, filtered by catalog type, network identifiers, and schema-type URIs, with paginated results. All filters are optional: omitting filters.type defaults to MASTER; omitting or leaving filters.networkIds empty matches all networks; omitting or leaving filters.schemaTypes empty matches all schema types. limit MUST be between 1 and 100 (default 20); offset MUST be non-negative (default 0). The response is synchronous and is wrapped in a Fabric envelope with context.action set to catalog/on_search.

Request

{ "context": { "version": "2.0.0", "action": "catalog/search", "messageId": "...", "transactionId": "..." }, "message": { "filters": { "type": "MASTER", "networkIds": ["local-retail.net/grocery-net"], "schemaTypes": ["https://schema.beckn.io/RetailResource/2.1/context.jsonld"] }, "limit": 20, "offset": 0 } }

Field

Default

Description

filters.type

MASTER

MASTER or REGULAR

filters.networkIds

all

Restrict to these networks

filters.schemaTypes

all

Restrict to these JSON-LD @context URIs

limit / offset

20 / 0

Pagination, limit ∈ [1, 100]

Response

{ "context": { "version": "2.0.0", "action": "catalog/on_search", "messageId": "d4e5f6a7-0000-0000-0000-000000000050", "transactionId": "d4e5f6a7-0000-0000-0000-000000000051", "timestamp": "2026-05-27T10:05:00.000Z" }, "message": { "catalogs": [ { "id": "CAT-...", "descriptor": { "name": "...", "shortDesc": "..." }, "provider": { "id": "...", "descriptor": { "name": "..." } }, "resources": [ /* assembled resource objects */ ] } ], "pagination": { "total": 12, "limit": 20, "offset": 0 } } }



POST /discover and POST /on_discover

Discovery is a Discovery Service endpoint, not a CATALG endpoint. It is included here so the full network flow is visible from one reference page.

The Consumer Node sends a discovery request to the DS, which evaluates the supplied intent against its indexed catalog store and returns matching results asynchronously via /on_discover. The DS keeps its index synchronized by subscribing to CATALG — the CN never interacts with CATALG directly for discovery.

Intent mechanisms overview

Mechanism

Field

Description

Text search

intent.textSearch

Free-text or natural-language query — keywords, phrases

JSONPath filter

intent.filters

Structured attribute filter using RFC 9535 JSONPath expressions

Spatial filter

intent.spatial[]

Geometry operators — proximity radius, polygon, shape relations

Any combination of these may appear in a single intent. Each mechanism is described and illustrated separately below.

Text search

intent.textSearch accepts a free-text string — keywords or a natural-language phrase. The DS queries its full-text index and returns resources whose name, description, or indexed attributes match the query.

{ "context": { "version": "2.0.0", "action": "discover", "messageId": "a1b2c3d4-0000-0000-0000-000000000001", "transactionId": "a1b2c3d4-0000-0000-0000-000000000002", "timestamp": "2026-05-27T10:10:00.000Z", "bapId": "cn.myapp.local-retail.net", "bapUri": "https://cn.myapp.local-retail.net" }, "message": { "intent": { "textSearch": "fresh vegetables" } } }

Field

Type

Description

intent.textSearch

string

Keywords or natural-language phrase. No operators or syntax — plain text only.

JSONPath filter

intent.filters applies a structured attribute filter using an RFC 9535 JSONPath expression. Only resources that satisfy the expression are returned.

JSONPath expressions MUST be absolute paths rooted at $, where $ is the message payload root. Resources are addressed as $.catalogs[*].resources[*] and offers as $.catalogs[*].offers[*].

{ "context": { "version": "2.0.0", "action": "discover", "messageId": "b2c3d4e5-0000-0000-0000-000000000003", "transactionId": "b2c3d4e5-0000-0000-0000-000000000004", "timestamp": "2026-05-27T10:10:00.000Z", "bapId": "cn.myapp.local-retail.net", "bapUri": "https://cn.myapp.local-retail.net" }, "message": { "intent": { "filters": { "type": "jsonpath", "expression": "$.catalogs[*].resources[*] ? (@.resourceAttributes.isActive == true)" } } } }

Field

Type

Description

intent.filters.type

string

Always "jsonpath"

intent.filters.expression

string

RFC 9535 filter expression. MUST be an absolute path from $. Use @ for the current node inside ? (...).

Path examples:

Target

Expression

Active resources only

$.catalogs[*].resources[*] ? (@.resourceAttributes.isActive == true)

Resources with a specific brand

$.catalogs[*].resources[*] ? (@.resourceAttributes.brand == "India Gate")

Offers with discount type FLAT

$.catalogs[*].offers[*] ? (@.offerAttributes.discountType == "FLAT")

Spatial filter

intent.spatial[] is an array of spatial constraints. Each constraint specifies a geometry operation, a target field path, and a GeoJSON geometry. Only resources whose indexed location satisfies all constraints are returned.

{ "context": { "version": "2.0.0", "action": "discover", "messageId": "c3d4e5f6-0000-0000-0000-000000000005", "transactionId": "c3d4e5f6-0000-0000-0000-000000000006", "timestamp": "2026-05-27T10:10:00.000Z", "bapId": "cn.myapp.local-retail.net", "bapUri": "https://cn.myapp.local-retail.net" }, "message": { "intent": { "spatial": [ { "op": "S_DWITHIN", "targets": "$.catalogs[*].provider.availableAt[*].geo", "geometry": { "type": "Point", "coordinates": [77.6278, 12.9352] }, "distanceMeters": 3000 } ] } } }

Field

Type

Description

spatial[].operation

string

Spatial operation to apply — see table below

spatial[].targets

string

Absolute JSONPath to the geo field being tested. MUST match exactly how the field was indexed. For provider-level coordinates use $.catalogs[*].provider.availableAt[*].geo.

spatial[].geometry

GeoJSON

The reference geometry — Point, Polygon, MultiPolygon, etc. Coordinates are [longitude, latitude].

spatial[].distanceMeters

number

Required for s_dwithin only — radius in metres from the reference point.

targets must be an absolute JSONPath. The DS matches the targets value exactly against the path stored during indexing. Using a wrong root (e.g. $['availableAt'][*]['geo']) or the wrong level (e.g. $.catalogs[*].resources[*].availableAt[*].geo when availableAt is on provider) returns zero spatial results with no error.

Results are delivered asynchronously to the CN's bapUri at POST /on_discover, correlated by messageId.



POST /on_discover callback

The DS calls this endpoint on the CN to deliver Catalog objects matching the original intent. The message MUST carry a non-empty catalogs array. The CN MUST use context.messageId to correlate this callback with the originating /discover request. The Context carries a requestDigest binding the callback to that request.

{ "context": { "version": "2.0.0", "action": "on_discover", "messageId": "e5f6a7b8-0000-0000-0000-000000000009", "transactionId": "e5f6a7b8-0000-0000-0000-000000000010", "timestamp": "2026-05-27T10:10:01.800Z", "bppId": "ds.local-retail.net", "bppUri": "https://ds.local-retail.net", "requestDigest": { "digest": "BLAKE-512=Pn4XmJ2sQyR8tBv6Wz3LcK1NfDgEoIuVaH5bMyT0xZpUjC9rEqAhWk7yLeRsTd3vNcBqLpZmRsT3xUwYpKjAg==" } }, "message": { "catalogs": [ { "id": "CAT-FRESHMART-2026", "descriptor": { "name": "FreshMart Daily Catalog" }, "provider": { "id": "freshmart-blr-001", "descriptor": { "name": "FreshMart Koramangala" } }, "resources": [ /* matching resource objects */ ], "offers": [ /* offers that apply */ ] } ] } }

The CN correlates the callback to its original /discover request by messageId.

Errors

All NACK responses, AckNoCallback responses, and asynchronous error callbacks share the canonical Fabric Error shape:

{ "message": { "status": "NACK", "messageId": "...", "error": { "code": "SCH_FIELD_NOT_ALLOWED", "message": "Human-readable description of the failure", "details": { "path": "$.message.catalogs[0].resources[0].resourceAttributes" } } } }

error.code MUST be a value from the canonical Fabric ErrorCode enum. The prefix indicates the protocol stack layer where the error originated:

Prefix

Layer

CTX_

Context / routing

AUT_

Authentication / trust

SCH_

Core / linked-data schema

NET_

Networking / runtime

BIZ_

Application / business logic

POL_

Policy

Common codes by HTTP status

HTTP

Code

Used when

400

SCH_INVALID_JSON

Request body is not valid JSON

400

SCH_REQUIRED_FIELD_MISSING

A required field is missing

400

SCH_SCHEMA_VALIDATION_FAILED

Body is JSON but does not conform to the endpoint schema

413

SCH_SCHEMA_VALIDATION_FAILED

Request body exceeds the 5 MB payload limit (BODY_SIZE_LIMIT)

400

SCH_FIELD_NOT_ALLOWED

Body contains an unrecognised field

400

SCH_INVALID_ENUM

A field value is not in the allowed enum

400

SCH_INVALID_FORMAT

A field value does not match its declared format

400

CTX_MISSING_FIELD

A required context field is absent

400

CTX_INVALID_FIELD

A context field has an invalid value

400

CTX_ACTION_MISMATCH

context.action does not match the endpoint being called

400

CTX_VERSION_UNSUPPORTED

context.version is not supported

400

CTX_TTL_EXPIRED

timestamp + ttl has elapsed before processing

401

AUT_SIGNATURE_MISSING

Authorization header is absent

401

AUT_SIGNATURE_INVALID

Signature did not verify against the resolved public key

401

AUT_KEY_NOT_FOUND

keyId could not be resolved in the registry

401

AUT_KEY_EXPIRED_OR_REVOKED

Resolved key has expired or been revoked

401

AUT_SUBSCRIBER_NOT_FOUND

Subscriber identity is not registered

403

AUT_UNAUTHORIZED_ACTION

Caller is authenticated but not permitted to perform this action on this resource

403

AUT_DOMAIN_NOT_ALLOWED

Caller's domain is not permitted for the requested action

429

AUT_RATE_LIMITED

Caller has exceeded the request rate limit

400

NET_NOT_IMPLEMENTED

A spec-defined capability is not yet enabled — e.g. mode: INCREMENTAL on /catalog/pull

500

NET_INTERNAL_ERROR

Unexpected server failure

503

NET_DOWNSTREAM_UNAVAILABLE

A downstream service required to process the request is unavailable

503

NET_OVERLOADED

Service is overloaded — retry after backoff

Asynchronous (callback) errors

Errors discovered only after the synchronous ACK — i.e. during async processing — are reported on the relevant callback. Subscription-level failures detectable at request time (subscriptionId unknown, ownership mismatch, missing/invalid signature) are returned synchronously on /catalog/pull itself, not on the callback. The same Error shape applies.

Reported on

Code

When

catalog/on_publish (per results[] entry)

SCH_SCHEMA_VALIDATION_FAILED

Resource attributes do not conform to the declared @context

catalog/on_publish (per results[] entry)

BIZ_GENERIC_ERROR

Business-rule rejection that does not fit a more specific code

catalog/on_pull

NET_CATALOG_SOURCE_UNAVAILABLE

Catalog source could not be read while assembling the pull result

catalog/on_pull

NET_CATALOG_STALE

Available catalog data is older than the requested time window

See the full canonical ErrorCode enum in the Beckn Protocol v2.0 specification.



Glossary

Term

Meaning

CATALG

Fabric's catalog publication and distribution service — the source of truth for what is published

DISCOVR

Fabric's discovery service — searches catalogs CATALG has distributed

Provider Node (PN)

A network participant that publishes catalogs to CATALG

Discovery Service (DS)

A network participant that subscribes to CATALG and serves discovery queries

Consumer Node (CN)

A consumer-facing application that calls /discover on a DS

Network Facilitator

The participant that creates a network and publishes master (template) catalogs

networkId

DeDi-registered subnet identifier in the form namespace_id/registry_id. Used in publishDirectives[].visibleTo to scope distribution and in subscription networkIds[] to scope what a DS receives. Defaults to the global network nfh.global/beckn-nodes when visibleTo is omitted

keyId

Beckn HTTP Signature key identifier in the form {subscriberId}|{recordId}|{algorithm}

subscriberId

First segment of keyId — the participant identity registered in the DeDi registry

recordId

Second segment of keyId — identifies the specific signing-key record within the participant's registration

Master catalog

Network-wide template, published by the Network Facilitator. No offers

Regular catalog

Commercial catalog published by a Provider Node

MERGE / FULL

Update modes — incremental upsert vs. complete replacement of a catalog's resource set