> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.credit.aarthiklabs.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.credit.aarthiklabs.com/_mcp/server.

# Integrations Guide

> Start offer discovery, poll for offers, and continue a selected offer in Hosted Experience.

Hybrid Experience is a back-end-led integration. Your front-end talks to your back-end. Your back-end talks to Aarthik Labs with a server-side API key.

Hybrid product flows use the same three-call pattern. The product is selected by the URL path:

| Product                   | Path prefix                                |
| ------------------------- | ------------------------------------------ |
| Personal Loan             | `/api/v1/hybrid/personal-loan`             |
| Gold Loan                 | `/api/v1/hybrid/gold-loan`                 |
| Loan Against Mutual Funds | `/api/v1/hybrid/loan-against-mutual-funds` |
| Business Loan             | `/api/v1/hybrid/business-loan`             |

Business Loan offer discovery can return both `BUSINESS_LOAN` and self-employed `PERSONAL_LOAN` offers, subject to availability for your application. Use each offer's `productType` to label it correctly. Store the `data.journey.id` returned by Create A Journey and pass it as `journeyID` on both continuation calls.

```txt
GET /api/v1/hybrid/business-loan/borrowers/{borrowerProviderID}/journey/offers?journeyID={journeyID}
GET /api/v1/hybrid/business-loan/borrowers/{borrowerProviderID}/journey/offers/{offerID}/hosted?journeyID={journeyID}
```

The production flow uses three back-end calls:

1. Start a new application attempt for the borrower.
2. Poll lender-grouped offers for that journey and Account Aggregator readiness when the selected source supports it.
3. Request a Hosted Experience URL for that journey after the borrower selects an offer.

## Environment Setup

Set these on the back-end that calls Aarthik Labs. Do not expose the API key to browsers or mobile apps.

```bash
PLATFORM_BASE_URL="https://preprod.credit.aarthiklabs.com"
PLATFORM_API_KEY="sk_test_xxx"
```

For production, use the production base URL and live API key provided for your application.

## 1. Create A Journey

Your back-end starts the borrower's Hybrid journey.

```txt
POST /api/v1/hybrid/personal-loan/borrowers/{borrowerProviderID}/journey
POST /api/v1/hybrid/gold-loan/borrowers/{borrowerProviderID}/journey
POST /api/v1/hybrid/loan-against-mutual-funds/borrowers/{borrowerProviderID}/journey
POST /api/v1/hybrid/business-loan/borrowers/{borrowerProviderID}/journey
```

Use a stable `borrowerProviderID` for the same borrower across sessions and application attempts. This keeps borrower history, resume, and post-disbursal tracking tied to one borrower record.

Prefer sending an `Idempotency-Key` header on Start:

* Use a fresh `Idempotency-Key` for each new application attempt.
* Reuse the same `Idempotency-Key` only when retrying the same Start request after a timeout or temporary failure.
* If you omit `Idempotency-Key`, the platform treats the call as a new non-idempotent application attempt by generating an internal attempt key. This preserves fresh-journey creation for compatibility, but client retries can create duplicates.
* Do not rotate `borrowerProviderID` to create a new application attempt.

Send borrower profile, work, address, and journey information according to the API Reference. Personal Loan can also accept banking information and optional buyer-supplied bureau data. Loan Against Mutual Funds follows the Personal Loan-style shape and can additionally accept optional free-form `mfc` data. Business Loan follows the Personal Loan-style shape and adds business-specific first-form fields in `businessLoan`. Gold Loan does not accept `bureau`, banking information, or `loanPreferences` in this start call. For Gold Loan, send pledged-gold fields as top-level `goldInformation` and use `journey.consent`.

In the API Reference, Loan Against Mutual Funds examples are split into **Credit Line** and **Term Loan** tabs. Both tabs use the same `/loan-against-mutual-funds` endpoints; the start call only changes the optional `loanPreferences.facilitytype` preference, while the offer and handoff responses show the facility-specific fields.

The example below shows the Gold Loan shape. Use the API Reference for the product-specific Personal Loan, Loan Against Mutual Funds, and Business Loan examples.

```json
{
  "personal": {
    "userType": "individual",
    "pan": "ABCPE1234F",
    "panName": "John Doe",
    "dob": "1990-01-01",
    "gender": "male",
    "personalemail": "john@example.com",
    "contactNumber": "9876543210"
  },
  "work": {
    "employmentType": "salaried",
    "income": 90000,
    "incomeType": "monthly",
    "companyName": "Acme Global Inc."
  },
  "address": {
    "addressL1": "101 Sunshine Heights",
    "addressL2": "Gold Coast Road",
    "city": "Rajkot",
    "state": "Gujarat",
    "pincode": "360001"
  },
  "journey": {
    "endUse": "medicalTreatmentAndEmergencies",
    "consent": true
  },
  "goldInformation": {
    "jewelleryWeightGrams": "52",
    "purity": "22K"
  }
}
```

