Offer Polling & Rate Limits

Hybrid Experience keeps the borrower in your UI while Aarthik Labs talks to lenders. Because lender responses are asynchronous, your back-end polls the Hybrid offers endpoint until offers, an Account Aggregator action, or a no-offer outcome is ready.

The polling contract is server-owned. Your app should not invent its own timer schedule. Use the polling.nextPoll value returned by the API and handle 429 responses as a normal part of the integration.

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

Hybrid product paths use the same polling object: shouldPoll, lastPoll, and nextPoll. Personal Loan and Business Loan use the Personal Loan-style lender-grouped offer shape in the API Reference. Gold Loan polling responses omit data.account_aggregator and can include lender branch locations in data.lenders[<lenderName>].branches. Loan Against Mutual Funds uses a LAMF-specific lender-grouped shape for Credit Line and Term Loan offers.

What Your App Controls

Your product controls the borrower-facing wait state and decides how to present offers. Aarthik Labs controls the legal poll cadence, lender timeout windows, Account Aggregator URL readiness, and rate limits.

Your back-end should:

  • call the offers endpoint from your server, not from the browser
  • return the latest offer state to your front-end
  • schedule the next poll from data.polling.nextPoll
  • treat 429 as “too early, retry at the server-provided time”
  • open Account Aggregator only when data.account_aggregator.url is non-null
  • stop polling when data.polling.shouldPoll is false

Polling Object

When offer polling is active, the response includes a polling object:

1{
2 "data": {
3 "status": "PROFILED",
4 "polling": {
5 "shouldPoll": true,
6 "lastPoll": "2026-05-11T10:00:20.000Z",
7 "nextPoll": "2026-05-11T10:00:30.000Z"
8 },
9 "lenders": {}
10 },
11 "error": null
12}
FieldMeaning
shouldPolltrue means the journey can still produce new offer state and your back-end should poll again at nextPoll. false means this polling window is complete.
lastPollServer time at which the current accepted poll was processed.
nextPollEarliest server time at which the next poll can be accepted. Polling before this time can return 429.

Use nextPoll as an absolute timestamp. Do not convert the schedule into a fixed client-side interval. If your app wakes up late, call the endpoint once and use the next polling object returned by the server.

Gold Loan Branches

Gold Loan offer polling returns branch locations received from lender on_select responses. These branches are grouped under the lender alongside the offer rows.

1{
2 "data": {
3 "status": "OFFERED",
4 "lenders": {
5 "Aurum Bank": {
6 "minimumLoanAmount": "50000.00",
7 "maximumLoanAmount": "5000000.00",
8 "branches": [
9 {
10 "id": "L1",
11 "gps": "28.574909, 77.194029",
12 "name": "Mumbai Central",
13 "address": "123 Marine Drive, Mumbai, Maharashtra",
14 "pincode": "400001"
15 }
16 ],
17 "offers": [
18 {
19 "consentHandleAvailable": false,
20 "annualPercentageRate": "5.00",
21 "installmentAmount": "88460.00",
22 "ltvRatio": null,
23 "status": "OFFER_RECEIVED",
24 "offerID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
25 "amountPrincipal": "400000",
26 "interestRate": "12.00",
27 "offerTerm": "5 months",
28 "processingFee": "1100.00",
29 "applicationFee": "1000.00",
30 "offerValidity": "15 days",
31 "ondcTransactionID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
32 "offerSignals": {
33 "offerType": "FINAL_OFFER",
34 "fulfillmentJourneyType": "COMBINED_ONLINE_OFFLINE",
35 "conversionChance": "VERY_HIGH"
36 }
37 }
38 ]
39 }
40 },
41 "polling": {
42 "shouldPoll": true,
43 "lastPoll": "1970-01-01T00:00:20.000Z",
44 "nextPoll": "1970-01-01T00:00:30.000Z"
45 }
46 },
47 "error": null
48}

Loan Against Mutual Funds Offer Types

Loan Against Mutual Funds polling supports two offer types: Credit Lines and Term Loans. LAMF lender groups intentionally do not include minimumLoanAmount or maximumLoanAmount.

LAMF offer typeIncludesDoes not include
Credit LineproductType, facilitytype, sanctionedLimit, renewalFrequency, fees, validity, pledge requirementsamountPrincipal, installmentAmount, ltvRatio
Term LoanproductType, facilitytype, amountPrincipal, installmentAmount, fees, validity, pledge requirementssanctionedLimit, renewalFrequency, ltvRatio

