> For the complete documentation index, see [llms.txt](https://docs.openg2p.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.openg2p.org/platform/platform-services/consent-management/partner-integration-guide.md).

# Partner Integration Guide

End-to-end guide for a partner (data consumer) who wants to fetch a beneficiary's data from an OpenG2P registry — onboarding, keys, policy, obtaining consent (through the Consent Manager, or signing y

You are a **partner** — a system that wants to fetch some data about a beneficiary (e.g. a farmer's profile) held in an OpenG2P **registry**. Data is released **only** against a valid consent, within your **access policy**. This page walks the whole journey from your side.

> **The one thing to internalise:** you do **not** fetch data from the Consent Manager. You call the **registry's** data API; the registry (the enforcement point) asks the Consent Manager for the consent and its decision, and returns **only** the fields the consent + policy allow. The Consent Manager is the decision-maker, never the data source.

There are two ways to hold the consent:

|                                               | How consent is collected                                                                                                      | What you send the registry                                                                          | Steps     |
| --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | --------- |
| **Held by the Consent Manager** (the default) | Through the Consent Manager, e.g. your staff in its partner portal with the beneficiary's national-ID login and a signed form | Nothing: you search the person by identifier and the Consent Manager finds the consent              | 1–4, 7–10 |
| **Signed by you**                             | In your own trusted system                                                                                                    | A consent object you sign, embedded in each request. Only if your policy allows it (off by default) | 1–10      |

## Who you talk to

```mermaid
sequenceDiagram
  participant P as You (Partner)
  participant PM as Partner Management
  participant R as Registry (data controller / PEP)
  participant CM as Consent Manager (PDP)
  Note over P,PM: One-time: onboard, register your PUBLIC key, get an access policy
  P->>PM: onboarding request (partner ID + public key + policy), approved by PM staff
  Note over P,R: Per data fetch
  Note over P,CM: Consent collected through the CM (or signed by you, if allowed)
  P->>R: call the registry data API (signed request; your consent object, or none)
  R->>CM: POST /consent/v1/validate (your object, or the person searched + its own data_controller)
  CM->>PM: read your policy for this registry (+ your public key, for your own object)
  CM-->>R: decision = permit + effective_data_scopes (or deny + reason)
  R-->>P: only the permitted fields (or an error)
```

| You interact with           | For                                                                   | How                                                            |
| --------------------------- | --------------------------------------------------------------------- | -------------------------------------------------------------- |
| **Partner Management (PM)** | onboarding, registering + rotating your keys, your access policy      | through the PM operator (PM staff UI / API)                    |
| **The Registry**            | the actual data fetch                                                 | the registry's data API                                        |
| **Consent Manager (CM)**    | collecting consent; your consents, their receipts and consent records | its partner portal; its public keys (`/.well-known/jwks.json`) |

***

## Step 1 — Generate and hold your signing key

You sign every request to a registry (the DCI envelope) and, if you sign your own consents, every consent object with your **private key**; only your **public key** is shared (with PM). Supported algorithms: **EdDSA (Ed25519, recommended)**, **ES256** (EC P-256), **RS256** (RSA).

* Keep the private key in a secure keystore. A **PKCS#12 (`.p12`)** file is the recommended format (it holds the private key, optionally with a cert):

  ```bash
  # Ed25519 example
  openssl genpkey -algorithm ed25519 -out partner_priv.pem
  openssl pkey -in partner_priv.pem -pubout -out partner_pub.pem   # PUBLIC half → PM
  # bundle the private key into a .p12 you keep
  openssl pkcs12 -export -inkey partner_priv.pem -nocerts -out partner.p12
  ```
* Choose a stable **`kid`** (key id), e.g. `partnerA-2025-01`. You put it in every signature header and use it to rotate keys (Step 10). **Never** share the private key or the `.p12` — the Consent Manager never needs it and never asks for it.

***

## Step 2 — Onboard in Partner Management

Register yourself as a partner in **Partner Management (PM)** and upload your **public** key(s). See [Partner Management](/platform/platform-services/partner-management.md) and [how CM consumes PM keys](/platform/platform-services/consent-management/design/partner-management-integration.md).

* Agree your **audience** — the ID you put in `aud` of every consent and use as DCI `sender_id` (e.g. `bank-a`). Your PM partner ID follows from it: upper-case, `-` → `_`, prefix `PARTNER_` (`bank-a` → `PARTNER_BANK_A`). See the [partner ID convention](/platform/platform-services/consent-management/design/partner-management-integration.md#partner-id-convention).
* The PM operator raises an onboarding request with that partner ID, a name, and your public key(s) as PEM or JWK with `kid` + `algorithm` — and, usually, your access policy (Step 3) — and approves it.
* After approval, PM serves your keys inside the cluster to the registries and the CM, which verify your signatures with them. PM answers nothing for an unknown or disabled partner, which they treat as **reject**, so make sure your partner is **active**.

> PM stores only your **public** key. Rotation and revocation are also done here (Step 10).

***

## Step 3 — Get an access policy (per registry)

For each registry whose data you want, you need an **access policy** in Partner Management for that registry (data controller): the data scopes, purposes, subject-id types, validity limit, fetch type and signing algorithms you may **ever** receive, and whether you may present consents you sign yourself (**May present its own signed consents**, off by default). See [Access policies](/platform/platform-services/partner-management/functional-specifications.md#access-policies).

* This policy is the **ceiling**: what you actually get back is always `consent scope ∩ policy`. You cannot exceed it, no matter what the consent says.
* Ask the PM operator for exactly the scopes and purposes you need. They add the policy to your onboarding request, or raise a policy request later; it applies once approved in PM. Plan for lead time.
* You'll agree on the registry's **`data_controller`** identifier to use in the consent object (Step 5).
* **Data scopes are the registry's scope IDs**, `<data_controller>.<name>` (e.g. `farmer-registry.land`), from that registry's scope catalogue: `POST /partner/data_scopes` on its partner API (a signed call, or an unsigned `GET` where the registry allows it) lists each scope with its label, description and fields. A scope is a named group of the registry's own fields, not a field of an output format such as DCI. See [Data Scopes](/products/registry/registry/design/data-scopes.md).
* Needing data from **several registries** means one policy per registry, all for the same partner. You still collect **one** consent from the beneficiary (Step 5).

***

## Step 4 — Obtain the beneficiary's consent

Sharing requires the **beneficiary's consent** for the specific purpose and scopes. Consent is captured one of two ways:

1. **Through the Consent Manager** (preferred) — your staff raise a consent request in the CM's **partner portal**; the beneficiary logs in with their national ID and signs a form, which CM staff verify ([consent collection](/platform/platform-services/consent-management/design/consent-collection.md)). The Consent Manager then holds the consent. You need not keep its ID: when you search the person by identifier without a consent, the CM finds it. **Skip to Step 7.**
2. **In your own system** — only if your policy allows it (Step 3): you obtain the beneficiary's consent through your channel and **assert** it in a consent object you sign (Steps 5–6). The Consent Manager can verify only your signature, not that the beneficiary agreed.

Either way, the consent must stay within what the beneficiary agreed to **and** within the policy (Step 3). Misrepresenting consent is a compliance breach — the signed object + the issued receipt (Step 9) are the audit evidence.

***

## Step 5 — Construct the consent claims

*Only for a consent you sign yourself (Step 4, way 2).* Build the consent claims — these become the **payload** of the JWS you sign in Step 6. Fields:

| Field        | Meaning                                                                                                                                                                                                       |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `jti`        | Unique id for THIS object (replay guard — never reuse)                                                                                                                                                        |
| `subject_id` | `{ type, value }` — the beneficiary (e.g. `national_id` / `FARMER_1234`)                                                                                                                                      |
| `grants`     | `[{ data_controller, data_scopes }]` — one grant per registry: the registry's controller id (agreed in Step 3) and the registry's data scope IDs you're requesting from it (subset of that registry's policy) |
| `aud`        | **Your** audience (e.g. `bank-a`), agreed in Step 2                                                                                                                                                           |
| `purpose`    | `{ code, text }` — must be allowed by the policy                                                                                                                                                              |
| `fetch_type` | `oneshot` or `periodic`                                                                                                                                                                                       |
| `validity`   | `{ valid_from, valid_until }` (within the policy's max)                                                                                                                                                       |
| `issued_at`  | Now (UTC) — must be within the freshness window                                                                                                                                                               |

```json
{
  "@context": "https://openg2p.org/contexts/consent_object.jsonld",
  "@type": "ConsentObject",
  "jti": "b2f1-unique-per-object",
  "subject_id": { "type": "national_id", "value": "FARMER_1234" },
  "aud": "bank-a",
  "purpose": { "code": "share_farm_profile", "text": "Share farmer profile with Partner A" },
  "grants": [
    { "data_controller": "farmer-registry", "data_scopes": ["farmer-registry.personal_details", "farmer-registry.land"] },
    { "data_controller": "crop-sown-registry", "data_scopes": ["crop-sown-registry.crop_season"] }
  ],
  "fetch_type": "oneshot",
  "validity": { "valid_from": "2025-05-01T12:00:00Z", "valid_until": "2026-05-01T12:00:00Z" },
  "issued_at": "2025-05-01T11:59:50Z"
}
```

There is **no `signature` field** — the whole object is signed as a JWS in Step 6.

* List each registry once in `grants`. Each registry sees and uses **only its own grant**; another registry's grant never widens what it returns.
* `data_scopes` are scope IDs from each registry's catalogue (Step 3). A registry ignores an ID it does not know or one from another registry's namespace: it grants nothing. When a registry later adds a field to a scope, consents issued before that (by `issued_at`) do not get it.
* **Single registry (backward compatible):** instead of `grants` you may put `data_controller` + `data_scopes` at the top level, as before. Don't combine the two forms — that is `malformed_object`.

***

## Step 6 — Sign the consent object as a compact JWS

*Only for a consent you sign yourself.*

The consent object is signed as a **compact JWS** (RFC 7515) — the standard `header.payload.signature` form used by JWTs and OIDC. The claims from Step 5 are the payload; the protected header carries `alg` and `kid`. This is a standard operation in any JWS/JWT library — **no custom canonicalisation to get wrong**.

```python
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from jwt.api_jws import PyJWS  # PyJWT
import json

def sign_consent_jws(claims: dict, priv, kid: str, alg: str = "EdDSA") -> str:
    payload = json.dumps(claims, separators=(",", ":")).encode("utf-8")
    return PyJWS().encode(payload, priv, algorithm=alg, headers={"kid": kid})

priv = load_pem_private_key(open("partner_priv.pem", "rb").read(), password=None)
consent_jws = sign_consent_jws(consent_claims, priv, kid="partnerA-2025-01", alg="EdDSA")
# -> "eyJhbGciOiJFZERTQS<...>.eyJqdGkiOiJ<...>.<signature>"
```

> The JWS header `kid` **must** match a key you registered in PM (Step 2), and `alg` must match that key's algorithm and be permitted by your policy's `allowed_signing_algs` — otherwise verification fails. Any RFC-7515 JWS library (jose, jsonwebtoken, etc.) works; you don't have to use Python/PyJWT.

***

## Step 7 — Call the registry's data API

Call the **registry's** data endpoint, per that registry's API contract — for the OpenG2P registry, a signed DCI search ([Registry integration](/platform/platform-services/consent-management/design/registry-integration.md)):

* **Consent held by the CM:** send **no** consent and search **one person by identifier** (`query_type: idtype-value`, `id_type` and `id_value`); the registry asks its CM for your consent for that person. Other searches (e.g. a predicate) need a consent object. None found → `consent_not_found`.
* **Your own consent:** embed the consent JWS at `search_criteria.authorize.consent_jws`.

You do **not** call the Consent Manager's `/validate` — the registry does that for you, naming itself as `data_controller` so the CM picks its grant.

**Several registries, one consent.** A consent may hold a grant per registry. Send your own consent JWS unchanged to each registry you need (a consent the CM holds is found by each registry's CM the same way). Each validates its own grant and returns its own `consent_id` and `receipt_id`; the same `jti` gets one decision per registry (presenting it again to that registry returns the same decision). The `subject_id` in the consent must be the person you search for at each registry (a registry may accept a different identifier for the same person when its own data links them, e.g. a farmer ID recorded with the farmer's national ID).

**Composite services and aggregators.** If a composite service fans your request out to several registries, it presents your consent (or asks the Exchange CM to find it, when you send none) to an Exchange CM, which issues one signed consent token per registry ([Exchange consent tokens](/platform/platform-services/consent-management/design/exchange-consent-tokens.md)). The composite sends each registry only its own consent token, signs each registry call with its own key, and names you in `header.meta.on_behalf_of`. At such an Exchange you are granted **use cases** rather than registry scopes: you need a policy for the use case you call in the Exchange's Partner Management (one policy per use case) ([use-case mode](/platform/platform-services/consent-management/design/verification-and-enforcement.md#use-case-mode)). A consent your staff collect in the Exchange's partner portal is made for one of those use cases and can be used only for it ([consent collection](/platform/platform-services/consent-management/design/consent-collection.md#partner-portal)). For reference, the call the registry makes is [`POST /consent/v1/validate`](/platform/platform-services/consent-management/api/verification-api.md). It is reachable only inside the cluster, by registries and the Composite: the decision and the data both come to you via the registry.

***

## Step 8 — Read the outcome & handle denials

You receive back **only** the effective fields (`consent scope ∩ policy`), or an error the registry surfaces from the decision's `reason_code`:

| `reason_code`                  | What it means / what to fix                                                                                                                                                                                                                     |
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ok`                           | Permitted — you got `effective_data_scopes`.                                                                                                                                                                                                    |
| `unknown_partner`              | Your partner isn't active in PM, or you have no active policy for this registry there. Re-check Steps 2 & 3.                                                                                                                                    |
| `policy_unavailable`           | The Consent Manager could not read your policy from PM. Temporary — retry later.                                                                                                                                                                |
| `consent_not_found`            | You sent no consent and the CM holds no active, valid consent of yours for that person (and registry or use case). Collect one (Step 4).                                                                                                        |
| `partner_consent_not_accepted` | You sent a consent you signed, but your policy does not allow your own consents. Use a consent held by the CM, or ask for the policy change (Step 3).                                                                                           |
| `signature_invalid`            | The consent JWS didn't verify — wrong `kid`, a key/alg mismatch, or the JWS was altered after signing.                                                                                                                                          |
| `audience_mismatch`            | `aud` / `data_controller` don't match your policy. Re-check Steps 2, 3 & 5.                                                                                                                                                                     |
| `controller_not_granted`       | Your consent has no grant for the registry you called. Add a grant for it (Step 5).                                                                                                                                                             |
| `subject_mismatch`             | The subject declared to the CM is not the consent's subject. (The registry runs its own check too: if what you search is not the consent's subject, it rejects the item with a search-criteria error.)                                          |
| `purpose_not_allowed`          | Purpose isn't in your policy — ask the PM operator for a policy change (Step 3).                                                                                                                                                                |
| `scope_exceeds_policy`         | You requested a scope outside the policy ceiling — narrow it or ask for a policy change (Step 3).                                                                                                                                               |
| `use_case_not_granted`         | (Through an Exchange) you have no policy for the use case you called — ask for one (Step 3).                                                                                                                                                    |
| `expired`                      | `validity` window passed — issue a fresh consent.                                                                                                                                                                                               |
| `revoked`                      | The subject revoked this consent — stop; you may need fresh consent.                                                                                                                                                                            |
| `replay`                       | `issued_at` outside the freshness window — sync clocks, issue fresh; or a `jti` you already used for a different consent — use a new `jti` per consent. (Re-sending the same signed consent to the same registry returns its earlier decision.) |
| `malformed_object`             | The object failed schema validation — check required fields; use `grants` **or** `data_controller` + `data_scopes`, not both, and list each registry once.                                                                                      |

***

## Step 9 — Receipts, status & revocation

* **Receipt (proof):** every consent has a signed **consent receipt** (a consent you sign yourself gets one per registry that validated it). For a consent collected in the partner portal, download the receipt or the full [consent record](/platform/platform-services/consent-management/design/consent-collection.md#the-partners-copy-of-a-consent) there. A receipt verifies against the Consent Manager's public `/.well-known/jwks.json`, so you can prove the consent without the CM. Keep them for audit.
* **Status and revocation:** the beneficiary can revoke at any time. Every fetch is checked again, so a revoked or expired consent is refused on the next call (`revoked` / `expired`); the partner portal shows each consent's status. Revocation is per `consent_id`: for a consent you signed, per registry.

***

## Step 10 — Maintain your keys (rotation & compromise)

* **Rotate** by registering a **new** key (new `kid`) in PM (a key-update request), keeping the old key active briefly so in-flight objects still verify, then revoking the old one. Start signing new objects with the new `kid`. The Consent Manager picks up rotations quickly (it re-fetches on an unknown `kid`).
* **Compromise:** revoke the affected key in PM immediately. The Consent Manager fails **closed** for anything signed with a revoked/absent key.
* Never rotate by reusing a `kid` for a different key.

***

## Common pitfalls (checklist)

* [ ] You have an active policy in PM for each registry (or Exchange use case) you call.
* [ ] With no consent in the request, you search one person by identifier (`idtype-value`), and the CM holds a consent of yours for that person.
* [ ] Your own consents only: your policy allows them; the consent object is a valid **compact JWS** (Step 6), signed with your PM key; header `kid` + `alg` match a key you registered in PM (and the policy's `allowed_signing_algs`).
* [ ] Own consents: `aud` = your audience; each grant's `data_controller` = a registry you have a policy for; its `data_scopes` are that registry's scope IDs and, with the `purpose`, within your policy.
* [ ] The consent's subject is the person you search for at every registry.
* [ ] Own consents: `issued_at` is fresh and clocks are synced; `jti` is unique.
* [ ] You handle `deny` outcomes and honour `revoked` / `expired`.

## Related pages

* [Verification API](/platform/platform-services/consent-management/api/verification-api.md) — the exact `/validate` request and response the registry makes on your behalf.
* [Partner Management integration](/platform/platform-services/consent-management/design/partner-management-integration.md) — how your keys and policy are read and cached.
* [Access policies](/platform/platform-services/partner-management/functional-specifications.md#access-policies) — the policy that bounds what you can receive.
* [Security & trust](/platform/platform-services/consent-management/design/security-and-trust.md) · [Data model](/platform/platform-services/consent-management/design/data-model.md) — the consent object, artefact and receipt in detail.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.openg2p.org/platform/platform-services/consent-management/partner-integration-guide.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
