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

# Requirements

> Onboarding requirements are the single list of what a provider needs from a merchant: what each type asks for, how its status resolves, where it comes from and what CELCOIN and CAPPTA require.

export const OnboardingRequirementsExplorer = () => {
  const COMPANY = {
    id: 'a7089150-0d61-498f-b7ca-4767e4e4d6a4',
    cnpj: '12345678000195',
    contactCpf: '52998224806',
    personCpf: '39053344020'
  };
  const IDS = {
    IDENTITY_DOCUMENT: '9d6c5c69-1c92-4d25-93cf-fc0a63f1251c',
    BIOMETRIC_VERIFICATION: 'a688bf7d-513e-4786-9ee0-73eaf57da58f',
    ARTICLES_OF_INCORPORATION: '9571ddf8-d605-4879-84cd-02056ab48244',
    POWER_OF_ATTORNEY: 'cbca4307-c152-4dc3-a5da-b301e8c0ded7',
    identityFront: '161e2065-1179-4484-9ea3-faf62199595e',
    identityBack: '3268b183-1ceb-40e0-b258-40aaa4715e21',
    verification: '336b4995-d8ed-4837-97b2-b94990b30240'
  };
  const PROVIDERS = [{
    value: 'CELCOIN',
    hint: 'Documents and biometrics'
  }, {
    value: 'CAPPTA',
    hint: 'Nothing up front'
  }];
  const SHAPES = [{
    value: 'PJ',
    label: 'Limited company or corporation',
    hint: 'PJ'
  }, {
    value: 'ME',
    label: 'Microenterprise',
    hint: 'ME'
  }, {
    value: 'MEI',
    label: 'Individual microentrepreneur',
    hint: 'MEI'
  }, {
    value: 'PF',
    label: 'Natural person',
    hint: 'PF'
  }];
  const ROLES = [{
    value: 'PARTNER',
    label: 'A partner'
  }, {
    value: 'REPRESENTATIVE',
    label: 'A representative'
  }];
  const LABELS = {
    IDENTITY_DOCUMENT: 'Identity document',
    BIOMETRIC_VERIFICATION: 'Biometric verification',
    ARTICLES_OF_INCORPORATION: 'Articles of incorporation',
    POWER_OF_ATTORNEY: 'Power of attorney'
  };
  const HOW = {
    IDENTITY_DOCUMENT: 'A CNH_DIGITAL, or the front and back of a CNH, RG or RNE',
    BIOMETRIC_VERIFICATION: 'A selfie with liveness check, done by the person',
    ARTICLES_OF_INCORPORATION: 'The consolidated contrato social or the estatuto social',
    POWER_OF_ATTORNEY: 'The document that lets the contact represent the merchant'
  };
  const DEFAULTS = {
    provider: 'CELCOIN',
    shape: 'PJ',
    role: 'PARTNER'
  };
  const [provider, setProvider] = useState(DEFAULTS.provider);
  const [shape, setShape] = useState(DEFAULTS.shape);
  const [role, setRole] = useState(DEFAULTS.role);
  const [identityOnFile, setIdentityOnFile] = useState(false);
  const [verified, setVerified] = useState(false);
  const [view, setView] = useState('requirements');
  const isPerson = shape === 'PF';
  const collects = provider === 'CELCOIN';
  const contactCpf = isPerson ? COMPANY.personCpf : COMPANY.contactCpf;
  const companyDocument = isPerson ? COMPANY.personCpf : COMPANY.cnpj;
  const reset = () => {
    setProvider(DEFAULTS.provider);
    setShape(DEFAULTS.shape);
    setRole(DEFAULTS.role);
    setIdentityOnFile(false);
    setVerified(false);
  };
  const baseSet = () => {
    if (!collects) return [];
    const rows = [{
      type: 'IDENTITY_DOCUMENT',
      subject: contactCpf,
      role: 'CONTACT'
    }, {
      type: 'BIOMETRIC_VERIFICATION',
      subject: contactCpf,
      role: 'CONTACT'
    }];
    if (shape === 'PJ' || shape === 'ME') {
      rows.push({
        type: 'ARTICLES_OF_INCORPORATION',
        subject: companyDocument,
        role: 'COMPANY'
      });
    }
    if (!isPerson && role === 'REPRESENTATIVE') {
      rows.push({
        type: 'POWER_OF_ATTORNEY',
        subject: companyDocument,
        role: 'COMPANY'
      });
    }
    return rows.map(row => {
      const satisfied = row.type === 'IDENTITY_DOCUMENT' && identityOnFile || row.type === 'BIOMETRIC_VERIFICATION' && verified;
      return {
        ...row,
        status: satisfied ? 'SATISFIED' : 'PENDING'
      };
    });
  };
  const rows = baseSet();
  const pending = rows.filter(row => row.status === 'PENDING').length;
  const outcome = () => {
    if (!collects) {
      return {
        status: 'PROCESSING',
        tone: 'ok',
        step: 1,
        text: 'Submitted at once with the merchant data and the settlement bank account. If the risk analysis asks for more, new requirements arrive on this same affiliation.'
      };
    }
    if (pending > 0) {
      return {
        status: 'WAITING_DOCUMENTS',
        tone: 'wait',
        step: 0,
        text: `${pending} ${pending === 1 ? 'item is' : 'items are'} pending. onboarding_url carries the link, and Rinne submits on its own when the last one arrives.`
      };
    }
    return {
      status: 'PENDING_APPROVAL',
      tone: 'ok',
      step: 1,
      text: 'Everything is already satisfied, so the affiliation is submitted to the provider at once. No link is needed.'
    };
  };
  const accepted = type => {
    if (type === 'IDENTITY_DOCUMENT') {
      return ['CNH_DIGITAL', 'CNH_FRONT', 'CNH_BACK', 'RG_FRONT', 'RG_BACK', 'RNE_FRONT', 'RNE_BACK'];
    }
    if (type === 'BIOMETRIC_VERIFICATION') return null;
    return [type];
  };
  const satisfiedBy = row => {
    if (row.status !== 'SATISFIED') return null;
    if (row.type === 'IDENTITY_DOCUMENT') {
      return {
        onboarding_document_ids: [IDS.identityFront, IDS.identityBack],
        biometric_verification_id: null,
        answer: null
      };
    }
    return {
      onboarding_document_ids: [],
      biometric_verification_id: IDS.verification,
      answer: null
    };
  };
  const json = () => JSON.stringify({
    data: rows.map(row => ({
      id: IDS[row.type],
      company_id: COMPANY.id,
      provider,
      type: row.type,
      subject_document_number: row.subject,
      subject_role: row.role,
      origin: 'BASE',
      description: null,
      status: row.status,
      accepted_document_types: accepted(row.type),
      satisfied_by: satisfiedBy(row),
      waived_at: null,
      created_at: '2026-10-02T11:58:10.000Z',
      updated_at: '2026-10-02T11:58:10.000Z'
    }))
  }, null, 2);
  const highlight = source => {
    const pattern = /("(?:\\.|[^"\\])*")(\s*:)?|\b(true|false|null)\b|(-?\d+(?:\.\d+)?)/g;
    const parts = [];
    let last = 0;
    let match;
    while ((match = pattern.exec(source)) !== null) {
      if (match.index > last) parts.push(source.slice(last, match.index));
      const key = match.index;
      if (match[1] && match[2]) {
        parts.push(<span key={key} className="rco-tk-key">{match[1]}</span>, match[2]);
      } else if (match[1]) {
        parts.push(<span key={key} className="rco-tk-str">{match[1]}</span>);
      } else if (match[3]) {
        parts.push(<span key={key} className="rco-tk-lit">{match[3]}</span>);
      } else {
        parts.push(<span key={key} className="rco-tk-num">{match[4]}</span>);
      }
      last = pattern.lastIndex;
    }
    parts.push(source.slice(last));
    return parts;
  };
  const checkIcon = () => <svg viewBox="0 0 16 16" aria-hidden="true"><path d="M3.5 8.5l3 3 6-7" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /></svg>;
  const clockIcon = () => <svg viewBox="0 0 16 16" aria-hidden="true"><circle cx="8" cy="8" r="5.5" fill="none" stroke="currentColor" strokeWidth="1.6" /><path d="M8 5v3.2l2 1.3" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" /></svg>;
  const resetIcon = () => <svg viewBox="0 0 16 16" aria-hidden="true"><path d="M3 8a5 5 0 1 0 1.6-3.7M3 2.5v2.8h2.8" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" /></svg>;
  const chips = (name, value, options, onChange) => <div className="rco-chips" role="radiogroup" aria-label={name}>
      {options.map(option => <button key={option.value} type="button" role="radio" aria-checked={value === option.value} className="rco-chip" onClick={() => onChange(option.value)}>
          <span className="rco-chip-dot" aria-hidden="true" />
          {option.label || option.value}
        </button>)}
    </div>;
  const shapeCards = () => <div className="rco-cards" role="radiogroup" aria-label="Merchant shape">
      {SHAPES.map(option => <button key={option.value} type="button" role="radio" aria-checked={shape === option.value} className="rco-card" onClick={() => setShape(option.value)}>
          <span className="rco-card-title">{option.label}</span>
          <code className="rco-card-hint">{option.hint}</code>
        </button>)}
    </div>;
  const toggle = (label, checked, onChange, disabled) => <label className={disabled ? 'rco-switch is-off' : 'rco-switch'}>
      <input type="checkbox" checked={checked && !disabled} disabled={disabled} onChange={e => onChange(e.target.checked)} />
      <span className="rco-track" aria-hidden="true" />
      <span className="rco-switch-label">{label}</span>
    </label>;
  const requirementRow = row => {
    const done = row.status === 'SATISFIED';
    return <li key={row.type} className={done ? 'rco-req is-done' : 'rco-req'}>
        <span className="rco-req-icon">{done ? checkIcon() : clockIcon()}</span>
        <div className="rco-req-body">
          <div className="rco-req-top">
            <span className="rco-req-title">{LABELS[row.type]}</span>
            <span className={done ? 'rco-tag ok' : 'rco-tag wait'}>{row.status}</span>
          </div>
          <code className="rco-req-type">{row.type}</code>
          <p className="rco-req-how">{HOW[row.type]}</p>
          <div className="rco-req-meta">
            <span className="rco-tag">{row.role}</span>
            <code>{row.subject}</code>
          </div>
        </div>
      </li>;
  };
  const track = result => {
    const steps = ['Collect what is pending', 'Rinne submits', 'The provider decides'];
    return <ol className="rco-track-steps" aria-label="What happens next">
        {steps.map((label, index) => {
      const skipped = index === 0 && result.step > 0;
      const state = skipped ? 'is-skipped' : index < result.step ? 'is-done' : index === result.step ? 'is-now' : '';
      return <li key={label} className={`rco-step ${state}`}>
              <span className="rco-step-dot" aria-hidden="true" />
              <span className="rco-step-label">{skipped ? 'Nothing to collect' : label}</span>
            </li>;
    })}
      </ol>;
  };
  const result = outcome();
  const path = `/v1/onboarding-requirements?provider=${provider}`;
  return <div className="rco">
      <div className="rco-studio">
        <section className="rco-panel rco-inspector" aria-label="The merchant">
          <header className="rco-panel-head">
            <div className="rco-panel-title">
              Configure
              <span className="rco-badge"><span className="rco-live" aria-hidden="true" />Simulation</span>
            </div>
            <button type="button" className="rco-btn-ghost" onClick={reset}>
              {resetIcon()}
              Reset
            </button>
          </header>

          <div className="rco-fields">
            <div className="rco-field">
              <div className="rco-label">Provider</div>
              {chips('Provider', provider, PROVIDERS, setProvider)}
              <p className="rco-note">{PROVIDERS.find(option => option.value === provider).hint}</p>
            </div>

            <div className="rco-field">
              <div className="rco-label">Its contact</div>
              {isPerson ? <p className="rco-note rco-note-box">A natural person is their own contact.</p> : chips('Contact role', role, ROLES, setRole)}
              {!isPerson && <p className="rco-note"><code>{role}</code> on the merchant's contact</p>}
            </div>

            <div className="rco-field rco-field-wide">
              <div className="rco-label">The merchant</div>
              {shapeCards()}
            </div>

            <div className="rco-field rco-field-wide">
              <div className="rco-label">Already on file in your organization</div>
              <div className="rco-switches">
                {toggle('The contact’s identity document, sent for another merchant', identityOnFile, setIdentityOnFile, !collects)}
                {toggle('An approved biometric verification of the contact', verified, setVerified, !collects)}
              </div>
            </div>
          </div>

          <footer className="rco-panel-foot">Simulation only. Nothing here calls the API.</footer>
        </section>

        <section className="rco-panel rco-stage" aria-label="What Rinne answers">
          <div className="rco-tabs" role="tablist" aria-label="View">
            <button type="button" role="tab" aria-selected={view === 'requirements'} className="rco-tab" onClick={() => setView('requirements')}>
              Requirements
            </button>
            <button type="button" role="tab" aria-selected={view === 'response'} className="rco-tab" onClick={() => setView('response')}>
              Response
            </button>
          </div>

          <div className="rco-request">
            <span className="rco-method">GET</span>
            <code>{path}</code>
          </div>

          {view === 'requirements' ? <div className="rco-stage-body">
              {rows.length === 0 ? <div className="rco-empty">
                  <span className="rco-empty-icon">{checkIcon()}</span>
                  <div>
                    <strong>No requirements up front.</strong>
                    <p>Anything the provider asks for later arrives here as a new requirement.</p>
                  </div>
                </div> : <div>
                  <div className="rco-summary">
                    <span className="rco-summary-count">
                      {pending === 0 ? 'All on file' : `${pending} of ${rows.length} to collect`}
                    </span>
                    <span className="rco-meter" aria-hidden="true">
                      {rows.map(row => <i key={row.type} className={row.status === 'SATISFIED' ? 'is-done' : ''} />)}
                    </span>
                  </div>
                  <ul className="rco-reqs">{rows.map(requirementRow)}</ul>
                </div>}

              <div className="rco-outcome">
                <div className="rco-outcome-head">
                  <span className="rco-method rco-method-post">POST</span>
                  <code>/v1/affiliations</code>
                  <span className="rco-arrow" aria-hidden="true">→</span>
                  <span className={`rco-tag ${result.tone}`}>{result.status}</span>
                </div>
                <p className="rco-outcome-text">{result.text}</p>
                {track(result)}
              </div>
            </div> : <div className="rco-code">
              <div className="rco-code-bar">
                <span className="rco-code-status">200 OK</span>
                <span>application/json</span>
              </div>
              <pre tabIndex={0}>{highlight(json())}</pre>
            </div>}
        </section>
      </div>
    </div>;
};