For both LAMF Credit Line and Term Loan offers, productType is LAMF. facilitytype is CREDIT_LINE or TERM_LOAN depending on the offer. LAMF offer signals are documented as FINAL_OFFER, FULLY_DIGITAL_STP, and VERY_HIGH.

Offer Signals

When lender signal configuration is available, each offer row can include offerSignals. These signals are borrower-facing display hints only. They should be shown as badges or supporting labels, not used to reorder lenders or auto-select an offer.

FieldValues
offerTypeDEDUPE_OFFER, PROVISIONAL_OFFER, FINAL_OFFER
fulfillmentJourneyTypeFULLY_DIGITAL_STP, COMBINED_ONLINE_OFFLINE, OFFLINE_ASSISTED
conversionChanceVERY_HIGH, HIGH, MODERATE, LOW, VERY_LOW

Polling Phases

Hybrid offer polling has two phases.

Pre-AA And Non-AA Window

This window starts when the Hybrid journey starts offer fetch.

Default accepted poll slots:

SlotTime after offer fetch startsWhat can happen
1+20sFirst accepted read for non-AA offers and early AA readiness.
2+30sAccount Aggregator URL can be returned if all required consent handles are ready. AA lenders missing consent handles are timed out.
3+40sContinue reading non-AA lender responses.
4+50sContinue reading non-AA lender responses.
5+60sFinal pre-window poll. Non-AA lenders still awaiting are timed out.

If at least one offer is available, your UI can show it immediately. If polling.shouldPoll remains true, you may keep refreshing at nextPoll to pick up additional lender outcomes, but you should not block the borrower from seeing available offers unless your own product policy requires that.

Account Aggregator URL Timing

The Account Aggregator URL is intentionally not returned immediately when offer fetch starts. This section applies when the offers response includes data.account_aggregator. Gold Loan Hybrid does not return Account Aggregator in the current flow.

By default, Aarthik Labs waits until the second pre-window poll slot, +30s, before returning the AA URL. This gives lenders time to share the required consent handles.

When AA is applicable, the response can include:

1{
2 "data": {
3 "account_aggregator": {
4 "url": null,
5 "expires_at": null,
6 "lenders": ["AA Bank"]
7 }
8 }
9}

Use this object as follows:

ShapeMeaningWhat your app should do
account_aggregator is absentNo borrower-facing AA action is currently applicable.Continue rendering offers, waiting states, or terminal states from the rest of the response.
url is nullAA is applicable, but the URL is not ready yet.Keep the borrower in a wait state and poll again at polling.nextPoll.
url is a stringA short-lived AA URL is ready.Ask the borrower to continue with consent and open the returned URL immediately.

The account_aggregator object only contains:

  • url
  • expires_at
  • lenders

It does not contain status, available, or poll_after_ms. The polling object is the source of truth for when to poll again.

Post-AA Window

The post-AA window starts after the borrower completes the Account Aggregator flow and returns to the app.

Default accepted poll slots:

SlotTime after post-AA offer fetch startsWhat can happen
1+30sFirst accepted read for AA-backed offers.
2+45sContinue reading AA-backed lender responses.
3+60sContinue reading AA-backed lender responses.
4+75sContinue reading AA-backed lender responses.
5+90sFinal post-AA poll. AA lenders still awaiting are timed out.

After the final post-AA poll, polling.shouldPoll becomes false for that window.

Total Accepted Polls

In the default full AA journey, there can be up to ten accepted polls:

  • five pre-AA / non-AA accepted polls
  • five post-AA accepted polls

Additional calls before the next legal slot are not accepted. They return 429 and do not count as successful offer reads.

Rate Limits

Polling faster than polling.nextPoll can return:

1HTTP/1.1 429 Too Many Requests
2Retry-After: 10
3X-Aarthik-Next-Poll-At: 2026-05-11T10:00:30.000Z
1{
2 "data": {
3 "polling": {
4 "shouldPoll": true,
5 "lastPoll": "2026-05-11T10:00:20.000Z",
6 "nextPoll": "2026-05-11T10:00:30.000Z"
7 }
8 },
9 "error": {
10 "code": "OFFER_POLL_RATE_LIMITED",
11 "message": "Offer polling is rate limited. Please poll again at 2026-05-11T10:00:30.000Z.",
12 "retryable": true,
13 "details": {
14 "polling": {
15 "shouldPoll": true,
16 "lastPoll": "2026-05-11T10:00:20.000Z",
17 "nextPoll": "2026-05-11T10:00:30.000Z"
18 }
19 }
20 }
21}

