> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ripio.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Investor Profile (Brazil)

> The suitability questionnaire customers of Brazilian partner accounts must answer before operating: when it applies, how to collect it via the API, how long it lasts, and what happens when it expires.

## Overview

**This page applies only to partners whose Ripio account is in Brazil.** What decides it is your account's country, not the customer's: a Brazilian national operating through a partner in Argentina is never asked for an investor profile, while every customer of a Brazilian account is. Accounts in Argentina, Colombia and Mexico are not affected by anything described here.

Resolução BCB nº 520/2025 (arts. 58 and 59) requires Ripio to know the risk profile of every customer operating through a Brazilian account — their familiarity with virtual assets, financial goals and risk tolerance — before they operate, and to keep it on record. Ripio collects it through a short **investor profile** questionnaire: five single-choice questions whose answers produce one of three profiles.

When the investor profile is enabled for your account, a customer **cannot create on-ramp or off-ramp orders or sessions, or register a destination account, without a valid profile**. As an API partner, you are responsible for showing the questionnaire to your customers and submitting their answers.

<Note>
  Partners on the [Ramps widget](/ramps-api/widget/overview) do not implement any of this: the widget presents the questionnaire itself. See [Investor profile](/ramps-api/widget/flows/investor-profile) in the widget docs.
</Note>

## When it applies

The investor profile is enabled by Ripio **per account**, and only for accounts whose country is Brazil. Your widget integration and your API integration use different accounts, so each one is enabled on its own.

Enforcement starts on **October 30, 2026 at 00:00 Brasília time** (`2026-10-30T03:00:00Z`). Until then the questionnaire is already available and the customer is asked for it (`REQUIRED_SOON`), but **nothing is blocked**: use that window to collect profiles from your existing customers before they hit an error. An account enabled after that date is enforced from day one.

<Warning>
  There is no grace period per customer once enforcement starts. An existing customer without a profile is blocked until they answer the questionnaire — see `20100` below.
</Warning>

## Where it fits in the flow

The only hard requirement is that the profile exists **before the first order or session**. The recommended place is right after KYC, which is where the widget asks for it:

```
Create customer → Accept T&C → KYC (COMPLETED) → Investor profile → quote → order / session
```

You can also collect it earlier — during your own onboarding, for example. Submission does not depend on the KYC status; it only needs the customer to have an email set, given when [creating the customer](/ramps-api/customers/create-customer) or later with [Update Customer](/ramps-api/customers/update-customer). Otherwise it is refused with `20014`.