An **onboarding requirement** is one thing a provider needs from a merchant before it can approve an affiliation: an identity document, a biometric verification, the articles of incorporation, an answer to a question. Requirements are the source of truth for what is missing. The affiliation status says who is waiting on whom; the requirements say exactly what is pending, for which person or merchant, and what would satisfy it.

Read them with `GET /v1/onboarding-requirements?provider=CELCOIN`, or `GET /v1/merchants/{merchantId}/onboarding-requirements?provider=CELCOIN` for one of your merchants. `provider` must be a provider that onboards merchants through Rinne, `CELCOIN` or `CAPPTA`; any other answers `400`.

## Requirements belong to the merchant

A requirement belongs to a merchant, per provider, and outlives any single affiliation. That is what makes reuse automatic:

* Created the first time Rinne evaluates the merchant for a provider (a requirements read, an affiliation creation or the link), and kept for as long as the merchant exists.
* Satisfied the moment matching evidence exists, whichever way it arrived and whenever: before the affiliation, through the link, from an earlier attempt, or from another merchant of your organization where the same person appears.
* Re-evaluated on every read. When the merchant changes shape, for example its contact becomes a `REPRESENTATIVE`, the next read adds what became required, waives what no longer applies, and restores a waived one that applies again.

## What each provider requires

