> 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.

# Back-End Integrations

> Keep the API key server-side and return only the Hosted Experience URL to your app.

Your back-end is the trusted boundary for Hosted Experience. It authenticates with Aarthik Labs, decides which borrower can start a journey, passes pre-fill data, and returns only `embedURL` to the browser or mobile app.

Do not call Aarthik Labs directly from your front-end.

## Backend Responsibilities

Your back-end should:

* store `PLATFORM_API_KEY` in server-side configuration only
* validate the authenticated borrower in your own app
* send a stable `borrowerProviderID`
* send an explicit `journeyType`
* pass only pre-fill data that your app is allowed to share
* return only `{ "embedURL": "..." }` to the front-end
* avoid caching session-creation responses

## Recommended Endpoint

Most integrations create a small back-end endpoint such as:

```txt
POST /api/credit/hosted-session
```

Your front-end calls this endpoint. This endpoint calls Aarthik Labs.

## Next.js App Router Example

Create a route such as `app/api/credit/hosted-session/route.ts`.

```ts
import { NextResponse } from "next/server";

type JourneyType = "PERSONAL_LOAN" | "BUSINESS_LOAN" | "GOLD_LOAN";

type HostedSessionRequest = {
  borrowerProviderID?: string;
  journeyType?: JourneyType;
  profile?: {
    contactNumber?: string;
    pan?: string;
    panName?: string;
    dob?: string;
    gender?: "male" | "female" | "transgender";
    personalemail?: string;
  };
  workProfile?: {
    employmentType?: "salaried" | "selfEmployed";
    officialemail?: string;
    income?: string | number;
    incomeType?: "monthly" | "annual";
    companyName?: string;
    udyamNumber?: string;
  };
  address?: {
    addressL1?: string;
    addressL2?: string;
    city?: string;
    state?: string;
    pincode?: string;
  };
  journey?: {
    endUse?: string;
    creditReportConsent?: boolean;
  };
  goldLoan?: {
    userType?: "individual" | "non-individual";
    constitution?: string | null;
    jewelleryWeightGrams?: string | number;
    purity?: "24K" | "22K" | "21K" | "18K" | "14K" | "9K";
    endUse?:
      | "marriage"
      | "familyFunctions"
      | "medicalTreatmentAndEmergencies"
      | "travelEducationExpenses"
      | "businessExpansion"
      | "agricultureAndFarmRelatedNeeds"
      | "purchaseOfEquipment"
      | "other";
    aaID?: string;
    requestedAmount?: string | number;
    requestedTenureMonths?: number;
  };
};

type HostedSessionResponse = {
  embedURL: string;
};

export async function POST(request: Request) {
  const payload = (await request.json().catch(() => null)) as
    | HostedSessionRequest
    | null;

  const borrowerProviderID = payload?.borrowerProviderID?.trim();
  const journeyType = payload?.journeyType;

  if (!borrowerProviderID) {
    return NextResponse.json(
      { error: "Missing borrowerProviderID." },
      { status: 400 },
    );
  }

  if (journeyType !== "PERSONAL_LOAN" && journeyType !== "GOLD_LOAN") {
    return NextResponse.json(
      { error: "journeyType must be PERSONAL_LOAN or GOLD_LOAN." },
      { status: 400 },
    );
  }

  const platformBaseURL = process.env.PLATFORM_BASE_URL;
  const platformAPIKey = process.env.PLATFORM_API_KEY;

  if (!platformBaseURL || !platformAPIKey) {
    return NextResponse.json(
      { error: "Hosted Experience configuration is missing." },
      { status: 500 },
    );
  }

  const response = await fetch(`${platformBaseURL}/api/lab/sessions`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${platformAPIKey}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      borrowerProviderID,
      journeyType,
      profile: payload?.profile,
      workProfile: payload?.workProfile,
      address: payload?.address,
      journey: payload?.journey,
      goldLoan: payload?.goldLoan,
    }),
    cache: "no-store",
  });

  if (!response.ok) {
    const details = await response.json().catch(() => ({}));
    return NextResponse.json(
      { error: "Failed to create Hosted Experience URL.", details },
      { status: response.status },
    );
  }

  const data = (await response.json()) as HostedSessionResponse;

  return NextResponse.json(
    { embedURL: data.embedURL },
    {
      headers: {
        "Cache-Control": "no-store",
      },
    },
  );
}
```

## Product Availability Endpoint

If your app shows product-specific CTAs, call `GET /api/lab/features` from your back-end and use the returned catalogue to decide what to show.

```ts
type HostedLenderProductCatalogue = {
  products: Array<{
    key: "personalLoan" | "businessLoan" | "goldLoan";
    type: "PERSONAL_LOAN" | "BUSINESS_LOAN" | "GOLD_LOAN";
    name: string;
    available: boolean;
    unavailableReason?: string | null;
    lenders: Array<{
      id: string;
      name: string;
      available: boolean;
      checks: Array<{
        key:
          | "CREDIT_REPORT"
          | "BANK_ACCOUNT_STATEMENT"
          | "GST_INFORMATION"
          | "UDYAM_INFORMATION"
          | "GOLD_INFORMATION";
        required: boolean;
      }>;
      informationNeeded: {
        lenderSpecificRequirementsKnown: boolean;
        required: Array<{
          key: string;
          sendIn: string;
        }>;
        optional: Array<{
          key: string;
          sendIn: string;
        }>;
        conditional: Array<{
          key: string;
          sendIn: string;
        }>;
      };
    }>;
  }>;
};

export async function readLenderProductCatalogue() {
  const platformBaseURL = process.env.PLATFORM_BASE_URL;
  const platformAPIKey = process.env.PLATFORM_API_KEY;

  const response = await fetch(`${platformBaseURL}/api/lab/features`, {
    method: "GET",
    headers: {
      Authorization: `Bearer ${platformAPIKey}`,
    },
    cache: "no-store",
  });

  if (!response.ok) {
    throw new Error("Failed to read lender product catalogue.");
  }

  return response.json() as Promise<HostedLenderProductCatalogue>;
}
```

Typical UI checks:

```ts
const catalogue = await readLenderProductCatalogue();
const personalLoanAvailable = catalogue.products.some(
  (product) => product.key === "personalLoan" && product.available,
);
const lenderFields =
  catalogue.products
    .find((product) => product.key === "personalLoan")
    ?.lenders.at(0)?.informationNeeded.required ?? [];
```

## Error Handling

Handle platform responses as follows:

| Status | Meaning                                                                          | Recommended action                                                              |
| ------ | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| `400`  | Request shape or required data is invalid.                                       | Fix the payload before retrying.                                                |
| `401`  | API key is missing, invalid, or from the wrong environment.                      | Check server-side configuration and key rotation.                               |
| `404`  | Tenant, application, borrower, or requested journey scope could not be resolved. | Verify API key scope and product availability.                                  |
| `500`  | Unexpected platform error.                                                       | Retry later and share request context with Aarthik Labs support if it persists. |

## Important Rules

* Send `profile.contactNumber` for all journeys. It is a required field.
* Send `journeyType` explicitly so product selection is predictable.
* Create a fresh `embedURL` when the borrower returns later.
* Do not persist or expose platform API keys in front-end code, mobile apps, logs, or analytics events.