The platform stores the pre-fill data and starts the offer-fetch pipeline.

A successful response does not mean an offer is already available. Store `data.applicationID` and `data.journey.id`. If `data.operation.status` is `ACCEPTED`, work is still pending. Continue by polling offers with the same `journeyID` and follow the returned schedule. Do not create a new attempt or change the retry key just because discovery is pending. `OFFER_AWAITED` is a waiting state; wait for an offer ID and terms before requesting a Hosted URL.

## Business Loan Eligibility

Eligibility is optional and does not require Dedupe. Start the operation with POST (HTTP `202`), then use GET on the same path with the returned `journeyID` to read progress and lender decisions. Start responses omit `polling`. Make the first GET after acceptance, then follow the GET response’s `polling.nextPoll` while `polling.shouldPoll` is true; do not POST again to poll. Supply at least one of Bureau, Banking or GST, or combine all three. See [Eligibility Inputs](/documentation/hybrid-experience/integrations-guide/eligibility-inputs) for the input structure and four named examples, also available in the API Reference selector.

```http
POST /api/v1/hybrid/business-loan/borrowers/{borrowerProviderID}/journey/eligibility?journeyID=<existing-journey-id>
```

Omit the query parameter to create a journey when Eligibility is the first step. Otherwise reuse the ID returned by Dedupe. Pass the returned `data.journeyID` to Create A Journey as `?journeyID=<same-journey-id>`. Dedupe can also be used without Eligibility, and both can be skipped.

## 2. Poll Offers And Account Aggregator Readiness

Your back-end reads the current offer state while the borrower stays in your UI.

```txt
GET /api/v1/hybrid/personal-loan/borrowers/{borrowerProviderID}/journey/offers
GET /api/v1/hybrid/gold-loan/borrowers/{borrowerProviderID}/journey/offers
GET /api/v1/hybrid/loan-against-mutual-funds/borrowers/{borrowerProviderID}/journey/offers
GET /api/v1/hybrid/business-loan/borrowers/{borrowerProviderID}/journey/offers
GET /api/v1/hybrid/personal-loan/borrowers/{borrowerProviderID}/journey/offers?journeyID={data.journey.id}
GET /api/v1/hybrid/gold-loan/borrowers/{borrowerProviderID}/journey/offers?journeyID={data.journey.id}
```

Pass the `data.journey.id` value returned by Start as `journeyID`. The alias `journey_id` is also accepted. If you omit the journey ID, the borrower-only fallback is legacy behavior and may return a 409 ambiguity error when the same borrower has more than one active Hybrid application attempt.

This response can contain:

* current journey status
* a `polling` object with the next legal poll time
* lender-grouped offer state
* selectable offers with `OFFER_RECEIVED`
* `processingFee`, `applicationFee`, and `offerValidity` on Personal Loan, Gold Loan, and Business Loan offer rows when available
* optional `offerSignals` on offer rows, when configured for that lender
* branch locations for Gold Loan lenders, when returned by the lender
* lenders waiting for Account Aggregator consent, when applicable
* an Account Aggregator URL when consent is ready, when applicable

Gold Loan Hybrid does not include Account Aggregator right now. Gold Loan offer polling uses the same `data.polling.shouldPoll`, `data.polling.lastPoll`, and `data.polling.nextPoll` cadence, but the response omits `data.account_aggregator` and can include `branches` inside each lender group.

Business Loan uses the Personal Loan-style lender-grouped offer shape. Loan Against Mutual Funds uses a LAMF-specific lender-grouped shape: lender groups do not expose `minimumLoanAmount` or `maximumLoanAmount`, and each selectable offer is either a Credit Line or a Term Loan.

| LAMF offer type | Key fields                                                                                                       |
| --------------- | ---------------------------------------------------------------------------------------------------------------- |
| Credit Line     | `productType: "LAMF"`, `facilitytype: "CREDIT_LINE"`, `sanctionedLimit`, `renewalFrequency`, pledge requirements |
| Term Loan       | `productType: "LAMF"`, `facilitytype: "TERM_LOAN"`, `amountPrincipal`, `installmentAmount`, pledge requirements  |

LAMF offers do not expose `ltvRatio`. LAMF Credit Line and Term Loan offer signals are documented as `FINAL_OFFER`, `FULLY_DIGITAL_STP`, and `VERY_HIGH`.

