Skip to main content
This is the page that most Partner API integrations get a little wrong on the first try. The mental model is closer to OAuth than to “API key allows access to my data.” Worth a careful read.

The model

A consent is a row in our database that says:
User U has granted Partner L access to scope S until expiry E.
Every snapshot fetch must reference an active consent. The user can revoke at any time. The scope determines what the snapshot includes. The expiry caps how long the access lasts. Crucially, the consent is held on Guild’s side, not the partner’s. You don’t store a copy of the user’s grant. You reference the consent by its ID on every request. We check it’s still active before we serve the snapshot. Two paths.

Path 1: User-initiated (from their Guild dashboard)

The user opens partners.guild.com.ng. They see a “Connect a partner” button. They search for your organisation (we maintain a directory of approved partners). They tap Grant access. A prompt explains exactly what you’ll be able to read:
X Financier is requesting access to your Guild data. They will be able to read:
  • Your standing tier and score
  • Your audit ledger summary for the last 90 days
  • Your KYC verification status (Tier 3)
Access will expire on 14 November 2026 (90 days). You can revoke any time. [Grant] [Decline]
User taps Grant. We write a consent row. We fire consent.granted webhook to your endpoint. The webhook payload contains the consent ID, the user ID, the scopes, and the expiry. The more common pattern. Inside your loan application flow, you generate a consent link via the API:
Response:
You redirect the user to consentUrl. They sign into Guild with their phone + OTP if not already signed in. They see the consent prompt (same content as path 1). They grant or decline. We redirect them back to your callbackUrl with ?status=granted&consentId=cnst_... or ?status=declined. This is the OAuth-shaped pattern. Familiar to most partner backends.

The scope vocabulary

Each consent specifies which scopes the partner is granted. Scopes compose; the snapshot you receive is the intersection of what you requested and what the user granted. Two rules:
  1. Only one ledger_window_* scope per consent. They’re mutually exclusive.
  2. repayment_history and disbursement_history are aggregated across all partners; you don’t see which partner extended the credit, only that someone did.
What you can read from GET /partner/v1/consents/:consentId:
Statuses cycle through PENDING (consent link generated, not yet acted on), ACTIVE (user granted), EXPIRED (passed expiresAt), REVOKED (user pulled it). Every snapshot fetch includes the consent ID:
We check:
  • The consent exists.
  • It belongs to the partner associated with your API key.
  • It hasn’t expired.
  • It hasn’t been revoked.
  • It includes the scopes needed for the data being returned.
If any of those fail, the request returns 403 with a specific error_code. Your code should treat each failure mode distinctly — consent_revoked means ask the user to re-grant, consent_expired means renew, consent_not_found means a programming bug.

Revocation

The user can revoke at any time from partners.guild.com.ng. The moment they tap revoke:
  1. Consent status flips to REVOKED. revokedAt is set.
  2. Any in-flight snapshot fetches with that consent ID fail with consent_revoked (no race condition — we check at the start of every request).
  3. consent.revoked webhook fires to your endpoint.
  4. The user sees a confirmation in their dashboard.
You can request a new consent. The user gets a fresh prompt, with the same content as the original grant. They can grant or decline as they see fit. What revocation doesn’t do: invalidate snapshots you already pulled. Underwriting works on a fixed point in time — you make your decision against the snapshot you fetched at moment X. If the user revokes at moment Y > X, your decision stands, and any active loan continues. You just can’t pull a fresh snapshot. This is intentional. Borrowers shouldn’t be able to escape a contract by revoking consent post-hoc.

Renewal

When a consent approaches expiry (we configure a 14-day warning window by default), the user gets a notification in their Guild dashboard with a one-tap renewal:
Your consent with X Financier expires in 7 days. Renew for another 90 days?
If they tap Renew, the same scopes extend by another window. If they tap Decline, the consent expires on schedule. We fire consent.renewed (or, on expiry, consent.expired) webhooks. You can also drive renewal from your side — generate a new consent link with the same scopes and email it to the user a week before expiry. A user might grant a single partner consent for multiple products (a savings sweep + a credit line + an inventory advance). The model handles this two ways:
  1. One broad consent per partner. Grant standing + ledger_window_90 + kyc_status + repayment_history, valid for 365 days. The partner uses this consent for all products. Simpler for the user.
  2. Separate consents per product. Each product has its own consent and its own scope set. Cleaner audit trail but requires the user to tap grant multiple times.
We’ve seen both work. Most partners go with pattern 1.

Why not OAuth?

You’ll notice the model looks a lot like OAuth, but we don’t expose the /oauth/authorize redirect dance directly. Two reasons:
  1. The user must always come back to Guild for the prompt. OAuth assumes the redirect can be fully customised by the relying party; we prefer a consistent prompt that the user recognises across every partner. Consent links are the abstraction over this.
  2. No tokens. OAuth issues access tokens that the partner stores. We don’t — every snapshot fetch references a consent by ID, and we check on every call. This is more chatty but means a revocation propagates instantly across your infrastructure.
If your existing systems are built around OAuth, the consent-link pattern maps cleanly onto your existing redirect flows. Most partners find it familiar within an afternoon. Every consent state change writes to a consent_audit table that’s append-only with BEFORE UPDATE OR DELETE triggers. The full history of grants, expiries, revocations, and renewals is permanent. Both you and the user can read this audit. The user from their dashboard. The partner from GET /partner/v1/consents/:consentId/audit. Disputes resolve quickly because the trail is uneditable.

Next

Snapshots

The actual data you read once you have a consent.

Webhooks

Push notifications when consents grant, revoke, or expire.