Partner Guide
How a partner (bank, MFI, agritech) joins Agri Stack and uses it end to end: onboarding, keys, CM bindings, the farmer's consent with a grant per registry, signing, calling a use case, and reading and
You are a partner: a bank, MFI or agritech that needs farmer data held across several registries, for an approved use case such as loan-profile. You send one signed request with the farmer's identifier and one consent to the Composite, and get back one signed response with a status per registry.
How it fits together
The diagram shows the sample use case loan-profile, which reads the Farmer Registry and then the Crop Sown Registry; another use case reads its own registries in its own order.
You never call the registries or /validate yourself. The Composite takes your consent to the Exchange Consent Manager (the CM of the Agri Stack Exchange), which checks it against your bindings and policies there and issues one signed consent token per registry, limited to grant ∩ policy ∩ the use case's scopes. Each registry gets only its own consent token and validates it at its own department CM, which trusts the Exchange CM's consent tokens. The Composite only routes, maps and signs; it stores nothing. See consent.
What you need, and who sets it up
A binding and data-share policy for each registry, in the Consent Manager
The CM administrator of the Exchange (if AWE approval is switched on, each policy waits for it)
Step 1 — Generate your signing key
You sign two things with the same key: the consent and the request envelope. Supported algorithms: EdDSA (Ed25519), ES256 (EC P-256) and RS256 (RSA). Keep the private key in a keystore (a .p12 is recommended); share only the public key. Choose a stable kid (e.g. bank-a-2026-01) and put it in every JWS header.
Key generation is shown in the Consent Manager's Partner Integration Guide, step 1. The partner test kit generates an EC P-256 key (ES256) for testing.
Step 2 — Get onboarded in Partner Management
Partner Management (PM) is the trust root: the Composite, every registry and the Consent Manager fetch your public key from it. In the first version of PM the portal is admin-run: a PM administrator files your onboarding request and approves it. There is no partner self-service.
What you give the PM administrator:
Partner ID
bank-a
Your DCI sender_id, used in every request, and the consent's aud. Lower-case with hyphens is the convention.
Name and organisation
Bank A
Public key(s)
PEM, X.509 certificate, or JWK; or a jwks_url to import from once
With kid and algorithm (EdDSA, ES256 or RS256)
The PM ID. PM stores you as PARTNER_<SENDER_ID>, upper-cased with - replaced by _: partner bank-a is PARTNER_BANK_A. The Composite and the registries look your key up under that ID. The Composite is itself a PM partner: agri-composite is PARTNER_AGRI_COMPOSITE.
After approval your keys are served at GET {pm}/keys/PARTNER_BANK_A (and /keys/PARTNER_BANK_A/jwks.json). Ask the administrator to confirm; PM returns 404 for an unknown or disabled partner, which everyone treats as a rejection. Key rotation is a key-update request, also filed by the administrator. See the PM API reference and functional specifications.
Step 3 — Get a binding and policy for each registry
For each registry the use case reads, the Exchange Consent Manager needs a binding of your partner to that registry (data controller) with a data-share policy: the data scopes, purposes, subject identifier types, signing algorithms, validity limit and fetch type you may ever receive. The policy is the ceiling: you get grant ∩ policy, never more. The CM administrator creates them; if AWE approval is switched on in the CM (it is off by default), a new or wider policy waits for approval by the department. See Partner policy binding & approval.
The registries a use case reads are its sources' controllers, listed as consent_grants_needed when you describe the use case.
Binding (one per registry):
Policy (one per binding):
allowed_data_scopes
The data you may ever receive from this registry
Scope IDs from the registry's scope catalogue, <controller>.<name>. The registry publishes it at POST /partner/data_scopes on its partner API, a signed call like every other (sign {header, message} with your key, message may be empty; the unsigned GET is off by default — ask the registry operator if it is not reachable to you): each scope with a label, a description and the fields it covers. Ask for the scopes the use case's output needs, and no more.
allowed_subject_id_types
Identifier types the consent's subject may use
The use case's input.subject.id_types (step 5)
allowed_signing_algs
Algorithms your consents may be signed with
Your key's algorithm (step 1): EdDSA, ES256 or RS256
max_validity_duration
The longest consent validity accepted
Agreed with the department; an ISO 8601 duration such as P90D
fetch_type
Access pattern
oneshot for a use case you call per request
A data scope is a named group of the registry's own fields, not a key of the record you get back: the registry filters each record to the scopes' fields before rendering it. A scope ID the registry does not have grants nothing. See Data Scopes.
Sources that depend on another. When a use case queries one registry with a value read from another registry's record (a source with depends_on), your scopes for the first registry must include the one that carries that value. Otherwise the dependent sources have nothing to query with.
Example: loan-profile
Two bindings, for farmer-registry and crop-sown-registry, both with audience = bank-a and partner_mgmt_id = PARTNER_BANK_A:
Policy field
farmer-registry
crop-sown-registry
allowed_data_scopes
farmer-registry.farmer_identifiers, farmer-registry.personal_details, farmer-registry.household_location, farmer-registry.land, farmer-registry.land_location, farmer-registry.main_crops
crop-sown-registry.farmer_reference, crop-sown-registry.crop_season, crop-sown-registry.measures, crop-sown-registry.location
allowed_purposes
credit-assessment
credit-assessment
allowed_subject_id_types
FAYDA_FAN, FARMER_ID
FAYDA_FAN, FARMER_ID
allowed_signing_algs
ES256
ES256
max_validity_duration
P90D
P90D
fetch_type
oneshot
oneshot
The crop sources query the Crop Sown Registry by farmer ID, read from the farmer record, so the Farmer Registry scopes include farmer-registry.farmer_identifiers.
Step 4 — Get allowed for the use case
Until Partner Management holds policies and partner associations, each use case lists the partners that may call it in allowed_partners (for example, the sample loan-profile ships with [bank-a]). Ask the Agri Stack operator to add your partner ID; otherwise every call is rejected 403 partner_not_allowed. See Use-Case Configuration.
Step 5 — Discover the use case
The operator gives you the Composite's URL, e.g. https://agri-composite.<namespace>.openg2p.org, and its public key for verifying responses. The describe endpoints need no signature:
The description tells you what to set up and send: purpose (for the policies and the consent), input.subject.id_types and input.parameters (for the request), sources and consent_grants_needed (the registries to get bindings for and to grant in the consent), consent_scopes (per registry, the data scopes the consent must grant, required, and may grant, optional), and output_fields.
Example: loan-profile.
consent_grants_needed lists the registries your consent needs a grant for, and consent_scopes the data scopes of each grant: grant every required scope (and the optional ones you want). A grant for a mandatory source, with its required scopes, is needed or the request fails (consent_scope_missing); without them for an optional source, that source is reported denied and not called. Scopes beyond the use case's are never asked for.
Step 6 — Collect the farmer's consent
Data is released only against the farmer's consent for the specific purpose and scopes.
There are two ways to present it.
A consent held in the Consent Manager (consent_id). Your staff record the farmer's consent in the CM's partner portal, for example in person at a branch: the farmer logs in with their national ID (Fayda in the reference setup, through eSignet) when that is switched on, the portal records the farmer's identifiers the department requires (e.g. the Farmer ID) and the evidence (stored by the CM), and CM staff verify it (checking the Farmer ID in the registry) before it becomes active. You then send the consent's ID as message.consent_id (step 8). The CM holds the consent; you can download its consent record, signed by the CM. This needs the Composite's Exchange mode. See consent and consent collection.
A consent you sign (consent_jws). You obtain the consent from the farmer through your own channel and assert it in a consent object signed with your key (the "partner-embedded" flow, step 7). What that means for you:
Obtain real, informed consent for the use case's purpose (e.g.
credit-assessmentforloan-profile) and for each registry's data, before you sign. Tell the farmer which registries hold the data being shared (e.g. the Farmer Registry and the Crop Sown Registry forloan-profile) and what each scope covers (its label and description in the registry's catalogue), as the CM consent screen would.Stay within the policy. The scopes you grant per registry must be within that registry's policy (step 3), and within what the farmer agreed to.
Keep evidence of how the consent was obtained, for audit and disputes: who consented, when, through which channel and how they were authenticated, for which purpose and which registries' data. The capture method and evidence a department accepts are part of your agreement with it when your binding and policy are set up; follow what it requires.
The signed consent and the Consent Manager's receipts are the audit trail. CM issues a receipt per registry for your consent (Partner Integration Guide, step 9). Misrepresenting consent is a compliance breach.
Step 7 — Build and sign the consent
Skip this step if you send a consent_id. The consent is a compact JWS signed with your key, with one grant per registry. The claims are defined in the Partner Integration Guide, step 5; for Agri Stack:
jti
a new UUID
New for every consent. Re-sending the same signed consent to the same registry returns its earlier decision; a different consent reusing a jti is denied replay.
aud
your partner ID
subject_id
{"type": …, "value": …}, a type from the use case's input.subject.id_types
Must equal message.subject in your request: same type and value
purpose
{"code": <the use case's purpose>}
Allowed by each registry's policy
grants
[{"data_controller": <registry>, "data_scopes": [<scope IDs>]}], one per registry in consent_grants_needed
Each registry once; scope IDs from that registry's catalogue, within its policy and within what the farmer agreed to
fetch_type
oneshot
validity
{"valid_from": …, "valid_until": …}
Within the policy's max_validity_duration; the Composite rejects a consent outside its window
issued_at
now (UTC)
Within ±300 seconds of the Consent Manager's clock (its freshness window), so sign a fresh consent for each request and keep clocks in sync
Example: loan-profile, for partner bank-a.
Sign it with your key and kid (any RFC 7515 library):
The Composite presents this consent to the Exchange CM, and each registry gets its own consent token.
Step 8 — Sign the request envelope
The request is a DCI-style envelope: header, message, and a detached JWS over {header, message}.
header.version
1.0.0
header.message_id
a new UUID
header.message_ts
now, ISO 8601 UTC, e.g. 2026-10-01T10:00:00.000Z. Must be within 300 seconds of the Composite's clock.
header.action
query
header.sender_id
your partner ID
header.receiver_id
the Composite's ID, agri-composite (if present, it must match)
message.subject
{"type": "FAYDA_FAN" | "FARMER_ID", "value": "…"}
message.parameters
the use case's input.parameters, e.g. {"crop_year": 2019, "season": "SEASON_MEHER"} for loan-profile; omit or {} for none
message.consent_jws
the consent from step 7
message.consent_id
instead of consent_jws: the ID of a consent the farmer gave through the Consent Manager (e.g. collected in person in the partner portal and verified by staff; see consent collection). The Exchange Consent Manager checks it (active, in its validity, obtained by you, about this farmer) and what it grants; needs the Composite's Exchange consent mode. Send one of the two, not both.
The signature is a JWS whose payload is the canonical JSON of {"header": …, "message": …} (keys sorted, no whitespace, UTF-8), with the payload part removed: <protected header>..<signature>.
Example (loan-profile, partner bank-a):
Step 9 — Call the use case
<use_case>gets the highest published major version;<use_case>@1pins major 1 (or send the headerX-Use-Case-Major: 1). For exampleloan-profileorloan-profile@1.How many subjects one request may carry is the use case's
input.batch.max_subjects(one forloan-profile).
Step 10 — Read the response
The response is an envelope signed by the Composite. Example (loan-profile):
header.statusissuccorrjct; onrjct,header.status_reason_codeandstatus_reason_messagesay why, andmessagecarrieserror: {code, message}instead ofdata.message.request_idis the Composite's ID for this request; quote it when reporting a problem (audit events are linked by it).message.sourceshas a status per source, and adetailwhen there is something to say:
ok
The registry returned records
no_record
The registry answered, with no matching record
denied
The registry refused (consent, policy or subject check), or your consent has no grant for it (an optional source is then not called)
unavailable
The registry could not be reached, timed out, or answered 408/429/5xx; or the overall timeout was reached
error
Any other failure (a rejected search, a malformed answer)
A source whose dependency is not ok is not called; it takes the dependency's status, with detail: "not called: …". With partial_response: allowed (e.g. loan-profile), only a failing mandatory source fails the request; optional sources that fail just show their status, and their output fields are null (a source that answered with no record gives [] or 0).
HTTP status and error codes:
HTTP
error.code
Meaning
200
—
Success (some optional sources may still be denied / unavailable / error)
400
invalid_envelope, wrong_receiver, stale_request, invalid_use_case, invalid_input
Bad envelope, wrong receiver_id, message_ts more than 300 s from now, bad @major, bad subject or parameters
401
signature_invalid
The envelope signature does not verify against your PM key (or you are unknown in PM)
403
partner_not_allowed
You are not in the use case's allowed_partners
403
consent_required, consent_malformed, consent_signature_invalid, consent_subject_mismatch, consent_not_yet_valid, consent_expired, consent_grant_missing, consent_scope_missing
A consent problem found by the Composite before any registry is called (consent_scope_missing: a mandatory source's required scopes are not granted)
403
consent_denied
The Exchange Consent Manager denied the consent; its reason code is in the message
403
consent_receipt_missing
The Exchange Consent Manager issued no consent token for a mandatory source's registry
502
consent_exchange_error
The Exchange Consent Manager rejected the call or gave an unreadable answer
503
consent_exchange_unavailable
The Exchange Consent Manager could not be reached, timed out, or is not configured
403
source_denied
A mandatory source was denied (e.g. by the Consent Manager at the registry)
404
unknown_use_case
No published use case of that name / major
429
rate_limited
Rate limit reached
502 / 503 / 504
source_error / source_unavailable
A mandatory source failed: error / unavailable / timed out
500
signing_unavailable
The Composite cannot sign (its key is missing); the body is unsigned
When a registry denies your consent, the reason in sources.<id>.detail comes from the Consent Manager's reason_code; see the table in the Partner Integration Guide, step 8.
Step 11 — Verify the Composite's signature
Verify every response before you use it. The signature is a detached JWS over the canonical JSON of the response's {header, message}, signed with the Composite's key (PARTNER_AGRI_COMPOSITE in PM; the JWS header carries its kid):
Get the Composite's public key from the operator, or from PM's key API (GET {pm}/keys/PARTNER_AGRI_COMPOSITE) where it is reachable to you; the key-fetch API is served on the cluster-internal gateway.
Rate limits
Each use case sets a rate per partner (its rate_per_partner; loan-profile allows 60 requests per minute), and 429 rate_limited is returned beyond it. The limit is applied per Composite pod and worker, so it is a floor rather than an exact ceiling; limits across all pods and daily quotas are not built yet.
Worked example: loan-profile
Request: the sample farmer of a demo install (FAN 946053125409, farmer ID FR-0007), Meher 2019.
What the Composite does:
Verifies your signature (
PARTNER_BANK_A), checksallowed_partners, the parameters and the rate limit.Checks the consent: your signature, subject
FAYDA_FAN 946053125409= the request's, valid now, a grant forfarmer-registry(mandatory) andcrop-sown-registry. Then gets a consent token for each registry from the Exchange CM.Calls the Farmer Registry:
reg_type: Farmer, searchfoundational_id $eq 946053125409.Reads
FARMER_IDfrom the farmer record, then calls the Crop Sown Registry twice in parallel: season summaries (FARMER_SEASON_SUMMARY, crop year 2019, Meher) and crop seasons (crop year 2019, Meher).Maps and derives the output, signs and returns it.
Response (message; record contents abbreviated — each keeps its registry's DCI record shape):
land.total_sizeis the sum of each parcel'sland_size, in the parcels' unit (farm_details[].measurement).crops.total_area_sown_hasumsmeasures.area_sown_haover the crop seasons returned; withcrop_yearandseasonit is that season's.Without
crop_yearandseason, all of the farmer's seasons come back, newest first.
If the farmer had not consented to the Crop Sown Registry (no grant), season_summaries and crop_seasons would be denied with detail: "the consent has no grant for crop-sown-registry", crops.* would be null, and the request would still succeed. If the Farmer Registry denied the consent, the request would fail 403 source_denied.
Test kit
scripts/partner_kit.py does all of the above for testing (pip install cryptography pyjwt):
keys(defaults: partnerbank-a, Compositeagri-composite) prints the PM onboarding requests forPARTNER_BANK_AandPARTNER_AGRI_COMPOSITE, thekubectl create secret generic agri-composite-signing …command, and the CM bindings and policies for audiencebank-a.callbuilds a consent with both grants (--controllersnarrows it), signs the envelope, verifies the Composite's signature on the answer and prints it.
To go through these steps as a test partner against an environment, use the partner test.
Checklist
Last updated
Was this helpful?