Your app should keep polling until one of these outcomes is available:

| Outcome                             | What your app should do                                                          |
| ----------------------------------- | -------------------------------------------------------------------------------- |
| Offers are available                | Show lender-grouped offers and let the borrower choose one.                      |
| Account Aggregator is ready         | Show an AA consent action and open the returned URL when the borrower continues. |
| Account Aggregator is not ready yet | Keep the borrower in a waiting state and poll again at `data.polling.nextPoll`.  |
| No offers are available             | Show a clear no-offer state.                                                     |

The offers endpoint enforces a server-owned polling cadence. Calls before `data.polling.nextPoll` can return `429` with the same `polling` object and `Retry-After` / `X-Aarthik-Next-Poll-At` headers. Treat that as a normal timing response and retry at the returned time.

Read [Offer Polling & Rate Limits](/documentation/hybrid-experience/integrations-guide/offer-polling-rate-limits) for the full cadence, Account Aggregator timing, and rate-limit behavior.

### Offer Signals

Offer rows can include an optional `offerSignals` object:

```json
{
  "offerSignals": {
    "offerType": "PROVISIONAL_OFFER",
    "fulfillmentJourneyType": "COMBINED_ONLINE_OFFLINE",
    "conversionChance": "HIGH"
  }
}
```

Use these values as display hints for the borrower. They do not change offer ordering, eligibility, lender availability, or the Hosted handoff contract.

| Field                    | Values                                                             |
| ------------------------ | ------------------------------------------------------------------ |
| `offerType`              | `DEDUPE_OFFER`, `PROVISIONAL_OFFER`, `FINAL_OFFER`                 |
| `fulfillmentJourneyType` | `FULLY_DIGITAL_STP`, `COMBINED_ONLINE_OFFLINE`, `OFFLINE_ASSISTED` |
| `conversionChance`       | `VERY_HIGH`, `HIGH`, `MODERATE`, `LOW`, `VERY_LOW`                 |

## 3. Request Hosted URL

After the borrower selects an offer in your UI, your back-end requests the URL that continues that offer. For non-Gold products, offer acceptance is controlled by Aarthik Labs configuration for the tenant, application, and lender. The configured value wins and defaults to enabled when no lender policy exists.

```txt
GET /api/v1/hybrid/personal-loan/borrowers/{borrowerProviderID}/journey/offers/{offerID}/hosted
GET /api/v1/hybrid/gold-loan/borrowers/{borrowerProviderID}/journey/offers/{offerID}/branches/{branchID}/hosted
GET /api/v1/hybrid/loan-against-mutual-funds/borrowers/{borrowerProviderID}/journey/offers/{offerID}/hosted
GET /api/v1/hybrid/business-loan/borrowers/{borrowerProviderID}/journey/offers/{offerID}/hosted
GET /api/v1/hybrid/personal-loan/borrowers/{borrowerProviderID}/journey/offers/{offerID}/hosted?journeyID={data.journey.id}
GET /api/v1/hybrid/gold-loan/borrowers/{borrowerProviderID}/journey/offers/{offerID}/branches/{branchID}/hosted?journeyID={data.journey.id}
GET /api/v1/hybrid/personal-loan/borrowers/{borrowerProviderID}/journey/offers/{offerID}/hosted?journeyID={data.journey.id}&isOfferAccepted=true
```

`isOfferAccepted` is retained only for backward compatibility. Supplied values, including malformed or repeated values, are ignored. Remove it from new integrations.

| Effective server policy   | Behavior                                                                      |
| ------------------------- | ----------------------------------------------------------------------------- |
| Enabled or not configured | Aarthik Labs applies API acceptance at the established acceptance point.      |
| Disabled                  | The borrower continues through Hosted offer and KFS review before acceptance. |

The response always reports the effective decision in `data.offer_acceptance`. `requested` is the effective server-controlled value, not the caller's query value. Check `applied` to determine whether this call applied acceptance.

* When `applied` is `true`, the API has accepted the selected offer and may prepare the next lender step before issuing a URL.
* When `applied` is `false`, open the returned `hosted_url`; the borrower completes the standard offer and KFS review in Hosted Experience.

The call is idempotent for the same selected offer. A different offer after the selection is locked returns HTTP 409 and no Hosted URL.

When `data.offer_acceptance.applied` is true, inspect `data.redirection` and `data.polling`:

```json
{
  "data": {
    "hosted_url": "https://credit.aarthiklabs.com/lab/handoff?handoff=...",
    "expires_at": "2026-07-16T10:05:00.000Z",
    "offer_acceptance": {
      "requested": true,
      "applied": true
    },
    "redirection": null,
    "polling": {
      "shouldPoll": false,
      "lastPoll": "2026-07-16T10:00:00.000Z",
      "nextPoll": null
    }
  }
}
```