<Tabs>
  <Tab title="CELCOIN">
    | Requirement | About | When |
    | - | - | - |
    | `IDENTITY_DOCUMENT` | The contact | Always |
    | `BIOMETRIC_VERIFICATION` | The contact | Always |
    | `ARTICLES_OF_INCORPORATION` | The merchant | A merchant with a CNPJ whose `document_tax_type` is `PJ` or `ME` |
    | `POWER_OF_ATTORNEY` | The merchant | A merchant with a CNPJ whose contact's `role` is `REPRESENTATIVE` |

    A natural person (`PF`) needs only the contact's identity document and biometric verification, and so does a `MEI` whose contact is a `PARTNER`; a `MEI` represented by a `REPRESENTATIVE` also needs the `POWER_OF_ATTORNEY`. Anything else the provider asks for arrives as a new requirement after a [rejection](/onboarding/rejections), and is collected for a new affiliation.
  </Tab>

  <Tab title="CAPPTA">
    Nothing up front: the affiliation is submitted at once with the merchant data and the settlement bank account. The provider's risk analysis may then ask for documents or information. They arrive as new requirements on the same affiliation, which moves to `WAITING_DOCUMENTS` and continues once they are supplied.
  </Tab>

  <Tab title="RINNE">
    No onboarding requirements. The affiliation is created as described in [Affiliations](/concepts/affiliations).
  </Tab>
