> For the complete documentation index, see [llms.txt](https://docs.bullcredtech.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.bullcredtech.com/api-reference/apis-en/proposal-flow/post-proposals-credit-analysis.md).

# 4. Credit analysis

Credit analysis can be triggered in **three modes**, all compatible with the same polling endpoint [`GET /proposals/{proposalId}/credit-analysis`](/api-reference/apis-en/proposal-flow/get-proposals-credit-analysis.md):

| Mode                     | Endpoint                                                  | When to use                                                                                                                                                     |
| ------------------------ | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Hybrid (recommended)** | `POST /proposals/{proposalId}/credit-analysis`            | Waits up to 28s trying to respond synchronously; if it exceeds that, it returns `202` + tracking and continues in the background. **Default** since 2026-05-11. |
| **Explicit async**       | `POST /proposals/{proposalId}/credit-analysis/async`      | Always returns `202` immediately. Suitable for mobile/web clients that should not block the screen while the analysis runs.                                     |
| **Legacy async**         | `POST /proposals/{proposalId}/credit-analysis?async=true` | Semantic equivalent to `/async` — kept for compatibility. Prefer the explicit route.                                                                            |

## Hybrid mode (default)

**POST** `/proposals/{proposalId}/credit-analysis`

Performs the client's full credit analysis — checks available payroll margin (Celcoin/Dataprev), credit engine (PoD), and generates personalized offers.

**Behavior:**

* ✅ If the analysis finishes in **up to \~28 seconds**: returns `201 Created` with the full result (generated offers or rejection reason).
* ⏱️ If it exceeds **\~28 seconds** (typical during slow Celcoin or PoD windows): returns `202 Accepted` with a tracking payload. Processing **continues in the background** and the client polls at [`GET /proposals/{proposalId}/credit-analysis`](/api-reference/apis-en/proposal-flow/get-proposals-credit-analysis.md) to obtain the result.

{% hint style="info" %}
**Why the 28s limit?** The API Gateway cuts off requests at 30s (CloudFront limit). The hybrid mode returns earlier (28s) to ensure a structured response `202` instead of `504 Gateway Timeout` generic gateway error.
{% endhint %}

{% hint style="warning" %}
**Asynchronous Processing — critical step**

Upon receiving `202`, **the analysis has not finished yet**. The client MUST poll at [`GET /proposals/{proposalId}/credit-analysis`](/api-reference/apis-en/proposal-flow/get-proposals-credit-analysis.md) until the status changes (recommended: 2s interval between polls). Webhook is also supported.
{% endhint %}

## Explicit async mode

**POST** `/proposals/{proposalId}/credit-analysis/async`

Always returns `202 Accepted` immediate. Equivalent to `?async=true`, but with a semantic URL.

Use when the client wants to **release the UI** as soon as the request is accepted, without waiting for any synchronous moment.

## URL Parameters

| Parameter    | Type          | Description |
| ------------ | ------------- | ----------- |
| `proposalId` | string (UUID) | Proposal ID |

## Query Parameters (legacy mode)

| Parameter | Type    | Description                                                                                                       |
| --------- | ------- | ----------------------------------------------------------------------------------------------------------------- |
| `async`   | boolean | If `true`, it is equivalent to `POST /credit-analysis/async`. Kept for compatibility — prefer the explicit route. |

## Request Body

| Field                   | Type   | Required | Description                   | Example                      |
| ----------------------- | ------ | :------: | ----------------------------- | ---------------------------- |
| `ip`                    | string |    Yes   | Client IP address             | `"203.0.113.12"`             |
| `geolocation`           | object |    Yes   | Client geographic coordinates | -                            |
| `geolocation.latitude`  | number |    Yes   | Latitude                      | `-23.5505`                   |
| `geolocation.longitude` | number |    Yes   | Longitude                     | `-46.6333`                   |
| `document_text`         | string |    Yes   | Consent text for analysis     | Free text                    |
| `channel`               | string |    Yes   | Origination channel           | `"web"`, `"mobile"`, `"app"` |

## Request Example

```json
{
  "ip": "203.0.113.12",
  "geolocation": {
    "latitude": -23.5505,
    "longitude": -46.6333
  },
  "document_text": "I authorize the processing of my data for payroll loan credit analysis.",
  "channel": "web"
}
```

## Success Response (201 Created)

```json
{
  "message": "Credit analysis completed with generated offer",
  "proposal_id": "99afc6ce-4720-4746-9cf6-0a23e85cdf75",
  "status": "OFFERS_RECEIVED",
  "offer": {
    "id": "a69660f8-2309-4a55-a0c6-e6a701a04a67",
    "proposal_id": "99afc6ce-4720-4746-9cf6-0a23e85cdf75",
    "offer_type": "STANDARD",
    "status": "ACTIVE",
    "description": "Payroll-Deducted Credit Offer",
    "principal_amount": 25153.66,
    "interest_rate": 4.99,
    "annual_interest_rate": 79.3805,
    "installments": 18,
    "installment_amount": 2327.96,
    "total_amount": 41903.28,
    "iof_amount": 497.87,
    "tac_amount": 0,
    "insurance_amount": 0,
    "first_due_date": "2025-12-21T00:00:00.000Z",
    "final_due_date": "2027-05-21T00:00:00.000Z",
    "expires_at": "2025-10-20T20:45:19.204Z",
    "calculation_details": {
      "monthly_rate": 0.0499,
      "financed_amount": 25651.53,
      "disbursement_amount": 25153.66,
      "total_processing_cost": 497.87,
      "calculation_method": "api",
      "generated_at": "2025-10-13T20:45:19.204Z"
    },
    "terms_and_conditions": {
      "contract_type": "CCB",
      "guarantee_type": "salary_assignment",
      "early_payment_discount": 0.02,
      "late_payment_fee": 0.02,
      "default_interest": 0.01
    },
    "priority": 1,
    "is_available": true
  },
  "available_margin": "3071.60",
  "maxLoanAmount": 25153.66,
  "maxInstallments": 18,
  "maxMonthlyPayment": "3071.60",
  "minMonthlyPayment": 1000,
  "bank_skip": false
}
```

### Response Fields

| Field               | Type    | Description                                                                                        |
| ------------------- | ------- | -------------------------------------------------------------------------------------------------- |
| `offer`             | object  | Standard offer (without insurance)                                                                 |
| `insuredOffer`      | object  | Offer with payment protection insurance — **present only if the partner has insurance configured** |
| `product`           | object  | Product associated with the standard offer                                                         |
| `insuredProduct`    | object  | Product with insurance — **present only if the partner has insurance configured**                  |
| `available_margin`  | string  | Available payroll-deductible margin                                                                |
| `maxLoanAmount`     | number  | Maximum available loan amount                                                                      |
| `maxInstallments`   | number  | Maximum number of installments allowed                                                             |
| `maxMonthlyPayment` | number  | Maximum monthly installment amount                                                                 |
| `bank_skip`         | boolean | Whether bank validation can be skipped                                                             |

{% hint style="info" %}
**Payment Protection Insurance**

The fields `insuredOffer` and `insuredProduct` are only returned when the partner has insurance configured by the Bull team. If the partner does not have insurance enabled, only `offer` and `product` are returned.
{% endhint %}

### Offer Fields (`offer` / `insuredOffer`)

| Field                  | Type              | Description                                                   |
| ---------------------- | ----------------- | ------------------------------------------------------------- |
| `id`                   | string (UUID)     | **Offer ID** - required for pre-acceptance                    |
| `principal_amount`     | number            | Net amount to be credited to the client                       |
| `interest_rate`        | number            | Monthly interest rate (%)                                     |
| `annual_interest_rate` | number            | Annual interest rate (APR)                                    |
| `installments`         | number            | Number of installments                                        |
| `installment_amount`   | number            | Amount of each installment                                    |
| `total_amount`         | number            | Total amount to be paid                                       |
| `iof_amount`           | number            | IOF amount                                                    |
| `insurance_amount`     | number            | Total payment protection insurance amount (0 if no insurance) |
| `first_due_date`       | string (ISO 8601) | Date of the first due date                                    |
| `final_due_date`       | string (ISO 8601) | Date of the last due date                                     |
| `expires_at`           | string (ISO 8601) | Offer validity (usually 7 days)                               |

### Calculation Details (`calculation_details`)

| Field                   | Type   | Description                                     |
| ----------------------- | ------ | ----------------------------------------------- |
| `monthly_rate`          | number | Monthly rate in decimal format (0.0499 = 4.99%) |
| `financed_amount`       | number | Financed amount (includes charges)              |
| `disbursement_amount`   | number | Net amount for disbursement                     |
| `total_processing_cost` | number | Total costs (IOF + TAC)                         |

### Limits and Parameters

| Field               | Type    | Description                            |
| ------------------- | ------- | -------------------------------------- |
| `maxLoanAmount`     | number  | Maximum available loan amount          |
| `maxInstallments`   | number  | Maximum number of installments allowed |
| `maxMonthlyPayment` | string  | Maximum monthly installment amount     |
| `minMonthlyPayment` | number  | Minimum monthly installment amount     |
| `available_margin`  | string  | Available payroll-deductible margin    |
| `bank_skip`         | boolean | Whether bank validation can be skipped |

## Response with Insurance (when enabled)

When the partner has insurance configured, the response additionally includes the fields `insuredOffer` and `insuredProduct`:

```json
{
  "message": "Credit analysis completed with generated offer",
  "proposal_id": "99afc6ce-4720-4746-9cf6-0a23e85cdf75",
  "status": "OFFERS_RECEIVED",
  "offer": { "...": "standard offer without insurance" },
  "insuredOffer": {
    "id": "b71770g9-3420-5b66-b1d7-f7b812b15b78",
    "offer_type": "STANDARD",
    "principal_amount": 25153.66,
    "interest_rate": 4.99,
    "installments": 18,
    "installment_amount": 2450.00,
    "insurance_amount": 122.04,
    "total_amount": 44100.00
  },
  "insuredProduct": {
    "id": "e93c0445-1def-505d-c525-e23c467f65f7",
    "name": "Private payroll loan with insurance"
  }
}
```

## Asynchronous Response (202 Accepted)

Returned in **two scenarios**:

1. **Hybrid mode — synchronous timeout exceeded (\~28s).** The analysis did not finish within the HTTP request time budget and was moved to the background.
2. **Explicit async mode (`POST /credit-analysis/async`) or legacy (`?async=true`).** Always `202` immediate — the analysis never tried sync.

**In both cases:** the analysis is **in progress** and the client MUST poll at [`GET /proposals/{proposalId}/credit-analysis`](/api-reference/apis-en/proposal-flow/get-proposals-credit-analysis.md) (full URL in the field `status_url`).

```json
{
  "async_processing": true,
  "message": "Processing started in the background",
  "proposal_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "CREDIT_ANALYSIS_IN_PROGRESS",
  "processing_started_at": "2025-12-01T14:30:00.000Z",
  "status_url": "/proposals/a1b2c3d4-e5f6-7890-abcd-ef1234567890/credit-analysis"
}
```

| Field                   | Meaning                                                                                 |
| ----------------------- | --------------------------------------------------------------------------------------- |
| `async_processing`      | Always `true` in this response.                                                         |
| `message`               | Human-readable text. In hybrid mode, indicates that the synchronous limit was exceeded. |
| `proposal_id`           | Proposal identifier — use to correlate with webhooks/logs.                              |
| `status`                | Always `CREDIT_ANALYSIS_IN_PROGRESS` while the background runs.                         |
| `processing_started_at` | ISO 8601 timestamp (UTC) of when the background started.                                |
| `status_url`            | Relative URL for polling — will point to `GET /proposals/{id}/credit-analysis`.         |

**Recommended polling pattern:** 2-second interval between polls; abandon after \~3 minutes without changes (background has an internal 3-minute watchdog). Status `200 OK` indicates that the result is ready; `202` continues in progress.

## Possible Errors

| Code | Error Code                          | Message                                    |
| ---- | ----------------------------------- | ------------------------------------------ |
| 404  | -                                   | Proposal not found                         |
| 409  | `CREDIT_ANALYSIS_ALREADY_COMPLETED` | Credit analysis has already been performed |
| 409  | -                                   | Credit analysis is already in progress     |

## Possible Rejection Reasons

* Insufficient credit score
* CPF with restrictions
* Insufficient payroll margin
* No employment relationship
* Client not authorized by employer

## New Analysis

It is possible to request a new analysis when the proposal is in the status:

* `OFFER_SELECTED`
* `PRE_ACCEPTED`
* `BANK_DATA_SUBMITTED`

{% hint style="success" %}
**Next Step**

Confirm the selected offer: [POST /proposals/{id}/pre-accept](/api-reference/apis-en/proposal-flow/post-proposals-pre-accept.md)

Or simulate different scenarios before confirming: [POST /proposals/{id}/simulate](/api-reference/apis-en/proposal-flow/post-proposals-simulate.md)
{% endhint %}


---

# 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 dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.bullcredtech.com/api-reference/apis-en/proposal-flow/post-proposals-credit-analysis.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