* Open the fresh signed Aarthik `hosted_url` immediately. The API never exposes the raw lender URL.
* `redirection.state: READY` means the Hosted URL is ready to open; the lender destination may still be preparing behind its loader.
* If the lender step is pending, the URL opens a minimal unbranded loader. The loader waits internally and automatically redirects when the canonical lender URL is ready; the integrator does not poll this third API.
* If the journey step is complete or terminal, the URL opens the Hosted offer and journey tracker so the borrower can review status, offer details, servicing, payments, and IGM actions when available.
* Multiple-redirection journeys always use the first incomplete stage in this order: KYC, bank mandate when applicable, e-mandate, then loan agreement.

For Personal Loan responses where acceptance was applied, opening `hosted_url` automatically forwards the borrower when an external lender step is current. A single-redirection journey goes to the lender destination. A multiple-redirection journey goes to the current external stage, such as KYC, e-mandate, or loan agreement/e-sign. The borrower does not need to click **Verify** or interact with an Aarthik Labs screen while waiting.

Bank mandate remains Hosted when it is an Aarthik-owned or local borrower action. A server policy that disables API acceptance retains the Hosted review behavior. If direct handoff validation fails or expires, the browser receives a minimal non-sensitive error; request a fresh `hosted_url`.

For Business Loans, use the same `journeyID` for a selected `BUSINESS_LOAN` or `PERSONAL_LOAN` offer. Supported LAMF API acceptance is limited to online single-redirection journeys; unsupported LAMF modes return `HYBRID_HOSTED_STAGE_UNSUPPORTED`. Gold Loan behavior is unchanged.

Every returned `hosted_url` is short-lived and should be opened immediately in the borrower browser, webview, or app shell.

Hosted Experience then takes over the selected-offer journey, including tracking, KFS, KYC, mandates, agreements, lender redirects, post-disbursal actions, and servicing.

## End-To-End Flow

```mermaid
flowchart TD
  A["Your UI collects borrower profile"] --> B["Your back-end starts journey"]
  B --> C["Your UI shows offer-fetch wait state"]
  C --> D["Your back-end polls offers"]
  D --> E{"What is ready?"}
  E --> F["Selectable offers"]
  E --> G["Account Aggregator URL"]
  E --> H["Still processing"]
  E --> I["No offer"]
  G --> J["Borrower completes AA consent"]
  J --> D
  H --> D
  F --> K["Borrower selects offer"]
  K --> L["Backend requests Hosted URL"]
  L --> M["UI opens Hosted Experience"]
```

## Implementation Notes

* Keep `PLATFORM_API_KEY` server-side.
* Use your own back-end endpoint as the front-end boundary.
* Treat `borrowerProviderID` as stable application-owned borrower identity.
* Use a fresh `Idempotency-Key` for each new Start attempt; reuse the same key only to retry that exact Start request.
* Omitting `Idempotency-Key` is allowed for compatibility and creates a fresh non-idempotent attempt with a server-generated internal key. Add a client-supplied key when you need safe retries.
* Do not reuse expired Account Aggregator or Hosted URLs.
* Follow `data.polling.nextPoll` to avoid unnecessary polling load and `429` responses.
* Store `data.journey.id` from Start and pass it as `journeyID` on Offers and Hosted calls. Store `data.journeyID` from Offers and `data.journey.id` from Hosted responses for reconciliation; these values match `data.journey.id` in lifecycle web-hooks.
* Store offer identifiers when your product needs offer-level support, analytics, or CRM reconciliation.
* For Gold Loan, read branch locations from the offers polling response, let the borrower select a branch, and pass that branch ID in the Hosted handoff path. The Hosted handoff response returns the selected lender, selected offer, relationship manager from fulfillment-agent data, and fresh Hosted URL.

## Mobile Applications

If your Hybrid journey runs inside a mobile app, treat the Hosted handoff as a hosted mobile integration.

Your app should be ready to:

* open Account Aggregator and Hosted URLs in an approved browser or app-controlled surface
* keep the Aarthik Labs API key on your back-end
* register the mobile bridge before loading Hosted screens
* support lender-owned external steps and return behavior
* provide camera, file, location, and related permissions when a journey step requires them
* handle Hosted exit events cleanly

Read the Mobile Applications docs for the bridge and permission requirements:

* [Bridge](/documentation/common/mobile-applications/bridge)
* [Permissions](/documentation/common/mobile-applications/permissions)