</Tabs>

The same rules apply to every merchant; only its shape changes the list.

<OnboardingRequirementsExplorer />

## Requirement types

| Type | Satisfied by |
| - | - |
| `IDENTITY_DOCUMENT` | A `CNH_DIGITAL` alone, or the front and back of one document: `CNH_FRONT` and `CNH_BACK`, `RG_FRONT` and `RG_BACK`, or `RNE_FRONT` and `RNE_BACK` |
| `BIOMETRIC_VERIFICATION` | An `APPROVED` [biometric verification](/onboarding/biometric-verification) of the person |
| `ARTICLES_OF_INCORPORATION` | An `ARTICLES_OF_INCORPORATION` document: the merchant's constitutive document, the consolidated *contrato social* of a limited company or the *estatuto social* of a corporation |
| `POWER_OF_ATTORNEY` | A `POWER_OF_ATTORNEY` document granting the contact the power to represent the merchant |
| `PROOF_OF_ADDRESS` | A `PROOF_OF_ADDRESS` document |
| `PROOF_OF_INCOME` | A `PROOF_OF_INCOME` document |
| `FINANCIAL_STATEMENT` | A `FINANCIAL_STATEMENT` document |
| `INFORMATION` | A text [answer](/onboarding/api#4-answer-questions-and-evidence-requests) to the question in `description` |
| `EVIDENCE_PACKAGE` | An explanation and any number of files that back it, closed by an answer. Sent to the provider as one unit, for that request only |
| `OTHER` | An `OTHER` document, for something the other types do not cover |

`accepted_document_types` lists the document types that satisfy each document requirement at that provider, and is `null` for the types no document satisfies (`BIOMETRIC_VERIFICATION`, `INFORMATION`, `EVIDENCE_PACKAGE`).

## Who a requirement is about

`subject_document_number` is the CPF or CNPJ the requirement is about, digits only, and `subject_role` says who that is for the merchant:

| `subject_role` | Who |
| - | - |
| `CONTACT` | The merchant's registered contact, the person who represents it |
| `COMPANY` | The merchant itself, by its CNPJ, or by its CPF for a natural person |
| `PARTNER_COMPANY` | A company that holds a stake in the merchant, when a provider asks for its documents |

Upload each document under the subject the requirement names.

## Status

| Status | Meaning | What to do |
| - | - | - |
| `PENDING` | Nothing valid satisfies it | Upload the document, start the biometric verification, answer the question, or send the link |
| `SATISFIED` | A usable document, an approved biometric verification or an answer covers it; `satisfied_by` names it | Nothing |
| `REJECTED` | The only evidence is one the provider refused | Supply a new one; the requirement becomes `SATISFIED` again |
| `WAIVED` | Rinne's review concluded the provider no longer needs it, or the merchant's shape no longer calls for it (`waived_at` is set) | Nothing |

A requirement is evaluated every time it is read. It becomes `SATISFIED` the moment a matching document is stored, a verification is approved or an answer is given, with no call on your side, and every affiliation waiting on it is re-evaluated at the same moment: the last item in submits it.

## Origin

| `origin` | Where it came from | `description` |
| - | - | - |
| `BASE` | The provider's base set for the merchant's shape | `null` |
| `PROVIDER_REQUEST` | A provider's rejection or request for more, translated by Rinne's review | What is being asked, written for the merchant's contact |
| `STAFF` | Added by Rinne staff, for example the articles of a partner company above 25 % of the ownership | What is being asked, written for the merchant's contact |

The base set holds one requirement per type and subject. Every request adds a requirement of its own, so two `INFORMATION` questions are two requirements with two answers.

## The response

<ResponseField name="id" type="string" required>
  The requirement's ID. Use it to answer an `INFORMATION` or `EVIDENCE_PACKAGE` requirement and to attach files to a package.
</ResponseField>

<ResponseField name="company_id" type="string" required>
  The merchant the requirement applies to.
</ResponseField>

<ResponseField name="provider" type="string" required>
  `CELCOIN` or `CAPPTA`.
</ResponseField>

<ResponseField name="type" type="string" required>
  What is required. See [Requirement types](#requirement-types).
</ResponseField>

<ResponseField name="subject_document_number" type="string" required>
  The CPF or CNPJ the requirement is about, digits only.
</ResponseField>

<ResponseField name="subject_role" type="string" required>
  `CONTACT`, `COMPANY` or `PARTNER_COMPANY`.
</ResponseField>

<ResponseField name="origin" type="string" required>
  `BASE`, `PROVIDER_REQUEST` or `STAFF`.
</ResponseField>

<ResponseField name="description" type="string | null">
  What is being asked, for `PROVIDER_REQUEST` and `STAFF` requirements; `null` on `BASE` ones.
</ResponseField>

<ResponseField name="status" type="string" required>
  `PENDING`, `SATISFIED`, `REJECTED` or `WAIVED`, derived at read time.
</ResponseField>

<ResponseField name="accepted_document_types" type="array | null">
  The document types that satisfy the requirement at this provider; `null` for a requirement no document satisfies.
</ResponseField>

<ResponseField name="satisfied_by" type="object | null">
  The evidence that satisfies it; `null` unless `SATISFIED`.

  <Expandable title="satisfied_by properties">
    <ResponseField name="onboarding_document_ids" type="array">
      The documents that satisfy it: both sides of an identity document, or the attachments of an `EVIDENCE_PACKAGE` the provider has not refused.
    </ResponseField>

    <ResponseField name="biometric_verification_id" type="string | null">
      The approved verification that satisfies a `BIOMETRIC_VERIFICATION` requirement.
    </ResponseField>

    <ResponseField name="answer" type="string | null">
      The text that answers an `INFORMATION` or `EVIDENCE_PACKAGE` requirement.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="waived_at" type="string | null">
  When the requirement was waived.
</ResponseField>

A complete example response is in [Build with the API](/onboarding/api#1-read-what-is-pending).

## Examples

<AccordionGroup>
  <Accordion title="A bakery (PJ) whose contact is a partner" icon="store">
    `CELCOIN` requires the contact's `IDENTITY_DOCUMENT` and `BIOMETRIC_VERIFICATION` and the merchant's `ARTICLES_OF_INCORPORATION`. If the same partner was already verified in another merchant of your organization, only the identity document and the articles are pending.
  </Accordion>

  <Accordion title="The same bakery, now represented by a manager" icon="user-tie">
    Change the contact to the manager with `role` `REPRESENTATIVE`. The next read adds a `POWER_OF_ATTORNEY` requirement for the merchant, and the identity document and biometric verification are now about the manager.
  </Accordion>

  <Accordion title="A freelancer onboarded by CPF (PF)" icon="user">
    `CELCOIN` requires only the person's `IDENTITY_DOCUMENT` and `BIOMETRIC_VERIFICATION`. There is no merchant document to send.
  </Accordion>

  <Accordion title="A provider asks for proof that the business exists" icon="magnifying-glass-location">
    After Rinne reviews the request, the merchant has a new `EVIDENCE_PACKAGE` requirement with `origin` `PROVIDER_REQUEST` and a `description` such as "Show that the business operates at the registered address". Attach a storefront photo and a map print, then answer it with a short explanation.
  </Accordion>
</AccordionGroup>


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