<Note>
  This applies even if your customers verify their identity in [Ripio's hosted KYC](/ramps-api/kyc/kyc-with-ripio): the hosted flow does not include the questionnaire, so it is still yours to collect via the API.
</Note>

When a customer is missing more than one step, the errors arrive in that same order: a deactivated customer, then Terms & Conditions (`20039`), then KYC, then the investor profile (`20100`). Fix them in the order you receive them.

## Statuses

[Get Investor Profile Status](/ramps-api/investor-profile/get-investor-profile-status) tells you where a customer stands:

| `status` | Meaning | Can operate? | What to do |
| - | - | :-: | - |
| `NOT_REQUIRED` | The investor profile does not apply to your account. | ✅ | Nothing. |
| `REQUIRED_SOON` | No valid profile, but enforcement has not started yet. `deadline` is the date it starts. | ✅ | Ask for it now, and let the customer postpone. |
| `REQUIRED` | Never answered, and it is enforced. | ❌ | Show the questionnaire before operating. |
| `VALID` | Valid profile, with more than 30 days left. | ✅ | Nothing. |
| `EXPIRING_SOON` | Valid profile that expires within 30 days. `deadline` is when it stops being valid. | ✅ | Ask the customer to renew, and let them postpone. |
| `EXPIRED` | The profile expired. | ❌ | Show the questionnaire again before operating. |

Only `REQUIRED` and `EXPIRED` block. `deadline` is set only for `REQUIRED_SOON` and `EXPIRING_SOON`, the two statuses where there is something to say "before" about — use it in your copy ("answer before …").

<Warning>
  The status changes with time and without any action on your side: a `VALID` profile becomes `EXPIRING_SOON` and then `EXPIRED`. Re-check it before the customer operates, or handle `20100` wherever you create an order or a session — do not store it as a flag.
</Warning>

## Collecting the profile

<Steps>
  <Step title="Check whether the customer needs to answer">
    ```http theme={null}
    GET /api/v1/customers/{customerId}/investorProfile/
    ```

    ```json theme={null}
    {
      "status": "REQUIRED",
      "deadline": null,
      "investorProfile": null
    }
    ```

    Show the questionnaire for `REQUIRED` and `EXPIRED`; offer it, with the option to postpone, for `REQUIRED_SOON` and `EXPIRING_SOON`.

    → [Get Investor Profile Status](/ramps-api/investor-profile/get-investor-profile-status)
  </Step>

  <Step title="Fetch the active questionnaire">
    ```http theme={null}
    GET /api/v1/investorProfile/questionnaire/
    ```

    ```json theme={null}
    {
      "version": 1,
      "questions": [
        {
          "questionId": "familiarity_experience",
          "questionText": "Como você descreveria sua experiência e seu conhecimento com ativos virtuais?",
          "answers": [
            { "answerId": "never_invested", "answerText": "Nunca investi" },
            { "answerId": "invested_occasionally", "answerText": "Já investi algumas vezes" },
            { "answerId": "invests_regularly", "answerText": "Invisto regularmente" }
          ]
        }
      ]
    }
    ```

    The example is cut to one question; the questionnaire has five. Keep the `version`: you send it back in the next step.

    → [Get Investor Questionnaire](/ramps-api/investor-profile/get-investor-questionnaire)
  </Step>

  <Step title="Show it and submit the answers">
    Show every question with its answers, let the customer pick exactly one per question, and submit them all at once:

    ```http theme={null}
    POST /api/v1/customers/{customerId}/investorProfile/
    ```

    ```json theme={null}
    {
      "questionnaireVersion": 1,
      "answers": [
        { "questionId": "familiarity_experience", "answerId": "invests_regularly" },
        { "questionId": "investment_goal", "answerId": "maximize_returns" },
        { "questionId": "investment_horizon", "answerId": "more_than_3_years" },
        { "questionId": "drawdown_reaction", "answerId": "buy_more" },
        { "questionId": "savings_allocation", "answerId": "most_savings" }
      ]
    }
    ```

    The response (`201`) is the same as the status call, now `VALID`, with the resulting profile. The customer can operate right away.

    → [Submit Investor Profile](/ramps-api/investor-profile/submit-investor-profile)
  </Step>
</Steps>

### The resulting profile

```json theme={null}
{
  "status": "VALID",
  "deadline": null,
  "investorProfile": {
    "profile": "AGGRESSIVE",
    "description": "Você tolera alta volatilidade e possíveis perdas em busca de maior rentabilidade.",
    "questionnaireVersion": 1,
    "createdAt": "2026-10-01T12:34:56.789012Z",
    "validUntil": "2027-10-01T12:34:56.789012Z",
    "answers": [
      {
        "questionId": "familiarity_experience",
        "questionText": "Como você descreveria sua experiência e seu conhecimento com ativos virtuais?",
        "answerId": "invests_regularly",
        "answerText": "Invisto regularmente"
      }
    ]
  }
}
```

`profile` is one of `CONSERVATIVE`, `MODERATE` or `AGGRESSIVE`. Each answer comes back with the texts of the questionnaire version the customer answered, so the record stands on its own even after the questionnaire changes. Scores are not exposed.

What you do with the profile is up to you: showing it to the customer is a good practice, but nothing in the API requires it, and **the profile does not restrict which operations the customer can make** — it only has to exist and be valid.

## Language

The API has no language parameter: question, answer and profile texts are **always in Portuguese**. Every one of them also carries a stable identifier — `questionId`, `answerId` and `profile` — so you can translate by id into your own app's languages, and fall back to the Portuguese text for an id you do not know yet.

Ids do not change within a questionnaire version. A change to the questionnaire is published as a new version, which may bring new ids.

## Validity and renewal

* A profile is valid for **one year** from the moment it is submitted (`validUntil`).
* During its **last 30 days** the status is `EXPIRING_SOON`. The customer can still operate; it is the window to ask them to renew without interrupting them.
* Once `validUntil` passes, the status is `EXPIRED`: the customer is blocked until they answer again.
* The questionnaire can be answered again **at any time**. Every submission creates a new profile valid for one more year; earlier ones are kept as history, and the status call always shows the latest.

## When the questionnaire changes

Ripio may publish a new version of the questionnaire. Only the active version can be answered, so a customer who fetched version `1` and submits after version `2` went live gets `20096`:

```json theme={null}
{
  "code": 20096,
  "type": "InvestorQuestionnaireVersionNotActiveException",
  "detail": {
    "message": "Investor questionnaire version '1' is not active for this country."
  },
  "status": 400
}
```

Fetch the questionnaire again, show it, and submit with the new `version`. A new version does not invalidate existing profiles: they remain valid until their own `validUntil`.

## Operating without a valid profile: `20100`

When the profile is enforced and the customer has none, or it expired, these endpoints answer `400` with code `20100`:

| Endpoint | Page |
| - | - |
| `POST /api/v1/onramp/` · `POST /api/v1/onrampSession/` | [Create On-Ramp Order](/ramps-api/on-ramp/create-on-ramp-order) · [Create On-Ramp Session](/ramps-api/on-ramp/create-on-ramp-session) |
| `POST /api/v1/offramp/` · `POST /api/v1/offrampSession/` | [Create Off-Ramp Order](/ramps-api/off-ramp/create-off-ramp-order) · [Create Off-Ramp Session](/ramps-api/off-ramp/create-off-ramp-session) |
| `POST /api/v1/fiatAccounts/`, and updating a destination account | [Create Fiat Account](/ramps-api/fiat-accounts/create-fiat-account) |

```json theme={null}
{
  "code": 20100,
  "type": "InvestorProfileRequiredException",
  "detail": {
    "message": "Customer '3fa85f64-5717-4562-b3fc-2c963f66afa6' does not have a valid investor profile."
  },
  "status": 400
}
```

The `message` carries the `customerId` you sent. Send the customer through the questionnaire, then retry the same request.

Registering a destination account (a PIX key) is gated like an operation because it is the first step of an off-ramp: a customer without a valid profile cannot add one. That includes the replacement account for [Change Fiat Account and Reprocess](/ramps-api/off-ramp/change-fiat-account) — the reprocess itself is not blocked, but the new account it points to has to be created first.

Quotes, the [deposit account](/ramps-api/customers/create-deposit-account), KYC, refunds, [Sell and Pay](/ramps-api/sell-and-pay/create-sell-and-pay) and the read endpoints are **not** affected: a customer without a profile can still get a quote or check their history.

## Keeping profiles current

There are no webhooks for the investor profile: nothing tells you that a profile is about to expire or has expired. Two ways to stay ahead of it:

* **Check before operating.** Call [Get Investor Profile Status](/ramps-api/investor-profile/get-investor-profile-status) when the customer is about to buy or sell, and show the questionnaire if needed. This is what the widget does.
* **Schedule a reminder.** Store `validUntil` when you submit a profile, and ask the customer to renew in the 30 days before it — the `EXPIRING_SOON` window — so they never hit `20100`.

## Testing in sandbox

The investor profile is enabled per account in sandbox too. Ask the Ripio team to enable it on your **sandbox** account; until then, the status call reads `NOT_REQUIRED` and submissions are refused with `20099`. [Get Investor Questionnaire](/ramps-api/investor-profile/get-investor-questionnaire) does not depend on it: it answers for any Brazilian account, so you can build the questionnaire screens before the feature is enabled.

Once enabled, you can go through the whole flow — fetch the questionnaire, submit it, see `VALID`, and get `20100` on a customer who has not answered. Expiry cannot be simulated: a profile submitted today stays `VALID` for eleven months, so test `EXPIRING_SOON` and `EXPIRED` against the examples on [Get Investor Profile Status](/ramps-api/investor-profile/get-investor-profile-status).

## Errors

| Code | Type | Status | Where | Meaning |
| - | - | :-: | - | - |
| `20100` | `InvestorProfileRequiredException` | 400 | Create order / session / destination account | No valid profile. Collect it and retry. |
| `20096` | `InvestorQuestionnaireVersionNotActiveException` | 400 | Submit | The questionnaire changed. Fetch it again. |
| `20097` | `InvalidInvestorProfileAnswersException` | 400 | Submit | An answer is missing, repeated, or not one of the offered options. |
| `20099` | `InvestorProfileNotRequiredException` | 400 | Submit | The investor profile is not enabled for your account. |
| `20014` | `EmailRequiredException` | 400 | Submit | The customer has no email set yet. |
| `40004` | `NotFound` | 404 | Questionnaire | Your account is not a Brazilian account. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.