When you receive 429:

  • do not show it to the borrower as a failure
  • wait until X-Aarthik-Next-Poll-At or error.details.polling.nextPoll
  • use Retry-After as a fallback if timestamp scheduling is not available
  • keep the current borrower wait state or currently visible offers
  • retry the same offers endpoint once the server-provided time arrives

Back-End Polling Loop

Use a single scheduled poll per borrower journey. Avoid multiple browser tabs, mobile retries, and background jobs all polling the platform independently.

1type Polling = {
2 shouldPoll: boolean;
3 lastPoll: string;
4 nextPoll: string;
5};
6
7type HybridProduct =
8 | "personal-loan"
9 | "gold-loan"
10 | "loan-against-mutual-funds"
11 | "business-loan";
12
13async function pollHybridOffers(
14 product: HybridProduct,
15 borrowerProviderID: string,
16) {
17 const response = await fetch(
18 `${PLATFORM_BASE_URL}/api/v1/hybrid/${product}/borrowers/${borrowerProviderID}/journey/offers`,
19 {
20 headers: {
21 Authorization: `Bearer ${PLATFORM_API_KEY}`,
22 },
23 },
24 );
25
26 const body = await response.json();
27
28 if (response.status === 429) {
29 const polling = body.error?.details?.polling ?? body.data?.polling;
30 scheduleNextPoll(borrowerProviderID, polling.nextPoll);
31 return { rateLimited: true, polling };
32 }
33
34 if (!response.ok) {
35 throw new Error(body.error?.message ?? "Unable to poll offers.");
36 }
37
38 const polling = body.data?.polling as Polling | undefined;
39 if (polling?.shouldPoll) {
40 scheduleNextPoll(borrowerProviderID, polling.nextPoll);
41 }
42
43 return body.data;
44}

The important behavior is simple: schedule from the server response, not from a hardcoded interval.

UI Behavior

Your UI can use the offer response to choose the current borrower state.

Response stateUI behavior
One or more OFFER_RECEIVED rowsShow available offers. Continue refreshing only if polling.shouldPoll is still true and your product wants later lender updates.
account_aggregator.url is a stringShow a clear AA consent action and open the URL when the borrower continues.
account_aggregator.url is null and polling.shouldPoll is trueShow a wait state and poll again at polling.nextPoll.
All lenders are NO_OFFER_AVAILABLEShow a no-offer state.
polling.shouldPoll is falseStop automatic polling for this window.

Configuration

The default schedule can be configured per application by Aarthik Labs.

Config fieldDefaultMeaning
polling_pre_timeout60Pre-AA / non-AA timeout window in seconds.
polling_pre_initial_time20First accepted pre-window poll in seconds.
polling_pre_subsequent_cadence10Cadence for later pre-window accepted polls in seconds.
polling_post_timeout90Post-AA timeout window in seconds.
polling_post_initial_time30First accepted post-AA poll in seconds.
polling_post_subsequent_cadence15Cadence for later post-AA accepted polls in seconds.

These fields are application-level platform configuration. Integrating partners should still follow the polling.nextPoll value returned by the API because the effective schedule can differ by application.

Practical Rules

  • Poll from your back-end only.
  • Use polling.nextPoll for the next attempt.
  • Treat 429 as a normal timing response, not an integration failure.
  • Do not use poll_after_ms; it is not part of the current Hybrid polling contract.
  • Do not infer AA readiness from lender names alone. Use account_aggregator.url.
  • For Gold Loan, do not wait for Account Aggregator. Render offers or waiting states from status, polling, and lenders.
  • Do not keep polling after polling.shouldPoll becomes false.
  • Do not store Account Aggregator URLs as long-term resume links.
  • Log borrowerProviderID, journey ID, status, polling.nextPoll, and whether account_aggregator.url was present for support.

Checklist

Before going live, confirm that your integration:

  • starts polling only after the journey start call succeeds
  • schedules all follow-up polls from polling.nextPoll
  • handles 429 by waiting and retrying at the returned time
  • shows available offers while later lender responses may still be pending
  • opens AA only when account_aggregator.url is present
  • does not expect account_aggregator on Gold Loan
  • refreshes offers after the borrower returns from AA
  • stops automatic polling when polling.shouldPoll is false
  • avoids duplicate poll loops for the same borrower journey