# Authenticate a merchant's transaction with 3D Secure Source: https://docs.rinne.com.br/api-reference/3d-secure/authenticate-a-merchants-transaction-with-3d-secure /api-spec.yaml post /v1/merchants/{merchantId}/transactions/{transactionId}/authenticate Authenticates a transaction that is in AWAITING_3DS status using a 3D Secure session. Links the authenticated session to the transaction, updates card data with 3DS authentication details (cryptogram, ECI, authentication_type), and processes the payment with the provider. Requires the merchant.transaction.create permission. This endpoint is used in the transaction-first 3DS flow: 1. Transaction is created and enters AWAITING_3DS status 2. Organization creates a 3DS session and completes authentication 3. Organization calls this endpoint to link the session and process the payment The session must be authenticated, not consumed, not expired, and match the transaction's merchant, amount, and currency. # Authenticate a transaction with 3D Secure Source: https://docs.rinne.com.br/api-reference/3d-secure/authenticate-a-transaction-with-3d-secure /api-spec.yaml post /v1/transactions/{transactionId}/authenticate Authenticates a transaction that is in AWAITING_3DS status using a 3D Secure session. Links the authenticated session to the transaction, updates card data with 3DS authentication details (cryptogram, ECI, authentication_type), and processes the payment with the provider. Requires the transaction.create permission. This endpoint is used in the transaction-first 3DS flow: 1. Transaction is created and enters AWAITING_3DS status 2. Authenticated company creates a 3DS session and completes authentication 3. Authenticated company calls this endpoint to link the session and process the payment The session must be authenticated, not consumed, not expired, and match the transaction's company, amount, and currency. # Create a 3D Secure session Source: https://docs.rinne.com.br/api-reference/3d-secure/create-a-3d-secure-session /api-spec.yaml post /v1/3ds-sessions Creates a standalone 3D Secure (3DS) authentication session for a card payment in the authenticated company context. Requires the 3ds.create permission. **Flow:** 1. Create a 3DS session with encrypted card data, or with `card.card_id` to authenticate a card stored via `POST /v1/cards` 2. If `auth_status` is `ACTION_REQUIRED`, the cardholder must complete the 3DS challenge using the `tds_session_id` with the client-side SDK 3. If `auth_status` is `AUTHENTICATED`, the session is ready to be used in a transaction 4. Pass the session `id` as `three_d_secure_session_id` when creating a card transaction **Expiration:** Sessions expire 1 hour after creation. **Single use:** A session authenticates one amount and currency and can be spent on one transaction. Recurring charges need a new session each time — a stored card does not change this. **Supported card networks:** Visa, Mastercard, and American Express. Cards from other networks (e.g. Elo, Hipercard, Discover, Diners, JCB) cannot be authenticated and are rejected with `400 VALIDATION_ERROR`. # Create a 3D Secure session linked to a merchant Source: https://docs.rinne.com.br/api-reference/3d-secure/create-a-3d-secure-session-linked-to-a-merchant /api-spec.yaml post /v1/merchants/{merchantId}/3ds-sessions Creates a standalone 3D Secure (3DS) authentication session for a card payment. Requires the merchant.3ds.create permission. **Flow:** 1. Create a 3DS session with encrypted card data, or with `card.card_id` to authenticate a card stored via `POST /v1/cards` 2. If `auth_status` is `ACTION_REQUIRED`, the cardholder must complete the 3DS challenge using the `tds_session_id` with the client-side SDK 3. If `auth_status` is `AUTHENTICATED`, the session is ready to be used in a transaction 4. Pass the session `id` as `three_d_secure_session_id` when creating a card transaction **Expiration:** Sessions expire 1 hour after creation. **Single use:** A session authenticates one amount and currency and can be spent on one transaction. Recurring charges need a new session each time — a stored card does not change this. **Supported card networks:** Visa, Mastercard, and American Express. Cards from other networks (e.g. Elo, Hipercard, Discover, Diners, JCB) cannot be authenticated and are rejected with `400 VALIDATION_ERROR`. # Create a new affiliation for a specific merchant Source: https://docs.rinne.com.br/api-reference/affiliations/create-a-new-affiliation-for-a-specific-merchant /api-spec.yaml post /v1/merchants/{merchantId}/affiliations Requires the merchant.affiliation.create permission. Creates a new affiliation for a merchant that belongs to the authenticated organization # Get a specific affiliation for a merchant Source: https://docs.rinne.com.br/api-reference/affiliations/get-a-specific-affiliation-for-a-merchant /api-spec.yaml get /v1/merchants/{merchantId}/affiliations/{id} Requires the merchant.affiliation.view permission. Get details of a specific affiliation for a merchant that belongs to the authenticated organization # Get affiliations for all merchants Source: https://docs.rinne.com.br/api-reference/affiliations/get-affiliations-for-all-merchants /api-spec.yaml get /v1/merchants/affiliations Requires the merchant.affiliation.list permission. Returns a paginated list of affiliations for all merchants that belong to the authenticated organization # Get onboarding document with download URL for a merchant's affiliation Source: https://docs.rinne.com.br/api-reference/affiliations/get-onboarding-document-with-download-url-for-a-merchants-affiliation /api-spec.yaml get /v1/merchants/{merchantId}/affiliations/{affiliationId}/onboarding-documents/{documentId} Requires the merchant.affiliation.view permission. Retrieves a single onboarding document with a signed download URL. The signed URL expires after 1 hour. Use this endpoint to download onboarding documents that were uploaded during the KYC/onboarding process. # List affiliations for a specific merchant Source: https://docs.rinne.com.br/api-reference/affiliations/list-affiliations-for-a-specific-merchant /api-spec.yaml get /v1/merchants/{merchantId}/affiliations Requires the merchant.affiliation.list permission. Get a list of affiliations for a specific merchant that belongs to the authenticated organization # Request settlement bank account update for a merchant's affiliation Source: https://docs.rinne.com.br/api-reference/affiliations/request-settlement-bank-account-update-for-a-merchants-affiliation /api-spec.yaml post /v1/merchants/{merchantId}/affiliations/{affiliationId}/settlement-bank-account Creates a new pending settlement bank account for a specific merchant's affiliation. Triggers a call to the payment provider to initiate bank account validation. The new account starts with PENDING_VALIDATION status; the current ACTIVE account continues to be used for card receivables settlements until validation completes. Only one PENDING_VALIDATION account is allowed per affiliation at a time. # Update anticipation configuration for a merchant's affiliation Source: https://docs.rinne.com.br/api-reference/affiliations/update-anticipation-configuration-for-a-merchants-affiliation /api-spec.yaml patch /v1/merchants/{merchantId}/affiliations/{id} Requires the merchant.affiliation.edit permission. Synchronously updates the anticipation configuration of a merchant's affiliation. The change is propagated to the underlying payment provider; if the provider doesn't support this operation, returns a 400. The affiliation must be ACTIVE and enabled, and the merchant must belong to the authenticated organization. # Create an API key Source: https://docs.rinne.com.br/api-reference/api-keys/create-an-api-key /api-spec.yaml post /v1/api-keys Requires the api_key.create permission and JWT (dashboard) authentication — API-key authentication is rejected on key-management endpoints. Creates a new API key for the authenticated company. The response is the only place the full key is ever returned (show-once); afterwards only the masked prefix and last 4 characters are visible. A company can hold at most 20 active keys. # Create an API key for a merchant Source: https://docs.rinne.com.br/api-reference/api-keys/create-an-api-key-for-a-merchant /api-spec.yaml post /v1/merchants/{merchantId}/api-keys Requires the api_key.create permission and JWT (dashboard) authentication — API-key authentication is rejected on key-management endpoints. Creates a new API key for a merchant that belongs to the authenticated organization. The response is the only place the full key is ever returned (show-once); afterwards only the masked prefix and last 4 characters are visible. A company can hold at most 20 active keys. # List API keys Source: https://docs.rinne.com.br/api-reference/api-keys/list-api-keys /api-spec.yaml get /v1/api-keys Requires the api_key.list permission and JWT (dashboard) authentication — API-key authentication is rejected on key-management endpoints. Returns the authenticated company's active (non-revoked, non-expired) API keys, masked (prefix and last 4 characters only). # List API keys of a merchant Source: https://docs.rinne.com.br/api-reference/api-keys/list-api-keys-of-a-merchant /api-spec.yaml get /v1/merchants/{merchantId}/api-keys Requires the api_key.list permission and JWT (dashboard) authentication — API-key authentication is rejected on key-management endpoints. Returns the active (non-revoked, non-expired) API keys of a merchant that belongs to the authenticated organization, masked (prefix and last 4 characters only). # Revoke an API key Source: https://docs.rinne.com.br/api-reference/api-keys/revoke-an-api-key /api-spec.yaml delete /v1/api-keys/{apiKeyId} Requires the api_key.delete permission and JWT (dashboard) authentication — API-key authentication is rejected on key-management endpoints. Soft-revokes the key: it immediately stops authenticating but the record is retained for audit. Revocation cannot be undone. # Revoke an API key of a merchant Source: https://docs.rinne.com.br/api-reference/api-keys/revoke-an-api-key-of-a-merchant /api-spec.yaml delete /v1/merchants/{merchantId}/api-keys/{apiKeyId} Requires the api_key.delete permission and JWT (dashboard) authentication — API-key authentication is rejected on key-management endpoints. Soft-revokes a key of a merchant that belongs to the authenticated organization: it immediately stops authenticating but the record is retained for audit. Revocation cannot be undone. # Change current password Source: https://docs.rinne.com.br/api-reference/authentication/change-current-password /api-spec.yaml post /v1/auth/change-password Changes the authenticated user's password. Requires current password for verification. User must be authenticated with a valid JWT token. # Get current user information Source: https://docs.rinne.com.br/api-reference/authentication/get-current-user-information /api-spec.yaml get /v1/auth/me Returns information about the currently authenticated user based on JWT token. Includes user identifiers, auth methods, and current company context if set. When the bearer is an admin impersonation JWT (ADR-0018), the `user` payload is synthesized from the JWT claims (no real `User` row exists for the operator inside the target organization) and an additional `impersonation` block is included with the actor identity and the target organization id. Clients can detect impersonation by the presence of that block. # Select company context Source: https://docs.rinne.com.br/api-reference/authentication/select-company-context /api-spec.yaml post /v1/auth/select-company Selects company context for authenticated user and returns new JWT token with company-specific permissions. Used after login when user has multiple companies. # User login with password Source: https://docs.rinne.com.br/api-reference/authentication/user-login-with-password /api-spec.yaml post /v1/auth/login Authenticates user with email/phone and password. Returns JWT token. Users of both organizations and merchants can log in; the selected company's type is carried on the token as `current_company_type` and the session's permissions come from the role held in that company. If the user holds roles in a single company it is auto-selected. If the user holds roles in more than one company — of any type, so a user placed in both an organization and one of its merchants counts — the response instead lists `available_companies` (each carrying its `type`) with `requires_company_selection: true`, and the token has no company context until `POST /v1/auth/select-company` is called. # User logout Source: https://docs.rinne.com.br/api-reference/authentication/user-logout /api-spec.yaml post /v1/auth/logout Logs out the authenticated user by invalidating their JWT token. Note: JWT tokens are stateless, so they will continue to be valid until expiration. Future implementation will include token blacklisting for immediate invalidation. # Create a PIX key for a merchant Source: https://docs.rinne.com.br/api-reference/bank-accounts/create-a-pix-key-for-a-merchant /api-spec.yaml post /v1/merchants/{merchantId}/pix-keys Requires the merchant.banking.create permission. Registers a new PIX key for a merchant that belongs to the authenticated organization company. A DICT lookup is performed against the merchant's active affiliation before creating the key, and the result is returned in `dict_key_information`. Per BACEN's rules, several fields may be masked (e.g. account number, account type, owner document) as part of DICT's read-attack prevention; values should be passed through as returned when initiating payments. The key is normalized and validated before the lookup: CPF/CNPJ keys are stored bare (`123.456.789-09` → `12345678909`, alphanumeric CNPJs uppercased), phone keys must be in international format and lose their separators (`+55 (11) 98765-4321` → `+5511987654321`), email keys are lowercased, and EVPs are lowercase UUIDs. A key matching none of these forms is rejected with 400 before any DICT lookup. If the merchant already has an active PIX key with the same `key` (after normalization), the request fails with 409 and no DICT lookup is performed. The request fails with 404 if the merchant is not found, does not belong to the authenticated organization, or does not have an active valid affiliation with a bank account. # Delete a PIX key for a merchant Source: https://docs.rinne.com.br/api-reference/bank-accounts/delete-a-pix-key-for-a-merchant /api-spec.yaml delete /v1/merchants/{merchantId}/pix-keys/{id} Requires the merchant.banking.delete permission. Soft deletes a specific PIX key for a merchant that belongs to the authenticated organization company. The response includes `dict_key_information` from the last persisted DICT snapshot when available (`null` otherwise). # Get a PIX key for a merchant Source: https://docs.rinne.com.br/api-reference/bank-accounts/get-a-pix-key-for-a-merchant /api-spec.yaml get /v1/merchants/{merchantId}/pix-keys/{id} Requires the merchant.banking.view permission. Returns details of a specific PIX key for a merchant that belongs to the authenticated organization company, including `dict_key_information` when a DICT snapshot exists for the key (otherwise `null`). # Get specific bank account details for a merchant Source: https://docs.rinne.com.br/api-reference/bank-accounts/get-specific-bank-account-details-for-a-merchant /api-spec.yaml get /v1/merchants/{merchantId}/bank-accounts/{id} Returns details of a specific bank account for a merchant that belongs to the authenticated organization company # List all bank accounts for a merchant Source: https://docs.rinne.com.br/api-reference/bank-accounts/list-all-bank-accounts-for-a-merchant /api-spec.yaml get /v1/merchants/{merchantId}/bank-accounts Requires the merchant.banking.list permission. Returns all bank accounts for a merchant that belongs to the authenticated organization company # List all PIX keys for a merchant Source: https://docs.rinne.com.br/api-reference/bank-accounts/list-all-pix-keys-for-a-merchant /api-spec.yaml get /v1/merchants/{merchantId}/pix-keys Requires the merchant.banking.list permission. Returns all PIX keys for a merchant that belongs to the authenticated organization company. Each item includes `dict_key_information` when a DICT snapshot has been persisted for that key; otherwise the field is present and `null`. # Registers a new external bank account for a merchant Source: https://docs.rinne.com.br/api-reference/bank-accounts/registers-a-new-external-bank-account-for-a-merchant /api-spec.yaml post /v1/merchants/{merchantId}/bank-accounts Registers a new external bank account for a merchant that belongs to the authenticated organization company # Soft delete bank account for a merchant Source: https://docs.rinne.com.br/api-reference/bank-accounts/soft-delete-bank-account-for-a-merchant /api-spec.yaml delete /v1/merchants/{merchantId}/bank-accounts/{id} Soft deletes a specific bank account for a merchant that belongs to the authenticated organization company # Update a PIX key for a merchant Source: https://docs.rinne.com.br/api-reference/bank-accounts/update-a-pix-key-for-a-merchant /api-spec.yaml patch /v1/merchants/{merchantId}/pix-keys/{id} Requires the merchant.banking.edit permission. Updates a specific PIX key for a merchant that belongs to the authenticated organization company. When `key` is sent, a DICT lookup is performed using the merchant's active enabled CELCOIN affiliation (same flow as create), the result is persisted, and returned in `dict_key_information`. Per BACEN's rules, several fields may be masked as part of DICT read-attack prevention. Updates that only change `primary` do not perform a lookup. When `key` is sent it is normalized and validated before the lookup (CPF/CNPJ stored bare, phone separators stripped, emails lowercased, EVPs lowercase UUIDs); a key matching no canonical form is rejected with 400 before any DICT lookup. Returns 404 if the merchant or PIX key is not found, the merchant does not belong to the authenticated organization, or the merchant has no active enabled CELCOIN affiliation, 409 if another active PIX key already holds the new `key`, and 400 if the update includes `key` while the PIX key has cashouts outside a failed state (updates that only change `primary` are not subject to this restriction). In all of those cases no DICT lookup is performed. # Update bank account information for a merchant Source: https://docs.rinne.com.br/api-reference/bank-accounts/update-bank-account-information-for-a-merchant /api-spec.yaml patch /v1/merchants/{merchantId}/bank-accounts/{id} Updates a specific bank account for a merchant that belongs to the authenticated organization company # Add balance to merchant bank account (Sandbox Only) Source: https://docs.rinne.com.br/api-reference/banking/add-balance-to-merchant-bank-account-sandbox-only /api-spec.yaml post /v1/merchants/{merchantId}/balance Adds balance to a merchant's bank account for testing purposes. **This endpoint is only available in sandbox environment.** This endpoint allows developers to add test funds to merchant bank accounts for testing PIX payments and other banking operations. The account number is automatically retrieved from the merchant's active affiliation. # Add balance to organization bank account (Sandbox Only) Source: https://docs.rinne.com.br/api-reference/banking/add-balance-to-organization-bank-account-sandbox-only /api-spec.yaml post /v1/banking/balance Adds balance to the authenticated organization's own bank account for testing purposes. **This endpoint is only available in sandbox environment.** Unlike the merchant variant (`/v1/merchants/{merchantId}/balance`), this endpoint does not require a `merchantId` parameter. The organization is identified from the authenticated user's company ID, which must be of type `ORGANIZATION`. # Create a cashout request for a specific merchant Source: https://docs.rinne.com.br/api-reference/banking/create-a-cashout-request-for-a-specific-merchant /api-spec.yaml post /v1/merchants/{merchantId}/cashouts Creates a new cashout request for a merchant that belongs to the authenticated organization # Create a new internal transfer request Source: https://docs.rinne.com.br/api-reference/banking/create-a-new-internal-transfer-request /api-spec.yaml post /v1/banking/internal-transfers Creates a new internal transfer request for the authenticated company or a merchant company. Internal transfers move money between bank accounts within the same provider. The transfer will be processed by the provider and status updates will be received via webhooks. Only organization companies may create internal transfers. Requires the banking.create permission. # Get a specific cashout for a merchant Source: https://docs.rinne.com.br/api-reference/banking/get-a-specific-cashout-for-a-merchant /api-spec.yaml get /v1/merchants/{merchantId}/cashouts/{id} Returns details of a specific cashout for a merchant that belongs to the authenticated organization # Get a specific internal transfer by ID Source: https://docs.rinne.com.br/api-reference/banking/get-a-specific-internal-transfer-by-id /api-spec.yaml get /v1/banking/internal-transfers/{id} Returns details of a specific internal transfer for the authenticated company. The internal transfer ID can be either the internal ID or the external request ID. Requires the banking.view permission. # Get banking balance for a specific merchant Source: https://docs.rinne.com.br/api-reference/banking/get-banking-balance-for-a-specific-merchant /api-spec.yaml get /v1/merchants/{merchantId}/balance Returns current banking balance for a merchant that belongs to the authenticated organization company # Get banking statement for a specific merchant Source: https://docs.rinne.com.br/api-reference/banking/get-banking-statement-for-a-specific-merchant /api-spec.yaml get /v1/merchants/{merchantId}/statement Returns paginated account statement for a merchant that belongs to the authenticated organization company # Get merchant cashout receipt Source: https://docs.rinne.com.br/api-reference/banking/get-merchant-cashout-receipt /api-spec.yaml get /v1/merchants/{merchantId}/cashouts/{id}/receipt Generates a receipt for a merchant's cashout operation. Only accessible by the organization that owns the merchant. Returns JSON with cashout details, account information, and a base64-encoded PDF. Only available for cashouts with status COMPLETED, PARTIALLY_RETURNED, or RETURNED. Requires the cashout to have an end-to-end ID. # List cashouts for a specific merchant Source: https://docs.rinne.com.br/api-reference/banking/list-cashouts-for-a-specific-merchant /api-spec.yaml get /v1/merchants/{merchantId}/cashouts Returns paginated list of cashouts for a merchant that belongs to the authenticated organization # List internal transfers for authenticated company Source: https://docs.rinne.com.br/api-reference/banking/list-internal-transfers-for-authenticated-company /api-spec.yaml get /v1/banking/internal-transfers Returns a paginated list of internal transfers for the authenticated company with optional filters. Internal transfers move money between bank accounts within the same provider. For organization users, the origin_bank_account_id and destination_bank_account_id parameters can be used to filter transfers from/to a specific bank accounts. If not provided, transfers from the organization's and it's merchants bank accounts will be returned. Requires the banking.list permission. # List judicial blockages for a specific merchant Source: https://docs.rinne.com.br/api-reference/banking/list-judicial-blockages-for-a-specific-merchant /api-spec.yaml get /v1/merchants/{merchantId}/banking/judicial-blockages Returns a paginated list of judicial block/release movements for a merchant that belongs to the authenticated organization. Supports filtering by type, bank account, process number, and occurrence date range. Requires the merchant.banking.list permission. # Delete a merchant's stored card Source: https://docs.rinne.com.br/api-reference/cards/delete-a-merchants-stored-card /api-spec.yaml delete /v1/merchants/{merchantId}/cards/{cardId} Requires the merchant.card.delete permission. The authenticated organization must own the merchant. Soft-deletes the card. # Delete a stored card Source: https://docs.rinne.com.br/api-reference/cards/delete-a-stored-card /api-spec.yaml delete /v1/cards/{cardId} Requires the card.delete permission. Soft-deletes the card. The record is retained for audit but excluded from all subsequent reads and cannot be restored. # Get a merchant's stored card Source: https://docs.rinne.com.br/api-reference/cards/get-a-merchants-stored-card /api-spec.yaml get /v1/merchants/{merchantId}/cards/{cardId} Requires the merchant.card.view permission. The authenticated organization must own the merchant. # Get a stored card Source: https://docs.rinne.com.br/api-reference/cards/get-a-stored-card /api-spec.yaml get /v1/cards/{cardId} Requires the card.view permission. Returns a single stored card owned by the authenticated company. # List a merchant's stored cards Source: https://docs.rinne.com.br/api-reference/cards/list-a-merchants-stored-cards /api-spec.yaml get /v1/merchants/{merchantId}/cards Requires the merchant.card.list permission. The authenticated organization must own the merchant. # List stored cards Source: https://docs.rinne.com.br/api-reference/cards/list-stored-cards /api-spec.yaml get /v1/cards Requires the card.list permission. Returns a paginated list of the authenticated company's stored cards. # Store a card Source: https://docs.rinne.com.br/api-reference/cards/store-a-card /api-spec.yaml post /v1/cards Requires the card.create permission. Stores a card for the authenticated company and returns a reusable card reference. The card number must be encrypted ciphertext (`ev:` prefix); Rinne never receives the raw PAN and never stores CVC. # Store a card for a merchant Source: https://docs.rinne.com.br/api-reference/cards/store-a-card-for-a-merchant /api-spec.yaml post /v1/merchants/{merchantId}/cards Requires the merchant.card.create permission. The authenticated organization must own the merchant. # Create a company PIX key Source: https://docs.rinne.com.br/api-reference/companies/create-a-company-pix-key /api-spec.yaml post /v1/companies/me/pix-keys Requires the banking.create permission. Registers a new PIX key for the authenticated company. A DICT lookup is performed against the company's active affiliation bank account before creating the key, and the result is returned in `dict_key_information`. Per BACEN's external DICT API, several fields may be masked (e.g. account number, account type, owner document) as part of DICT's read-attack prevention; values should be passed through as returned when initiating payments. The key is normalized and validated before the lookup: CPF/CNPJ keys are stored bare (`123.456.789-09` → `12345678909`, alphanumeric CNPJs uppercased), phone keys must be in international format and lose their separators (`+55 (11) 98765-4321` → `+5511987654321`), email keys are lowercased, and EVPs are lowercase UUIDs. A key matching none of these forms is rejected with 400 before any DICT lookup. If the company already has an active PIX key with the same `key` (after normalization), the request fails with 409 and no DICT lookup is performed. The request fails with 404 if no active valid affiliation with a bank account is found for the company. # Delete a company PIX key Source: https://docs.rinne.com.br/api-reference/companies/delete-a-company-pix-key /api-spec.yaml delete /v1/companies/me/pix-keys/{id} Requires the banking.delete permission. Soft deletes a specific PIX key for the authenticated company. The response includes `dict_key_information` from the last persisted DICT snapshot when available (`null` otherwise). # Get a company PIX key Source: https://docs.rinne.com.br/api-reference/companies/get-a-company-pix-key /api-spec.yaml get /v1/companies/me/pix-keys/{id} Requires the banking.view permission. Returns details of a specific PIX key for the authenticated company, including `dict_key_information` when a DICT snapshot exists for the key (otherwise `null`). # Get authenticated company information Source: https://docs.rinne.com.br/api-reference/companies/get-authenticated-company-information /api-spec.yaml get /v1/companies/me Requires the company.list permission. Returns information about the authenticated company # Get specific bank account details Source: https://docs.rinne.com.br/api-reference/companies/get-specific-bank-account-details /api-spec.yaml get /v1/companies/me/bank-accounts/{id} Requires the banking.view permission. Returns details of a specific bank account for the authenticated company # List all bank accounts Source: https://docs.rinne.com.br/api-reference/companies/list-all-bank-accounts /api-spec.yaml get /v1/companies/me/bank-accounts Requires the banking.list permission. Returns all bank accounts for the authenticated company # List all company PIX keys Source: https://docs.rinne.com.br/api-reference/companies/list-all-company-pix-keys /api-spec.yaml get /v1/companies/me/pix-keys Requires the banking.list permission. Returns all PIX keys for the authenticated company. Each item includes `dict_key_information` when a DICT snapshot has been persisted for that key; otherwise the field is present and `null`. # List all MCCs Source: https://docs.rinne.com.br/api-reference/companies/list-all-mccs /api-spec.yaml get /v1/companies/mccs Requires the company.list permission. Returns all MCCs for the authenticated company. # Registers a new bank account Source: https://docs.rinne.com.br/api-reference/companies/registers-a-new-bank-account /api-spec.yaml post /v1/companies/me/bank-accounts Requires the banking.create permission. Registers a new bank account for the authenticated company # Soft delete bank account Source: https://docs.rinne.com.br/api-reference/companies/soft-delete-bank-account /api-spec.yaml delete /v1/companies/me/bank-accounts/{id} Requires the banking.delete permission. Soft deletes a specific bank account for the authenticated company # Update a company PIX key Source: https://docs.rinne.com.br/api-reference/companies/update-a-company-pix-key /api-spec.yaml patch /v1/companies/me/pix-keys/{id} Requires the banking.edit permission. Updates a specific PIX key for the authenticated company. When `key` is sent, a DICT lookup is performed using the company's active enabled CELCOIN affiliation (same flow as create), the result is persisted, and returned in `dict_key_information`. Per BACEN's rules, several fields may be masked as part of DICT read-attack prevention. Updates that only change `primary` do not perform a lookup. When `key` is sent it is normalized and validated before the lookup (CPF/CNPJ stored bare, phone separators stripped, emails lowercased, EVPs lowercase UUIDs); a key matching no canonical form is rejected with 400 before any DICT lookup. Returns 404 if the PIX key is not found or the company has no active enabled CELCOIN affiliation, 409 if another active PIX key already holds the new `key`, and 400 if the update includes `key` while the PIX key has cashouts outside a failed state (updates that only change `primary` are not subject to this restriction). In all of those cases no DICT lookup is performed. # Update authenticated company Source: https://docs.rinne.com.br/api-reference/companies/update-authenticated-company /api-spec.yaml patch /v1/companies/me Requires the company.edit permission. Updates the authenticated company with provided fields. Only specific fields can be updated via API for security reasons. # Update bank account information Source: https://docs.rinne.com.br/api-reference/companies/update-bank-account-information /api-spec.yaml patch /v1/companies/me/bank-accounts/{id} Requires the banking.edit permission. Updates a specific bank account for the authenticated company # Create new affiliation Source: https://docs.rinne.com.br/api-reference/company-affiliations/create-new-affiliation /api-spec.yaml post /v1/affiliations Creates a new affiliation for the authenticated company. The company_id is automatically set from the authenticated client. Requires the affiliation.create permission. # Get a specific affiliation Source: https://docs.rinne.com.br/api-reference/company-affiliations/get-a-specific-affiliation /api-spec.yaml get /v1/affiliations/{id} Get details of a specific affiliation belonging to the authenticated company # Get onboarding document with download URL Source: https://docs.rinne.com.br/api-reference/company-affiliations/get-onboarding-document-with-download-url /api-spec.yaml get /v1/affiliations/{affiliationId}/onboarding-documents/{documentId} Retrieves a single onboarding document with a signed download URL for an affiliation belonging to the authenticated company. The signed URL expires after 1 hour. Use this endpoint to download onboarding documents that were uploaded during the KYC/onboarding process. # List affiliations Source: https://docs.rinne.com.br/api-reference/company-affiliations/list-affiliations /api-spec.yaml get /v1/affiliations Gets a list of affiliations for the authenticated company with optional filters and pagination. Requires the affiliation.list permission. # Request settlement bank account update Source: https://docs.rinne.com.br/api-reference/company-affiliations/request-settlement-bank-account-update /api-spec.yaml post /v1/affiliations/{affiliationId}/settlement-bank-account Creates a new pending settlement bank account for an affiliation. Triggers a call to the payment provider to initiate bank account validation. The new account starts with PENDING_VALIDATION status; the current ACTIVE account continues to be used for card receivables settlements until validation completes. Only one PENDING_VALIDATION account is allowed per affiliation at a time. # Update affiliation anticipation configuration Source: https://docs.rinne.com.br/api-reference/company-affiliations/update-affiliation-anticipation-configuration /api-spec.yaml patch /v1/affiliations/{id} Requires the affiliation.edit permission. Synchronously updates an affiliation's anticipation configuration. The change is propagated to the underlying payment provider; if the provider doesn't support this operation, returns a 400. The affiliation must be ACTIVE and enabled. # Create a new cashout request Source: https://docs.rinne.com.br/api-reference/company-banking/create-a-new-cashout-request /api-spec.yaml post /v1/banking/cashouts Creates a new cashout request for the authenticated company. The cashout will be processed by the provider and status updates will be received via webhooks. Requires the banking.create permission. # Get a specific cashout by ID Source: https://docs.rinne.com.br/api-reference/company-banking/get-a-specific-cashout-by-id /api-spec.yaml get /v1/banking/cashouts/{id} Returns details of a specific cashout for the authenticated company. The cashout ID can be either the internal ID or the external request ID. Requires the banking.view permission. # Get account balance for a company Source: https://docs.rinne.com.br/api-reference/company-banking/get-account-balance-for-a-company /api-spec.yaml get /v1/banking/balance Retrieves the current company account balance in cents for a specific provider. Requires an active affiliation with the specified provider. Requires the banking.view permission. # Get account statement (movements) for a company Source: https://docs.rinne.com.br/api-reference/company-banking/get-account-statement-movements-for-a-company /api-spec.yaml get /v1/banking/statement Retrieves the company account statement (movements) for a specific provider within a date range. Requires an active affiliation with the specified provider. Note: The provider enforces a maximum date range of 7 days and a maximum page size of 50. Requires the banking.view permission. # Get cashout receipt Source: https://docs.rinne.com.br/api-reference/company-banking/get-cashout-receipt /api-spec.yaml get /v1/banking/cashouts/{id}/receipt Generates a receipt for a cashout operation. Returns JSON with cashout details, account information, and a base64-encoded PDF. Only available for cashouts with status COMPLETED, PARTIALLY_RETURNED, or RETURNED. Requires the cashout to have an end-to-end ID. # List cashouts Source: https://docs.rinne.com.br/api-reference/company-banking/list-cashouts /api-spec.yaml get /v1/banking/cashouts Returns a paginated list of cashouts for the authenticated company. Supports filtering by status, method, and date range. Requires the banking.list permission. # List judicial blockages Source: https://docs.rinne.com.br/api-reference/company-banking/list-judicial-blockages /api-spec.yaml get /v1/banking/judicial-blockages Returns a paginated list of judicial block/release movements for the authenticated company's bank accounts. Supports filtering by type, bank account, process number, and occurrence date range. Requires the banking.list permission. # Get a single ledger entry by ID Source: https://docs.rinne.com.br/api-reference/company-ledger/get-a-single-ledger-entry-by-id /api-spec.yaml get /v1/ledger-entries/{ledgerEntryId} Requires the ledger.view permission. Returns a single ledger entry by its ID. # Get ledger entries Source: https://docs.rinne.com.br/api-reference/company-ledger/get-ledger-entries /api-spec.yaml get /v1/ledger-entries Requires the ledger.list permission. Returns a paginated list of ledger entries for the authenticated company. # Create a new PIX key Source: https://docs.rinne.com.br/api-reference/company-pix/create-a-new-pix-key /api-spec.yaml post /v1/pix/keys Requires the pix.create permission. Creates a new PIX key for the authenticated company. The key will be registered with the specified provider. For EVP (random) keys, the provider will generate the key value automatically. All other key types require a valid key value matching the type's format. # Delete a PIX key Source: https://docs.rinne.com.br/api-reference/company-pix/delete-a-pix-key /api-spec.yaml delete /v1/pix/keys Requires the pix.delete permission. Deletes a PIX key for the authenticated company. The key will be unregistered from the provider and marked as deleted in the system. # Get a specific PIX key by ID Source: https://docs.rinne.com.br/api-reference/company-pix/get-a-specific-pix-key-by-id /api-spec.yaml get /v1/pix/keys/{id} Requires the pix.view permission. Returns a PIX key by its ID for the authenticated company. Only returns keys that belong to the authenticated company. # List all PIX keys Source: https://docs.rinne.com.br/api-reference/company-pix/list-all-pix-keys /api-spec.yaml get /v1/pix/keys Requires the pix.list permission. Returns all undeleted PIX keys for the authenticated company. Only keys with status ACTIVE, INACTIVE, or PENDING_DELETION are returned. # Cancel existing pending refund for a transaction by ID Source: https://docs.rinne.com.br/api-reference/company-transactions/cancel-existing-pending-refund-for-a-transaction-by-id /api-spec.yaml patch /v1/transactions/{id}/refunds/cancel Requires the transaction.refund permission. Cancels an existing pending refund for a transaction by ID belonging to the authenticated company. **Important Notes:** - Only existing pending refunds with PENDING status can be cancelled. - Cancelling a refund will revert the transaction status based on remaining refunds - If no other refunds exist, transaction status reverts to APPROVED - If other completed refunds exist, transaction status becomes PARTIALLY_REFUNDED or REFUNDED accordingly # Create a new transaction Source: https://docs.rinne.com.br/api-reference/company-transactions/create-a-new-transaction /api-spec.yaml post /v1/transactions Requires the transaction.create permission. Creates a new payment transaction. For PIX, supports dynamic QR code generation and provider integration. For card payments, `require_3ds: true` can be used to force an immediate `AWAITING_3DS` status (transaction-first 3DS flow), without waiting for risk or soft-decline triggers. Use `refuse_on_challenge: true` to fail fast instead of transitioning to `AWAITING_3DS` when a challenge would be required. # Get company transactions overview Source: https://docs.rinne.com.br/api-reference/company-transactions/get-company-transactions-overview /api-spec.yaml get /v1/transactions/overview Returns an overview of transactions for the authenticated company with aggregated statistics by status and a daily breakdown of successful transactions only (APPROVED, AUTHORIZED). Requires the transaction.list permission. # Get transaction by ID Source: https://docs.rinne.com.br/api-reference/company-transactions/get-transaction-by-id /api-spec.yaml get /v1/transactions/{id} Returns a specific transaction by its ID (must belong to authenticated company). Requires the transaction.view permission. # Get transaction receipts Source: https://docs.rinne.com.br/api-reference/company-transactions/get-transaction-receipts /api-spec.yaml get /v1/transactions/{id}/receipt Requires the transaction.view permission. Generates receipts for a transaction. Returns a list of receipts including: - The original payment receipt (if transaction was approved) - Refund receipts (one per completed refund with provider identification) Each receipt includes JSON with account information and a base64-encoded PDF. **Requirements:** - Transaction status must be APPROVED, REFUNDED, or PARTIALLY_REFUNDED - Only PIX payment method transactions are currently supported - Transaction must have payer information (pix_payer) - Transaction must have an end-to-end ID for the payment receipt - Refunds must have provider_refund_identification for refund receipts # Get transactions Source: https://docs.rinne.com.br/api-reference/company-transactions/get-transactions /api-spec.yaml get /v1/transactions Returns a paginated list of transactions for the authenticated company with optional filters. Requires the transaction.list permission. # Refund a transaction Source: https://docs.rinne.com.br/api-reference/company-transactions/refund-a-transaction /api-spec.yaml post /v1/transactions/{id}/refunds Requires the transaction.refund permission. Refunds a transaction by calling the provider's refund method. **Important Notes:** - Only APPROVED or PARTIALLY_REFUNDED transactions can be refunded - BOLEPIX transactions do not support refunds - Refund amount cannot exceed the approved amount minus already refunded amount - `refund_amount` is optional. When omitted, it resolves to the transaction's original approved amount. Because the resolved amount must still satisfy the exceeds-approved guard, omission only succeeds on a transaction with no prior `COMPLETED`, `PENDING`, `PROCESSING`, or `TIMEOUT` refunds; `FAILED` and `CANCELLED` priors are ignored. To refund only the remainder of a partially refunded transaction, send `refund_amount` explicitly. - Partial refunds are supported for providers that allow them (transaction status becomes PARTIALLY_REFUNDED); providers that only accept full cancellations (e.g. Cappta) reject partial refund requests with a 400 — omit `refund_amount` or submit the full approved amount instead. - Card refunds on providers that cannot refund post-settlement (e.g. Cappta) are rejected with a 400 once the pre-settlement cancellation window has closed. The cutoff depends on payment method and anticipation: `DEBIT_CARD` and anticipated `CREDIT_CARD` settle on the next business day after approval, while non-anticipated `CREDIT_CARD` settles ~29 calendar days later (then the next business day), so its refund window stays open much longer. The window closes at 23:59 (America/Sao_Paulo) the night before that settlement day. The error message is "This transaction can no longer be refunded because settlement is already in progress." Refunds across a weekend/holiday remain allowed until that boundary. - Full refunds change status to REFUNDED - Transaction may return with PENDING_REFUND status if the provider call fails for a transient reason (e.g., timeout, insufficient balance). The refund will be retried indefinitely by background jobs until manually cancelled. # Affiliation bank account operational status changed Source: https://docs.rinne.com.br/api-reference/events/affiliation-bank-account-operational-status-changed /api-spec.yaml webhook affiliation-bank-account.operational-status-changed Triggered when the operational status of an affiliation bank account changes, for example when a court order places the account under a judicial block (`JUDICIAL_BLOCK`) or when the block is lifted (`REGULAR`). While an account is under `JUDICIAL_BLOCK`, its balance is restricted by the banking provider. The payload carries the full bank account object as it stood when the event was emitted, so no follow-up request is needed. # Affiliation created Source: https://docs.rinne.com.br/api-reference/events/affiliation-created /api-spec.yaml webhook affiliation.created Triggered when a new affiliation is created in the system. This event is sent immediately after an affiliation is successfully created via API. # Affiliation status changed Source: https://docs.rinne.com.br/api-reference/events/affiliation-status-changed /api-spec.yaml webhook affiliation.status-changed Triggered when an affiliation status changes (e.g., from PENDING_APPROVAL to ACTIVE). This event is sent when: - Provider approves/rejects an affiliation - Status changes due to compliance issues - Manual status updates by administrators # Cashout created Source: https://docs.rinne.com.br/api-reference/events/cashout-created /api-spec.yaml webhook cashout.created Triggered when a new cashout request is created in the system. This event is sent immediately after a cashout request is successfully created via API. # Cashout status changed Source: https://docs.rinne.com.br/api-reference/events/cashout-status-changed /api-spec.yaml webhook cashout.status-changed Triggered when a cashout status changes throughout its lifecycle. This event is sent when: - Cashout is processed by provider (PENDING → PROCESSING) - Cashout is completed (PROCESSING → COMPLETED) - Cashout fails (PROCESSING → FAILED) - Other status transitions occur # Company created Source: https://docs.rinne.com.br/api-reference/events/company-created /api-spec.yaml webhook company.created Triggered when a new company is created in the system. This event is sent immediately after a company is successfully created via API. # Company status changed Source: https://docs.rinne.com.br/api-reference/events/company-status-changed /api-spec.yaml webhook company.status-changed Triggered when a company status changes throughout its lifecycle. This event is sent when: - Company is activated (PENDING_ACTIVATION → ACTIVE) - Company is suspended (ACTIVE → SUSPENDED) - Company is deactivated (ACTIVE → INACTIVE) - Other status transitions occur # Dispute created Source: https://docs.rinne.com.br/api-reference/events/dispute-created /api-spec.yaml webhook dispute.created Triggered when a dispute is opened against one of the organization's merchants, from a provider notification. Rinne is never the claimant — it ingests the provider's report and relays it. Event names describe Rinne's own lifecycle rather than a provider's vocabulary, so an organization integrates once regardless of which rail a dispute came from. The payload carries the full dispute object as it stood when the event was emitted, so no follow-up request is needed. # Dispute status changed Source: https://docs.rinne.com.br/api-reference/events/dispute-status-changed /api-spec.yaml webhook dispute.status-changed Triggered when a dispute moves forward — `OPEN` to `UNDER_REVIEW`, or either of those to `CLOSED` with a verdict in `outcome`. A dispute only moves forward, so `old_status` is always strictly earlier than `new_status` and this event never reports a regression. Stale or duplicate provider deliveries produce no event at all. # Internal transfer created Source: https://docs.rinne.com.br/api-reference/events/internal-transfer-created /api-spec.yaml webhook internal-transfer.created Triggered when a new internal transfer is created in the system. This event is sent immediately after an internal transfer is successfully created via API. # Internal transfer status changed Source: https://docs.rinne.com.br/api-reference/events/internal-transfer-status-changed /api-spec.yaml webhook internal-transfer.status-changed Triggered when an internal transfer status changes throughout its lifecycle. This event is sent when: - Transfer is processed by provider (PENDING → PROCESSING) - Transfer is completed (PROCESSING → COMPLETED) - Transfer fails (PROCESSING → FAILED) - Other status transitions occur # Judicial blockage created Source: https://docs.rinne.com.br/api-reference/events/judicial-blockage-created /api-spec.yaml webhook judicial-blockage.created Triggered when a judicial movement is recorded against an affiliation bank account. `type` distinguishes a blocked amount (`BLOCK`) from a released one (`RELEASE`), so both directions of a court-ordered movement arrive on this event. The payload carries the full judicial blockage object as it stood when the event was emitted, so no follow-up request is needed. # Kyc completed Source: https://docs.rinne.com.br/api-reference/events/kyc-completed /api-spec.yaml webhook kyc.completed Triggered when a KYC (Know Your Customer) check completes for a merchant company. This event is sent after all configured KYC rules have been evaluated for a newly created or updated merchant. The parent organization receives this webhook automatically via Svix. The `rule_executions` array contains one entry per rule defined in the active KYC policy. Each entry reports whether the rule passed, found issues, or failed, the `expected` field values required to pass, the `received` field values actually observed, and a human-readable `description` when it did not pass. # Ledger entry settled Source: https://docs.rinne.com.br/api-reference/events/ledger-entry-settled /api-spec.yaml webhook ledger-entry.settled Triggered when a ledger entry transitions from settled=false to settled=true. # Transaction created Source: https://docs.rinne.com.br/api-reference/events/transaction-created /api-spec.yaml webhook transaction.created Triggered when a new transaction is created in the system. This event is sent immediately after a transaction is successfully created via API. # Transaction status changed Source: https://docs.rinne.com.br/api-reference/events/transaction-status-changed /api-spec.yaml webhook transaction.status-changed Triggered when a transaction status changes throughout its lifecycle. This event is sent when: - Payment is processed by provider (PROCESSING → APPROVED/REFUSED) - Provider HTTP call fails (PROCESSING → FAILED) - Transaction is refunded (APPROVED → REFUNDED/PARTIALLY_REFUNDED) - Transaction expires (WAITING_PAYMENT → EXPIRED) - Transaction cancellation is requested (WAITING_PAYMENT → PENDING_CANCELLATION) - Transaction is cancelled (PENDING_CANCELLATION → CANCELLED) - Transaction requires 3DS authentication (PROCESSING → AWAITING_3DS) - Transaction is authenticated via 3DS (AWAITING_3DS → PROCESSING → APPROVED/REFUSED) - Other status transitions occur # API Reference Source: https://docs.rinne.com.br/api-reference/introduction Complete reference for the Rinne API The Rinne API is organized around REST principles. It uses standard HTTP methods, authentication, and status codes. All requests and responses use JSON format. ## Base URLs ```bash Sandbox theme={null} https://api-sandbox.rinne.com.br/core ``` ```bash Production theme={null} https://api.rinne.com.br/core ``` ## Authentication The API uses API key authentication via the `x-api-key` header: ```bash theme={null} curl https://api-sandbox.rinne.com.br/core/v1/companies/me \ -H "x-api-key: YOUR_API_KEY" ``` Some endpoints also support JWT authentication via the `Authorization` header: ```bash theme={null} curl https://api-sandbox.rinne.com.br/core/v1/auth/me \ -H "Authorization: Bearer JWT_TOKEN" ``` See the [Authentication guide](/guides/authentication) for details. ## Permissions All authenticated requests are subject to permission checks. Each endpoint requires specific permissions in the format `resource[.subresource].action` (e.g., `transaction.list`, `merchant.company.create`). * **API key auth**: Permissions are validated against the key's own permission set, configured at creation time * **JWT auth**: Permissions are validated against the user's roles API keys are created with specific permissions (defaulting to `*.*` if none are specified). See the [Authentication guide](/guides/authentication#managing-api-keys) for details on creating keys with scoped permissions. Wildcard permissions grant broader access: * `resource.*`: All actions for a resource * `merchant.*`: All merchant-scoped operations * `*.*`: All permissions See the [User management guide](/guides/user-management#roles-and-permissions) for the full permission list. ## Request format All POST, PATCH, and PUT requests must include a `Content-Type: application/json` header and a JSON body: ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/merchants \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "full_name": "Store Name Ltda.", "document_number": "16525269000121", ... }' ``` ## Response format ### Success responses Successful requests return the requested data directly: ```json theme={null} { "id": "merchant-123", "name": "My Store", "status": "ACTIVE", ... } ``` ### Error responses Errors return an `error` object with detailed information: ```json theme={null} { "error": { "code": "VALIDATION_ERROR", "message": "Validation error", "status": 400, "details": { "issues": [...] } } } ``` See the [Error handling guide](/guides/error-handling) for details. ## Pagination All list endpoints return paginated responses with a unified structure. Use query parameters to control pagination: | Parameter | Type | Default | Description | | --------- | ------- | ------- | ----------------------------- | | `page` | integer | 1 | Page number to retrieve | | `limit` | integer | 20 | Items per page (maximum: 100) | ```bash theme={null} curl "https://api-sandbox.rinne.com.br/core/v1/merchants?page=2&limit=10" \ -H "x-api-key: YOUR_API_KEY" ``` Every paginated response wraps the results in a `data` array and includes a `pagination` object: ```json theme={null} { "data": [...], "pagination": { "page": 2, "limit": 10, "total": 50, "totalPages": 5, "hasNext": true, "hasPrev": true } } ``` ### Pagination fields | Field | Type | Description | | ------------ | ------- | ---------------------------------------- | | `page` | integer | Current page number | | `limit` | integer | Number of items per page | | `total` | integer | Total number of items matching the query | | `totalPages` | integer | Total number of pages | | `hasNext` | boolean | Whether a next page exists | | `hasPrev` | boolean | Whether a previous page exists | ## Filtering and sorting Many list endpoints support filtering and sorting: ### Filtering ```bash theme={null} # Filter by status GET /v1/merchants?status=ACTIVE,INACTIVE # Filter by date range GET /v1/transactions?created_at_from=2025-01-01T00:00:00Z&created_at_to=2025-01-31T23:59:59Z # Filter by amount range GET /v1/transactions?amount_from=5000&amount_to=20000 ``` ### Sorting Use the `sort` parameter with field names. Prefix with `-` for descending order: ```bash theme={null} # Sort by creation date (newest first) GET /v1/merchants?sort=-created_at # Multiple sort fields GET /v1/transactions?sort=-created_at,amount ``` ## Idempotency Use the `request_id` field to make requests idempotent: ```json theme={null} { "request_id": "unique-request-123", "amount": 10000, ... } ``` If you retry a request with the same `request_id`, you'll receive the existing resource instead of creating a duplicate. ## Amounts in cents All monetary values are represented as integers in cents: * `10000` = R\$ 100.00 * `1050` = R\$ 10.50 * `99` = R\$ 0.99 Never use decimal values or floating-point numbers for amounts. ## Date and time format All timestamps use ISO 8601 format with UTC timezone: ``` 2025-01-21T10:30:00.000Z ``` ## Supported providers The API supports multiple payment providers: * `CELCOIN`: PIX integration and banking services * `RINNE`: Internal provider Provider names are case-insensitive in requests but always returned in uppercase. ## Rate limiting The API enforces two request rate limits, each applied as a shared pool across all matching endpoints rather than per-endpoint: * **1000 req/s** for every `POST` request for transaction creation and 3DS session creation and authentication. * **50 req/s** for all other requests. Limits behave identically across all environments, including sandbox. Requests within the limit always succeed; only the excess requests above the limit are rejected with a `429` status code, with no account block or penalty. See the [Rate limits guide](/guides/rate-limits) for full details. ## Webhooks Rinne sends webhooks for real-time event notifications. See the [Webhooks guide](/guides/webhooks) for setup and handling. ## API versioning The current API version is `v1`, indicated in the URL path: ``` /v1/transactions /v1/merchants ``` Future versions will be released as `v2`, `v3`, etc. Version 1 will remain supported for backward compatibility. ## Next steps Implement credit and debit card API flows Process Apple Pay and Google Pay via backend APIs Implement session-first and transaction-first 3DS flows Build QR code and due date payment flows # Get a single ledger entry by ID for organization Source: https://docs.rinne.com.br/api-reference/ledger/get-a-single-ledger-entry-by-id-for-organization /api-spec.yaml get /v1/merchants/ledger-entries/{ledgerEntryId} Requires the merchant.ledger.view permission. Returns a single ledger entry by its ID. # Get ledger entries for a specific merchant Source: https://docs.rinne.com.br/api-reference/ledger/get-ledger-entries-for-a-specific-merchant /api-spec.yaml get /v1/merchants/{merchantId}/ledger-entries Requires the merchant.ledger.list permission. Returns a paginated list of ledger entries for a specific merchant that belongs to the authenticated organization. # Get ledger entries for all merchants Source: https://docs.rinne.com.br/api-reference/ledger/get-ledger-entries-for-all-merchants /api-spec.yaml get /v1/merchants/ledger-entries Requires the merchant.ledger.list permission. Returns a paginated list of ledger entries for all merchants that belong to the authenticated organization. # Create new merchant Source: https://docs.rinne.com.br/api-reference/management/create-new-merchant /api-spec.yaml post /v1/merchants Requires the merchant.company.create permission. Creates a new merchant company under the authenticated organization. The organization_id is automatically set from the authenticated organization. Only organizations may create merchants. A merchant-scoped session is rejected with 403. # Get merchant by ID Source: https://docs.rinne.com.br/api-reference/management/get-merchant-by-id /api-spec.yaml get /v1/merchants/{merchantId} Requires the merchant.company.view permission. Returns a specific merchant by its ID. The merchant must belong to the authenticated organization. # Get merchant overview Source: https://docs.rinne.com.br/api-reference/management/get-merchant-overview /api-spec.yaml get /v1/merchants/overview Requires the merchant.company.list permission. Returns an overview of merchants for the authenticated company # Get public settings for a merchant Source: https://docs.rinne.com.br/api-reference/management/get-public-settings-for-a-merchant /api-spec.yaml get /v1/merchants/{merchantId}/public-settings Returns public settings for a merchant including rinne-js configuration and available payment methods. This is a public endpoint that does not require authentication. The available payment methods are collected from all active and enabled affiliations for the merchant. # List merchants Source: https://docs.rinne.com.br/api-reference/management/list-merchants /api-spec.yaml get /v1/merchants Requires the merchant.company.list permission. Returns a paginated list of merchants belonging to the authenticated organization with optional filters # Update merchant Source: https://docs.rinne.com.br/api-reference/management/update-merchant /api-spec.yaml patch /v1/merchants/{merchantId} Requires the merchant.company.edit permission. Updates a specific merchant. The merchant must belong to the authenticated organization. Only specific fields can be updated via API for security reasons. Note that merchant status, document fields, and organization relationship cannot be modified through the API. # Get all available permissions Source: https://docs.rinne.com.br/api-reference/permissions/get-all-available-permissions /api-spec.yaml get /v1/permissions Returns a list of all permissions available in the system with their description keys # Create a new PIX key for a specific merchant Source: https://docs.rinne.com.br/api-reference/pix-keys/create-a-new-pix-key-for-a-specific-merchant /api-spec.yaml post /v1/merchants/{merchantId}/pix/keys Requires the merchant.pix.create permission. Creates a new PIX key for a merchant that belongs to the authenticated organization. The key will be registered with the specified provider. For EVP (random) keys, the provider will generate the key value automatically. # Delete a PIX key for a specific merchant Source: https://docs.rinne.com.br/api-reference/pix-keys/delete-a-pix-key-for-a-specific-merchant /api-spec.yaml delete /v1/merchants/{merchantId}/pix/keys Requires the merchant.pix.delete permission. Deletes a PIX key for a merchant that belongs to the authenticated organization. The key will be unregistered from the provider and marked as deleted in the system. # List all PIX keys for a specific merchant Source: https://docs.rinne.com.br/api-reference/pix-keys/list-all-pix-keys-for-a-specific-merchant /api-spec.yaml get /v1/merchants/{merchantId}/pix/keys Requires the merchant.pix.list permission. Returns all undeleted PIX keys for a merchant that belongs to the authenticated organization. Only keys with status ACTIVE, INACTIVE, or PENDING_DELETION are returned. # Create a new fee policy Source: https://docs.rinne.com.br/api-reference/pricing/create-a-new-fee-policy /api-spec.yaml post /v1/pricing/fee-policies Requires the fee_policy.create permission. Creates a new fee policy for the authenticated company with pricing rules # List cost policies Source: https://docs.rinne.com.br/api-reference/pricing/list-cost-policies /api-spec.yaml get /v1/pricing/cost-policies Requires the cost_policy.list permission. Get a list of cost policies with optional filters and pagination # List fee policies Source: https://docs.rinne.com.br/api-reference/pricing/list-fee-policies /api-spec.yaml get /v1/pricing/fee-policies Requires the fee_policy.list permission. Get a list of fee policies with optional filters and pagination # Replace a fee policy Source: https://docs.rinne.com.br/api-reference/pricing/replace-a-fee-policy /api-spec.yaml put /v1/pricing/fee-policies/{id} Replace an existing fee policy for the authenticated company. The final state of the policy will match exactly what is provided in the request. **Concurrency Protection:** - Uses stronger isolation to prevent race conditions - If concurrent modifications occur, one request will fail with serialization error - Clients can retry on failure **Rule Replacement Behavior:** - Rules WITH IDs: Updated in place (ID must exist in the policy, returns 400 if not) - Rules WITHOUT IDs: Created as new rules - Existing rules NOT in the payload: Deleted **Key Difference from PATCH:** - PATCH preserves rules not mentioned in the request - PUT removes rules not included in the request To keep an existing rule, include it in the request with its ID. To remove a rule, simply omit it from the request. To add a new rule, include it without an ID. # Update a fee policy Source: https://docs.rinne.com.br/api-reference/pricing/update-a-fee-policy /api-spec.yaml patch /v1/pricing/fee-policies/{id} Requires the fee_policy.edit permission. Update an existing fee policy for the authenticated company. All fields are optional, allowing partial updates. The fee policy must belong to the authenticated company. **Rule Update Behavior:** - Rules with IDs: Updates those specific rules while preserving unmentioned rules - Rules without IDs: Creates new rules and adds them to the policy - Rules not mentioned: Remain unchanged and preserved - When updating a rule's conditions, conditions for that rule are replaced (not merged) # Create a new role Source: https://docs.rinne.com.br/api-reference/roles/create-a-new-role /api-spec.yaml post /v1/roles Creates a new role with the provided name, permissions, and optional description. One of `company_id` or `organization_id` is required: a role must be scoped to a company or an organization, and the referenced company must be the authenticated company or one of its merchants; any other company is reported as not found. # Delete role Source: https://docs.rinne.com.br/api-reference/roles/delete-role /api-spec.yaml delete /v1/roles/{id} Deletes a specific role # Get role by ID Source: https://docs.rinne.com.br/api-reference/roles/get-role-by-id /api-spec.yaml get /v1/roles/{id} Returns a specific role by its ID. Requires the role.list permission. # List roles Source: https://docs.rinne.com.br/api-reference/roles/list-roles /api-spec.yaml get /v1/roles Returns a paginated list of roles with optional filters. Requires the role.list permission. # Update role Source: https://docs.rinne.com.br/api-reference/roles/update-role /api-spec.yaml put /v1/roles/{id} Updates a specific role with provided fields. Requires the role.edit permission. # API health status Source: https://docs.rinne.com.br/api-reference/system/api-health-status /api-spec.yaml get /health Returns the API health status, database, and payment providers # Cancel a BOLEPIX transaction Source: https://docs.rinne.com.br/api-reference/transactions/cancel-a-bolepix-transaction /api-spec.yaml post /v1/transactions/{id}/cancel Cancel a BOLEPIX transaction that is in WAITING_PAYMENT status. Only BOLEPIX payment method transactions can be cancelled. Requires the transaction.cancel permission. **Non-terminal cancellation:** The cancellation may be non-terminal. If the provider times out, the transaction is set to `PENDING_CANCELLATION`. Final reconciliation (PENDING_CANCELLATION → CANCELLED) is performed asynchronously when the provider sends the `bolepix.cancelled` webhook. Consumers should poll the transaction or subscribe to the `transaction.status-changed` webhook rather than assuming immediate finality. # Cancel a merchant's BOLEPIX transaction Source: https://docs.rinne.com.br/api-reference/transactions/cancel-a-merchants-bolepix-transaction /api-spec.yaml post /v1/merchants/{merchantId}/transactions/{transactionId}/cancel Cancel a BOLEPIX transaction for a specific merchant that is in WAITING_PAYMENT status. Only BOLEPIX payment method transactions can be cancelled. Requires the merchant.transaction.cancel permission. **Non-terminal cancellation:** The cancellation may be non-terminal. If the provider times out, the transaction is set to `PENDING_CANCELLATION`. Final reconciliation (PENDING_CANCELLATION → CANCELLED) is performed asynchronously when the provider sends the `bolepix.cancelled` webhook. Consumers should poll the transaction or subscribe to the `transaction.status-changed` webhook rather than assuming immediate finality. # Cancel existing pending refund for a merchant transaction by ID Source: https://docs.rinne.com.br/api-reference/transactions/cancel-existing-pending-refund-for-a-merchant-transaction-by-id /api-spec.yaml patch /v1/merchants/{merchantId}/transactions/{transactionId}/refunds/cancel Requires the merchant.transaction.refund permission. Cancels an existing pending refund for a transaction by ID belonging to a merchant that is part of the authenticated organization. **Important Notes:** - Only existing pending refunds with PENDING status can be cancelled. - Cancelling a refund will revert the transaction status based on remaining refunds - If no other refunds exist, transaction status reverts to APPROVED - If other completed refunds exist, transaction status becomes PARTIALLY_REFUNDED or REFUNDED accordingly # Create a new transaction for a specific merchant Source: https://docs.rinne.com.br/api-reference/transactions/create-a-new-transaction-for-a-specific-merchant /api-spec.yaml post /v1/merchants/{merchantId}/transactions Requires the merchant.transaction.create permission. Creates a new transaction for a merchant that belongs to the authenticated organization # Get a specific transaction by ID across all merchants Source: https://docs.rinne.com.br/api-reference/transactions/get-a-specific-transaction-by-id-across-all-merchants /api-spec.yaml get /v1/merchants/transactions/{transactionId} Requires the merchant.transaction.view permission. Retrieves detailed information about a specific transaction by its ID, without requiring the merchant ID. The transaction must belong to a merchant that is part of the authenticated organization. Returns full transaction details including ledger entries, refunds, and settlement information. # Get a specific transaction for a merchant Source: https://docs.rinne.com.br/api-reference/transactions/get-a-specific-transaction-for-a-merchant /api-spec.yaml get /v1/merchants/{merchantId}/transactions/{transactionId} Requires the merchant.transaction.view permission. Retrieves detailed information about a specific transaction for a merchant that belongs to the authenticated organization. This endpoint returns the full transaction details including ledger entries, which provides comprehensive information about the transaction's financial impact and settlement status. # Get merchant transactions overview Source: https://docs.rinne.com.br/api-reference/transactions/get-merchant-transactions-overview /api-spec.yaml get /v1/merchants/transactions/overview Requires the merchant.transaction.list permission. Returns an overview of transactions for the authenticated company with aggregated statistics by status and a daily breakdown of successful transactions only (APPROVED, AUTHORIZED) # Get transaction receipts for a specific merchant Source: https://docs.rinne.com.br/api-reference/transactions/get-transaction-receipts-for-a-specific-merchant /api-spec.yaml get /v1/merchants/{merchantId}/transactions/{transactionId}/receipt Requires the merchant.transaction.view permission. Generates receipts for a specific merchant's transaction. Only accessible by the organization that owns the merchant. Returns a list of receipts including: - The original payment receipt (if transaction was approved) - Refund receipts (one per completed refund with provider identification) Each receipt includes JSON with account information and a base64-encoded PDF. **Requirements:** - Transaction status must be APPROVED, REFUNDED, or PARTIALLY_REFUNDED - Only PIX payment method transactions are currently supported - Transaction must have payer information (pix_payer) - Transaction must have an end-to-end ID for the payment receipt - Refunds must have provider_refund_identification for refund receipts # Get transactions for all merchants Source: https://docs.rinne.com.br/api-reference/transactions/get-transactions-for-all-merchants /api-spec.yaml get /v1/merchants/transactions Requires the merchant.transaction.list permission. Returns a paginated list of transactions for all merchants that belong to the authenticated organization # Pay organization's own PIX or BOLEPIX transaction via QR code (Sandbox Only) Source: https://docs.rinne.com.br/api-reference/transactions/pay-organizations-own-pix-or-bolepix-transaction-via-qr-code-sandbox-only /api-spec.yaml post /v1/transactions/{transactionId}/pay Processes a PIX payment using a QR code from a transaction owned by the authenticated organization company, for testing purposes. **This endpoint is only available in sandbox environment.** Unlike the merchant variant (`/v1/merchants/{merchantId}/transactions/{transactionId}/pay`), this endpoint does not require a `merchantId` parameter. The transaction is looked up using the authenticated organization's company ID as the transaction owner. The `payer_company_id` must be different from the transaction owner (the authenticated company). **Supported payment methods:** - **PIX:** Uses the QR code from `pix_data.qr_code` - **BOLEPIX:** Uses the PIX EMV code from `bolepix_data.pix_emv` # Pay PIX or BOLEPIX transaction via QR code (Sandbox Only) Source: https://docs.rinne.com.br/api-reference/transactions/pay-pix-or-bolepix-transaction-via-qr-code-sandbox-only /api-spec.yaml post /v1/merchants/{merchantId}/transactions/{transactionId}/pay Processes a PIX payment using a QR code from an existing transaction for testing purposes. **This endpoint is only available in sandbox environment.** This endpoint allows developers to test PIX payments by paying existing transactions that have PIX QR codes. The QR code is automatically retrieved from the transaction. **Supported payment methods:** - **PIX:** Uses the QR code from `pix_data.qr_code` - **BOLEPIX:** Uses the PIX EMV code from `bolepix_data.pix_emv` The endpoint automatically detects the payment method based on the available QR code data. # Refund a transaction for a specific merchant Source: https://docs.rinne.com.br/api-reference/transactions/refund-a-transaction-for-a-specific-merchant /api-spec.yaml post /v1/merchants/{merchantId}/transactions/{transactionId}/refunds Requires the merchant.transaction.refund permission. Refunds a transaction for a merchant that belongs to the authenticated organization. **Important Notes:** - Only APPROVED or PARTIALLY_REFUNDED transactions can be refunded - BOLEPIX transactions do not support refunds - Refund amount cannot exceed the approved amount minus already refunded amount - `refund_amount` is optional. When omitted, it resolves to the transaction's original approved amount. Because the resolved amount must still satisfy the exceeds-approved guard, omission only succeeds on a transaction with no prior `COMPLETED`, `PENDING`, `PROCESSING`, or `TIMEOUT` refunds; `FAILED` and `CANCELLED` priors are ignored. To refund only the remainder of a partially refunded transaction, send `refund_amount` explicitly. - Partial refunds are supported for providers that allow them (transaction status becomes PARTIALLY_REFUNDED); providers that only accept full cancellations (e.g. Cappta) reject partial refund requests with a 400 — omit `refund_amount` or submit the full approved amount instead. - Card refunds on providers that cannot refund post-settlement (e.g. Cappta) are rejected with a 400 once the pre-settlement cancellation window has closed. The cutoff depends on payment method and anticipation: `DEBIT_CARD` and anticipated `CREDIT_CARD` settle on the next business day after approval, while non-anticipated `CREDIT_CARD` settles ~29 calendar days later (then the next business day), so its refund window stays open much longer. The window closes at 23:59 (America/Sao_Paulo) the night before that settlement day. The error message is "This transaction can no longer be refunded because settlement is already in progress." Refunds across a weekend/holiday remain allowed until that boundary. - Full refunds change status to REFUNDED - Transaction may return with PENDING_REFUND status if the provider call fails for a transient reason (e.g., timeout, insufficient balance). The refund will be retried indefinitely by background jobs until manually cancelled. # Activate user Source: https://docs.rinne.com.br/api-reference/users/activate-user /api-spec.yaml post /v1/users/{userId}/activate Activates a user. Requires the user.edit permission. # Create a new user Source: https://docs.rinne.com.br/api-reference/users/create-a-new-user /api-spec.yaml post /v1/users Creates a new user with the provided identifiers and authentication methods. All identifiers start as unverified - user must complete verification flow. This endpoint requires authentication and the user.create permission. # Get user by ID Source: https://docs.rinne.com.br/api-reference/users/get-user-by-id /api-spec.yaml get /v1/users/{userId} Retrieves detailed information about a specific user by their ID. The user must hold a role in the caller's company; otherwise the request returns 404. Requires the user.view permission. # List users Source: https://docs.rinne.com.br/api-reference/users/list-users /api-spec.yaml get /v1/users Returns a paginated, organization-scoped list of users. Supports sorting by creation time. # Suspend user Source: https://docs.rinne.com.br/api-reference/users/suspend-user /api-spec.yaml post /v1/users/{userId}/suspend Suspends a user. Requires the user.edit permission. # Update user information Source: https://docs.rinne.com.br/api-reference/users/update-user-information /api-spec.yaml patch /v1/users/{userId} Updates user information such as first name, last name, roles and metadata. Requires the user.edit permission. # Get webhook dashboard URL Source: https://docs.rinne.com.br/api-reference/webhooks/get-webhook-dashboard-url /api-spec.yaml get /v1/webhooks/dashboard Requires the webhook.list permission. Gets the webhook dashboard URL for the authenticated company # Get zipcode information Source: https://docs.rinne.com.br/api-reference/zipcode/get-zipcode-information /api-spec.yaml get /v1/zipcodes/{id} Retrieves address information for a given zipcode using external service # Affiliations Source: https://docs.rinne.com.br/concepts/affiliations Connect merchants to payment providers for transaction processing Affiliations represent the connection between a merchant and a payment provider. Before processing transactions, merchants must have an active affiliation with a provider that supports their desired payment methods. ## What is an affiliation? An affiliation is a merchant account with a payment provider (such as Celcoin, Rinne, or Cappta) that enables transaction processing. Each affiliation: * Links a merchant to a specific provider * Defines allowed payment methods (PIX, credit card, etc.) * Defines allowed capture methods (e-commerce, POS, etc.) * Stores provider-specific merchant credentials * Tracks activation status and onboarding progress ### Entity relationships ```mermaid theme={null} graph LR M[Merchant] -->|has many| A[Affiliations] A -->|connects to| P[Provider
Celcoin, Rinne, Cappta] A -->|enables| T[Transactions] A -->|linked to| ABA[Affiliation
Bank Account] A -->|linked to| SBA[Settlement
Bank Account] A -->|creates| PK[PIX Keys] style M fill:#7B89FF,color:#fff style A fill:#5B68EB,color:#fff style P fill:#4A56D9,color:#fff style T fill:#7B89FF,color:#fff style ABA fill:#5B68EB,color:#fff style SBA fill:#5B68EB,color:#fff style PK fill:#7B89FF,color:#fff ``` ## Affiliation lifecycle The following diagram shows the complete affiliation status flow: ```mermaid theme={null} stateDiagram-v2 [*] --> PENDING: Create affiliation PENDING --> PENDING_APPROVAL: Submit to provider PENDING_APPROVAL --> ACTIVE: Provider approves PENDING_APPROVAL --> WAITING_DOCUMENTS: Documents required PENDING_APPROVAL --> REJECTED: Provider rejects PENDING_APPROVAL --> INVALID_SETTLEMENT_BANK_ACCOUNT: Settlement bank invalid WAITING_DOCUMENTS --> PROCESSING_DOCUMENTS: Submit documents PROCESSING_DOCUMENTS --> ACTIVE: Documents approved PROCESSING_DOCUMENTS --> REJECTED: Documents rejected ACTIVE --> BLOCKED: Provider blocks BLOCKED --> ACTIVE: Provider unblocks PENDING_APPROVAL --> FAILED: Technical error INVALID_SETTLEMENT_BANK_ACCOUNT --> ACTIVE: Settlement validated; status reconciled REJECTED --> [*] FAILED --> [*] ``` ### 1. Creation When you create an affiliation, it starts with status `PENDING_APPROVAL`: ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/affiliations \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "provider": "CELCOIN", "allowed_capture_methods": ["ECOMMERCE"], "allowed_payment_methods": ["PIX"], "anticipation_type": "AUTOMATIC", "anticipation_days": 1 }' ``` ### 2. Provider approval The provider reviews the merchant information and either: * **Approves**: Status changes to `ACTIVE` * **Requests documents**: Status changes to `WAITING_DOCUMENTS` * **Rejects**: Status changes to `REJECTED` You'll receive webhook notifications for status changes. ### 3. Activation Once approved, the affiliation becomes `ACTIVE` and you can: * Process transactions using this provider * Receive payments to the affiliated bank account * Access provider-specific features ## Affiliation status | Status | Description | | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `PENDING` | Initial creation, awaiting submission | | `PENDING_APPROVAL` | Submitted to provider, awaiting review | | `WAITING_DOCUMENTS` | Provider requires additional documentation | | `PROCESSING_DOCUMENTS` | Provider is reviewing submitted documents | | `ACTIVE` | Approved and ready for transactions | | `BLOCKED` | Temporarily blocked by provider | | `REJECTED` | Provider rejected the affiliation | | `INVALID_SETTLEMENT_BANK_ACCOUNT` | Provider rejected or could not validate the settlement bank account—not terminal. Submit a replacement via [settlement bank account update](#updating-the-settlement-bank-account); when async validation succeeds, affiliation status is reconciled (often back to `ACTIVE`) | | `FAILED` | Technical failure during creation | ## Payment and capture methods ### Payment methods Specify which payment types the merchant can accept: * `PIX`: Instant payments via Brazil's PIX system * `CREDIT_CARD`: Credit card payments * `DEBIT_CARD`: Debit card payments * `BOLEPIX`: Hybrid boleto + PIX payments ### Capture methods Specify how payments are captured: * `ECOMMERCE`: Online transactions * `EMV`: Chip card (POS terminal) * `MAGSTRIPE`: Magnetic stripe (POS terminal) * `CONTACTLESS_ICC`: Contactless payments (NFC) The provider must support your selected payment and capture methods. Check provider capabilities before creating affiliations. ## Anticipation configuration Affiliations support anticipation configuration to control how and when merchants receive their funds from credit card transactions. Anticipation settings only affect **credit card transactions**. Other payment methods like PIX have their own settlement rules. ### Anticipation type The `anticipation_type` field determines the settlement strategy: | Type | Description | | ----------- | ------------------------------------------------------------------------------------------------------------------------------- | | `AUTOMATIC` | Credit card funds are automatically settled on a recurring schedule based on `anticipation_days`. This is the default behavior. | | `SPOT` | Credit card funds are settled on-demand or at specific intervals defined by your agreement. | ### Anticipation days The `anticipation_days` field specifies the number of days for the credit card anticipation cycle: * **Minimum value**: 1 day * **Default value**: 1 day (if not specified) Anticipation configuration affects when merchants receive their settled funds from credit card sales. Work with your account manager to determine the best configuration for your business needs. ### Example with anticipation settings ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/affiliations \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "provider": "CELCOIN", "allowed_capture_methods": ["ECOMMERCE"], "allowed_payment_methods": ["CREDIT_CARD", "DEBIT_CARD"], "anticipation_type": "AUTOMATIC", "anticipation_days": 1 }' ``` ### Updating the anticipation configuration After an affiliation exists, you can change its anticipation type and/or days without recreating it. The update is **synchronous** — by the time the request returns, the change has been applied at the provider and persisted on the affiliation. This operation is supported today only for affiliations created with the **`CAPPTA`** provider. Affiliations with other providers return a `400`. **Permissions** * Organization (company) affiliation: `affiliation.edit` * Merchant-scoped affiliation: `merchant.affiliation.edit` **Preconditions** * Affiliation `status` must be `ACTIVE` and `enabled` must be `true`. * When `anticipation_type` is `AUTOMATIC`, `anticipation_days` must be `1` — only D+1 automatic anticipation is supported by the provider. `SPOT` accepts any `anticipation_days >= 1`. #### Request fields Both fields are optional, and **at least one must be provided**. Any field you omit keeps its current value. New anticipation type for the affiliation. One of: `AUTOMATIC`, `SPOT`. New number of days for the anticipation cycle. Must be at least `1`. When `anticipation_type` is `AUTOMATIC`, this must be `1`. #### Request examples **Merchant context** (organization acting on a merchant): ```bash cURL (merchant-scoped) theme={null} curl -X PATCH "https://api-sandbox.rinne.com.br/core/v1/merchants/b4ece1d3-0a8a-5097-9dbb-58880c4f3894/affiliations/a3dbd0c2-9f79-4f86-8caa-47779b3f2794" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "anticipation_type": "SPOT", "anticipation_days": 30 }' ``` **Organization (own company) affiliation**: ```bash cURL (organization-scoped) theme={null} curl -X PATCH "https://api-sandbox.rinne.com.br/core/v1/affiliations/a3dbd0c2-9f79-4f86-8caa-47779b3f2794" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "anticipation_type": "AUTOMATIC", "anticipation_days": 1 }' ``` #### Success response (200 OK) Returns the updated affiliation with the new `anticipation_type` and `anticipation_days`: ```json 200 OK theme={null} { "id": "a3dbd0c2-9f79-4f86-8caa-47779b3f2794", "provider": "CAPPTA", "merchant_id": "12345678", "status": "ACTIVE", "enabled": true, "company_id": "b4ece1d3-0a8a-5097-9dbb-58880c4f3894", "organization_id": "c5fdf2e4-1b9b-6108-aece-69991d5e4905", "allowed_capture_methods": ["ECOMMERCE"], "allowed_payment_methods": ["CREDIT_CARD", "DEBIT_CARD"], "anticipation_type": "SPOT", "anticipation_days": 30, "bank_account": null, "settlement_bank_accounts": [ { "id": "e7afa4c6-3d1d-8320-cbec-81113f7b6127", "branch_number": "1234", "account_number": "567890", "account_type": "CHECKING", "account_holder_name": "My Store LTDA", "account_holder_document_number": "12345678000190", "ispb": "00000000", "status": "ACTIVE", "created_at": "2026-03-01T10:00:00Z", "updated_at": "2026-04-15T14:30:00Z" } ], "onboarding_documents": [], "created_at": "2026-03-01T10:00:00Z", "updated_at": "2026-05-18T14:30:00Z" } ``` If the requested `anticipation_type` and `anticipation_days` already match the affiliation's current values, the call is a **no-op** — the response still returns `200` with the affiliation unchanged. #### Error responses **Provider does not support anticipation updates** (any provider other than `CAPPTA`): ```json 400 Bad Request theme={null} { "error": { "code": "INVALID_STATE_ERROR", "message": "Provider CELCOIN does not support anticipation config updates", "status": 400 } } ``` **Affiliation is not active or not enabled**: ```json 400 Bad Request theme={null} { "error": { "code": "INVALID_STATE_ERROR", "message": "Affiliation must be active and enabled", "status": 400 } } ``` **`AUTOMATIC` requested with `anticipation_days` other than `1`**: ```json 400 Bad Request theme={null} { "error": { "code": "VALIDATION_ERROR", "message": "Only D+1 automatic anticipation is supported for Cappta affiliations", "status": 400 } } ``` ## Company snapshot When an affiliation is created, Rinne captures an immutable point-in-time snapshot of the company data, including contact information. This snapshot preserves the company's state at the moment of affiliation creation, which is useful for: * Compliance and audit trails * Historical record keeping * Tracking changes to company information over time The `company_snapshot` field contains a complete copy of the company data (excluding sensitive information like API keys and internal configurations) as it existed when the affiliation was created. The company snapshot is stored automatically but is **not included in API responses by default** to reduce payload size. Use the `include_company_snapshot=true` query parameter to retrieve it when needed. ### Retrieving company snapshots To include the company snapshot in affiliation responses, add the `include_company_snapshot=true` query parameter: ```bash theme={null} curl "https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/affiliations/AFFILIATION_ID?include_company_snapshot=true" \ -H "x-api-key: YOUR_API_KEY" ``` The response will include the `company_snapshot` field with the preserved company data: ```json theme={null} { "id": "affiliation-123", "provider": "CELCOIN", "status": "ACTIVE", "company_snapshot": { "id": "company-456", "full_name": "My Store LTDA", "document_number": "12345678000190", "document_type": "CNPJ", "contact": { "first_name": "John", "last_name": "Doe", "email": "john@example.com", "phone": "+5511999999999" }, ... }, ... } ``` ## Onboarding documents During the affiliation onboarding process, payment providers may require specific documents for compliance and KYC (Know Your Customer) verification. These documents are uploaded to secure storage and tracked as part of the affiliation. Each onboarding document includes: * **file\_type**: Type of document (e.g., CNH\_FRONT, SELFIE, CONTRATO\_SOCIAL) * **file\_name**: Generated filename in storage * **content\_type**: MIME type of the document (e.g., application/pdf, image/jpeg) * **file\_size\_bytes**: Size of the file in bytes * **created\_at**: When the document was uploaded The `onboarding_documents` array is included in affiliation responses, providing metadata about all uploaded documents. To download a specific document, use the dedicated endpoint which returns a signed download URL that expires after 1 hour. ## Multiple affiliations Merchants can have multiple affiliations with different providers: ```bash theme={null} # Affiliation 1: Celcoin for PIX { "provider": "CELCOIN", "allowed_payment_methods": ["PIX"] } # Affiliation 2: Cappta for cards { "provider": "CAPPTA", "allowed_payment_methods": ["CREDIT_CARD", "DEBIT_CARD"], "settlement_bank_account": { "ispb": "00000000", "branch_number": "1234", "account_number": "567890", "account_type": "CHECKING" } } ``` However, each merchant can only have **one affiliation per provider**. ## Bank accounts Affiliations use two distinct types of bank accounts depending on the provider and payment method: **Cashouts** use different resources: external bank accounts and external PIX key destinations under the **Cashouts** APIs (`/bank-accounts`, `/pix-keys`), documented in [Cashouts](/concepts/cashouts). Those are not the same as affiliation BaaS accounts below or card **settlement** bank accounts in this section. ### Affiliation bank accounts Affiliation bank accounts are BaaS (Banking as a Service) provider accounts created and managed by Rinne. They are used for PIX and bolepix payment methods through providers like Celcoin. * Created automatically by Rinne during the affiliation process * Used for receiving PIX and bolepix payments * Managed internally — merchants don't need to provide these Each affiliation bank account has an `operational_status` that reflects whether the bank account is restricted: | Status | Meaning | | ---------------- | -------------------------------------- | | `REGULAR` | Account operates normally | | `JUDICIAL_BLOCK` | Balance is restricted by a court order | When `operational_status` changes, Rinne sends an `affiliation-bank-account.operational-status-changed` webhook with a full bank account snapshot in `entity`. See [Webhooks](/guides/webhooks) for the payload and handler examples. Affiliation bank accounts can be subject to court-ordered fund blocks. See [Banking → Judicial blockages](/concepts/banking#judicial-blockages) for how to list block and release movements, and subscribe to `judicial-blockage.created` for real-time notifications of each movement. ### Settlement bank accounts Settlement bank accounts are external bank accounts that organizations provide during a merchant's affiliation creation for card payment methods. They are used to settle card receivables (credit and debit card transactions) through card providers like Cappta. * Provided in the **`settlement_bank_account`** object on affiliation **creation** (required for RINNE and CAPPTA when card methods apply; see the API reference for your provider) * Returned as **`settlement_bank_accounts`**: an array of accounts, each with its own `id`, **`status`**, and timestamps—an affiliation can have more than one record over time (for example a previous account marked `DISABLED` after a successful change) * Sent to the card provider for validation (ownership and account verification) * Account holder document must match the merchant's document #### Settlement bank account status Each entry in `settlement_bank_accounts` includes a `status` that reflects provider validation: | Status | Meaning | | -------------------- | ------------------------------------------------------- | | `ACTIVE` | Account in use for card receivables settlement | | `PENDING_VALIDATION` | Submitted to the provider; async validation in progress | | `VALIDATION_FAILED` | Provider rejected the account during async validation | | `FAILED` | Unrecoverable error (for example provider client error) | | `DISABLED` | Superseded when a newer account becomes `ACTIVE` | While a new account is `PENDING_VALIDATION`, any existing **`ACTIVE`** settlement bank account **continues** to receive settlements until the new account is validated and promoted. If the replacement account fails validation (`VALIDATION_FAILED` or `FAILED`), the **affiliation status remains unchanged** — it stays `ACTIVE` because the previous settlement account is still valid and in use. The affiliation only transitions to `INVALID_SETTLEMENT_BANK_ACCOUNT` when there is **no** valid active settlement account (for example during initial onboarding or after the sole active account is invalidated by the provider). Only **one** `PENDING_VALIDATION` settlement bank account is allowed per affiliation at a time. Submit another update only after the pending request completes (success or failure) or your integration handles the terminal outcome. On **create**, use the singular field **`settlement_bank_account`** in the request body. List and get affiliation responses expose **`settlement_bank_accounts`** (array). ### Updating the settlement bank account After an affiliation exists, you can request a new settlement bank account without recreating the affiliation. The API creates a new row in `PENDING_VALIDATION` and calls the provider to start async validation. **Permissions** * Organization (company) affiliation: `affiliation.edit` * Merchant-scoped affiliation: `merchant.affiliation.edit` #### Request fields ISPB code of the bank (exactly 8 digits). Identifies the bank institution. Branch number (digits only, no separators). Account number (digits only, no separators). Type of bank account. One of: `CHECKING`, `SAVINGS`, `SALARY`, `PAYMENT`. #### Request examples **Merchant context** (organization acting on a merchant): ```bash cURL (merchant-scoped) theme={null} curl -X POST "https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/affiliations/AFFILIATION_ID/settlement-bank-account" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "ispb": "00000000", "branch_number": "1234", "account_number": "567890", "account_type": "CHECKING" }' ``` **Organization (own company) affiliation**: ```bash cURL (organization-scoped) theme={null} curl -X POST "https://api-sandbox.rinne.com.br/core/v1/affiliations/AFFILIATION_ID/settlement-bank-account" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "ispb": "00000000", "branch_number": "1234", "account_number": "567890", "account_type": "CHECKING" }' ``` #### Success response (201 Created) The response returns the full affiliation object. The `settlement_bank_accounts` array now includes the new entry in `PENDING_VALIDATION`: ```json 201 Created theme={null} { "id": "a3dbd0c2-9f79-4f86-8caa-47779b3f2794", "provider": "CAPPTA", "merchant_id": "12345678", "status": "ACTIVE", "enabled": true, "company_id": "b4ece1d3-0a8a-5097-9dbb-58880c4g3894", "organization_id": "c5fdf2e4-1b9b-6108-aece-69991d5h4905", "allowed_capture_methods": ["ECOMMERCE"], "allowed_payment_methods": ["CREDIT_CARD", "DEBIT_CARD"], "anticipation_type": "AUTOMATIC", "anticipation_days": 1, "bank_account": null, "settlement_bank_accounts": [ { "id": "d6geg3f5-2c0c-7219-bfdf-70002e6i5016", "branch_number": "0001", "account_number": "999888", "account_type": "CHECKING", "account_holder_name": "My Store LTDA", "account_holder_document_number": "12345678000190", "ispb": "16501555", "status": "DISABLED", "created_at": "2026-03-01T10:00:00Z", "updated_at": "2026-04-15T14:30:00Z" }, { "id": "e7hfh4g6-3d1d-8320-cgeg-81113f7j6127", "branch_number": "1234", "account_number": "567890", "account_type": "CHECKING", "account_holder_name": "My Store LTDA", "account_holder_document_number": "12345678000190", "ispb": "00000000", "status": "PENDING_VALIDATION", "created_at": "2026-04-15T14:30:00Z", "updated_at": "2026-04-15T14:30:00Z" } ], "onboarding_documents": [], "created_at": "2026-03-01T10:00:00Z", "updated_at": "2026-04-15T14:30:00Z" } ``` #### Error response (400 — provider does not support operation) Providers that do not support external settlement bank accounts (such as Celcoin) return a validation error: ```json 400 Validation Error theme={null} { "error": "VALIDATION_ERROR", "message": "Provider does not support settlement bank account updates", "status_code": 400 } ``` #### Tracking the validation lifecycle Once the settlement bank account is created: 1. The account starts in **`PENDING_VALIDATION`**. Your existing `ACTIVE` settlement account continues receiving settlements normally. 2. A scheduled background job polls the provider every \~5 minutes. It checks all `PENDING_VALIDATION` accounts and reconciles status: * **`ACTIVE`**: Provider validated successfully — the new account takes over, and the previous `ACTIVE` account becomes `DISABLED`. * **`VALIDATION_FAILED`**: Provider rejected the account (for example invalid bank details). * **`FAILED`**: Unrecoverable error on the provider side. * **`DISABLED`**: Provider deactivated the account. 3. If validation fails and there is no other valid settlement account, the affiliation may transition to **`INVALID_SETTLEMENT_BANK_ACCOUNT`**. You can submit a replacement via the same endpoint and the process repeats. 4. When the replacement account is validated and becomes **`ACTIVE`**, the affiliation status is **reconciled from the provider** — an affiliation in `INVALID_SETTLEMENT_BANK_ACCOUNT` returns to **`ACTIVE`** instead of remaining stuck. **How to monitor:** * Subscribe to the **`affiliation.status-changed`** webhook — watch for `new_status === 'INVALID_SETTLEMENT_BANK_ACCOUNT'` to know when the provider rejected the bank account. * **Poll** `GET /v1/affiliations/{id}` (or the merchant-scoped equivalent) and check `settlement_bank_accounts[].status` until the pending entry resolves. * When the new account reaches `ACTIVE`, the affiliation status is also reconciled — recovering from `INVALID_SETTLEMENT_BANK_ACCOUNT` back to `ACTIVE`. ## Sandbox testing behavior When testing affiliations in the sandbox environment, you can control the affiliation flow by using specific phone number patterns for the merchant contact: | Phone ending | Behavior | | ------------ | --------------------------------------------------------------------------------------------- | | **1** | Complete flow - affiliation will be approved/active after collecting and processing documents | | **2** | Early rejection - a rejected webhook will arrive before requesting documents | | **3** | Late rejection - affiliation will be rejected after collecting and processing documents | ### Example ```bash theme={null} # This merchant will have affiliations approved after document processing curl -X POST https://api-sandbox.rinne.com.br/core/v1/merchants \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "contact": { "phone": "+5511999999991" # Ends with 1 - will be approved } }' # This merchant will have affiliations rejected before documents curl -X POST https://api-sandbox.rinne.com.br/core/v1/merchants \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "contact": { "phone": "+5511999999992" # Ends with 2 - early rejection } }' ``` This behavior only applies to the sandbox environment and is designed to help you test different affiliation scenarios. ## Checking affiliation status List all affiliations for a merchant: ```bash theme={null} curl https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/affiliations \ -H "x-api-key: YOUR_API_KEY" ``` Filter by status or provider: ```bash theme={null} curl "https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/affiliations?status=ACTIVE&provider=CELCOIN" \ -H "x-api-key: YOUR_API_KEY" ``` ## Next steps Process payments through affiliations Provider keys for receiving PIX payments # Banking Source: https://docs.rinne.com.br/concepts/banking Manage balances, review statements, receive PIX payments, perform internal transfers, configure automatic settlements, and tracking court-ordered fund blocks on affiliation bank accounts. Rinne provides banking operations through integrated payment providers: checking balances, viewing statements, managing affiliation PIX keys used to receive transactions, moving funds between your own accounts (internal transfers), and configuring automatic settlement behavior. Affiliation PIX keys are not the same as external PIX key destinations under `/pix-keys`, which exist only for cashouts. See [Cashouts](/concepts/cashouts). ## Bank accounts Merchants can register multiple bank accounts for receiving settlements and processing transfers. ### Entity relationships ```mermaid theme={null} graph LR M[Merchant] -->|has many| BA[Bank Accounts] BA -->|linked to| A[Affiliation] BA -->|receives| S[Settlements] BA -->|source for| CO[Cashouts] BA -->|used in| IT[Internal Transfers] M -->|checks| B[Balance] M -->|views| ST[Statement] style M fill:#7B89FF,color:#fff style BA fill:#5B68EB,color:#fff style A fill:#4A56D9,color:#fff style S fill:#7B89FF,color:#fff style CO fill:#5B68EB,color:#fff style IT fill:#4A56D9,color:#fff style B fill:#7B89FF,color:#fff style ST fill:#5B68EB,color:#fff ``` ### Creating a bank account Account numbers must contain only digits. Do not include dashes, spaces, or other special characters. ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/bank-accounts \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "nickname": "Main operations account", "branch_number": "0001", "account_number": "123456", "account_type": "CHECKING", "account_holder_name": "Store Name Ltda.", "account_holder_document_number": "16525269000121", "ispb": "00000000" }' ``` ### Nicknames `nickname` is an optional user-defined label (1–100 characters) for a company bank account. It never leaves Rinne—the provider is not given the nickname—and it exists purely to help you distinguish accounts when a company holds several withdrawal accounts, even at the same bank. Unlike the other bank-account fields, `nickname` stays editable even on accounts with successful or ongoing cashouts. Nickname-only `PATCH` requests bypass the "no updates once used" guard, and you can clear the label by sending `"nickname": null`. ### Account types * `CHECKING`: Checking account (conta corrente) * `SAVINGS`: Savings account (poupança) * `SALARY`: Salary account (conta salário) * `PAYMENT`: Payment account (conta pagamento) ### Primary account Set one account as primary for default settlement operations: ```bash theme={null} curl -X PATCH https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/bank-accounts/ACCOUNT_ID \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "primary": true }' ``` ## Adding balance in sandbox In the sandbox environment, you can add balance to accounts for testing purposes. ### Add balance to a merchant account ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/balance \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "provider": "CELCOIN", "amount": 10000 }' ``` ### Add balance to an organization account Use this endpoint when you need to add balance directly to the authenticated organization's own bank account. No `merchantId` parameter is needed. ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/banking/balance \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "provider": "CELCOIN", "amount": 10000 }' ``` Only companies with type `ORGANIZATION` can use the `/v1/banking/balance` POST endpoint. Both endpoints return a status and request ID: ```json theme={null} { "request_id": "f9b978a6-ab7e-4460-997d-1234567890ab", "status": "CONFIRMED" } ``` ## Checking balance Get the current balance for a merchant's account: ```bash theme={null} curl "https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/balance?provider=CELCOIN" \ -H "x-api-key: YOUR_API_KEY" ``` Response: ```json theme={null} { "company_id": "merchant-123", "provider": "CELCOIN", "balance": 150000, "blocked_balance": 5000, "total_balance": 155000, "currency": "BRL" } ``` * `balance`: Available balance (in cents) * `blocked_balance`: Temporarily blocked funds * `total_balance`: Total balance including blocked funds ## Account statements Retrieve transaction history for a merchant's account: ```bash theme={null} curl "https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/statement?provider=CELCOIN&date_from=2025-01-01T00:00:00Z&date_to=2025-01-07T23:59:59Z" \ -H "x-api-key: YOUR_API_KEY" ``` Providers enforce a maximum 7-day date range for statements. Response includes all account movements: ```json theme={null} { "items": [ { "movement_id": "mov-123", "occurred_at": "2025-01-05T10:30:00Z", "amount": 10000, "type": "CREDIT", "movement_type": "PIXPAYMENTIN", "balance_after": 150000 } ], "page": 1, "page_size": 50, "total": 25, "total_pages": 1 } ``` ## Internal transfers Transfer funds between bank accounts within the same provider (organization only). ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/banking/internal-transfers \ -H "x-api-key: YOUR_ORG_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "request_id": "transfer-123", "origin_bank_account_id": "account-1", "destination_bank_account_id": "account-2", "amount": 25000, "metadata": { "transfer_reason": "settlement", "custom_field": "value" } }' ``` ### Internal transfer requirements * Both accounts must have active affiliations * Accounts must be with the same provider * Only organizations can create internal transfers * Origin and destination must be different accounts ## Automatic transfers Configure automatic settlement transfers for merchants: ```json theme={null} { "transfer_configurations": { "automatic_transfer_enabled": true, "transfer_frequency": "DAILY", "transfer_date": 1, "rail": "PIX" } } ``` * `DAILY`: Transfer every day * `WEEKLY`: Transfer on specified day of week * `MONTHLY`: Transfer on specified day of month (1-28) Automatic transfers work like regular cashouts, but Rinne creates them for you on schedule instead of you calling the API. Money goes to the company's active primary bank account if one is set up; if not, it goes to the active primary PIX key instead. If neither is set up yet, the current cycle is skipped; no transfer is created. A later scheduled cycle can run once an active primary destination exists. See [Cashouts → Automatic cashouts](/concepts/cashouts#automatic-cashouts) for more detail. ## Judicial blockages Courts can order funds in a bank account to be frozen (blocked) or unfrozen (released). When this happens to an affiliation bank account, the payment provider applies the order and notifies Rinne, which records each movement. You can list these movements through the API to understand why funds are unavailable and to reconcile blocked amounts. Each judicial blockage record represents a single movement: * `BLOCK`: Funds were frozen by court order. * `RELEASE`: Previously blocked funds were unblocked. Records are created automatically from provider notifications—there is no write API. The list is append-only: a block and its later release appear as two separate records. When a movement is recorded, Rinne also sends a `judicial-blockage.created` webhook. Account-level restriction changes (`REGULAR` ↔ `JUDICIAL_BLOCK`) arrive separately as `affiliation-bank-account.operational-status-changed`. See [Webhooks](/guides/webhooks) for payload details and handler examples. While an account is under a judicial block, the affected funds are frozen at the provider and cannot be withdrawn or transferred. ### Listing judicial blockages List movements for the authenticated organization's own accounts (requires the `banking.list` permission): ```bash theme={null} curl "https://api-sandbox.rinne.com.br/core/v1/banking/judicial-blockages?type=BLOCK&sort=-occurred_at" \ -H "x-api-key: YOUR_API_KEY" ``` Or for a specific merchant that belongs to your organization (requires the `merchant.banking.list` permission): ```bash theme={null} curl "https://api-sandbox.rinne.com.br/core/v1/merchants/1c9f4a67-3b2e-4d80-9f15-6a8c0d72e394/banking/judicial-blockages" \ -H "x-api-key: YOUR_API_KEY" ``` Response: ```json theme={null} { "data": [ { "id": "b2f6a1de-4c39-4f8e-9a71-2d5c8e0f3b64", "affiliation_bank_account_id": "a41c7b02-95d3-4e6f-8c2a-1f0b9d67e358", "provider_movement_id": "mov_123456789", "type": "BLOCK", "amount": 250000, "description": "Bloqueio judicial", "process_number": "0012345-67.2026.8.26.0100", "author_name": "John Doe", "court_name": "1ª Vara Cível de São Paulo", "occurred_at": "2026-07-10T14:22:00Z", "created_at": "2026-07-10T14:22:05Z", "updated_at": "2026-07-10T14:22:05Z" } ], "pagination": { "page": 1, "limit": 20, "total": 1, "totalPages": 1, "hasNext": false, "hasPrev": false } } ``` * `amount`: Movement amount in cents * `process_number`, `author_name`, `court_name`: Judicial process details, when provided by the court order (may be `null`) * `occurred_at`: When the movement occurred at the provider ### Filtering and sorting Both endpoints support the same query parameters: | Parameter | Description | | ------------------------------------- | ------------------------------------------------------------------------------------------------------ | | `type` | Filter by movement type: `BLOCK`, `RELEASE`, or both (comma-separated) | | `affiliation_bank_account_id` | Filter by a specific affiliation bank account | | `process_number` | Filter by judicial process number | | `occurred_at_from` / `occurred_at_to` | Filter by occurrence date range (ISO 8601) | | `sort` | Sort by `occurred_at`, `created_at`, or `amount`; prefix with `-` for descending (e.g. `-occurred_at`) | | `page` / `limit` | Pagination (default page 1, limit 20, max 100) | ## Next steps Process instant PIX payments Understand fee and cost policies # Cashouts Source: https://docs.rinne.com.br/concepts/cashouts Understand outbound PIX transfers—origins and destinations, how they map to the API, lifecycle states, and reconciliation Cashouts are outbound transfers from one of your bank accounts at the provider (the origin) to exactly one registered external recipient: either an external bank account record or an external PIX key record. The API creates one cashout resource per attempt; execution and status updates happen asynchronously through providers and events. Affiliation PIX keys (`/pix/keys`) are not the same as external PIX key destinations under `/pix-keys`. The latter exists only for cashout recipients. See [Banking](/concepts/banking) and [PIX Payments](/guides/pix-payments#pix-key-management-for-receiving-payments). ## Origins and destinations The origin is always identified by `origin_bank_account_id`: an account UUID that corresponds to a Bank Account from an Affiliation with available balance. The destination is either `destination_bank_account_id` or `destination_pix_key_id`, never both. The API validates this: sending both fields, or neither, returns a `400` validation error. Register destinations before you create the cashout. ### Entity relationships ```mermaid theme={null} graph LR M[Merchant or company] -->|owns| OBA[Origin bank accounts] OBA -->|linked to| A[Affiliation] M -->|registers for recipients| EB[External bank account records] M -->|registers for recipients| PK[External PIX keys] OBA -->|debited by| CO[Cashout] EB -->|or| CO PK -->|or| CO CO -->|executed via| PR[Provider] CO -->|notifies via| WH[Webhooks] style M fill:#7B89FF,color:#fff style OBA fill:#5B68EB,color:#fff style A fill:#4A56D9,color:#fff style EB fill:#4A56D9,color:#fff style PK fill:#7B89FF,color:#fff style CO fill:#5B68EB,color:#fff style PR fill:#4A56D9,color:#fff style WH fill:#7B89FF,color:#fff ``` ### Operational rules * One destination type per cashout: use a bank-account ID or a PIX-key ID exclusively. Requests with both (or neither) fail validation with a `400` error. * No implicit destination: the `primary` flag on bank accounts and PIX keys never affects a cashout you create through the API—every request names its destination explicitly. `primary` only matters for [automatic cashouts](#automatic-cashouts). * Balance: funds must be available on the origin. * Identifiers: `request_id` is required—use it for idempotency and to correlate your orders with ours. * DICT: PIX key destinations are validated with the network; responses may expose masked holder data per BACEN rules. * PIX key destinations require an active Affiliation as it is required for DICT lookup ## External PIX keys Create a `/pix-keys` record per recipient key your integration will route cashouts through. Resolution runs against DICT; creation responses include `dict_key_information` (sometimes masked). ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/pix-keys \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "key": "recipient@example.com", "primary": false }' ``` In a company context, use `/v1/companies/me/pix-keys` (create, read, update, delete) instead of `/v1/merchants/{merchantId}/pix-keys`. Full field lists and behavior are documented on those `/pix-keys` paths in the [API reference](/api-reference/introduction). ## External bank accounts Register recipient settlement details with `POST …/bank-accounts`. Accounts used as cashout destinations reuse the same merchant (or company) bank-account resources described in [Banking](/concepts/banking); the difference is how you use the returned `id` in the cashout payload. ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/bank-accounts \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "nickname": "Supplier · Acme Ltda.", "branch_number": "0001", "account_number": "12345678", "account_type": "CHECKING", "account_holder_name": "Recipient Legal Name", "account_holder_document_number": "12345678901", "ispb": "18236120" }' ``` Use digits only for `branch_number` and `account_number` (no dashes or spaces). Full field lists and validation errors are documented on `POST /v1/merchants/{merchantId}/bank-accounts` (and `/v1/companies/me/bank-accounts`) in the API reference. `nickname` is optional (1–100 characters) and only used to help you tell destinations apart when you have multiple external bank accounts registered—it is never sent to the provider. It can be edited at any time (including on accounts that already have cashouts) and cleared by sending `"nickname": null` on `PATCH`. Cashout responses echo the current label of the destination account as `destination_bank_account_nickname` (live value, not a snapshot; `null` for PIX-key destinations or accounts without a nickname). See [Banking → Nicknames](/concepts/banking#nicknames). ## Creating a cashout Each cashout `POST` debits `origin_bank_account_id`, transfers `amount` (in cents), and uses `method`: `PIX`. Omit one destination field entirely when you use the other. ### Bank account destination ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/cashouts \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "request_id": "cashout-order-789", "origin_bank_account_id": "223e4567-e89b-12d3-a456-426614174000", "destination_bank_account_id": "334e4567-e89b-12d3-a456-426614174001", "amount": 50000, "currency": "BRL", "method": "PIX" }' ``` ### PIX key destination Send `destination_pix_key_id` (the `id` from `POST …/pix-keys`) instead of `destination_bank_account_id`. ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/cashouts \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "request_id": "cashout-order-790", "origin_bank_account_id": "223e4567-e89b-12d3-a456-426614174000", "destination_pix_key_id": "445e4567-e89b-12d3-a456-426614174002", "amount": 25000, "method": "PIX" }' ``` A typical immediate response establishes the cashout resource; exact fields follow the `POST …/cashouts` schema in the API reference: ```json theme={null} { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "request_id": "cashout-order-789", "status": "PENDING", "amount": 50000, "method": "PIX", "destination_bank_account_id": "334e4567-e89b-12d3-a456-426614174001", "destination_bank_account_nickname": "Supplier · Acme Ltda.", "destination_pix_key_id": null } ``` ## Automatic cashouts Companies with automatic transfers enabled (`transfer_configurations.automatic_transfer_enabled`, see [Banking → Automatic transfers](/concepts/banking#automatic-transfers)) get cashouts created by Rinne on the configured schedule, without an API call. Because there is no request body, the destination is selected from the company's registered records by priority: 1. **Primary external bank account** — the company's `/bank-accounts` record flagged `primary` with status `ACTIVE`. 2. **Primary external PIX key** — used only when no active primary bank account exists: the company's `/pix-keys` record flagged `primary` with status `ACTIVE`. 3. **Neither registered** — the automatic cashout is skipped for that cycle; no transfer happens until an active primary destination exists. The priority applies to the company's **external destination records** (`/bank-accounts` and `/pix-keys`)—not to affiliation PIX keys (`/pix/keys`), which are for receiving payments and play no role in cashout routing. Automatic cashouts transfer the available balance of the origin account, respecting the company's configured minimum balance, and follow the same lifecycle and webhooks as manually created cashouts. ## Listing, status, and receipts You confirm outcomes by polling identifiers and subscribing to webhooks (preferred for production). ```bash theme={null} curl "https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/cashouts" \ -H "x-api-key: YOUR_API_KEY" ``` ```bash theme={null} curl "https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/cashouts/CASHOUT_ID" \ -H "x-api-key: YOUR_API_KEY" ``` ```bash theme={null} curl "https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/cashouts/CASHOUT_ID/receipt" \ -H "x-api-key: YOUR_API_KEY" ``` Receipts are only available for eligible finalized states, may require operational context such as end-to-end IDs, and are documented under `GET /v1/merchants/{merchantId}/cashouts/{id}/receipt` (organization: `GET /v1/banking/cashouts/{id}/receipt`). ## Lifecycle and returns Statuses move asynchronously: `PENDING`, `PROCESSING`, `COMPLETED`, `FAILED`, plus post-settlement reversal states `PARTIALLY_RETURNED` and `RETURNED`. ```mermaid theme={null} stateDiagram-v2 [*] --> PENDING PENDING --> PROCESSING PROCESSING --> COMPLETED PROCESSING --> FAILED COMPLETED --> PARTIALLY_RETURNED COMPLETED --> RETURNED PARTIALLY_RETURNED --> RETURNED ``` * `COMPLETED`: the outbound leg succeeded from the provider’s perspective. * `PARTIALLY_RETURNED` / `RETURNED`: subsequent reversal events alter what you reconcile—treat each transition as its own ledger event. Do not treat `COMPLETED` alone as immutable financial closure. Continue listening for status changes until you know reconciliation is settled. ## Webhooks Cashouts finish asynchronously after the API accepts `POST …/cashouts`: the provider executes the PIX leg and may emit returns later. Treat webhooks as the primary signal for lifecycle updates in production (use listing and `GET …/cashouts/{id}` when you need to backfill or audit). Rinne sends two banking events for cashouts: * **`cashout.created`**: Emitted when the cashout resource is created successfully. * **`cashout.status-changed`**: Emitted when status changes—covering progress (`PENDING`, `PROCESSING`), terminal outcomes (`COMPLETED`, `FAILED`), and post-settlement reversals (`PARTIALLY_RETURNED`, `RETURNED`). Payloads include identifiers and transition fields (for example `cashout_id`, `old_status`, `new_status`, and `error_message` when a failure occurs). Event envelopes also carry standard metadata such as `id`, `timestamp`, `company_id`, and `correlation_id`; see the Webhooks guide for the shared shape. Delivery is at-least-once. Use the event `id` for idempotency so you do not apply the same transition twice. For endpoint setup, Svix signature verification, retries, and handler examples, see [Webhooks](/guides/webhooks). Payload field definitions for each event live in the API reference under **Webhooks → Events** (`cashout.created`, `cashout.status-changed`). ## Next steps Step-by-step registration, payloads, and organization vs merchant paths Balances, statements, affiliation keys, internal transfers Event-driven lifecycle handling Exact request and response schemas for each cashout and destination endpoint # Disputes Source: https://docs.rinne.com.br/concepts/disputes How Rinne tracks provider-initiated challenges against settled payments and reports them through the dispute.created and dispute.status-changed webhooks A **dispute** is a challenge against a payment your merchant has already received, where a third party asks for those funds to be returned. Rinne ingests the challenge from the payment provider, tracks it through a single lifecycle, and reports every step to your organization through the `dispute.created` and `dispute.status-changed` webhooks. Rinne is never the claimant. It receives the provider's report and relays it, so a dispute always originates outside Rinne—there is no way for your organization or Rinne to open one. ## Where disputes come from Disputes arrive from the provider that processed the original transaction. Each rail has its own vocabulary for the same idea, and Rinne absorbs that difference at the provider boundary: event names and payload fields describe Rinne's lifecycle, not the provider's. You integrate once, regardless of which rail a dispute came from. The `type` field names the rail: | `type` | Rail | | ---------------- | ------------------------------------------------------------ | | `PIX_INFRACTION` | A PIX MED 2.0 infraction report raised against a PIX payment | | `CHARGEBACK` | A chargeback raised by a cardholder through the card network | Today disputes reach you from the PIX rail (`PIX_INFRACTION`). Card chargebacks will arrive through these same two events, so handling both types now needs no later change. There are no dispute endpoints to call. Disputes are delivered to you exclusively as webhooks. ## Lifecycle A dispute tracks **progress** and **verdict** on two separate fields: `status` says how far the claim has got, and `outcome` says how it ended. `status` is an ordered progression and a dispute only ever moves forward. `CLOSED` is the single terminal status, so a finished dispute is exactly `status == "CLOSED"`. ```mermaid theme={null} stateDiagram-v2 [*] --> OPEN: Claim received OPEN --> UNDER_REVIEW: Analysis starts OPEN --> CLOSED: Verdict reached UNDER_REVIEW --> CLOSED: Verdict reached CLOSED --> [*] ``` | `status` | Meaning | | -------------- | ----------------------------------------------------------------- | | `OPEN` | The claim has been received. Nothing has been analyzed yet. | | `UNDER_REVIEW` | The claim is under analysis. | | `CLOSED` | The claim is finished. Terminal, and always carries an `outcome`. | `outcome` is `null` for the entire life of the dispute until it closes, and is always set once it has: | `outcome` | Meaning | | ----------- | ------------------------------------------------------------------------------ | | `UPHELD` | The claim succeeded. Funds are returned. | | `DISMISSED` | The claim failed. No financial movement. | | `WITHDRAWN` | The claim ceased to exist before a verdict was reached. No financial movement. | A partially resolved claim is `UPHELD` with a smaller `amount`, not a separate outcome. A dispute never regresses and never reopens. If you receive an event whose `new_status` is not ahead of the status you have stored, treat it as a duplicate and ignore it—`old_status` is always strictly earlier than `new_status`, so a stale or repeated provider delivery produces no event at all. ## Disputes and the disputed transaction Every dispute is linked to the transaction it challenges through `transaction_id`. Use that field to reconcile a dispute against the payment in your own system. The dispute lifecycle is tracked separately from the transaction lifecycle: a `dispute.created` or `dispute.status-changed` event reports the claim's progress and is not a transaction status change. You receive dispute events independently of any [`transaction.status-changed`](/guides/webhooks) event for the same payment. A single transaction can accumulate more than one dispute. Key your own records on `dispute_id`, not on `transaction_id`. ## The dispute webhooks Both events use the same envelope, Svix signatures, and retry behavior as every other Rinne event—see [Webhooks](/guides/webhooks). In the envelope, `source` is `dispute-service` and `company_id` is the **merchant** the claim is filed against. Both payloads carry an `entity` field holding the full dispute object as it stood when the event was emitted, so you never need a follow-up request to act on a dispute. The API reference lists every field it contains; two of them are easy to misread: * `amount` is the amount **at risk**, in cents—an upper bound rather than an exact figure, currently derived from the disputed transaction. Rails that report an amount of their own will carry it here instead. * `dispute_details` is free text describing the claim, and its author is not recorded—it may be the payer in their own words, a risk analyst at the reporting institution, or a generated message. Display it and read it, but never parse it or branch your logic on its contents. ### `dispute.created` Emitted when Rinne records a dispute for the first time. ```json theme={null} { "id": "e6925e51-7157-47b7-9d09-ab1b711d70b0", "type": "dispute.created", "version": 1, "timestamp": "2026-08-05T15:16:57.000Z", "source": "dispute-service", "company_id": "6834d397-ab3c-4862-a148-d6216042e9bc", "correlation_id": "e6925e51-7157-47b7-9d09-ab1b711d70b0", "payload": { "dispute_id": "056213b4-bb9b-45d0-8872-c8a33f050367", "transaction_id": "33c83e9a-405e-4648-a6b8-3f241274fff9", "organization_id": "29add8ce-c317-4db8-b2b5-2854892cd4d4", "type": "PIX_INFRACTION", "status": "OPEN", "outcome": null, "amount": 9500, "entity": { "id": "056213b4-bb9b-45d0-8872-c8a33f050367", "transaction_id": "33c83e9a-405e-4648-a6b8-3f241274fff9", "company_id": "6834d397-ab3c-4862-a148-d6216042e9bc", "organization_id": "29add8ce-c317-4db8-b2b5-2854892cd4d4", "provider": "CELCOIN", "type": "PIX_INFRACTION", "status": "OPEN", "outcome": null, "amount": 9500, "provider_dispute_id": "9edf80da-0d09-40c9-a79d-3794cbef552c", "dispute_details": "the reporting institution describes an unauthorised transfer", "deadline_at": null, "concluded_at": null, "created_at": "2026-08-05T15:16:57.000Z", "updated_at": "2026-08-05T15:16:57.000Z" } } } ``` A dispute is usually created at `OPEN`. A dispute whose first notification to reach Rinne already describes a finished claim is created at `CLOSED`, with `outcome` already set—so handle a created event that is already closed rather than assuming a status change will follow. ### `dispute.status-changed` Emitted when the dispute moves forward: `OPEN` to `UNDER_REVIEW`, or either of those to `CLOSED` with a verdict in `outcome`. ```json theme={null} { "id": "04c9ab9c-16b2-41dd-b386-8b8ffdbcc6ca", "type": "dispute.status-changed", "version": 1, "timestamp": "2026-08-09T11:02:14.000Z", "source": "dispute-service", "company_id": "6834d397-ab3c-4862-a148-d6216042e9bc", "correlation_id": "04c9ab9c-16b2-41dd-b386-8b8ffdbcc6ca", "payload": { "dispute_id": "056213b4-bb9b-45d0-8872-c8a33f050367", "transaction_id": "33c83e9a-405e-4648-a6b8-3f241274fff9", "old_status": "UNDER_REVIEW", "new_status": "CLOSED", "outcome": "UPHELD", "entity": { "id": "056213b4-bb9b-45d0-8872-c8a33f050367", "transaction_id": "33c83e9a-405e-4648-a6b8-3f241274fff9", "company_id": "6834d397-ab3c-4862-a148-d6216042e9bc", "organization_id": "29add8ce-c317-4db8-b2b5-2854892cd4d4", "provider": "CELCOIN", "type": "PIX_INFRACTION", "status": "CLOSED", "outcome": "UPHELD", "amount": 9500, "provider_dispute_id": "9edf80da-0d09-40c9-a79d-3794cbef552c", "dispute_details": "the reporting institution describes an unauthorised transfer", "deadline_at": null, "concluded_at": "2026-08-09T11:02:14.000Z", "created_at": "2026-08-05T15:16:57.000Z", "updated_at": "2026-08-09T11:02:14.000Z" } } } ``` Group the events belonging to one dispute by `dispute_id`. Unlike some Rinne events, `correlation_id` here is unique per event rather than shared across a dispute's lifetime, so it identifies the delivery, not the claim. ## Next steps Endpoint setup, Svix signature verification, and retries The payment lifecycle a dispute challenges How settled amounts are recorded against a merchant Full dispute event schemas under Webhooks → Events # KYC Verification Source: https://docs.rinne.com.br/concepts/kyc How Rinne automatically verifies merchant identity data and reports the results to your organization via the kyc.completed webhook Rinne runs Know Your Customer (KYC) checks automatically whenever a merchant company is created or has its identification data updated. The checks compare the merchant's registration data against trusted data sources and evaluate a set of verification rules. Results are delivered to the parent organization through the `kyc.completed` webhook. KYC results are **informational**. They never block company creation or updates—the merchant resource is created or updated regardless of the outcome. Your organization decides how to act on the findings (for example, pausing onboarding or requesting corrected data). ## When KYC runs There are no KYC endpoints to call. Checks are triggered automatically for **merchant** companies: * **On creation**: every new merchant is checked. * **On update**: a new check runs only when a KYC-relevant field changes—the tax document type, the legal/full name, or the contact information. Other updates do not trigger a re-check. Organization-type companies are not checked. Each qualifying create or update produces a new, independent check and its own `kyc.completed` webhook. ```mermaid theme={null} graph LR C[Merchant created or updated] --> K[KYC rules evaluated] K --> W[kyc.completed webhook] W --> O[Organization endpoint] style C fill:#7B89FF,color:#fff style K fill:#5B68EB,color:#fff style W fill:#4A56D9,color:#fff style O fill:#7B89FF,color:#fff ``` ## Check types The set of rules evaluated depends on the merchant's document type, reported as `kyc_type` in the webhook: * `NATURAL_PERSON_COMPANY`: the merchant is an individual (CPF). * `LEGAL_PERSON_COMPANY`: the merchant is a legal entity (CNPJ). Rules also cover the registered contact person and the company's ownership structure. ## Rule results Each rule execution reports a `status`: | Status | Meaning | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `PASSED` | The data source confirms the merchant's data for this rule. | | `ISSUES_FOUND` | A discrepancy or risk signal was found—review it and decide how to proceed. | | `FAILED` | The rule could not be evaluated (for example, the data source did not return the needed information). Treat it as inconclusive, not as a problem with the merchant. | Every rule execution includes the `expected` values required to pass and, unless the underlying data was unavailable, the `received` values actually observed. A human-readable `description` is included whenever the rule does not pass. ### Rules for natural persons | Rule | Verifies | | ----------------------- | ------------------------------------------------------------ | | `DOCUMENT_NUMBER_VALID` | The CPF exists | | `DOCUMENT_STATUS_VALID` | The CPF is in a regular status | | `FULL_NAME_CORRECT` | The provided name matches the document holder | | `DATE_OF_BIRTH_CORRECT` | The provided date of birth matches the document holder | | `IS_ALIVE` | The document holder is not deceased | | `HAS_SANCTIONS` | The document holder is not sanctioned or politically exposed | ### Rules for legal entities | Rule | Verifies | | ------------------------------ | -------------------------------------------------------- | | `LEGAL_DOCUMENT_NUMBER_VALID` | The CNPJ exists | | `LEGAL_DOCUMENT_STATUS_VALID` | The CNPJ is in a regular status | | `LEGAL_NAME_CORRECT` | The provided legal name matches the registration | | `LEGAL_HAS_SANCTIONS` | The entity is not sanctioned | | `COMPANY_TYPE_CORRECT` | The declared company type matches the registration | | `IS_CONTACT_REPRESENTATIVE` | The registered contact is a representative of the entity | | `NO_SHAREHOLDERS_RESTRICTIONS` | No restrictions found on the entity's shareholders | For legal entities, the registered contact person (CPF) is also verified with contact-scoped counterparts of the natural-person rules: `CONTACT_DOCUMENT_NUMBER_VALID`, `CONTACT_DOCUMENT_STATUS_VALID`, `CONTACT_FULL_NAME_CORRECT`, `CONTACT_DATE_OF_BIRTH_CORRECT`, `CONTACT_IS_ALIVE`, and `CONTACT_HAS_SANCTIONS`. The exact set of rules executed comes from the KYC policy applied to the check, identified by `policy_id` and `policy_name` in the webhook payload. Handle the `rule_executions` array dynamically rather than expecting a fixed list. ## The `kyc.completed` webhook The webhook is delivered to the parent organization using the same envelope, Svix signatures, and retry behavior as every other Rinne event (see [Webhooks](/guides/webhooks)). In the envelope, `company_id` is the **merchant** that was checked and `correlation_id` equals the `kyc_check_id`. ```json theme={null} { "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "type": "kyc.completed", "version": 1, "timestamp": "2025-01-15T10:30:00.000Z", "source": "kyc", "company_id": "889fd9b3-1634-429a-b54f-eaa0004345cd", "correlation_id": "d01c7832-b63c-4cb6-90b0-74e821f7123e", "payload": { "kyc_check_id": "d01c7832-b63c-4cb6-90b0-74e821f7123e", "company_id": "889fd9b3-1634-429a-b54f-eaa0004345cd", "policy_id": "cb350742-a9b8-4a3c-9d7a-02aa8cbb24cb", "policy_name": "rinne-default-natural-person", "kyc_type": "NATURAL_PERSON_COMPANY", "rule_executions": [ { "rule_name": "DOCUMENT_NUMBER_VALID", "status": "PASSED", "expected": { "document_exists": true }, "received": { "document_exists": true } }, { "rule_name": "HAS_SANCTIONS", "status": "ISSUES_FOUND", "expected": { "is_sanctioned": false, "is_pep": false }, "received": { "is_sanctioned": true, "is_pep": false }, "description": "Document holder is currently sanctioned" } ] } } ``` ### Payload fields * `kyc_check_id`: Unique ID of this check run * `company_id`: The merchant company that was checked * `policy_id` / `policy_name`: The KYC policy that defined the rules * `kyc_type`: `NATURAL_PERSON_COMPANY` or `LEGAL_PERSON_COMPANY` * `rule_executions`: One entry per rule in the policy—`rule_name`, `status`, `expected`, and `received` (omitted only if the underlying data was unavailable); `description` is included when the rule does not pass Delivery is at-least-once. Use the event `id` for idempotency, and `kyc_check_id` to group rule results belonging to the same check when a merchant is checked more than once. ## Next steps Endpoint setup, Svix signature verification, and retries How merchants relate to your organization Provider onboarding and document requirements Full kyc.completed event schema under Webhooks → Events # Ledger entries Source: https://docs.rinne.com.br/concepts/ledger-entries Understanding Rinne's double-entry accounting system for tracking all monetary movements Ledger entries are the foundation of Rinne's financial accounting system. Every monetary movement in the platform—whether a transaction, fee, cost, or refund—is recorded as immutable ledger entries following double-entry bookkeeping principles. ## What are ledger entries? A ledger entry is an immutable accounting record that represents a single monetary movement. Each entry contains: * **Amount**: The monetary value (always positive, stored in cents) * **Operation**: Either CREDIT (money received) or DEBIT (money owed/paid) * **Type**: The economic subject (what the money represents) * **Owner**: Who the entry belongs to (company, platform, or provider) * **Payment date**: When the money is expected to move (date-only format: YYYY-MM-DD) Ledger entries are always created in balanced pairs or groups called **posting sets**, where the sum of all credits equals the sum of all debits. ### Entity relationships ```mermaid theme={null} graph LR T[Transaction] -->|creates| PS[Posting Set] R[Refund] -->|creates| PS C[Cashout] -->|creates| PS PS -->|contains| LE[Ledger Entries] LE -->|paired by| PT[Pair Token] LE -->|owned by| M[Merchant] LE -->|owned by| O[Organization] LE -->|owned by| PL[Platform] LE -->|owned by| PR[Provider] LE -->|settled by| SI[Settlement Items] style T fill:#5B68EB,color:#fff style R fill:#7B89FF,color:#fff style C fill:#7B89FF,color:#fff style PS fill:#4A56D9,color:#fff style LE fill:#5B68EB,color:#fff style SI fill:#7B89FF,color:#fff ``` ## Core concepts ### Posting sets A posting set is a balanced group of ledger entries created by a single business event (like a transaction approval). Every posting set must net to zero: ``` Σ(credits) = Σ(debits) ``` This ensures the integrity of the double-entry accounting system. #### Idempotency Each posting set includes an `idempotency_key` to prevent duplicate ledger entries when events are replayed. The idempotency key follows the pattern: * **Transaction approval**: `transaction-{id}-approved` * **Refund completion**: `refund-{id}-completed` * **Cashout completion**: `cashout-{id}-completed` If an event is processed multiple times (e.g., due to retries or system replays), Rinne will detect the existing posting set by its idempotency key and reuse it instead of creating duplicates. This ensures that ledger entries are created exactly once per business event. ### Pair tokens Ledger entries are linked in pairs using a `pair_token`. Each pair represents a complete accounting transaction with one CREDIT and one DEBIT entry. For example: * Merchant receives R$100 (CREDIT) ↔ Provider pays R$100 (DEBIT) ### Owner types Ledger entries can belong to three types of owners: * **COMPANY**: Organizations and merchants in your platform * **PLATFORM**: Rinne platform itself * **PROVIDER**: Payment providers (the system records provider-related entries internally) ## Ledger entry types ### Transaction processing When a transaction is approved, Rinne creates three public pairs of ledger entries: ```mermaid theme={null} graph TD T[Transaction Approved
R$100] --> P1[Pair 1: Transaction] T --> P2[Pair 2: Organization Fee] T --> P3[Pair 3: Platform Cost] P1 --> MC[Merchant CREDIT
+R$100] P1 --> PD[Provider DEBIT
-R$100] P2 --> MD[Merchant DEBIT
-R$2.50] P2 --> OC[Organization CREDIT
+R$2.50] P3 --> OD[Organization DEBIT
-R$1.00] P3 --> PLC[Platform CREDIT
+R$1.00] style T fill:#5B68EB,color:#fff style P1 fill:#5B68EB,color:#fff style P2 fill:#5B68EB,color:#fff style P3 fill:#5B68EB,color:#fff style MC fill:#7B89FF,color:#fff style OC fill:#7B89FF,color:#fff style PLC fill:#7B89FF,color:#fff style MD fill:#4A56D9,color:#fff style OD fill:#4A56D9,color:#fff style PD fill:#4A56D9,color:#fff ``` The main transaction amount owed to the merchant. **Example**: R\$100 PIX transaction * Merchant: R\$100 CREDIT (receives money) * Provider: R\$100 DEBIT (pays merchant) Commercial fee owed to the organization (parent company). **Example**: 2.5% fee on R\$100 transaction * Merchant: R\$2.50 DEBIT (pays fee) * Organization: R\$2.50 CREDIT (receives fee) Processing fee owed to Rinne platform. **Example**: 1.0% cost on R\$100 transaction * Organization: R\$1.00 DEBIT (pays cost) * Platform: R\$1.00 CREDIT (receives cost) ### Automatic anticipation When a credit card transaction is processed through an affiliation with `anticipation_type` set to `AUTOMATIC`, Rinne generates additional ledger entry pairs for anticipation fees and costs. These entries are created **per installment** alongside the standard transaction entries. Automatic anticipation replaces the standard credit card settlement date (D+30) with an earlier payment date based on the affiliation's `anticipation_days` setting (e.g., D+1). The anticipation fee and cost compensate for this early settlement. **Anticipation formula per installment:** ``` amount = installment_amount × (anticipation_percentage / 100 / 30) × days_anticipated ``` Where `days_anticipated = standard_payment_date - anticipated_payment_date` (calendar days). Fee charged to the merchant for receiving funds earlier than the standard settlement date. **Example**: 1.5% anticipation rate, 29 days anticipated on R\$1,000 installment * Merchant: R\$14.50 DEBIT (pays anticipation fee) * Organization: R\$14.50 CREDIT (receives anticipation fee) Cost charged to the organization for early settlement processing. **Example**: 0.5% anticipation rate, 29 days anticipated on R\$1,000 installment * Organization: R\$4.83 DEBIT (pays anticipation cost) * Platform: R\$4.83 CREDIT (receives anticipation cost) Anticipation entries are only generated for **credit card** transactions with `anticipation_type = AUTOMATIC`. Other payment methods and `SPOT` anticipation are not affected. When anticipation is active, all ledger entries for the transaction (including standard entries) use the anticipated payment date instead of the standard D+30 date. ### Fee calculation formulas Fees and costs are calculated using the following formulas: ```typescript theme={null} // Organization fee organizationFee = Math.round(amount * fee_percentage / 100) + fee_flat // If fee_minimum_price is set, the fee is at least that amount // Platform cost platformCost = Math.round(amount * cost_percentage / 100) + cost_flat // If cost_minimum_price is set, the cost is at least that amount ``` All amounts are integers in **cents** (no floating-point arithmetic). ### Supported payment methods Rinne creates ledger entries for the following payment methods: * **PIX**: Instant payment, settled same day (D+0) * **BOLEPIX**: Creates the same ledger entry pairs as PIX. Settlement depends on how the BOLEPIX was paid—PIX method when paid via PIX, BOLETO method when paid via boleto. * **Credit card**: Supports installments with separate entries per installment * **Debit card**: Single-installment payment ### Refund processing When a refund is processed, Rinne creates similar pairs but with reversed operations: Main refund amount returned to customer. **Example**: R\$50 refund * Merchant: R\$50 DEBIT (returns money) * Provider: R\$50 CREDIT (processes refund) Fee returned to the merchant for the refund. **Example**: 2.5% fee refund on R\$50 * Merchant: R\$1.25 CREDIT (receives fee back) * Organization: R\$1.25 DEBIT (returns fee) Platform cost for performing the refund operation. **Example**: 1.0% refund cost on R\$50 * Organization: R\$0.50 DEBIT (pays refund cost) * Platform: R\$0.50 CREDIT (receives refund cost) Additional refund-related entry types exist in the system: * `PLATFORM_COST_REFUND`: Refund of the original platform cost from the transaction * `TRANSACTION_REFUND_REVERSAL`: Reversal of a refund (e.g., if the refund fails) Internal entries for provider costs are also recorded but not visible to merchants or organizations. ### Cashout processing Cashouts also generate ledger entries. Unlike transactions, cashouts only create fee/cost pairs (3 pairs) since the transaction amount was already accounted for in previous transactions. The cashout ledger entries track the fees and costs associated with withdrawing funds. ## Complete transaction example Here's a complete R\$100 PIX transaction with the visible ledger entries: ```json theme={null} { "posting_set": { "id": "ps_123", "event_name": "transaction.status-changed", "idempotency_key": "transaction-tx_123-approved", "created_at": "2025-01-15T10:30:00Z" }, "ledger_entries": [ // 1. Transaction pair (R$100) { "owner_type": "COMPANY", "owner_id": "merchant_123", "amount": 10000, "operation": "CREDIT", "type": "TRANSACTION" }, { "owner_type": "PROVIDER", "owner_id": "provider", "amount": 10000, "operation": "DEBIT", "type": "TRANSACTION" }, // 2. Organization fee pair (R$2.50) { "owner_type": "COMPANY", "owner_id": "merchant_123", "amount": 250, "operation": "DEBIT", "type": "ORGANIZATION_FEE" }, { "owner_type": "COMPANY", "owner_id": "org_456", "amount": 250, "operation": "CREDIT", "type": "ORGANIZATION_FEE" }, // 3. Platform cost pair (R$1.00) { "owner_type": "COMPANY", "owner_id": "org_456", "amount": 100, "operation": "DEBIT", "type": "PLATFORM_COST" }, { "owner_type": "PLATFORM", "owner_id": "RINNE", "amount": 100, "operation": "CREDIT", "type": "PLATFORM_COST" } ] } ``` **Balance verification**: * Credits: R$100 + R$2.50 + R$1.00 = R$103.50 * Debits: R$100 + R$2.50 + R$1.00 = R$103.50 ✓ ## Settlement tracking Each ledger entry tracks its settlement status: * **outstanding\_amount**: How much is still unsettled * **settled**: Whether the entry is fully settled * **fully\_settled\_at**: When it became fully settled * **last\_clearing\_at**: Most recent settlement Ledger entries start with `outstanding_amount = amount` and `settled = false`. As [settlement items](/concepts/settlement-items) are created, the outstanding amount decreases until the entry is fully settled. ## Payment dates and installments ### Payment date calculation Payment dates are calculated based on the transaction approval date and payment method. "Business day" excludes weekends and official FEBRABAN banking holidays for Brazil. | Payment method | Payment date calculation | | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **PIX / BOLEPIX** | Same calendar day as approval (D+0) | | **Debit card** | Next business day after approval (skips weekends and FEBRABAN holidays) | | **Credit card (standard)** | Approval date + 29 calendar days, then next business day (\~D+30) for the 1st installment; approval date + (30 × installment number) calendar days, then next business day for subsequent installments | | **Credit card (automatic anticipation)** | D+`anticipation_days` (next business day if it falls on a non-business day). Applies when `affiliation.anticipation_type = AUTOMATIC`. All installments share the same anticipated payment date. | ### Installment support For credit card transactions with installments, Rinne creates separate ledger entries for each installment: ```json theme={null} { "installment": 1, "total_installments": 3, "amount": 3333, "payment_date": "2025-02-14" }, { "installment": 2, "total_installments": 3, "amount": 3333, "payment_date": "2025-03-17" }, { "installment": 3, "total_installments": 3, "amount": 3334, "payment_date": "2025-04-16" } ``` The `payment_date` field is returned in date-only format (YYYY-MM-DD) without time information, as these dates represent calendar days rather than specific timestamps. ## Precision and rounding Rinne uses a remainder-on-last strategy to ensure financial accuracy when splitting amounts across installments, backed by a defensive safety net. ### Installment distribution For each amount type, the system computes a base per-installment value and assigns the remainder to the **last** installment: ```typescript theme={null} const base = Math.round(total / numberOfInstallments); // Installments 1 through n-1 receive `base` // Last installment receives: total - base × (n - 1) ``` * `Math.round()` (half-up rounding) is applied to compute the base value * All amounts are integers in **cents** (no floating-point) * The same logic is applied to all entry types (transaction, organization fee, platform cost, etc.) * Installments with a zero or negative value are skipped (no ledger entries created for that position) Because the last installment is computed as `total - base × (n - 1)`, the sum of all installments equals the expected total exactly by construction. ### Balancing safety net As a defensive guard, the system compares the sum of generated entries against the expected totals after all installments are created. If any discrepancy is detected, the last installment entries are adjusted. Under normal operation this check finds no difference, but it protects against edge cases where the distribution and the entry-creation pipeline could diverge (e.g., skipped installments or concurrent modifications). #### Concrete example ``` Transaction: R$999.00 (99900 cents), 7 installments - Organization fee: 2.5% = R$24.98 (2498 cents) - Platform cost: 1.0% = R$9.99 (999 cents) Distribution (base × 6 installments + remainder on last): - Transaction: base = Math.round(99900/7) = 14271, last = 99900 - 14271 × 6 = 14274 - Org fee: base = Math.round(2498/7) = 357, last = 2498 - 357 × 6 = 356 - Platform cost: base = Math.round(999/7) = 143, last = 999 - 143 × 6 = 141 Result: - Transaction: 14271, 14271, 14271, 14271, 14271, 14271, 14274 = 99900 cents - Org fees: 357, 357, 357, 357, 357, 357, 356 = 2498 cents - Platform costs: 143, 143, 143, 143, 143, 143, 141 = 999 cents ``` #### Small-total edge cases (absorb from end) When a small total is split across many installments, `total - base × (n - 1)` can produce a zero or negative last installment. The distribution function handles this by reducing the effective installment count until the last installment is strictly positive: * **base rounds to zero**: `Math.round(2/12) = 0` — installments 1-11 produce zero and are skipped; installment 12 receives the full 2 cents. * **base rounds up**: `Math.round(1/2) = 1` — last installment would be `1 - 1 × 1 = 0`, so it is absorbed and only installment 1 receives 1 cent. Similarly, `Math.round(2/4) = 1` — last installment would be `2 - 1 × 3 = -1`, trailing installments are absorbed until a positive remainder is found, resulting in installments 1-2 receiving 1 cent each. In both cases the sum of emitted installments equals `total` exactly. ### Refund rounding (different algorithm) Refund fee distribution uses a different algorithm: `Math.floor()` for proportional distribution with the **last item** receiving the remainder (`total - accumulated`). This ensures exact totals without rounding errors. ## Immutability and corrections Ledger entries are **immutable** once created. You cannot modify the core fields: * amount * operation * type * owner information * payment\_date * business linkages (transaction\_id, refund\_id, etc.) If a correction is needed, create new ledger entries in a new posting set to represent the correction. ## Key principles Every posting set must balance: credits = debits Core fields cannot be changed after creation Sophisticated rounding ensures exact totals Full audit trail from creation to settlement ## Querying ledger entries Rinne provides API endpoints to retrieve ledger entries with filtering, pagination, and sorting capabilities. ### Company ledger entries Companies can query their own ledger entries: ```bash theme={null} # List ledger entries for your company GET /v1/ledger-entries # Get a specific ledger entry GET /v1/ledger-entries/{ledgerEntryId} ``` ### Merchant ledger entries (organization scope) Organizations can query ledger entries across all their merchants: ```bash theme={null} # List ledger entries for all merchants GET /v1/merchants/ledger-entries # List ledger entries for a specific merchant GET /v1/merchants/{merchantId}/ledger-entries # Get a specific ledger entry GET /v1/merchants/ledger-entries/{ledgerEntryId} ``` ### Filtering options You can filter ledger entries by: | Parameter | Description | | ------------------- | --------------------------------------------------- | | `posting_set_id` | Filter by posting set ID | | `type` | Filter by entry type (comma-separated for multiple) | | `operation` | Filter by CREDIT or DEBIT | | `payment_date_from` | Start date (inclusive, YYYY-MM-DD) | | `payment_date_to` | End date (inclusive, YYYY-MM-DD) | | `transaction_id` | Filter by transaction ID | | `refund_id` | Filter by refund ID | | `cashout_id` | Filter by cashout ID | | `settled` | Filter by settlement status (true/false) | ### Sorting Use the `sort` parameter with field names. Prefix with `-` for descending order: ```bash theme={null} # Sort by created_at descending (default) GET /v1/ledger-entries?sort=-created_at # Sort by payment_date ascending, then amount descending GET /v1/ledger-entries?sort=payment_date,-amount ``` Available sort fields: `created_at`, `payment_date`, `amount` ### Pagination All list endpoints return paginated results with `page` and `limit` query parameters. ```json theme={null} { "data": [...], "pagination": { "page": 1, "limit": 20, "total": 100, "totalPages": 5, "hasNext": true, "hasPrev": false } } ``` ## Next steps Learn how ledger entries are settled Understand fee and cost calculations # Organizations and merchants Source: https://docs.rinne.com.br/concepts/organizations-and-merchants Understand the company hierarchy in Rinne's B2B2B model Rinne uses a hierarchical company structure that enables B2B2B marketplace functionality. This model allows organizations to manage multiple merchants with complete data isolation and custom configurations. ## Company types ### Organizations Organizations are parent companies that manage multiple merchants. They have the ability to: * Create and manage merchants * View aggregated data across all merchants * Configure pricing policies for merchants * Access consolidated transaction reports * Manage users and permissions across the hierarchy Organizations are created by Rinne administrators and receive an API key for authentication. ### Merchants Merchants are child companies that belong to an organization. They can: * Process payment transactions * Manage external bank accounts and PIX destinations for cashouts ([Cashouts](/concepts/cashouts)) * Register affiliation PIX keys for receiving payments ([PIX Payments](/guides/pix-payments#pix-key-management-for-receiving-payments)) * Access their own transaction history * Configure transfer settings Merchants are created by organizations through the API and automatically inherit the organization's pricing policies. ## Company hierarchy The following diagram illustrates the B2B2B hierarchical structure: ```mermaid theme={null} graph TD O[Organization
Parent Company] --> M1[Merchant A] O --> M2[Merchant B] O --> M3[Merchant C] M1 -.-> T1[Transactions] M1 -.-> B1[Bank Accounts] M1 -.-> A1[Affiliations] M2 -.-> T2[Transactions] M2 -.-> B2[Bank Accounts] M2 -.-> A2[Affiliations] M3 -.-> T3[Transactions] M3 -.-> B3[Bank Accounts] M3 -.-> A3[Affiliations] style O fill:#5B68EB,color:#fff style M1 fill:#7B89FF,color:#fff style M2 fill:#7B89FF,color:#fff style M3 fill:#7B89FF,color:#fff ``` Each merchant operates independently with: * Unique API key * Isolated transaction data * Separate bank accounts * Individual provider affiliations * Custom transfer configurations ## Key differences | Feature | Organization | Merchant | | ---------------------- | ------------ | -------- | | Create merchants | ✅ Yes | ❌ No | | Process transactions | ✅ Yes | ✅ Yes | | View all merchant data | ✅ Yes | ❌ No | | Set pricing policies | ✅ Yes | ❌ No | | Manage affiliations | ✅ Yes | ✅ Yes | ## Authentication scope When you authenticate with an API key, the scope depends on the company type: **Organization API key**: Access to all merchants and aggregated data ```bash theme={null} # List all merchants GET /v1/merchants # View all merchant transactions GET /v1/merchants/transactions ``` **Merchant API key**: Access only to own data ```bash theme={null} # View own transactions GET /v1/transactions # Manage own affiliations GET /v1/affiliations ``` ## Creating merchants Organizations create merchants using the `/v1/merchants` endpoint: ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/merchants \ -H "x-api-key: YOUR_ORG_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "full_name": "Store Name Ltda.", "document_number": "16525269000121", "document_type": "CNPJ", "document_tax_type": "PJ", "mcc": "5912", "contact": { "first_name": "John", "last_name": "Silva", "phone": "+5511999999991", "email": "contact@store.com", "mother_name": "Maria Silva", "birth_date": "15-08-1990", "document_number": "81146431023", "politically_exposed": false, "declared_income": 15000, "occupation": "ADMINISTRATOR_MANAGER", "net_worth": 100000, "address": { ... } }, "address": { ... }, "transfer_configurations": { ... }, "declared_revenue": 500000 }' ``` The merchant is automatically linked to your organization and inherits your pricing policies. ## Document requirements ### CNPJ companies (legal entities) * Document must be exactly 14 digits * Tax type must be one of: `PJ`, `MEI`, or `ME` * Contact document (CPF) can be different from company document * `declared_revenue` is required ### CPF companies (individuals) * Document must be exactly 11 digits * Tax type must be `PF` * Contact document must match company document ### Updating document\_tax\_type The `document_tax_type` field can be updated after company creation, but only when the company has no affiliations in non-final statuses. This means: * **Allowed**: Company has no affiliations, or only `FAILED`/`REJECTED` affiliations * **Blocked**: Company has affiliations in `PENDING`, `IN_PROGRESS`, `ACTIVE`, or other non-final statuses When updating, the same document type compatibility rules apply: * CPF companies can only have `PF` tax type * CNPJ companies can have `PJ`, `MEI`, or `ME` tax type If you need to change the tax type for a company with active affiliations, you must wait for those affiliations to reach a final status (FAILED or REJECTED) or create a new company. ## Financial information Financial information fields are required for provider affiliation and regulatory compliance. All monetary values are stored as whole reais (BRL), not cents. ### Contact financial fields | Field | Type | Description | | ----------------- | ------- | ----------------------------------------------------------- | | `declared_income` | integer | Monthly income in whole reais. Default: 5000 (R\$ 5.000) | | `occupation` | string | Contact's occupation. Default: `OTHER` | | `net_worth` | integer | Total net worth in whole reais. Default: 50000 (R\$ 50.000) | ### Company financial fields | Field | Type | Description | | ------------------ | ------- | --------------------------------------------------------------------------------------- | | `declared_revenue` | integer | Annual revenue in whole reais. Default: 50000 (R\$ 50.000). Required for CNPJ companies | ### Celcoin provider translation When affiliating a merchant with the Celcoin provider, financial values are translated into income/revenue tiers. Understanding these ranges helps you provide accurate financial information. Monthly income values are mapped to the following tiers: | Value Range (reais) | Description | | ------------------- | --------------------- | | ≤ 5,000 | Very low income tier | | 5,001 - 10,000 | Low income tier | | 10,001 - 30,000 | Medium income tier | | 30,001 - 100,000 | High income tier | | > 100,000 | Very high income tier | Total net worth values are mapped to the following tiers: | Value Range (reais) | Description | | --------------------- | ------------------------ | | ≤ 50,000 | Very low net worth tier | | 50,001 - 200,000 | Low net worth tier | | 200,001 - 1,000,000 | Medium net worth tier | | 1,000,001 - 5,000,000 | High net worth tier | | > 5,000,000 | Very high net worth tier | Annual revenue values are mapped to Brazilian company size classifications: | Value Range (reais) | Description | | ---------------------- | ------------------------------ | | ≤ 81,000 | MEI | | 81,001 - 360,000 | Microempresa | | 360,001 - 4,800,000 | Empresa de Pequeno Porte (EPP) | | 4,800,001 - 78,000,000 | Média Empresa | | > 78,000,000 | Grande Empresa | ### Occupation values The following occupation values are supported: `ADMINISTRATOR_MANAGER`, `SALES_REPRESENTATIVE`, `HR_ANALYST`, `FINANCIAL_ANALYST`, `SOFTWARE_DEVELOPER`, `MARKETING_PROFESSIONAL`, `HEALTHCARE_PROFESSIONAL`, `TEACHER`, `ENGINEER`, `LAWYER`, `GENERAL_SERVICES`, `CONSTRUCTION_WORKER`, `DRIVER`, `RECEPTIONIST`, `TECHNICIAN`, `DESIGNER`, `MACHINE_OPERATOR`, `CONSULTANT`, `BEAUTY_PROFESSIONAL`, `SECURITY_AGENT`, `AGRICULTURAL_WORKER`, `TRAVEL_AGENT`, `JOURNALIST`, `PSYCHOLOGIST`, `PUBLIC_SERVANT`, `RESEARCHER`, `ARTISAN`, `RETIRED`, `STUDENT`, `SELF_EMPLOYED`, `OTHER` ## Next steps Connect merchants to payment providers Learn about payment processing # Pricing Source: https://docs.rinne.com.br/concepts/pricing Flexible rule-based pricing for fees and costs Rinne uses a dynamic pricing system based on policies and rules. This allows you to configure different pricing for various transaction types, payment methods, and merchant categories. ## Pricing components Every transaction has two pricing components: ### Entity relationships ```mermaid theme={null} graph LR O[Organization] -->|creates| FP[Fee Policies] FP -->|applied to| T[Transaction] CP[Cost Policies] -->|applied to| T P[Provider] -->|defines| CP MCC[Merchant MCC] -->|determines| CP FP -->|contains| FR[Fee Rules] CP -->|contains| CR[Cost Rules] FR -->|evaluates| C[Conditions] CR -->|evaluates| C style O fill:#5B68EB,color:#fff style FP fill:#7B89FF,color:#fff style CP fill:#4A56D9,color:#fff style T fill:#7B89FF,color:#fff style P fill:#5B68EB,color:#fff style MCC fill:#7B89FF,color:#fff ``` ### Fee policies Fees are what you charge to merchants for processing transactions. Fee policies are configured at the organization level and inherited by merchants. ```json theme={null} { "fee": { "percentage": 3.5, "flat": 0, "minimum_price": 100 } } ``` ### Cost policies Costs are what providers charge you for processing transactions. Cost policies are configured per provider and MCC (Merchant Category Code). ```json theme={null} { "cost": { "percentage": 0.5, "flat": 0, "minimum_price": 50 } } ``` ## Pricing structure Each pricing value can include: * **percentage**: Percentage of transaction amount (e.g., 3.5%) * **flat**: Fixed amount in cents (e.g., 50 cents) * **minimum\_price**: Minimum fee/cost in cents At least one component must be specified. ## Rule-based pricing Pricing policies use rules with conditions to apply different pricing based on transaction attributes. ### Example fee policy ```json theme={null} { "name": "standard-card-fees", "description": "Standard fee structure for card transactions", "is_active": true, "cashout_price": 350, "rules": [ { "conditions": [ { "field": "transaction.payment_method", "operator": "EQUALS", "value": "CREDIT_CARD" }, { "field": "transaction.installments", "operator": "EQUALS", "value": 1 } ], "price": { "percentage": 2.3 }, "priority": 1 }, { "conditions": [ { "field": "transaction.payment_method", "operator": "EQUALS", "value": "PIX" } ], "price": { "percentage": 0.99 }, "priority": 2 }, { "conditions": [], "price": { "percentage": 3.0 }, "priority": 99 } ] } ``` ### How rules work 1. Rules are evaluated in priority order (1 = highest) 2. First rule where all conditions match is applied 3. Empty conditions array = default rule (matches everything) 4. Each policy should have a default rule with lowest priority ### Rule evaluation flow ```mermaid theme={null} flowchart TD Start[Transaction created] --> Sort[Sort rules by priority] Sort --> Eval{Evaluate rule conditions} Eval -->|All match| Apply[Apply rule price] Eval -->|No match| Next{More rules?} Next -->|Yes| Eval Next -->|No| Default[Apply default rule] Apply --> End[Price calculated] Default --> End style Start fill:#5B68EB,color:#fff style Apply fill:#7B89FF,color:#fff style Default fill:#4A56D9,color:#fff style End fill:#5B68EB,color:#fff ``` ## Condition operators | Operator | Description | Example | | ------------------ | ---------------- | ------------------------------------------ | | `EQUALS` | Exact match | `payment_method = "PIX"` | | `NOT_EQUALS` | Not equal | `payment_method != "PIX"` | | `GREATER_THAN` | Greater than | `amount > 10000` | | `LESS_THAN` | Less than | `amount < 50000` | | `GREATER_OR_EQUAL` | Greater or equal | `installments >= 3` | | `LESS_OR_EQUAL` | Less or equal | `installments <= 12` | | `IN` | In array | `payment_method IN ["PIX", "CREDIT_CARD"]` | | `NOT_IN` | Not in array | `payment_method NOT IN ["BOLETO"]` | ## Transaction fields You can create conditions based on these transaction fields: * `transaction.amount`: Transaction amount in cents * `transaction.payment_method`: Payment method (PIX, CREDIT\_CARD, etc.) * `transaction.installments`: Number of installments * `transaction.capture_method`: Capture method (ECOMMERCE, EMV, etc.) * `transaction.automatic_anticipation`: Whether the transaction was automatically anticipated (`true` or `false`) ## Cashout pricing Both fee and cost policies include a `cashout_price` field (in cents) that defines the fixed fee/cost for cashout operations: ```json theme={null} { "cashout_price": 350 } ``` This is charged when merchants transfer funds from their provider account to external bank accounts. ## Managing fee policies ### Listing fee policies List all fee policies for your organization: ```bash theme={null} curl https://api-sandbox.rinne.com.br/core/v1/pricing/fee-policies \ -H "x-api-key: YOUR_ORG_API_KEY" ``` The response includes a `companies_with_fee_policy` field for each policy, showing how many companies are using that fee policy: ```json theme={null} { "data": [ { "id": "fee-policy-123", "name": "standard-fees", "is_active": true, "companies_with_fee_policy": 5, ... } ], "pagination": { "page": 1, "limit": 20, "total": 5, "totalPages": 1, "hasNext": false, "hasPrev": false } } ``` ### Creating fee policies Organizations can create custom fee policies: ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/pricing/fee-policies \ -H "x-api-key: YOUR_ORG_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "premium-merchant-fees", "description": "Reduced fees for premium merchants", "is_active": true, "cashout_price": 250, "rules": [ { "conditions": [ { "field": "transaction.payment_method", "operator": "EQUALS", "value": "PIX" } ], "price": { "percentage": 0.5 }, "priority": 1 }, { "conditions": [], "price": { "percentage": 2.0 }, "priority": 99 } ] }' ``` ### Updating fee policies You can update existing fee policies using partial updates. All fields are optional: ```bash theme={null} curl -X PATCH https://api-sandbox.rinne.com.br/core/v1/pricing/fee-policies/{id} \ -H "x-api-key: YOUR_ORG_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "updated-premium-fees", "is_active": false }' ``` #### Updating rules Rules can be selectively updated, added, or preserved: * **Rules with IDs**: Updates those specific rules while preserving unmentioned rules * **Rules without IDs**: Creates new rules and adds them to the policy * **Rules not mentioned**: Remain unchanged and preserved **Update a specific rule:** ```bash theme={null} curl -X PATCH https://api-sandbox.rinne.com.br/core/v1/pricing/fee-policies/{id} \ -H "x-api-key: YOUR_ORG_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "rules": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "conditions": [ { "field": "transaction.amount", "operator": "GREATER_THAN", "value": 10000 } ], "price": { "percentage": 1.5, "flat": 50 }, "priority": 1 } ] }' ``` **Add a new rule:** ```bash theme={null} curl -X PATCH https://api-sandbox.rinne.com.br/core/v1/pricing/fee-policies/{id} \ -H "x-api-key: YOUR_ORG_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "rules": [ { "conditions": [ { "field": "transaction.installments", "operator": "GREATER_OR_EQUAL", "value": 12 } ], "price": { "percentage": 4.0, "flat": 200 }, "priority": 3 } ] }' ``` When updating a rule's conditions, the conditions for that specific rule are replaced (not merged). Other rules remain unchanged. ## Cost policies Cost policies are configured by Rinne administrators for each provider and MCC combination. They define what the provider charges for processing transactions. Organizations can view their cost policies: ```bash theme={null} curl "https://api-sandbox.rinne.com.br/core/v1/pricing/cost-policies?provider=CELCOIN&mcc=5912" \ -H "x-api-key: YOUR_API_KEY" ``` ## MCC (Merchant Category Code) Each merchant is assigned an MCC that categorizes their business type. The MCC determines which cost policy applies to their transactions. Common MCCs: * `5912`: Drug stores and pharmacies * `5411`: Grocery stores and supermarkets * `5812`: Restaurants * `5999`: Miscellaneous retail List available MCCs for your organization: ```bash theme={null} curl https://api-sandbox.rinne.com.br/core/v1/companies/mccs \ -H "x-api-key: YOUR_API_KEY" ``` ## Per-transaction fee policy You can override the default fee policy for specific transactions: ```json theme={null} { "fee_policy_id": "custom-fee-policy-123", "amount": 10000, "payment_method": "PIX" } ``` The specified fee policy must belong to your organization. ## Next steps Learn about transaction processing Manage banking operations # Settlement items Source: https://docs.rinne.com.br/concepts/settlement-items Understanding how Rinne tracks actual money movements and settles ledger entries Settlement items are the bridge between accounting records ([ledger entries](/concepts/ledger-entries)) and actual money movements. While ledger entries record what *should* happen financially, settlement items track what *actually* happened—when money moved, how it moved, and to where. ## What are settlement items? A settlement item is a clearing link that applies a portion (or all) of a ledger entry to a real settlement operation. Each settlement item represents an actual money movement: * **PIX payment** to a merchant's bank account * **Internal transfer** between accounts * **Invoice payment** for monthly costs * **Boleto payment** for settlements ### Entity relationships ```mermaid theme={null} graph LR LE[Ledger Entry] -->|settled by| SI[Settlement Items] SI -->|references| T[Transaction] SI -->|references| IT[Internal Transfer] SI -->|references| CO[Cashout] SI -->|settles to| BA[Bank Account] SI -->|updates| LE style LE fill:#5B68EB,color:#fff style SI fill:#7B89FF,color:#fff style T fill:#4A56D9,color:#fff style IT fill:#5B68EB,color:#fff style CO fill:#7B89FF,color:#fff style BA fill:#5B68EB,color:#fff ``` ## Core attributes Each settlement item contains: * **ledger\_entry\_id**: Which ledger entry is being settled * **settled\_amount**: How much of the entry is being settled (in cents) * **settlement\_date**: When the money actually moved (date-only format: YYYY-MM-DD) * **method**: How the money moved (PIX, internal transfer, etc.) * **status**: Current state of the settlement * **operation\_id**: External reference (transaction ID, transfer ID, etc.) * **affiliation\_bank\_account\_id**: Destination bank account (if applicable) ## Settlement methods Rinne supports multiple settlement methods: Instant payments to merchant bank accounts (D+0) Transfers between Rinne accounts (organization fees) Monthly invoices for platform/provider costs Bank slip payments for settlements ## Settlement status lifecycle Settlement items follow a state machine with strict transition rules: ```mermaid theme={null} graph LR A[PENDING] --> B[PROCESSING] A --> C[PAID] A --> D[FAILED] B --> C B --> D C -.terminal.-> C D -.terminal.-> D ``` ### Valid transitions * **PENDING → PROCESSING**: Settlement initiated * **PENDING → PAID**: Instant settlement (PIX) * **PENDING → FAILED**: Settlement failed immediately * **PROCESSING → PAID**: Settlement completed * **PROCESSING → FAILED**: Settlement failed during processing ### Terminal states * **PAID**: Settlement completed successfully (no further transitions) * **FAILED**: Settlement failed permanently (no further transitions) ## PIX settlement flow For PIX transactions, Rinne creates settlement items immediately after the transaction is approved: ```mermaid theme={null} %%{init: {'theme':'base', 'themeVariables': { 'primaryColor':'#5B68EB','primaryTextColor':'#fff','primaryBorderColor':'#4A56D9','lineColor':'#7B89FF','secondaryColor':'#7B89FF','tertiaryColor':'#4A56D9'}}}%% sequenceDiagram participant T as Transaction participant LE as Ledger Entries participant SI as Settlement Items participant BA as Bank Account T->>T: Status: APPROVED T->>LE: Create posting set
(4 pairs) LE->>SI: Create settlement items SI->>SI: Status: PENDING SI->>BA: Transfer via PIX BA->>SI: Transfer complete SI->>SI: Status: PAID SI->>LE: Update outstanding_amount LE->>LE: settled = true Note over SI,BA: Instant settlement (D+0) ``` ### 1. Transaction settlement The main transaction amount is settled instantly: ```json theme={null} { "ledger_entry_id": "le_merchant_tx_credit", "settled_amount": 10000, "settlement_date": "2025-01-15", "method": "PIX", "status": "PAID", "operation_id": "trx_456", "affiliation_bank_account_id": "ba_merchant_account" } ``` The `settlement_date` field is returned in date-only format (YYYY-MM-DD) without time information, representing the calendar day when the settlement occurred. The provider's corresponding debit is also settled: ```json theme={null} { "ledger_entry_id": "le_provider_tx_debit", "settled_amount": 10000, "settlement_date": "2025-01-15", "method": "INTERNAL_TRANSFER", "status": "PAID", "operation_id": "trx_456" } ``` ### 2. Organization fee settlement Organization fees are settled via internal transfer: ```json theme={null} { "ledger_entry_id": "le_merchant_fee_debit", "settled_amount": 250, "settlement_date": "2025-01-15", "method": "INTERNAL_TRANSFER", "status": "PENDING", "operation_id": "internal_transfer_789" } ``` When the internal transfer completes, the status updates to `PAID`. ### 3. Platform and provider costs Platform and provider costs remain outstanding for monthly invoice settlement: ```json theme={null} { "ledger_entry_id": "le_platform_cost_credit", "outstanding_amount": 100, "settled": false } ``` ## Ledger entry updates When settlement items are created or updated, the corresponding ledger entries are automatically updated: ### Outstanding amount calculation ```typescript theme={null} outstanding_amount = amount - Σ(settled_amount from non-FAILED settlement items) ``` ### Settlement status ```typescript theme={null} settled = (outstanding_amount === 0) fully_settled_at = settled ? now() : null last_clearing_at = most_recent_settlement_date ``` ### Example progression **Initial state** (after ledger entry creation): ```json theme={null} { "id": "le_merchant_fee_debit", "amount": 250, "outstanding_amount": 250, "settled": false, "fully_settled_at": null, "last_clearing_at": null } ``` **After settlement item creation** (PAID): ```json theme={null} { "id": "le_merchant_fee_debit", "amount": 250, "outstanding_amount": 0, "settled": true, "fully_settled_at": "2025-01-15T10:35:00Z", "last_clearing_at": "2025-01-15T10:35:00Z" } ``` ## Partial settlements Settlement items support partial settlements, where a ledger entry is settled in multiple operations: ```json theme={null} // First settlement: R$50 of R$100 { "ledger_entry_id": "le_123", "settled_amount": 5000, "method": "PIX", "status": "PAID" } // Second settlement: R$30 of R$100 { "ledger_entry_id": "le_123", "settled_amount": 3000, "method": "PIX", "status": "PAID" } // Third settlement: R$20 of R$100 { "ledger_entry_id": "le_123", "settled_amount": 2000, "method": "PIX", "status": "PAID" } ``` The ledger entry tracks the total: ```json theme={null} { "id": "le_123", "amount": 10000, "outstanding_amount": 0, "settled": true } ``` ## Refund settlements Refund settlements follow the same pattern as transaction settlements: ### Transaction refund ```json theme={null} { "ledger_entry_id": "le_merchant_refund_debit", "settled_amount": 5000, "settlement_date": "2025-01-15", "method": "PIX", "status": "PAID", "operation_id": "refund_456" } ``` ### Organization fee refund ```json theme={null} { "ledger_entry_id": "le_merchant_fee_refund_credit", "settled_amount": 125, "settlement_date": "2025-01-15", "method": "INTERNAL_TRANSFER", "status": "PAID", "operation_id": "internal_transfer_890" } ``` ## Idempotency Settlement item creation is idempotent. If settlement items already exist for a ledger entry and operation ID, Rinne skips creation: ```typescript theme={null} // Check for existing settlement items const existingItems = await findSettlementItemsByLedgerEntryIdAndOperationId( ledgerEntryId, operationId ); // Calculate total settled amount (excluding FAILED) const totalSettled = existingItems .filter(item => item.status !== 'FAILED') .reduce((sum, item) => sum + item.settled_amount, 0); // Skip if already fully settled if (totalSettled >= ledgerEntry.amount) { return; // Already settled } ``` ## Settlement invariants Rinne enforces strict invariants for settlement items: ### Settlement bounds ```typescript theme={null} Σ(settled_amount) ≤ ledger_entry.amount ``` You cannot settle more than the original ledger entry amount. ### Outstanding accuracy ```typescript theme={null} ledger_entry.amount = Σ(settled_amount) + outstanding_amount ``` The sum of settled amounts plus outstanding amount must equal the original amount. ### Non-negative outstanding ```typescript theme={null} outstanding_amount ≥ 0 ``` Outstanding amounts can never be negative. ### Settlement status consistency ```typescript theme={null} settled = true ⟺ outstanding_amount = 0 ``` A ledger entry is settled if and only if the outstanding amount is zero. ## Complete settlement example Here's a complete example showing how a R\$100 PIX transaction is settled: ### 1. Ledger entries created ```json theme={null} { "merchant_credit": { "amount": 10000, "outstanding_amount": 10000, "settled": false }, "organization_fee_debit": { "amount": 250, "outstanding_amount": 250, "settled": false } } ``` ### 2. Settlement items created ```json theme={null} { "merchant_settlement": { "ledger_entry_id": "merchant_credit", "settled_amount": 10000, "method": "PIX", "status": "PAID" }, "fee_settlement": { "ledger_entry_id": "organization_fee_debit", "settled_amount": 250, "method": "INTERNAL_TRANSFER", "status": "PENDING" } } ``` ### 3. Ledger entries updated ```json theme={null} { "merchant_credit": { "amount": 10000, "outstanding_amount": 0, "settled": true, "fully_settled_at": "2025-01-15T10:30:00Z" }, "organization_fee_debit": { "amount": 250, "outstanding_amount": 0, "settled": true, "fully_settled_at": "2025-01-15T10:35:00Z" } } ``` ## Key principles Track real money movements, not just accounting Strict status transitions prevent invalid states Two-step creation prevents race conditions Safe to retry settlement operations ## Next steps Learn about the accounting foundation Understand banking operations # Transactions Source: https://docs.rinne.com.br/concepts/transactions Process and manage payment transactions Transactions represent payment operations processed through the Rinne platform. Each transaction is linked to a merchant, processed through a provider affiliation, and can support various payment methods. ## Transaction basics A transaction requires: * An active provider affiliation * Transaction amount (in cents) * Payment method (PIX, credit card, etc.) * Capture method (e-commerce, POS, etc.) * Unique request ID for idempotency ### Entity relationships ```mermaid theme={null} graph LR M[Merchant] -->|creates| T[Transaction] T -->|uses| A[Affiliation] A -->|via| P[Provider] T -->|generates| LE[Ledger Entries] T -->|can have| R[Refunds] T -->|applies| FP[Fee Policy] T -->|applies| CP[Cost Policy] T -->|paid by| C[Consumer] style M fill:#7B89FF,color:#fff style T fill:#5B68EB,color:#fff style A fill:#4A56D9,color:#fff style P fill:#7B89FF,color:#fff style LE fill:#5B68EB,color:#fff style R fill:#7B89FF,color:#fff style FP fill:#4A56D9,color:#fff style CP fill:#4A56D9,color:#fff style C fill:#7B89FF,color:#fff ``` ## Creating transactions ### PIX transaction #### PIX guidelines * **Installments**: Must be 1 for PIX (defaults to 1 if not sent). * **When `pix_data.due_date` is provided**: `consumer` is required with `full_name`, `document_number`, and `document_type`. Use `expiration_in_days_after_due_date`; do not send `expiration_in_seconds`. * **When `pix_data.due_date` is not provided**: Use `expiration_in_seconds`; do not send `expiration_in_days_after_due_date`. #### Immediate PIX payment ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/transactions \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "provider": "CELCOIN", "request_id": "unique-request-123", "amount": 10000, "currency": "BRL", "capture_method": "ECOMMERCE", "payment_method": "PIX", "pix_data": { "description": "Payment for order #123", "expiration_in_seconds": 3600 }, "consumer": { "full_name": "João Silva", "email": "joao@example.com", "document_type": "CPF", "document_number": "81146431023" } }' ``` The response includes a QR code for the customer to scan: ```json theme={null} { "id": "tx_123456789", "status": "WAITING_PAYMENT", "amount": 10000, "pix_data": { "qr_code": "00020126580014br.gov.bcb.pix...", "expires_at": "2025-01-21T11:00:00Z" } } ``` #### PIX with due date For invoice payments, you can create PIX transactions with a due date and pricing modifiers: ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/transactions \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "provider": "CELCOIN", "request_id": "invoice-456", "amount": 100000, "currency": "BRL", "capture_method": "ECOMMERCE", "payment_method": "PIX", "pix_data": { "description": "Invoice payment", "due_date": "2025-12-31", "expiration_in_days_after_due_date": 30, "interest": { "percentage": 2.0, "modality": "PERCENTAGE_PER_MONTH" }, "fine": { "percentage": 5.0, "modality": "PERCENTAGE" }, "discount": { "modality": "PERCENTAGE_UNTIL_DATE", "discount_dates_config": [ { "until_date": "2025-12-15", "percentage": 10.0 } ] } }, "consumer": { "full_name": "Maria Santos", "email": "maria@example.com", "document_type": "CPF", "document_number": "12345678901" } }' ``` **Due date features:** * `due_date`: Expected payment date (YYYY-MM-DD format) * `expiration_in_days_after_due_date`: Days after due date before expiration (1-365, default: 30) * `interest`: Applied when paid after due date * `fine`: One-time charge when paid after due date * `discount`: Reduces amount when paid early * `abatement`: Permanent amount reduction When using `due_date`, consumer information (`full_name`, `document_number`, `document_type`) is required. ### BolePix transaction BolePix is a hybrid payment method that generates both a boleto (bank slip) and a PIX QR code. Customers can pay using either method. ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/transactions \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "provider": "CELCOIN", "request_id": "bolepix-order-123", "amount": 50000, "currency": "BRL", "capture_method": "ECOMMERCE", "payment_method": "BOLEPIX", "bolepix_data": { "due_date": "2025-12-31", "expiration_in_days_after_due_date": 30, "discount": { "amount": 5000, "modality": "FIXED", "until_date": "2025-12-25" }, "fine_percentage": 2.0, "monthly_interest_percentage": 1.0 }, "consumer": { "full_name": "Maria Silva", "email": "maria@email.com", "document_type": "CPF", "document_number": "81146431023", "address": { "country": "076", "state": "SP", "city": "São Paulo", "zipcode": "01310100", "street": "Av Paulista", "street_number": "1000" } } }' ``` The response includes both boleto and PIX payment data: ```json theme={null} { "id": "tx_123456789", "status": "WAITING_PAYMENT", "amount": 50000, "bolepix_data": { "due_date": "2025-12-31", "bar_code": "00190000090299993600000000000000185830000050000", "bank_line": "00190.00009 02999.936001 00000.000001 8 58300000050000", "pix_emv": "00020126580014br.gov.bcb.pix..." } } ``` #### BolePix requirements * **Minimum amount**: 500 cents (R\$ 5.00) * **Maximum amount**: 1,500,000,000 cents (R\$ 15,000,000.00) * **Installments**: Must be 1 (no installment support) * **Consumer**: Required with `full_name`, `document_type`, `document_number`, and full address including `street_number` #### BolePix-specific fields * `due_date`: Payment due date in YYYY-MM-DD format (required, must be today or later) * `expiration_in_days_after_due_date`: Days after due date before expiration (0-59, default: 0) * `discount`: Optional discount configuration * `amount`: Fixed discount amount in cents (for FIXED modality) * `percentage`: Discount percentage (for PERCENTAGE modality) * `modality`: `FIXED` or `PERCENTAGE` * `until_date`: Date until which discount applies (must be before or equal to due\_date) * `fine_percentage`: Fine percentage applied after due date (0.01-100%) * `monthly_interest_percentage`: Monthly interest divided by 30 and applied per day after due date (0.01-100%) The minimum amount after discount is applied must be at least 500 cents (R\$ 5.00). ### Card transaction Use [Card transactions](/guides/card-transactions), [Wallet transactions](/guides/wallet-transactions), and [3D Secure authentication](/guides/three-d-secure-authentication) for production-ready implementation details. For credit and debit card transactions, you'll need to provide card data with one of three credentials: the card number, a network token (for tokenized payments like Apple Pay or Google Pay), or a `card_id` that references a [card stored on file](/guides/cards-on-file). The card number and network token are accepted only in encrypted form — these values must come from the rinne-js secure components, which encrypt them so raw card data never touches your systems and you avoid PCI scope issues. For example, a tokenized wallet transaction: ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/transactions \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "provider": "RINNE", "request_id": "unique-request-456", "amount": 25000, "currency": "BRL", "capture_method": "ECOMMERCE", "installments": 3, "payment_method": "CREDIT_CARD", "card_data": { "network_token": "ev:encrypted:network_token", "cryptogram": "ev:encrypted:cryptogram", "expiry_month": "12", "expiry_year": "2025", "cardholder_name": "Maria Santos", "last_digits": "1111", "authentication_type": "3DS", "wallet_type": "APPLE_PAY", "display_name": "Visa •••• 1111" }, "consumer": { "full_name": "Maria Santos", "email": "maria@example.com", "document_type": "CPF", "document_number": "12345678901" } }' ``` #### Card data requirements The `card_data` field is **required** for `CREDIT_CARD` and `DEBIT_CARD` payment methods and has the following validation rules: * Exactly one of `number` (card PAN), `network_token`, or `card_id` (a [card stored on file](/guides/cards-on-file)) must be provided — they are mutually exclusive * `number`, `cvv`, `network_token`, and `cryptogram` must arrive encrypted from rinne-js; plaintext values are rejected with `400 VALIDATION_ERROR` * With `card_id`, the stored card supplies the number, expiry, `last_digits`, `brand`, and `cardholder_name`; sending any of those alongside `card_id` is rejected with `400 VALIDATION_ERROR` * With `number` or `network_token`, send these fields inline: * `expiry_month`: Two-digit month (01-12) * `expiry_year`: Four-digit year (YYYY) * `cardholder_name`: Name of the cardholder as it appears on the card * `last_digits`: Last 4 digits of the card number (exactly 4 digits) * `cryptogram`: Required for authenticated/tokenized 3DS flows (directly or injected from `three_d_secure_session_id`) * `authentication_type`: Currently only `3DS` is supported #### 3DS challenge strategy flags Card transaction creation supports two optional flags: * `require_3ds`: Forces immediate `AWAITING_3DS` status and defers provider authorization until `/authenticate` is called with an authenticated 3DS session. * `refuse_on_challenge`: Refuses challenge-triggered paths immediately (`REFUSED`) instead of moving to `AWAITING_3DS`. These flags let you tune 3DS policy without changing your entire checkout architecture: * Use `require_3ds` to enforce 3DS while staying in a pure transaction-first flow (no separate session-first policy path required). * Use `refuse_on_challenge` to fail fast when you intentionally do not support challenge handling for a merchant or transaction segment. When `refuse_on_challenge` blocks a challenge-triggered path, the transaction uses: * `status`: `REFUSED` * `status_reason`: `CHALLENGE_NOT_ALLOWED` When `refuse_on_challenge` is enabled, non-challenge transactions still follow normal provider processing. `require_3ds` and `refuse_on_challenge` cannot both be `true` in the same transaction request. #### Tokenized payments For digital wallet payments (Apple Pay, Google Pay), use the `network_token` field instead of the card number: * Set `wallet_type` to `APPLE_PAY` or `GOOGLE_PAY` * Provide the `network_token` from the wallet provider * Include the `cryptogram` for secure authentication * Optionally include `display_name` for user-friendly card identification Use [rinne-js](/rinne-js) to easily integrate Apple Pay and Google Pay buttons in your web application. The SDK handles wallet authentication and provides encrypted card data ready for transaction processing. ## Transaction status Transactions move through different statuses during their lifecycle: ```mermaid theme={null} stateDiagram-v2 [*] --> PROCESSING: Create transaction PROCESSING --> WAITING_PAYMENT: PIX QR code generated PROCESSING --> AUTHORIZED: Card authorized PROCESSING --> AWAITING_3DS: 3DS required AWAITING_3DS --> PROCESSING: Transaction authenticated PROCESSING --> REFUSED: Payment declined PROCESSING --> FAILED: Provider error AWAITING_3DS --> REFUSED: Authentication failed WAITING_PAYMENT --> APPROVED: Customer pays WAITING_PAYMENT --> EXPIRED: Payment timeout WAITING_PAYMENT --> PENDING_CANCELLATION: Cancel requested PENDING_CANCELLATION --> CANCELLED: Cancellation confirmed AUTHORIZED --> APPROVED: Capture payment APPROVED --> PENDING_REFUND: Refund initiated PENDING_REFUND --> PARTIALLY_REFUNDED: Partial refund completed PENDING_REFUND --> REFUNDED: Full refund completed APPROVED --> CHARGEDBACK: Dispute REFUSED --> [*] FAILED --> [*] EXPIRED --> [*] ``` | Status | Description | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------ | | `PROCESSING` | Transaction is being processed | | `WAITING_PAYMENT` | Awaiting customer payment (PIX) | | `AWAITING_3DS` | Paused until your integration runs [3DS authentication](/guides/three-d-secure-authentication) and calls `/authenticate` | | `AUTHORIZED` | Payment authorized but not captured | | `APPROVED` | Payment successfully processed | | `REFUSED` | Payment declined by provider/acquirer for a business reason | | `FAILED` | Provider HTTP call errored (4xx/5xx, timeout, network error) | | `EXPIRED` | Payment window expired | | `PENDING_REFUND` | Refund is being processed | | `REFUNDED` | Fully refunded | | `PARTIALLY_REFUNDED` | Partially refunded | | `CHARGEDBACK` | Disputed and reversed | | `PENDING_CANCELLATION` | Cancellation in progress (BOLEPIX only) | | `CANCELLED` | Transaction cancelled before payment (BOLEPIX only) | **REFUSED vs FAILED:** `REFUSED` means the provider explicitly declined the transaction for a business reason (e.g., insufficient funds, invalid card). `FAILED` means the HTTP call to the provider errored (4xx/5xx, timeout, network failure). Both are terminal states. For `REFUSED` outcomes, `status_reason` can include values such as `PROVIDER`, `ACQUIRER`, `ANTIFRAUD`, `NO_ACQUIRER`, `ACQUIRER_TIMEOUT`, and `CHALLENGE_NOT_ALLOWED`. ## Cancelling BOLEPIX transactions You can cancel BOLEPIX transactions that are in `WAITING_PAYMENT` status before the customer pays: ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/transactions/TRANSACTION_ID/cancel \ -H "x-api-key: YOUR_API_KEY" ``` Or for a specific merchant's transaction: ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/transactions/TRANSACTION_ID/cancel \ -H "x-api-key: YOUR_API_KEY" ``` ### Cancellation behavior When you cancel a BOLEPIX transaction: 1. The transaction status changes to `PENDING_CANCELLATION` 2. A cancellation request is sent to the provider 3. If successful, the status changes to `CANCELLED` 4. If the provider times out, the status remains `PENDING_CANCELLATION` **Non-terminal cancellation:** The cancellation may be non-terminal. If the provider times out, the transaction remains in `PENDING_CANCELLATION`. Final reconciliation to `CANCELLED` is performed asynchronously when the provider sends the `bolepix.cancelled` webhook. Poll the transaction or subscribe to the `transaction.status-changed` webhook rather than assuming immediate finality. ### Cancellation requirements * **Payment method**: Only BOLEPIX transactions can be cancelled * **Status**: Transaction must be in `WAITING_PAYMENT` status * **Permissions**: Requires `transaction.cancel` or `merchant.transaction.cancel` permission ## Amounts in cents All monetary values in the API are represented in cents (integer values): * `10000` = R\$ 100.00 * `1050` = R\$ 10.50 * `99` = R\$ 0.99 Always use integers for amounts. Never use decimal values or floating-point numbers. ### Minimum amount validation Transaction amounts must be greater than the calculated fee. When creating a transaction, the system validates that the amount covers the fee based on the merchant's fee policy. If the amount is insufficient, the API returns a validation error: ```json theme={null} { "error": { "code": "VALIDATION_ERROR", "message": "Validation error", "status": 400, "details": { "issues": [ { "field": "amount", "type": "OUT_OF_RANGE", "value": 100, "constraints": { "minimumAmount": 351, "calculatedFee": 350 } } ] } } } ``` The `constraints` object provides: * `minimumAmount`: The minimum amount required for the transaction (calculated fee + 1 cent) * `calculatedFee`: The total fee that would be charged for this transaction To avoid this error, ensure your transaction amount is strictly greater than the calculated fee. You can use the fee policy details to estimate fees before creating transactions. ## Request ID for idempotency The `request_id` field ensures idempotent transaction creation. If you retry a request with the same `request_id`, you'll receive the existing transaction instead of creating a duplicate. ```bash theme={null} # First request - creates transaction POST /v1/transactions { "request_id": "order-123", "amount": 10000, ... } # Response: 201 Created # Retry with same request_id - returns existing transaction POST /v1/transactions { "request_id": "order-123", "amount": 10000, ... } # Response: 200 OK (same transaction) ``` ## Automatic anticipation Each transaction includes an `automatic_anticipation` boolean field, set at creation time based on the affiliation's `anticipation_type`. When the affiliation uses `AUTOMATIC` anticipation, this field is `true`; otherwise it is `false`. The value is immutable after creation. This field can be used as a condition in [pricing rules](/concepts/pricing#transaction-fields) to apply different fee rates depending on whether a transaction is automatically anticipated. ## Transaction pricing Each transaction has associated fees and costs: ```json theme={null} { "pricing": { "fee": { "percentage": 3.5, "flat": 0, "minimum_price": 0 }, "cost": { "percentage": 0.5, "flat": 0, "minimum_price": 0 } } } ``` * **Fee**: Amount charged to the merchant (your revenue) * **Cost**: Amount paid to the provider (your cost) Pricing is calculated using fee and cost policies configured for the merchant. ## Refunds You can refund approved PIX, credit card, and debit card transactions partially or fully through the same endpoint. The request body is identical regardless of payment method: ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/transactions/TRANSACTION_ID/refunds \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "refund_amount": 5000, "refund_reason": "CUSTOMER_REQUEST", "description": "Customer requested refund", "metadata": { "refund_source": "customer_request", "ticket_id": "support-123" } }' ``` ### Supported payment methods | Payment method | Refund support | | ---------------------------- | ------------------------------------------------------------------------------------------------------ | | `PIX` | Full and partial; multiple refunds allowed up to the remaining refundable amount | | `CREDIT_CARD` / `DEBIT_CARD` | One successful refund per transaction. Full any day; partial only from D+1 onward (see warnings below) | | `BOLEPIX` | Not supported | ### Refund rules * BOLEPIX transactions do not support refunds. * Only `APPROVED` or `PARTIALLY_REFUNDED` transactions can be refunded. * Refund amount cannot exceed: `approved_amount - refunded_amount`. * When a refund is initiated, the transaction status changes to `PENDING_REFUND`. * Partial refunds change the transaction status to `PARTIALLY_REFUNDED`; full refunds change it to `REFUNDED`. * Only one refund can be in progress at a time per transaction (`PENDING`, `PROCESSING`, or `TIMEOUT` status). * Card transactions accept only one successful refund. Once a `CREDIT_CARD` or `DEBIT_CARD` transaction has any `COMPLETED` refund, it is treated as terminally refunded and any further refund request is rejected synchronously with `HTTP 400`. PIX transactions are not subject to this rule. * Balance is checked before processing refunds to ensure sufficient funds. **Card transactions accept only one successful refund.** Once a `CREDIT_CARD` or `DEBIT_CARD` transaction has a refund in `COMPLETED` status, any additional refund request — partial or full — is rejected synchronously with `HTTP 400`. Plan the refund amount up front: if you need to refund a card transaction in pieces, the first successful refund must cover the full remaining amount you intend to return. Prior refunds in `FAILED` or `CANCELLED` status do not count toward this rule, so you can submit a new refund after a failed attempt. **Same-day partial refunds are not allowed for card transactions.** When the refund request lands on the same calendar day as the transaction's `approved_at` (D0), only a refund for the **full remaining refundable amount** is accepted. Partial refunds are allowed only from the next calendar day (D+1) onward. Same-day partial requests fail synchronously with `HTTP 400`. On D0 you can still issue a full refund, and on D+1 (or later) any partial amount up to the remaining refundable balance is accepted. See [Refunding card transactions](/guides/card-transactions#refunding-card-transactions) for an example. ### Refund reasons | Reason | Description | | -------------------------------- | ------------------------------ | | `CUSTOMER_REQUEST` | Customer requested refund | | `FRAUD` | Fraudulent transaction | | `BANKING_ERROR` | Banking system error | | `PIX_WITHDRAWAL_OR_CHANGE_ERROR` | PIX withdrawal or change error | ### Refund processing semantics The refund response always includes the current refund status. How that status is determined depends on the payment method. #### PIX refunds PIX refunds are processed asynchronously to improve reliability: 1. A refund record is created with `PENDING` status. 2. The transaction status changes to `PENDING_REFUND`. 3. The system attempts to process the refund with the provider. 4. On success, the refund status updates to `COMPLETED`. 5. On timeout, the refund status updates to `TIMEOUT` and is queried again later. 6. On other transient errors, the refund stays `PENDING` for automatic retry. For PIX, transient provider errors (timeout, insufficient balance, etc.) cause the refund to be automatically retried until it succeeds or is manually cancelled. #### Card refunds Card refunds resolve as far as possible on the same API call: * A definitive provider response (success or rejection) returns synchronously with the refund already in its terminal state — `COMPLETED` for a successful cancel, `FAILED` for a definitive rejection (for example, an acquirer business decline). `FAILED` card refunds are **terminal and not retried**; submit a new refund request to try again. * An uncertain outcome (acquirer 5xx or network timeout) triggers an inline status query against the acquirer. If that query resolves the outcome, you still get `COMPLETED` or `FAILED` on the same call. Otherwise the refund moves to `TIMEOUT` and is reconciled asynchronously. * A `PENDING_REFUND` response on a card transaction means the request reached us but did not produce a definitive answer; we will retry the (idempotent) cancel. ### Cancelling a pending refund You can cancel a refund that is in `PENDING` status: ```bash theme={null} curl -X PATCH https://api-sandbox.rinne.com.br/core/v1/transactions/TRANSACTION_ID/refunds/cancel \ -H "x-api-key: YOUR_API_KEY" ``` When a refund is cancelled: * The refund status changes to `CANCELLED` * The transaction status reverts based on remaining refunds: * `APPROVED` if no other completed refunds exist * `PARTIALLY_REFUNDED` if other completed refunds exist Only refunds with `PENDING` status can be cancelled. Refunds that are `PROCESSING`, `COMPLETED`, `FAILED`, or `TIMEOUT` cannot be cancelled. ### Refund information in transaction response When you retrieve a transaction with full details, the response includes a `refunds` array with complete refund information: ```json theme={null} { "id": "tx_123456789", "status": "PARTIALLY_REFUNDED", "amount": 10000, "refunded_amount": 5000, "refunds": [ { "id": "ref_123456789", "transaction_id": "tx_123456789", "request_id": "req_refund_123", "amount": 5000, "status": "COMPLETED", "reason": "CUSTOMER_REQUEST", "description": "Customer requested partial refund", "provider_refund_identification": "D13935893202307271346SPpDyb4f123", "metadata": { "refund_source": "customer_request", "ticket_id": "support-123" }, "created_at": "2023-12-01T10:00:00.000Z", "updated_at": "2023-12-01T10:00:00.000Z" } ] } ``` Each refund includes: * **id**: Unique refund identifier * **amount**: Refund amount in cents * **status**: Refund status (`PENDING`, `PROCESSING`, `COMPLETED`, `FAILED`, `TIMEOUT`, `CANCELLED`) * **reason**: Reason for the refund * **description**: Optional additional description (nullable) * **provider\_refund\_identification**: Provider-specific identifier for the refund, such as E2E ID for PIX or ARN for cards (nullable) * **metadata**: Optional custom key-value pairs for tracking and integration (nullable) * **created\_at** / **updated\_at**: Timestamps ### Refund status lifecycle | Status | Description | | ------------ | -------------------------------------------- | | `PENDING` | Refund created, awaiting processing or retry | | `PROCESSING` | Refund is being processed by the provider | | `COMPLETED` | Refund successfully processed | | `FAILED` | Refund failed permanently | | `TIMEOUT` | Provider request timed out, will be retried | | `CANCELLED` | Refund was manually cancelled | ### Automatic refund processing Rinne automatically monitors and processes refunds through background jobs: * **Pending refunds**: Processed every 30 minutes. PENDING refunds older than 2 minutes are retried with the provider. * **Timeout refunds**: Checked every 5 minutes. TIMEOUT refunds are queried at the provider to sync their status. If not found, they revert to PENDING for reprocessing. This ensures refund statuses stay synchronized with payment providers without manual intervention. ## Retrieving transactions ### Get a specific transaction You can retrieve a transaction by ID without specifying the merchant ID: ```bash theme={null} curl "https://api-sandbox.rinne.com.br/core/v1/merchants/transactions/TRANSACTION_ID" \ -H "x-api-key: YOUR_API_KEY" ``` This endpoint returns full transaction details including ledger entries, refunds, and settlement information. The transaction must belong to a merchant within your authenticated organization. ### List transactions for a specific merchant ```bash theme={null} curl "https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/transactions?status=APPROVED&page=1&limit=20" \ -H "x-api-key: YOUR_API_KEY" ``` ### List transactions for all merchants (organization) ```bash theme={null} curl "https://api-sandbox.rinne.com.br/core/v1/merchants/transactions?created_at_from=2025-01-01T00:00:00Z&created_at_to=2025-01-31T23:59:59Z" \ -H "x-api-key: YOUR_ORG_API_KEY" ``` ## Filtering and sorting Transactions support extensive filtering: * `status`: Filter by transaction status (array) * `payment_method`: Filter by payment method * `amount_from` / `amount_to`: Filter by amount range * `created_at_from` / `created_at_to`: Filter by date range * `provider`: Filter by provider * `sort`: Sort by fields (e.g., `-created_at,amount`) ## Transaction metadata Store custom data with transactions using the `metadata` field: ```json theme={null} { "metadata": { "order_id": "order-123", "customer_id": "cust-456", "store_location": "SP-001" } } ``` Metadata is returned with the transaction and can be used for reconciliation and reporting. ## Transaction receipts Generate PDF receipts for completed PIX transactions, including both original payments and refunds. ### Receipt eligibility Receipts are available for transactions that meet these requirements: * **Status**: `APPROVED`, `REFUNDED`, or `PARTIALLY_REFUNDED` * **Payment method**: PIX only (currently supported) * **Payer information**: Transaction must have payer data * **End-to-end ID**: Transaction must have a valid PIX end-to-end identifier ### Receipt types * **PAYMENT**: Receipt for the original transaction payment * **REFUND**: Receipt for each completed refund with a provider identification ### Generating receipts ```bash theme={null} curl "https://api-sandbox.rinne.com.br/core/v1/transactions/TRANSACTION_ID/receipt" \ -H "x-api-key: YOUR_API_KEY" ``` Or for a specific merchant's transaction: ```bash theme={null} curl "https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/transactions/TRANSACTION_ID/receipt" \ -H "x-api-key: YOUR_API_KEY" ``` ### Receipt response The response includes a list of receipts (payment and any refunds): ```json theme={null} { "receipts": [ { "type": "PAYMENT", "transaction_id": "tx_123456789", "refund_id": null, "amount": 10000, "currency": "BRL", "identification": "E12345678202501211034567890", "created_at": "2025-01-21T10:00:00.000Z", "payer": { "name": "John Doe", "document": "***.456.789-**", "bank_name": "BCO SANTANDER (BRASIL) S.A.", "ispb": "90400888", "branch": "0001", "account": "12345678" }, "receiver": { "name": "Company Name Ltda", "document": "12.345.678/0001-90", "bank_name": "BCO DO BRASIL S.A.", "ispb": "00000000", "branch": "1234", "account": "00012345" }, "pdf_file_base64": "JVBERi0xLjQK..." }, { "type": "REFUND", "transaction_id": "tx_123456789", "refund_id": "ref_987654321", "amount": 5000, "currency": "BRL", "identification": "D13935893202601271529YV9B1m6o2Ih", "created_at": "2025-01-22T14:30:00.000Z", "payer": { "name": "Company Name Ltda", "document": "12.345.678/0001-90", "bank_name": "BCO DO BRASIL S.A.", "ispb": "00000000", "branch": "1234", "account": "00012345" }, "receiver": { "name": "John Doe", "document": "***.456.789-**", "bank_name": "BCO SANTANDER (BRASIL) S.A.", "ispb": "90400888", "branch": "0001", "account": "12345678" }, "pdf_file_base64": "JVBERi0xLjQK..." } ] } ``` ### Payer/receiver direction * **Payment receipts**: Payer is the customer, receiver is the merchant * **Refund receipts**: Payer is the merchant, receiver is the customer (reversed) CPF documents are masked for privacy (e.g., `***.456.789-**`), while CNPJ documents are shown in full. ## Next steps Accept Apple Pay & Google Pay on the web Learn how to process PIX transactions Receive transaction status updates Explore transaction endpoints # Authentication Source: https://docs.rinne.com.br/guides/authentication Learn how to authenticate with the Rinne API using API keys and JWT tokens Rinne supports two authentication methods: **API keys** for server-to-server integrations and **JWT tokens** for user-facing applications. Both methods enforce granular [permission checks](/guides/user-management#roles-and-permissions) on every request. ## API key authentication Include your API key in the `x-api-key` header for all requests: ```bash theme={null} curl https://api-sandbox.rinne.com.br/core/v1/companies/me \ -H "x-api-key: YOUR_API_KEY" ``` Keep your API keys secure. Never commit them to version control, expose them in client-side code, or share them over insecure channels. ### How API keys work Each API key is: * **Hashed on creation** — Rinne stores only a SHA-256 hash. The raw key is returned exactly once at creation time (show-once) and can never be retrieved again. * **Scoped with permissions** — Every key carries its own set of [permissions](/guides/user-management#roles-and-permissions), enforced on every request. By default, keys are created with full access (`*.*`), but you can restrict them to specific operations. * **Optionally time-limited** — You can set an expiration date. Expired keys are automatically rejected. * **Soft-revocable** — Revoked keys stop authenticating immediately but their records are retained for audit. ### Authentication scope Your API key determines which resources you can access based on the company it belongs to: * **Organization scope** — Organization API keys can access organization-level resources and all merchants under the organization. * **Merchant scope** — Merchant API keys can only access the merchant's own resources. ## Managing API keys API key management is available through the API and requires **JWT authentication** — a leaked API key cannot be used to create or revoke keys, even if it contains permissions for doing so. API key management endpoints require JWT authentication and the `api_key.create`, `api_key.list`, or `api_key.delete` permissions respectively. ### Creating an API key You must be authenticated with a JWT token (not an API key) and have the `api_key.create` permission. ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/api-keys \ -H "Authorization: Bearer JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Production backend", "permissions": ["transaction.create", "transaction.list", "banking.view"], "expires_at": "2027-01-01T00:00:00Z" }' ``` A label to identify the key (max 100 characters). Permission strings granted to the key. Omit for full access (`*.*`). Must be valid permissions from the [permission catalog](/guides/user-management#permission-categories). Future ISO 8601 datetime when the key stops authenticating. Omit for a non-expiring key. The response includes the full key — this is the **only time** it will be shown: ```json theme={null} { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "Production backend", "key": "ak_Ab12Cd34Ef56Gh78Ij90Kl12Mn34Op56Qr78x9Zk", "prefix": "ak_Ab12", "last4": "x9Zk", "permissions": ["transaction.create", "transaction.list", "banking.view"], "last_used_at": null, "expires_at": "2027-01-01T00:00:00Z", "revoked_at": null, "created_at": "2026-07-30T12:00:00Z" } ``` Store the `key` value immediately in a secure location (e.g., a secrets manager or environment variable). You will not be able to retrieve it again. ### Listing API keys Retrieve your company's active (non-revoked, non-expired) keys. The raw key is never returned — only the masked `prefix` and `last4` are shown: ```bash theme={null} curl https://api-sandbox.rinne.com.br/core/v1/api-keys \ -H "Authorization: Bearer JWT_TOKEN" ``` ```json theme={null} [ { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "Production backend", "prefix": "ak_Ab12", "last4": "x9Zk", "permissions": ["transaction.create", "transaction.list", "banking.view"], "last_used_at": "2026-07-30T12:15:00Z", "expires_at": "2027-01-01T00:00:00Z", "revoked_at": null, "created_at": "2026-07-30T12:00:00Z" } ] ``` ### Revoking an API key Revoke a key to immediately stop it from authenticating. Revocation is permanent and cannot be undone: ```bash theme={null} curl -X DELETE https://api-sandbox.rinne.com.br/core/v1/api-keys/API_KEY_ID \ -H "Authorization: Bearer JWT_TOKEN" ``` A successful revocation returns a `204 No Content` response. ### Managing merchant API keys Organizations can manage API keys for their merchants using the merchant-scoped endpoints: ```bash Create theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/api-keys \ -H "Authorization: Bearer JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Merchant payment key", "permissions": ["transaction.create", "transaction.list"] }' ``` ```bash List theme={null} curl https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/api-keys \ -H "Authorization: Bearer JWT_TOKEN" ``` ```bash Revoke theme={null} curl -X DELETE https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/api-keys/API_KEY_ID \ -H "Authorization: Bearer JWT_TOKEN" ``` ### Limits Each company (organization or merchant) can have at most **20 active API keys** at any time. Active keys are those that are neither revoked nor expired. If you reach the limit, revoke unused keys before creating new ones. ## User authentication (JWT) For user-facing applications, Rinne provides JWT-based authentication for individual users. ### Login flow 1. **User login**: Authenticate with email/phone and password ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/auth/login \ -H "Content-Type: application/json" \ -d '{ "identifier": "user@company.com", "password": "SecurePassword123" }' ``` 2. **Company selection**: If user has multiple companies, select one ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/auth/select-company \ -H "Authorization: Bearer JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "company_id": "company-123" }' ``` 3. **Use JWT token**: Include token in Authorization header ```bash theme={null} curl https://api-sandbox.rinne.com.br/core/v1/auth/me \ -H "Authorization: Bearer JWT_TOKEN" ``` ### JWT token structure JWT tokens include: * User ID and identifiers * Selected company context * User permissions and roles * Token expiration time ## Password management ### First access New users receive a verification code via email: ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/auth/verify \ -H "Content-Type: application/json" \ -d '{ "identifier": "user@company.com", "code": "123456", "password": "SecurePassword123" }' ``` ### Forgot password Request a password reset code: ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/auth/forgot-password \ -H "Content-Type: application/json" \ -d '{ "identifier": "user@company.com" }' ``` Reset password with the code: ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/auth/reset-password \ -H "Content-Type: application/json" \ -d '{ "identifier": "user@company.com", "code": "123456", "new_password": "NewSecurePassword123" }' ``` ### Change password Authenticated users can change their password: ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/auth/change-password \ -H "Authorization: Bearer JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "current_password": "CurrentPassword123", "new_password": "NewSecurePassword123" }' ``` ## Security best practices * Use environment variables or a secrets manager for API keys * Never commit keys to version control * Use scoped permissions — avoid `*.*` for keys that only need specific operations * Set expiration dates on API keys when possible * Revoke keys that are no longer in use * Rotate keys periodically by creating a new key before revoking the old one * If a key is compromised, revoke it immediately via JWT authentication All API requests must use HTTPS. HTTP requests will be rejected. Implement exponential backoff for retries and respect rate limits to avoid throttling. When receiving webhooks, validate the signature to ensure requests are from Rinne. ## Error responses Authentication errors return a 401 status code: ```json theme={null} { "error": { "code": "AUTHENTICATION_ERROR", "message": "Invalid or missing API key", "status": 401 } } ``` Authorization errors return a 403 status code: ```json theme={null} { "error": { "code": "AUTHORIZATION_ERROR", "message": "You don't have permission to access this resource", "status": 403 } } ``` API key management endpoints return a `403` if you attempt to access them with API key authentication instead of JWT. This is a security measure — a leaked API key must not be able to create or revoke other keys. ## Next steps Follow the quickstart guide Manage users, roles, and permissions # Card transactions Source: https://docs.rinne.com.br/guides/card-transactions Implement credit and debit card transaction flows, challenge strategy flags, and 3DS transitions. Use this guide to implement `CREDIT_CARD` and `DEBIT_CARD` transactions with the Rinne Core API. Rinne officially supports both 3DS strategies for card payments: session-first and transaction-first. ## Transaction endpoints (self vs merchant) | Context | Create transaction | Authenticate pending transaction | | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ | | Self (company) | [`POST /v1/transactions`](/api-reference/company-transactions/create-a-new-transaction) | [`POST /v1/transactions/{id}/authenticate`](/api-reference/3d-secure/authenticate-a-transaction-with-3d-secure) | | Organization on behalf of merchant | [`POST /v1/merchants/{merchantId}/transactions`](/api-reference/transactions/create-a-new-transaction-for-a-specific-merchant) | [`POST /v1/merchants/{merchantId}/transactions/{id}/authenticate`](/api-reference/3d-secure/authenticate-a-merchants-transaction-with-3d-secure) | ## Prerequisites * Active affiliation for card payments. * `ECOMMERCE` capture enabled for online transactions. * One unique `request_id` per payment attempt. * Encrypted card values from rinne-js secure components. Do not collect raw PAN/CVC in custom inputs. Forward encrypted values from [Card Element](/rinne-js/card-element) only. ## Sending card data Rinne's API only accepts card credentials in encrypted form. The `card_data.number`, `card_data.cvv`, `card_data.network_token`, and `card_data.cryptogram` values must arrive encrypted — you can recognize encrypted values by their `ev:` prefix. Requests where one of these fields arrives in plain text are rejected with `400 VALIDATION_ERROR` before any processing happens. These encrypted values come from the rinne-js secure components: the [Card Element](/rinne-js/card-element) encrypts the card number and CVV in the customer's browser, and the [wallet elements](/rinne-js/wallet-elements) emit `network_token` and `cryptogram` already encrypted. Raw card data never touches your servers. ## Required transaction shape Payment provider configured for the affiliation. Idempotency key for creation. Amount in cents. Use `ECOMMERCE` for card-not-present checkout. `CREDIT_CARD` or `DEBIT_CARD`. Card payload for card transactions. ## Card data rules * Send exactly one of `card_data.number` (raw card entry), `card_data.network_token` (wallet), or `card_data.card_id` ([stored card](/guides/cards-on-file)). * Send `number` and `cvv` as encrypted values from rinne-js. Values that reach the API unencrypted are rejected with `400 VALIDATION_ERROR`. * With `number` or `network_token`, send `expiry_month` (`MM`), `expiry_year` (`YYYY`), `last_digits` (the last 4 digits of the card number, exactly 4 digits), and `cardholder_name` as it appears on the card. * With `card_id`, Rinne resolves those fields from the stored card — see [Example: charge a stored card](#example-charge-a-stored-card). * For authenticated/tokenized flows, provide cryptogram directly or via `three_d_secure_session_id` injection. * For wallet tokenized flows, include `wallet_type` (`APPLE_PAY` or `GOOGLE_PAY`). Do not send more than one of `number`, `network_token`, and `card_id` in the same request. ## 3DS challenge strategy flags `TransactionCreateRequest` supports two optional card-only flags: When `true`, card transaction creation always goes directly to `AWAITING_3DS`. Provider authorization is deferred until `/authenticate` is called with an authenticated 3DS session. When `true`, any path that would require a challenge (risk-triggered or soft-decline-triggered) is refused immediately (`REFUSED`) instead of transitioning to `AWAITING_3DS`. ### Why these flags are useful | Flag | Immediate status behavior | What your backend still handles | What this avoids | | --------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- | | `require_3ds` | Always creates card transactions in `AWAITING_3DS` | Create 3DS session, complete challenge when required, and call `/authenticate` | Implementing a second session-first architecture only to enforce 3DS policy | | `refuse_on_challenge` | Returns `REFUSED` with `status_reason=CHALLENGE_NOT_ALLOWED` when challenge would be required | Handle refusal/retry paths only | Implementing challenge UX and pending `AWAITING_3DS` operational queues for that scope | With `require_3ds`, organizations that already run transaction-first can enforce 3DS for all or selected transactions without adding a separate session-first policy path. The transaction enters `AWAITING_3DS` immediately, which is the status you already handle in this flow. With `refuse_on_challenge`, challenge-triggered flows do not enter `AWAITING_3DS`; they stop immediately as `REFUSED`, so your operation fails fast and carries no challenge-handling workload. `refuse_on_challenge` changes only challenge-triggered paths. Transactions that do not require challenge continue normal provider processing. ### `CHALLENGE_NOT_ALLOWED` status reason When `refuse_on_challenge` is `true` and a challenge would be required (risk-triggered or soft-decline-triggered), transaction response uses: * `status = REFUSED` * `status_reason = CHALLENGE_NOT_ALLOWED` `require_3ds` and `refuse_on_challenge` are mutually exclusive. Requests with both set to `true` must be rejected. ### Policy granularity Because these flags are part of each transaction request, you can apply policies with fine control: * Per merchant (choose behavior by `merchantId` in your backend routing). * Per payment flow (for example, mobile app vs web checkout). * Per transaction segment (risk tier, issuer group, customer cohort, high-value purchases). ## Example: standard card transaction ```bash cURL theme={null} curl -X POST 'https://api-sandbox.rinne.com.br/core/v1/transactions' \ -H 'x-api-key: YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "provider": "RINNE", "request_id": "order-card-0001", "amount": 25990, "currency": "BRL", "capture_method": "ECOMMERCE", "payment_method": "CREDIT_CARD", "installments": 1, "card_data": { "number": "ev:encrypted:card_number", "cvv": "ev:encrypted:cvc", "expiry_month": "12", "expiry_year": "2028", "cardholder_name": "Maria Santos", "last_digits": "1111" } }' ``` ## Example: charge a stored card To charge a card you [stored on file](/guides/cards-on-file), send its `id` as `card_data.card_id` in place of the card credentials. Rinne resolves the encrypted number, `expiry_month`, `expiry_year`, `last_digits`, `brand`, and `cardholder_name` from the stored card. ```bash cURL theme={null} curl -X POST 'https://api-sandbox.rinne.com.br/core/v1/transactions' \ -H 'x-api-key: YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "provider": "RINNE", "request_id": "order-stored-card-0001", "amount": 25990, "currency": "BRL", "capture_method": "ECOMMERCE", "payment_method": "CREDIT_CARD", "installments": 1, "card_data": { "card_id": "9c1f2e3a-4b5c-4d6e-8f90-a1b2c3d4e5f6", "cvv": "ev:encrypted:cvc" } }' ``` Include `cvv` for customer-present payments that re-collect it, and omit it for merchant-initiated payments such as recurring charges. Everything outside `card_data` — installments, the 3DS challenge strategy flags, and the merchant route — behaves exactly as it does with inline card data. Do not send `number`, `network_token`, `expiry_month`, `expiry_year`, `last_digits`, `brand`, or `cardholder_name` alongside `card_id`. The stored card supplies these fields, so sending them inline is ambiguous and is rejected with `400 VALIDATION_ERROR`. A `card_id` that Rinne cannot charge is rejected with `400 VALIDATION_ERROR` on the `card_data.card_id` field, in three cases: * **No such card in scope** — the card doesn't exist, was deleted, or belongs to another company. A card is only chargeable by the company that owns it. * **The card isn't active** — its `status` is not `ACTIVE`. * **The card has no `brand`** — `brand` is optional to store, but Rinne's providers currently require it to process a transaction. Store cards with a `brand` if you intend to charge them. ## Example: force transaction-first 3DS ```bash cURL theme={null} curl -X POST 'https://api-sandbox.rinne.com.br/core/v1/transactions' \ -H 'x-api-key: YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "provider": "RINNE", "request_id": "order-card-0002", "amount": 25990, "currency": "BRL", "capture_method": "ECOMMERCE", "payment_method": "CREDIT_CARD", "card_data": { "number": "ev:encrypted:card_number", "cvv": "ev:encrypted:cvc", "expiry_month": "12", "expiry_year": "2028", "cardholder_name": "Maria Santos", "last_digits": "1111" }, "require_3ds": true }' ``` ```json Example response theme={null} { "id": "tx_123456789", "status": "AWAITING_3DS", "amount": 25990, "currency": "BRL" } ``` ## Example: fail fast when challenge is not allowed ```bash cURL theme={null} curl -X POST 'https://api-sandbox.rinne.com.br/core/v1/transactions' \ -H 'x-api-key: YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "provider": "RINNE", "request_id": "order-card-0003", "amount": 25990, "currency": "BRL", "capture_method": "ECOMMERCE", "payment_method": "CREDIT_CARD", "card_data": { "number": "ev:encrypted:card_number", "cvv": "ev:encrypted:cvc", "expiry_month": "12", "expiry_year": "2028", "cardholder_name": "Maria Santos", "last_digits": "1111" }, "refuse_on_challenge": true }' ``` ```json Example response theme={null} { "id": "tx_123456789", "status": "REFUSED", "status_reason": "CHALLENGE_NOT_ALLOWED" } ``` ## Card transaction lifecycle highlights | Status | Meaning | Typical next action | | -------------- | ------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------- | | `PROCESSING` | Authorization in progress | Wait for webhook/result fetch | | `AWAITING_3DS` | Paused until your integration runs 3DS — Rinne does not contact the cardholder | Create a 3DS session, mount the [3DS element](/rinne-js/three-d-secure-element), then call `/authenticate` | | `AUTHORIZED` | Authorized but not captured | Follow capture policy | | `APPROVED` | Payment accepted | Fulfill order | | `REFUSED` | Payment declined | Retry with new attempt/payment method | Reproduce approvals, declines, and provider errors on demand in the sandbox with deterministic [test scenarios](/guides/testing) — the transaction amount selects the final payment outcome. ## Retry and idempotency 1. Generate one `request_id` per attempt. 2. Reuse same `request_id` only for safe retries of the same attempt. 3. Use a new `request_id` for a new customer attempt. ## Refunding card transactions Send `refund_amount` equal to the remaining refundable amount for a full refund, or any smaller amount for a partial refund (subject to the rules below). ```bash cURL theme={null} curl -X POST 'https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/transactions/TRANSACTION_ID/refunds' \ -H 'x-api-key: YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "refund_amount": 25990, "refund_reason": "CUSTOMER_REQUEST", "description": "Full refund - same day" }' ``` ### Card refund rules Two independent acquirer rules apply to every card refund. Both are pre-validated server-side and surface as `HTTP 400` `VALIDATION_ERROR` before any provider round-trip. **One successful refund per card transaction.** Once a `CREDIT_CARD` or `DEBIT_CARD` transaction has a refund in `COMPLETED` status, the acquirer treats it as terminally refunded. Any additional refund request — partial or full — is rejected with `HTTP 400`. Plan the refund amount up front: if you need to return funds in pieces, the first successful refund must cover the full remaining amount you intend to return. Refunds in `FAILED` or `CANCELLED` status do not count toward this rule, so you can submit a new attempt after a failed one. **Same-day partial refunds are not allowed.** When a refund request lands on the same calendar day as the transaction's `approved_at` (D0), only a refund for the **full remaining refundable amount** is accepted. Partial refunds are allowed only from the next calendar day (D+1) onward. The acceptance matrix on a transaction approved with `approved_amount = 10000` and **no prior `COMPLETED` refund**: | Day | Refund amount | Result | | ------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | D0 (same day) | 10000 (full) | Accepted (outcome resolved per response semantics below) | | D0 (same day) | 5000 (partial) | Rejected — `400` `VALIDATION_ERROR`: `Partial refund on the same day as the transaction is not allowed by the acquirer; submit the full remaining amount or wait until D+1` | | D+1 or later | 5000 (partial) | Accepted (outcome resolved per response semantics below) | | D+1 or later | 10000 (full) | Accepted (outcome resolved per response semantics below) | Once any refund on the transaction reaches `COMPLETED`, every row above is rejected with `HTTP 400` (`Card transaction has already been refunded; the acquirer does not allow additional refunds`), regardless of the day or whether the new request is partial or full. ### Card refund response semantics Card refunds resolve synchronously whenever the acquirer returns a definitive answer: * A successful cancel returns `refund.status = COMPLETED` and the transaction status becomes `REFUNDED` (full) or `PARTIALLY_REFUNDED` (partial). * A definitive provider rejection (for example, an acquirer business decline) returns `refund.status = FAILED`. **Failed card refunds are terminal and are not retried** — submit a new refund request to try again. * An uncertain outcome (acquirer 5xx or network timeout) triggers an inline status query against the acquirer. If that resolves the outcome, you still get `COMPLETED` or `FAILED` on the same call. Otherwise the refund moves to `TIMEOUT` and is reconciled asynchronously. * A `PENDING_REFUND` transaction status on a card transaction means the request was accepted but did not produce a definitive answer; we will retry the (idempotent) cancel. For the full set of refund rules, statuses, and the cancel-pending-refund endpoint, see [Refunds in the Transactions concept](/concepts/transactions#refunds). ## Integration with rinne-js * Use [Card Element](/rinne-js/card-element) for encrypted card capture. * Use [Guide: Card + 3DS Checkout](/rinne-js/guides/card-3ds-checkout) for frontend 3DS orchestration. * Use [3D Secure authentication](/guides/three-d-secure-authentication) for backend 3DS API contracts. Your card integration is production-ready when you correctly handle `PROCESSING`, `AWAITING_3DS`, `APPROVED`, `REFUSED`, and the `CHALLENGE_NOT_ALLOWED` status reason. # Cards on file Source: https://docs.rinne.com.br/guides/cards-on-file Store encrypted card credentials on file and charge them later with a stable card reference, without your systems handling raw card numbers. Use this guide to store cards on file and manage them with the Rinne Core API. Storing a card saves its encrypted number and card metadata, and returns a stable `id` you use to [charge the card](#charge-a-stored-card) later, or to retrieve or delete it. Rinne stores card credentials as ciphertext only — it never receives or persists a raw card number, and never stores a CVC. Supply the card number as an encrypted value, the same way you do for card transactions. ## Access levels (self vs merchant) Cards on file are available at two access levels, mirroring transactions: | Context | Base path | | -------------------------------------- | ---------------------------------- | | Self (company acting on its own cards) | `/v1/cards` | | Organization on behalf of a merchant | `/v1/merchants/{merchantId}/cards` | On merchant routes, the authenticated organization must own `{merchantId}`; otherwise the request returns `404`. Every operation exists at both levels. The merchant routes take the same request and response shapes as the self routes, under `/v1/merchants/{merchantId}/cards`. ## Endpoints | Method | Path | Permission | Description | | -------- | ------------------------------------------- | ---------------------- | ------------------------------- | | `POST` | `/v1/cards` | `card.create` | Store a card | | `GET` | `/v1/cards` | `card.list` | List stored cards | | `GET` | `/v1/cards/{cardId}` | `card.view` | Get a stored card | | `DELETE` | `/v1/cards/{cardId}` | `card.delete` | Delete a stored card | | `POST` | `/v1/merchants/{merchantId}/cards` | `merchant.card.create` | Store a card for a merchant | | `GET` | `/v1/merchants/{merchantId}/cards` | `merchant.card.list` | List a merchant's stored cards | | `GET` | `/v1/merchants/{merchantId}/cards/{cardId}` | `merchant.card.view` | Get a merchant's stored card | | `DELETE` | `/v1/merchants/{merchantId}/cards/{cardId}` | `merchant.card.delete` | Delete a merchant's stored card | ## Supplying the card number Rinne's API only accepts card numbers in encrypted form. Send `card.number` as an encrypted value — you can recognize encrypted values by their `ev:` prefix — exactly as you do for [card transactions](/guides/card-transactions#sending-card-data). A request where `number` arrives in plain text is rejected with `400 VALIDATION_ERROR` before any processing happens. These encrypted values come from the rinne-js [Card Element](/rinne-js/card-element), which encrypts the card number in the customer's browser so raw card data never touches your servers. Never collect or forward a raw card number from your own inputs. Use encrypted values from the [Card Element](/rinne-js/card-element). ## What to send A stored card needs the encrypted `number`, its `expiry_month` and `expiry_year`, and the card's `last_digits`. You can optionally include metadata such as the card `brand` and `cardholder_name`. The [Card Element](/rinne-js/card-element) returns `brand` and `last_digits` for you. For the complete request and response schema — every field, type, and constraint — see the [Cards API reference](/api-reference/cards/store-a-card). No CVC is sent or stored when you store a card, and expiry is validated for format only — a card with a past expiry date can still be stored. Include `brand` for any card you intend to charge. It's optional to store, but Rinne's providers currently require a brand to process a transaction, so a stored card without one is rejected at transaction time. ## Store a card ```bash Self theme={null} curl -X POST 'https://api-sandbox.rinne.com.br/core/v1/cards' \ -H 'x-api-key: YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "card": { "number": "ev:RFVC:jNQ8kR2mvZ9pXwLb7tYc1aVdGhFsK4o:aB3cD4eF5gH6iJ7kL8mN9oP0qR1sT2uV:$", "expiry_month": "12", "expiry_year": "2030", "last_digits": "4242", "brand": "VISA", "cardholder_name": "Maria Santos" } }' ``` ```bash Merchant theme={null} curl -X POST 'https://api-sandbox.rinne.com.br/core/v1/merchants/f47ac10b-58cc-4372-a567-0e02b2c3d479/cards' \ -H 'x-api-key: YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "card": { "number": "ev:RFVC:jNQ8kR2mvZ9pXwLb7tYc1aVdGhFsK4o:aB3cD4eF5gH6iJ7kL8mN9oP0qR1sT2uV:$", "expiry_month": "12", "expiry_year": "2030", "last_digits": "4242", "brand": "VISA", "cardholder_name": "Maria Santos" } }' ``` ```json 201 Created theme={null} { "id": "9c1f2e3a-4b5c-4d6e-8f90-a1b2c3d4e5f6", "status": "ACTIVE", "brand": "VISA", "last_digits": "4242", "expiry_month": "12", "expiry_year": "2030", "cardholder_name": "Maria Santos", "created_at": "2026-07-02T12:00:00.000Z", "updated_at": "2026-07-02T12:00:00.000Z" } ``` The response returns the card's `id` — the reference you use to charge, retrieve, or delete the card — along with its stored metadata and timestamps. The encrypted number is never returned by any endpoint. Keep the `id` you get back — it's how you reference the stored card later. To change a stored card's details, delete it and store it again. ## Charge a stored card To charge a stored card, pass its `id` as `card_data.card_id` when you [create a card transaction](/guides/card-transactions). Rinne resolves the encrypted number, expiry, `last_digits`, `brand`, and `cardholder_name` from the stored card, so `card_data` needs little more than the reference itself: ```json card_data theme={null} { "card_id": "9c1f2e3a-4b5c-4d6e-8f90-a1b2c3d4e5f6", "cvv": "ev:encrypted:cvc" } ``` Include `cvv` for customer-present payments that re-collect it, and omit it for merchant-initiated payments such as recurring charges. A stored card can only be charged by the company that owns it — a transaction that references a card outside your scope (one that doesn't exist, was deleted, or belongs to another company) is rejected with `400 VALIDATION_ERROR` on `card_data.card_id`, not the `404` that reading or deleting a card on the cards API returns. See [Card transactions](/guides/card-transactions#example-charge-a-stored-card) for the complete request, the fields you must not send alongside `card_id`, and the cases where a `card_id` is rejected. ## List stored cards Returns the company's stored cards, newest first, paginated. ```bash cURL theme={null} curl 'https://api-sandbox.rinne.com.br/core/v1/cards?page=1&limit=20' \ -H 'x-api-key: YOUR_API_KEY' ``` ```json 200 OK theme={null} { "data": [ { "id": "9c1f2e3a-4b5c-4d6e-8f90-a1b2c3d4e5f6", "status": "ACTIVE", "brand": "VISA", "last_digits": "4242", "expiry_month": "12", "expiry_year": "2030", "cardholder_name": "Maria Santos", "created_at": "2026-07-02T12:00:00.000Z", "updated_at": "2026-07-02T12:00:00.000Z" } ], "pagination": { "page": 1, "limit": 20, "total": 1, "totalPages": 1, "hasNext": false, "hasPrev": false } } ``` Use `page` (default `1`) and `limit` (default `20`, max `100`) to page through results. ## Get a stored card ```bash cURL theme={null} curl 'https://api-sandbox.rinne.com.br/core/v1/cards/9c1f2e3a-4b5c-4d6e-8f90-a1b2c3d4e5f6' \ -H 'x-api-key: YOUR_API_KEY' ``` Returns the same card object as the store response. A card that doesn't exist within the caller's scope returns `404`. ## Delete a stored card ```bash cURL theme={null} curl -X DELETE 'https://api-sandbox.rinne.com.br/core/v1/cards/9c1f2e3a-4b5c-4d6e-8f90-a1b2c3d4e5f6' \ -H 'x-api-key: YOUR_API_KEY' ``` A successful delete returns `204 No Content`. Deletion is permanent: a deleted card is excluded from all subsequent reads and cannot be restored. ## Scope and isolation * A stored card is only visible to the company that owns it. A request for a card outside the caller's scope — another company, or a sibling merchant — returns `404`. * On merchant routes, the authenticated organization must own `{merchantId}`; otherwise the request returns `404`. ## Errors * `400 VALIDATION_ERROR` — the request failed validation, most commonly a plain-text `number` (send it encrypted) or `last_digits` that isn't exactly 4 digits. * `404` — the card, or the merchant on a merchant route, isn't found within the caller's scope. See [Error handling](/guides/error-handling) for the standard error envelope and the full set of status codes. ## Next steps Process credit and debit card payments with 3DS. Capture encrypted card data in the browser with rinne-js. Authenticate API requests with your API key. Handle validation and API errors consistently. # Cashouts Source: https://docs.rinne.com.br/guides/cashouts Cash out via PIX using an origin account and an external destination Cashouts move money from an **origin bank account** (your balance with the provider) to **exactly one** destination: either a registered **external bank account** (`/bank-accounts`) or a registered **external PIX key** (`/pix-keys`). Request and response fields for those resources are documented on each endpoint in the API reference. For **receiving** PIX (`/pix/keys`), balances, and statements, see Banking. For **external** destinations, cashout lifecycle, returns, and receipts, see Cashouts (Core concepts). ## Before you start * **Origin**: `origin_bank_account_id` is the bank account **debited** for the cashout. It must be eligible per your setup (for example, an affiliated account with available balance). Use the `id` returned when you list bank accounts for the merchant or company. * **Destination**: Register either a **Company bank account** or a **Company PIX key** (paths under `/bank-accounts` or `/pix-keys`). In the cashout body you send `destination_bank_account_id` **or** `destination_pix_key_id`—never both. Requests with both fields (or neither) are rejected with a `400` validation error. The `primary` flag does not act as a fallback here: cashouts created through the API always use the destination you name in the request. * **Idempotency / tracking**: `request_id` is required; use a unique string per cashout attempt (for example a UUID or your internal reference). ## 1) Register an external PIX key destination `POST` accepts `key` (required) and optional `primary`. The API resolves the key via **DICT**; the create response can include `dict_key_information` (often masked per BACEN rules). ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/pix-keys \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "key": "recipient@example.com", "primary": false }' ``` ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/companies/me/pix-keys \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "key": "recipient@example.com", "primary": false }' ``` ## 2) Register an external bank account destination Required fields: `branch_number`, `account_number`, `account_type`, `account_holder_name`, `account_holder_document_number`, `ispb`. Use digits only for branch and account numbers. ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/bank-accounts \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "branch_number": "0001", "account_number": "12345678", "account_type": "CHECKING", "account_holder_name": "Recipient Legal Name", "account_holder_document_number": "12345678901", "ispb": "18236120" }' ``` ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/companies/me/bank-accounts \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "branch_number": "0001", "account_number": "12345678", "account_type": "CHECKING", "account_holder_name": "Recipient Legal Name", "account_holder_document_number": "12345678901", "ispb": "18236120" }' ``` ## 3) Create a cashout Request body includes: | Field | Required | Notes | | ----------------------------- | ----------------------- | ------------------------------------------ | | `request_id` | Yes | Client reference for the cashout | | `origin_bank_account_id` | Yes | UUID of the account to debit | | `amount` | Yes | Amount in **cents** | | `method` | Yes | Must be `PIX` | | `destination_bank_account_id` | One of destination pair | Omit when using a PIX key destination | | `destination_pix_key_id` | One of destination pair | Omit when using a bank account destination | | `currency` | No | Defaults to `BRL` | | `metadata` | No | Arbitrary key-value object | Send exactly one destination field. If the request contains both `destination_bank_account_id` and `destination_pix_key_id`, or neither, the API returns a `400` validation error. For companies with automatic transfers enabled, Rinne creates cashouts on a schedule and picks the destination by priority—primary active bank account first, then primary active PIX key. If neither exists, that cycle is skipped. See Automatic cashouts. ### Destination: external bank account ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/cashouts \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "request_id": "cashout-order-789", "origin_bank_account_id": "223e4567-e89b-12d3-a456-426614174000", "destination_bank_account_id": "334e4567-e89b-12d3-a456-426614174001", "amount": 50000, "currency": "BRL", "method": "PIX", "metadata": { "order_id": "order-789" } }' ``` ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/banking/cashouts \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "request_id": "cashout-order-789", "origin_bank_account_id": "223e4567-e89b-12d3-a456-426614174000", "destination_bank_account_id": "334e4567-e89b-12d3-a456-426614174001", "amount": 50000, "currency": "BRL", "method": "PIX" }' ``` ### Destination: external PIX key Omit `destination_bank_account_id` and send `destination_pix_key_id` (the UUID returned from `POST …/pix-keys`). ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/cashouts \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "request_id": "cashout-order-790", "origin_bank_account_id": "223e4567-e89b-12d3-a456-426614174000", "destination_pix_key_id": "445e4567-e89b-12d3-a456-426614174002", "amount": 25000, "method": "PIX" }' ``` ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/banking/cashouts \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "request_id": "cashout-order-790", "origin_bank_account_id": "223e4567-e89b-12d3-a456-426614174000", "destination_pix_key_id": "445e4567-e89b-12d3-a456-426614174002", "amount": 25000, "method": "PIX" }' ``` ## 4) List, get details, and receipt ```bash theme={null} curl "https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/cashouts" \ -H "x-api-key: YOUR_API_KEY" ``` ```bash theme={null} curl "https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/cashouts/CASHOUT_ID" \ -H "x-api-key: YOUR_API_KEY" ``` ```bash theme={null} curl "https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/cashouts/CASHOUT_ID/receipt" \ -H "x-api-key: YOUR_API_KEY" ``` ```bash theme={null} curl "https://api-sandbox.rinne.com.br/core/v1/banking/cashouts" \ -H "x-api-key: YOUR_API_KEY" ``` ```bash theme={null} curl "https://api-sandbox.rinne.com.br/core/v1/banking/cashouts/CASHOUT_ID" \ -H "x-api-key: YOUR_API_KEY" ``` ```bash theme={null} curl "https://api-sandbox.rinne.com.br/core/v1/banking/cashouts/CASHOUT_ID/receipt" \ -H "x-api-key: YOUR_API_KEY" ``` For organization cashouts, `CASHOUT_ID` can be internal id or external request id per API docs. Receipts are only returned for certain completed states and require an **end-to-end ID**—see Cashouts or the cashout receipt endpoints in the API reference. ## Cashout status values Typical cashout statuses are `PENDING`, `PROCESSING`, `COMPLETED`, `FAILED`, `PARTIALLY_RETURNED`, and `RETURNED`. ## Webhooks Subscribe to: * `cashout.created`: emitted when the cashout is successfully created * `cashout.status-changed`: emitted when status updates See Webhooks for handling events. ## Reference endpoints | Scope | Cashouts | External bank accounts | External PIX keys | | ------------ | ------------------------------------- | ------------------------------------------ | ------------------------------------- | | Merchant | `/v1/merchants/{merchantId}/cashouts` | `/v1/merchants/{merchantId}/bank-accounts` | `/v1/merchants/{merchantId}/pix-keys` | | Organization | `/v1/banking/cashouts` | `/v1/companies/me/bank-accounts` | `/v1/companies/me/pix-keys` | # Error handling Source: https://docs.rinne.com.br/guides/error-handling Understand and handle API errors Rinne uses standard HTTP status codes and provides detailed error responses to help you debug issues quickly. ## Error response format All errors follow a consistent structure: ```json theme={null} { "error": { "code": "VALIDATION_ERROR", "message": "Validation error", "status": 400, "path": "/v1/transactions", "timestamp": "2025-01-21T10:30:00.000Z", "requestId": "req_123456789", "details": { "issues": [ { "field": "amount", "type": "REQUIRED", "message": "Field 'amount' is required" } ] } } } ``` ### Error fields * `code`: Machine-readable error code * `message`: Human-readable error message * `status`: HTTP status code * `path`: API endpoint that generated the error * `timestamp`: When the error occurred * `requestId`: Unique request identifier for support * `details`: Additional error-specific information ## HTTP status codes | Code | Description | Common causes | | ----- | --------------------- | --------------------------------- | | `400` | Bad Request | Invalid input, validation errors | | `401` | Unauthorized | Missing or invalid API key | | `403` | Forbidden | Insufficient permissions | | `404` | Not Found | Resource doesn't exist | | `409` | Conflict | Duplicate resource, invalid state | | `500` | Internal Server Error | Server-side error | | `502` | Bad Gateway | Provider integration error | ## Common error codes ### VALIDATION\_ERROR (400) Input validation failed. Check the `details.issues` array for specific field errors. ```json theme={null} { "error": { "code": "VALIDATION_ERROR", "message": "Validation error", "details": { "issues": [ { "field": "amount", "type": "INVALID_TYPE", "message": "Expected number, received string", "value": "invalid" } ] } } } ``` **Solution**: Fix the invalid fields and retry the request. Card credential fields (`card_data.number`, `card_data.cvv`, `card_data.network_token`, `card_data.cryptogram`, and the 3DS `card.number`) are accepted only as encrypted values from rinne-js. A plaintext value triggers a per-field `VALIDATION_ERROR` issue telling you to send the encrypted value from rinne-js instead. The rejected value is never echoed back in the error response. ### AUTHENTICATION\_ERROR (401) API key is missing or invalid. ```json theme={null} { "error": { "code": "AUTHENTICATION_ERROR", "message": "Invalid or missing API key", "details": { "reason": "API key not found" } } } ``` **Solution**: Verify your API key is correct and included in the `x-api-key` header. ### AUTHORIZATION\_ERROR (403) You don't have permission to access the resource. ```json theme={null} { "error": { "code": "AUTHORIZATION_ERROR", "message": "You need 'transaction.create' permission to access this resource" } } ``` **Solution**: Check your user roles and permissions, or use an API key with appropriate access. ### RESOURCE\_NOT\_FOUND (404) The requested resource doesn't exist. ```json theme={null} { "error": { "code": "RESOURCE_NOT_FOUND", "message": "Transaction with ID 'tx_123' not found" } } ``` **Solution**: Verify the resource ID is correct and belongs to your company. ### CONFLICT\_ERROR (409) Resource already exists or operation conflicts with current state. ```json theme={null} { "error": { "code": "CONFLICT_ERROR", "message": "Company with this document_number already exists", "details": { "field": "document_number", "value": "16525269000121" } } } ``` **Solution**: Check for existing resources or verify the resource state allows the operation. ### INTEGRATION\_ERROR (502) Provider integration failed. ```json theme={null} { "error": { "code": "INTEGRATION_ERROR", "message": "Integration error with provider CELCOIN", "status": 502 } } ``` **Solution**: Retry the request. If the error persists, contact support with the `requestId`. ## Handling validation errors Validation errors include detailed information about each invalid field: ```javascript theme={null} try { const response = await createTransaction(data); } catch (error) { if (error.status === 400 && error.code === 'VALIDATION_ERROR') { error.details.issues.forEach(issue => { console.log(`Field ${issue.field}: ${issue.message}`); // Display error to user }); } } ``` ## Idempotency Use the `request_id` field to safely retry requests: ```bash theme={null} # First attempt - network error POST /v1/transactions { "request_id": "order-123", ... } # Network timeout # Retry with same request_id POST /v1/transactions { "request_id": "order-123", ... } # Returns existing transaction if first request succeeded ``` This prevents duplicate transactions when retrying failed requests. ## Rate limiting Rinne applies request rate limits to keep the platform stable: 1000 req/s for transaction creation and 3DS session creation and authentication `POST` requests and 50 req/s for all other requests. Each limit is a shared pool across all matching endpoints, not a per-endpoint allowance. Requests within the limit always succeed; only the excess requests above the limit are rejected, with no account block or penalty. See the [Rate limits guide](/guides/rate-limits) for full details. Rejected requests return a `429 Too Many Requests` status code with **no response body**. The limit details are returned in the response headers instead: ```http theme={null} HTTP/1.1 429 Too Many Requests x-ratelimited: true x-ratelimit-limit: 50, 50;w=1 x-ratelimit-remaining: 0 x-ratelimit-reset: 1 ``` Detect a rate limit rejection from the `429` status code and the `x-ratelimit-*` headers, not from a response body. Implement exponential backoff when retrying: ```javascript theme={null} async function retryWithBackoff(fn, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { return await fn(); } catch (error) { if (error.status === 429 && i < maxRetries - 1) { const delay = Math.pow(2, i) * 1000; // 1s, 2s, 4s await new Promise(resolve => setTimeout(resolve, delay)); continue; } throw error; } } } ``` ## Logging and debugging ### Request IDs Every response includes a `requestId` that you can use when contacting support: ```json theme={null} { "error": { "requestId": "req_123456789" } } ``` Save request IDs for failed requests to help support diagnose issues. ### Error monitoring Implement error monitoring to track API errors: ```javascript theme={null} app.use((error, req, res, next) => { // Log to monitoring service logger.error('Rinne API error', { code: error.code, status: error.status, requestId: error.requestId, path: req.path }); // Send to error tracking (Sentry, etc.) Sentry.captureException(error); }); ``` ## Next steps Handle webhook delivery failures View all error response schemas # PIX payments Source: https://docs.rinne.com.br/guides/pix-payments Process instant payments using Brazil's PIX system PIX is Brazil's instant payment system that enables real-time transfers 24/7. Rinne provides full support for PIX payments through integrated providers. ## How PIX works 1. Merchant creates a PIX transaction with a QR code 2. Customer scans the QR code with their banking app 3. Customer confirms the payment 4. Merchant receives instant notification 5. Funds are settled to merchant's account ## Prerequisites Before processing PIX payments, ensure: * Merchant has an active affiliation with a provider * Affiliation includes `PIX` in `allowed_payment_methods` * Merchant has at least one active PIX key ## Creating a PIX transaction ### Immediate PIX payment ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/transactions \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "provider": "CELCOIN", "request_id": "order-123", "amount": 10000, "currency": "BRL", "capture_method": "ECOMMERCE", "payment_method": "PIX", "pix_data": { "description": "Payment for order #123", "expiration_in_seconds": 3600 }, "consumer": { "full_name": "João Silva", "email": "joao@example.com", "document_type": "CPF", "document_number": "81146431023" } }' ``` ### PIX with due date Create PIX payments with a due date and optional pricing modifiers (interest, fine, discount, abatement): ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/transactions \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "provider": "CELCOIN", "request_id": "order-124", "amount": 100000, "currency": "BRL", "capture_method": "ECOMMERCE", "payment_method": "PIX", "pix_data": { "description": "Invoice payment with due date", "due_date": "2025-12-31", "expiration_in_days_after_due_date": 30, "interest": { "percentage": 2.0, "modality": "PERCENTAGE_PER_MONTH" }, "fine": { "percentage": 5.0, "modality": "PERCENTAGE" }, "discount": { "modality": "PERCENTAGE_UNTIL_DATE", "discount_dates_config": [ { "until_date": "2025-12-15", "percentage": 10.0 }, { "until_date": "2025-12-25", "percentage": 5.0 } ] }, "abatement": { "amount": 1000, "modality": "FIXED" } }, "consumer": { "full_name": "Maria Santos", "email": "maria@example.com", "document_type": "CPF", "document_number": "12345678901" } }' ``` ### PIX-specific fields #### pix\_data * `pix_key_id` (optional): Specific PIX key to use. If not provided, uses merchant's primary key * `description` (optional): Description shown to the payer * `expiration_in_seconds` (optional): QR code expiration time (1-86400 seconds, default: 86400). Must NOT be provided when `due_date` is provided * `due_date` (optional): Due date for the payment in YYYY-MM-DD format. Must be today or later. Required when using interest, fine, discount, or abatement * `expiration_in_days_after_due_date` (optional): Days after due date before expiration (1-365, default: 30). Must NOT be provided if `due_date` is also not provided #### Consumer information For PIX transactions, provide customer details: * `full_name`: Customer's full name (required when `due_date` is provided) * `email`: Customer's email * `document_type`: `CPF` or `CNPJ` (required when `due_date` is provided) * `document_number`: Customer's document number (required when `due_date` is provided) ## PIX QR code The response includes a PIX QR code that customers can scan: ```json theme={null} { "id": "tx_123456789", "status": "WAITING_PAYMENT", "pix_data": { "qr_code": "00020126580014br.gov.bcb.pix0136a3dbd0c2-9f79-4f86-8caa-47779b3f2793520400005303986540510.005802BR5913Store Name6008São Paulo62070503***6304E2CA", "expires_at": "2025-01-21T11:00:00Z", "pix_key_id": "pix-key-123" } } ``` Display the QR code to your customer using a QR code library or image generator. ## PIX transaction lifecycle ### 1. Transaction created Status: `WAITING_PAYMENT` The transaction is created with a QR code. Customer has until expiration to pay. ### 2. Payment received Status: `APPROVED` When the customer pays, you receive a webhook notification. The event includes a `payload` with the transaction id and status transition, plus standard metadata on the same object (such as `id`, `version`, `timestamp`, `source`, and `company_id`). See the **transaction.status-changed** event in the API reference for the full shape. ```json theme={null} { "type": "transaction.status-changed", "payload": { "transaction_id": "tx_123456789", "old_status": "WAITING_PAYMENT", "new_status": "APPROVED" } } ``` ### 3. Settlement Funds are settled to the merchant's bank account according to transfer configurations. ### 4. Expiration (if not paid) Status: `EXPIRED` If the customer doesn't pay before expiration, the transaction expires automatically. ## PIX key management for receiving payments The `/pix/keys` endpoints in this guide manage affiliation PIX keys used to receive PIX payments. For cashouts, use external PIX keys under `/pix-keys`. See Cashouts guide. ### Automatic affiliation PIX key creation When a merchant's affiliation is activated, Rinne automatically creates a primary EVP (random) PIX key. This ensures merchants can immediately receive PIX payments. ### Creating additional affiliation PIX keys Create additional affiliation PIX keys when you need more receiving keys: ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/pix/keys \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "provider": "CELCOIN", "key_type": "EVP", "primary": false }' ``` ### Listing affiliation PIX keys ```bash theme={null} curl https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/pix/keys \ -H "x-api-key: YOUR_API_KEY" ``` ## PIX refunds Refund PIX transactions using the refund endpoint: ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/transactions/TRANSACTION_ID/refunds \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "refund_amount": 10000, "refund_reason": "CUSTOMER_REQUEST", "description": "Customer requested refund" }' ``` ### PIX refund reasons * `CUSTOMER_REQUEST`: Customer requested refund * `FRAUD`: Fraudulent transaction * `BANKING_ERROR`: Banking system error * `PIX_WITHDRAWAL_OR_CHANGE_ERROR`: PIX withdrawal or change error ## Testing PIX and BOLEPIX in sandbox In the sandbox environment, you can simulate PIX and BOLEPIX payments. Two endpoints are available depending on your use case. The `payer_company_id` must be different from the transaction owner (merchant or organization). Providing the same company ID as the transaction owner returns a `400` validation error. ### Pay a merchant's transaction Use this endpoint when paying a transaction that belongs to a specific merchant: ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/transactions/TRANSACTION_ID/pay \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "payer_company_id": "PAYER_COMPANY_ID" }' ``` ### Pay an organization's own transaction Use this endpoint when paying a transaction owned by the authenticated organization company. No `merchantId` parameter is needed — the transaction is looked up using the authenticated organization's company ID. ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/transactions/TRANSACTION_ID/pay \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "payer_company_id": "PAYER_COMPANY_ID" }' ``` Only companies with type `ORGANIZATION` can use the `/v1/transactions/{transactionId}/pay` endpoint. Both endpoints automatically detect the payment method based on the transaction: * **PIX transactions**: Uses the QR code from `pix_data.qr_code` * **BOLEPIX transactions**: Uses the PIX EMV code from `bolepix_data.pix_emv` The payment is always for the full transaction amount. ## PIX with due date features ### Interest Interest is applied when payment is made after the due date. Configure using: * `amount`: Fixed amount in cents per period * `percentage`: Percentage per period (0-100, up to 2 decimal places) * `modality`: Calculation method **Interest modalities:** * `AMOUNT_PER_DAY`: Fixed amount per day * `PERCENTAGE_PER_DAY`: Percentage per day * `PERCENTAGE_PER_MONTH`: Percentage per month * `PERCENTAGE_PER_YEAR`: Percentage per year * `AMOUNT_PER_WORKING_DAY`: Fixed amount per working day * `PERCENTAGE_PER_WORKING_DAY`: Percentage per working day * `PERCENTAGE_PER_WORKING_MONTH`: Percentage per working month * `PERCENTAGE_PER_WORKING_YEAR`: Percentage per working year ### Fine Fine is a one-time charge applied when payment is made after the due date: * `amount`: Fixed amount in cents * `percentage`: Percentage of transaction amount (0-100) * `modality`: `FIXED` or `PERCENTAGE` ### Discount Discount reduces the payment amount when paid before or on specific dates: * `modality`: Discount calculation method * `discount_dates_config`: Array of up to 3 discount periods (for date-based modalities) * `amount` or `percentage`: Value for per-day modalities **Discount modalities:** * `FIXED_AMOUNT_UNTIL_DATE`: Fixed amounts valid until specific dates * `PERCENTAGE_UNTIL_DATE`: Percentages valid until specific dates * `AMOUNT_PER_DAY`: Amount reduction per day * `AMOUNT_PER_WORKING_DAY`: Amount reduction per working day * `PERCENTAGE_PER_DAY`: Percentage reduction per day * `PERCENTAGE_PER_WORKING_DAY`: Percentage reduction per working day ### Abatement Abatement permanently reduces the transaction amount: * `amount`: Fixed amount in cents * `percentage`: Percentage of transaction amount (0-100) * `modality`: `FIXED` or `PERCENTAGE` ## Best practices * E-commerce: 15-30 minutes (900-1800 seconds) * In-person: 5-10 minutes (300-600 seconds) * Invoices: 24 hours (86400 seconds) * Due date payments: Use `expiration_in_days_after_due_date` (default: 30 days) Monitor for expired transactions and notify customers to create a new payment. Don't poll for transaction status. Use webhooks to receive real-time updates. Ensure customer CPF/CNPJ is valid before creating transactions to reduce fraud. For invoice payments, use `due_date` with appropriate pricing modifiers to encourage early payment and handle late payments. ## Next steps Receive real-time payment notifications Bank accounts, balances, statements, and internal transfers Cashouts and external destinations # Rate limits Source: https://docs.rinne.com.br/guides/rate-limits How Rinne's request rate limits work, which endpoints they apply to, and how to build integrations that stay within them. Rinne applies rate limits to keep the platform fast and stable for everyone. Limits are enforced consistently, so you should design your integration to stay within them from the start. ## How the limits work There are two rate limits, both measured in requests per second (req/s): | Request type | Limit | | ------------------------------------------------------------------------------------ | ---------- | | `POST` requests for transaction creation and 3DS session creation and authentication | 1000 req/s | | Every other request | 50 req/s | Each limit is a **single shared pool**, not a per-endpoint allowance. The limiter counts the total number of requests you send across all endpoints in a bucket, then compares that sum against the limit. Sending requests to many different endpoints does not give you a higher combined throughput. The two pools are independent of each other. Transaction-creating or 3DS-creating requests draw from the 1000 req/s pool, while all other requests draw from the 50 req/s pool. ## Same across all environments Rate limits behave identically in every environment, including sandbox. This means you can validate your integration against the real limits before going to production. Use sandbox to load-test your request patterns. Because the limits match production exactly, the behavior you observe in sandbox is the behavior you will get in production. ## When you exceed a limit Exceeding a limit does not block your account or interrupt service. Requests that stay within the limit always succeed and the API keeps working normally. Only the excess requests above the limit are rejected. Rejected requests return a `429 Too Many Requests` status code with **no response body**. The rate limit details are returned in the response headers: ```http theme={null} HTTP/1.1 429 Too Many Requests x-ratelimited: true x-ratelimit-limit: 50, 50;w=1 x-ratelimit-remaining: 0 x-ratelimit-reset: 1 ``` | Header | Description | | ----------------------- | -------------------------------------------------------------------------------------- | | `x-ratelimited` | Set to `true` when the request was rejected for exceeding the limit. | | `x-ratelimit-limit` | The limit for the applicable pool. `50, 50;w=1` means 50 requests per 1-second window. | | `x-ratelimit-remaining` | Requests remaining in the current window. `0` when the limit has been reached. | | `x-ratelimit-reset` | Seconds until the window resets and requests are accepted again. | Unlike other API errors, a rate limit rejection has no JSON body. Detect it from the `429` status code and the `x-ratelimit-*` headers rather than parsing a response body. There is no penalty or cooldown beyond the rejected request itself. As soon as your request rate drops back within the limit, requests succeed again. ## Best practices Smooth out bursts on the client side. Spreading requests evenly across each second keeps you under the limit far more reliably than sending large bursts. * **Throttle outbound requests** so your sustained rate stays within the applicable pool. * **Retry rejected requests** with a short backoff. Because there is no penalty, a rejected request can be retried as soon as your rate is back within the limit. * **Use idempotency for transaction creation.** Include a `request_id` when creating transactions so safe retries never produce duplicates. See [Error handling](/guides/error-handling#idempotency) for details. ```javascript theme={null} // Assumes your HTTP client throws an error exposing the response status // (e.g. error.status === 429). A 429 has no body, so detect it from the status. async function retryWithBackoff(fn, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { return await fn(); } catch (error) { // Only retry rate-limit rejections, and only while retries remain. // Anything else (or the last attempt) is rethrown to the caller. if (error.status === 429 && i < maxRetries - 1) { const delay = Math.pow(2, i) * 1000; // 1s, 2s, 4s await new Promise((resolve) => setTimeout(resolve, delay)); continue; } throw error; } } } // Usage const transaction = await retryWithBackoff(() => createTransaction(data)); ``` ## Next steps Handle 429 and other error responses Explore all endpoints and schemas # Testing Source: https://docs.rinne.com.br/guides/testing Reproduce card approvals, declines, and provider errors on demand with deterministic sandbox test scenarios. Rinne's sandbox lets you build and verify your integration end to end without moving real money. For card payments, the sandbox returns **deterministic acquirer outcomes** — you choose an approval, a decline with a specific reason code, or a provider error on every request — so you can reproduce each branch of your checkout reliably instead of waiting for the real acquirer to happen to return them. ## Sandbox environment Point your requests at the sandbox host and authenticate with a sandbox API key: ```bash Sandbox theme={null} https://api-sandbox.rinne.com.br/core ``` ```bash Production theme={null} https://api.rinne.com.br/core ``` ```bash theme={null} curl https://api-sandbox.rinne.com.br/core/v1/companies/me \ -H "x-api-key: YOUR_API_KEY" ``` Card outcomes in the sandbox are **simulated deterministically** — the amount you send decides the result (see below). Production always routes to the real acquirer. ## Card payment test scenarios **The amount decides the outcome — not the card number.** Rinne selects the acquirer result from the transaction **amount**; a card number only sets the **brand** and the **3DS behavior**. This keeps one consistent selector across raw cards and wallet tokens (Apple Pay / Google Pay), which have no integrator-chosen card number. ### Drive a scenario Enter a [test card](#test-cards) into the [Card Element](/rinne-js/card-element), which encrypts the number and CVC in the customer's browser. You never send a raw PAN — card data that reaches the API unencrypted is rejected with `400 VALIDATION_ERROR` before any processing. Set the transaction `amount` (in cents) so its **last two digits** match the outcome you want from the [table below](#outcome-by-amount). For example, `1051` (R\$ 10.51) simulates an insufficient-funds decline. Card transactions resolve synchronously, so the create response already carries the final `status` and the `acquirer_response_code` you selected. ### Outcome by amount The outcome is chosen by the **last two digits** of the amount (in cents). Detect a successful payment from `status`; for declines, `acquirer_response_code` carries the acquirer's reason code, matching what production would store. | Amount ends in | Outcome | Status | Code | Meaning | | -------------------------------------- | -------------- | ---------- | ----- | ----------------------------------------------------------------------------------------------- | | `00` *(and anything not listed below)* | Approved | `APPROVED` | `—` | Authorized | | `51` | Declined | `REFUSED` | `51` | Insufficient funds | | `05` | Declined | `REFUSED` | `05` | Do not honor | | `54` | Declined | `REFUSED` | `54` | Expired card | | `14` | Declined | `REFUSED` | `14` | Invalid card | | `96` | Provider error | `FAILED` | `500` | Provider error (HTTP status, not an issuer reason code) — `status_reason` is `ACQUIRER_TIMEOUT` | Amounts are always in **cents**, so the selector is the last two digits of the cents value: `114` is R\$ 1.**14**, which **declines** on `14` (invalid card). Prefer a `…00` amount for approvals so a small test value doesn't land on a decline suffix by accident. Worked examples: `1000` → `APPROVED`; `1051` → `REFUSED` `51`; `1014` → `REFUSED` `14`; `1096` → `FAILED`. ### Test cards Any valid card number works. These are the recommended ones — the amount still decides approve vs. decline; the card only sets the brand and the 3DS behavior. | Card number | Brand | 3DS behavior | | ------------------ | ---------- | ------------------------ | | `5155901222280001` | Mastercard | No 3DS | | `4012001037141112` | Visa | No 3DS | | `4242424242424242` | Visa | 3DS — challenge required | | `5555555555554444` | Mastercard | 3DS — challenge required | | `4111110116638870` | Visa | 3DS — frictionless pass | | `5555550130659057` | Mastercard | 3DS — frictionless pass | | `4111111738973695` | Visa | 3DS — frictionless fail | | `5555550487847545` | Mastercard | 3DS — frictionless fail | The cards marked 3DS run the real 3D Secure flow (see [3D Secure authentication](/guides/three-d-secure-authentication)) — **challenge** cards present a verification step inside the mounted 3DS element, **frictionless** cards authenticate silently — while the acquirer outcome still comes from the amount. If nothing appears for a challenge card, your integration is not mounting the element — authentication only runs when it is mounted. For wallet payments (Apple Pay / Google Pay) there is no test card — pass the real wallet token and pick the outcome with the amount. An invalid or malformed card number is declined as `REFUSED` with `acquirer_response_code` `14` (invalid card), regardless of the amount. All the test cards above are valid. ### Examples The request is a standard card transaction — only the `amount` changes to select the outcome. This example uses the Mastercard test card `5155901222280001` (last 4 digits `0001`): ```bash cURL theme={null} curl -X POST 'https://api-sandbox.rinne.com.br/core/v1/transactions' \ -H 'x-api-key: YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "provider": "RINNE", "request_id": "test-card-approved-0001", "amount": 1000, "currency": "BRL", "capture_method": "ECOMMERCE", "payment_method": "CREDIT_CARD", "installments": 1, "card_data": { "number": "ev:encrypted:card_number", "cvv": "ev:encrypted:cvc", "expiry_month": "12", "expiry_year": "2028", "cardholder_name": "Maria Santos", "last_digits": "0001" } }' ``` Send the same request with a different `amount` to reach each branch: ```json Approved (amount 1000) theme={null} { "id": "a974a0aa-0b9b-4408-8f52-b83b687943bd", "status": "APPROVED", "amount": 1000, "currency": "BRL", "nsu": "000123456789" } ``` ```json Refused (amount 1051) theme={null} { "id": "b1966800-bb2a-40fa-b73f-57e8ce8f9d80", "status": "REFUSED", "acquirer_response_code": "51", "amount": 1051, "currency": "BRL" } ``` ```json Failed (amount 1096) theme={null} { "id": "c91e591f-ea0b-4628-9901-0f3cfa5acce1", "status": "FAILED", "status_reason": "ACQUIRER_TIMEOUT", "acquirer_response_code": "500", "amount": 1096, "currency": "BRL" } ``` A `REFUSED` result is a clean issuer decline — surface the reason and let the customer retry or use another card. A `FAILED` result means the payment couldn't be completed (a provider timeout or error) — treat it as retryable. See [Error handling](/guides/error-handling) for the full model. ### Refunds and cancellations Refunds and cancellations also resolve deterministically under simulation: a cancel succeeds and the refund reaches `COMPLETED`, moving the transaction to `REFUNDED` (full) or `PARTIALLY_REFUNDED` (partial). The acquirer refund rules still apply — one successful refund per card transaction, and no same-day partial refunds. See [Refunding card transactions](/guides/card-transactions#refunding-card-transactions). ## Testing 3D Secure The [Test cards](#test-cards) table above includes a card for each 3DS path — **challenge** (a verification step the cardholder completes), **frictionless pass** (silent success), and **frictionless fail** (silent failure). Pick the one matching the path you want to exercise and follow the [3D Secure authentication](/guides/three-d-secure-authentication) guide. The card drives the authentication path; the **amount still selects the final acquirer outcome**. For example, `4242424242424242` at amount `1000` runs a challenge and then approves, while the same card at `1051` runs the challenge and then declines with `51`. ## Next steps Implement the full credit and debit card flow Add session-first or transaction-first 3DS Receive real-time transaction status updates Handle declines, failures, and retries # 3D Secure authentication Source: https://docs.rinne.com.br/guides/three-d-secure-authentication Implement official 3DS session-first and transaction-first flows with self and merchant endpoints. 3D Secure (3DS) authenticates the cardholder before card authorization and helps reduce fraud in card-not-present payments. ## How authentication runs Every 3DS integration is the same three moves, split between your backend and your frontend: 1. **Backend — create a 3DS session** with the card data. The response carries two IDs: `tds_session_id` (for the frontend) and `id` (for the backend). 2. **Frontend — mount the [3D Secure Element](/rinne-js/three-d-secure-element)** with `tds_session_id`. **Mounting is what runs the authentication.** The element completes frictionless authentications silently and renders the issuer's verification UI (OTP, approve-in-app instructions, biometric) when a challenge is required. `onSuccess` fires when authentication completes — in both paths. 3. **Backend — link the session to a transaction** using the session `id` as `three_d_secure_session_id`. ```mermaid theme={null} sequenceDiagram participant B as Your backend participant F as Your frontend participant R as Rinne participant I as Issuer / card network B->>R: POST /3ds-sessions (encrypted card) R-->>B: id + tds_session_id (auth_status ACTION_REQUIRED) B-->>F: tds_session_id F->>F: threeDSecure.mount(tds_session_id) F->>I: Element runs authentication I-->>F: Frictionless pass, or challenge UI rendered inside the element F-->>B: onSuccess B->>R: POST /transactions (three_d_secure_session_id = id) ``` Creating a session does **not** start authentication. Nothing is sent to the cardholder or their bank — no push notification, no SMS — until your frontend mounts the 3D Secure Element. A session that is never mounted stays `ACTION_REQUIRED` until it expires. Do not build your own "waiting for bank confirmation" screen: when the issuer wants an in-app approval, the element itself shows that instruction to the cardholder and detects completion. Rinne officially supports two 3DS integration patterns: **session-first** and **transaction-first**. They differ only in when the transaction is created — the three moves above are identical in both. There is no platform-level preferred flow. Choose the flow that matches your organization architecture. ## Endpoint context (self vs merchant) Use endpoint family based on the API key context you are operating in. | Context | Create 3DS session | Authenticate pending transaction | Typical permission | | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | | Self (company acting as itself) | [`POST /v1/3ds-sessions`](/api-reference/3d-secure/create-a-3d-secure-session) | [`POST /v1/transactions/{transactionId}/authenticate`](/api-reference/3d-secure/authenticate-a-transaction-with-3d-secure) | `3ds.create`, `transaction.create` | | Organization on behalf of merchant | [`POST /v1/merchants/{merchantId}/3ds-sessions`](/api-reference/3d-secure/create-a-3d-secure-session-linked-to-a-merchant) | [`POST /v1/merchants/{merchantId}/transactions/{transactionId}/authenticate`](/api-reference/3d-secure/authenticate-a-merchants-transaction-with-3d-secure) | `merchant.3ds.create`, `merchant.transaction.create` | ## Security contract with rinne-js Use rinne-js secure components as your source of card values: * [Card Element](/rinne-js/card-element) for card number (encrypted), CVC (encrypted), and expiry (plain month/year). * [3D Secure Element](/rinne-js/three-d-secure-element) for running the authentication itself — frictionless and challenge. Do not collect raw PAN/CVC in custom form fields. The Card Element encrypts the card number and CVC client-side before exposing them via `mountedCard.values`. Expiry month and year are collected but not encrypted — they are sent as plain strings to the 3DS session creation endpoint. ## Critical ID mapping | Value | From | Use | | ---------------- | -------------------- | ------------------------------------------------------------------------------- | | `id` (UUID) | 3DS session response | Send as `three_d_secure_session_id` in transaction create/authenticate requests | | `tds_session_id` | 3DS session response | Pass to `rinne.elements.threeDSecure().mount(tds_session_id)` | ## How transactions enter `AWAITING_3DS` A card transaction can enter `AWAITING_3DS` through any of the following: 1. Risk/anti-fraud challenge requirement. 2. Soft-decline recovery path. 3. `require_3ds: true` in transaction create request. `refuse_on_challenge: true` modifies challenge-trigger behavior: * Challenge-triggered paths are refused immediately (`status = REFUSED`). * `status_reason` is set to `CHALLENGE_NOT_ALLOWED`. * This avoids waiting in `AWAITING_3DS` when your organization does not run challenge UX. `require_3ds` and `refuse_on_challenge` cannot both be `true` in the same transaction request. These flags are transaction-creation policies only. They do not create a 3DS session or run challenge automatically. ### Choosing the right flag strategy | Strategy | Immediate transaction outcome | What you still implement | Recommended when | | --------------------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | | `require_3ds` | Card transaction is created in `AWAITING_3DS` every time | Create session, run challenge when required, and call `/authenticate` | You want mandatory 3DS while keeping one transaction-first backend path | | `refuse_on_challenge` | Challenge-triggered paths return `REFUSED` with `CHALLENGE_NOT_ALLOWED` | Refusal handling/retry logic only | You do not want to implement challenge UX for a merchant, channel, or transaction segment | `require_3ds` is useful when your transaction-first integration already handles `AWAITING_3DS` and `/authenticate`, but you need deterministic 3DS enforcement. `refuse_on_challenge` is useful when you prefer immediate refusal over any pending challenge workload. With `refuse_on_challenge`, non-challenge transactions continue normal provider processing. Only challenge-triggered paths are refused. ## Flow A: Session-first Use this flow when authentication should happen before transaction creation. Required fields are `amount`, `currency`, and `card`. The `merchant.website` field is the support URL shown to cardholders during the challenge screen. If omitted, Rinne falls back to the website configured on your company or parent organization. The request fails if no website URL can be resolved from any source. Providing payer context (`payer_email`, `payer_name`, `payer_document`, `billing_address`) is optional but improves authentication rates. Issuers use this context to assess risk and reduce unnecessary challenges. ```bash cURL theme={null} curl -X POST 'https://api-sandbox.rinne.com.br/core/v1/3ds-sessions' \ -H 'x-api-key: YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "amount": 12990, "currency": "BRL", "card": { "number": "ev:encrypted:card_number", "expiry": { "month": "12", "year": "2028" } }, "merchant": { "website": "https://support.yourstore.com" }, "payer_email": "customer@example.com", "payer_name": "Maria Silva", "payer_document": "123.456.789-00", "billing_address": { "line": "Rua das Flores, 123", "city": "São Paulo", "state": "SP", "postal_code": "01001-000", "country": "BR" } }' ``` ```bash cURL theme={null} curl -X POST 'https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/3ds-sessions' \ -H 'x-api-key: YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "amount": 12990, "currency": "BRL", "card": { "number": "ev:encrypted:card_number", "expiry": { "month": "12", "year": "2028" } }, "merchant": { "website": "https://support.yourstore.com" }, "payer_email": "customer@example.com", "payer_name": "Maria Silva", "payer_document": "123.456.789-00", "billing_address": { "line": "Rua das Flores, 123", "city": "São Paulo", "state": "SP", "postal_code": "01001-000", "country": "BR" } }' ``` ```json 201 Created theme={null} { "id": "550e8400-e29b-41d4-a716-446655440000", "tds_session_id": "tds_visa_e7c2yfa3867c", "auth_status": "ACTION_REQUIRED", "consumption_status": "NOT_CONSUMED", "authentication_flow": null, "liability_shift": null, "failure_reason": null, "amount": 12990, "currency": "BRL", "expires_at": "2026-02-25T20:35:00.000Z", "created_at": "2026-02-25T19:35:00.000Z", "updated_at": "2026-02-25T19:35:00.000Z" } ``` ```json 201 Created (session creation failed — card rejected before flow started) theme={null} { "id": "550e8400-e29b-41d4-a716-446655440002", "tds_session_id": "tds_visa_e7c2yfa3867e", "auth_status": "FAILED", "consumption_status": "NOT_CONSUMED", "authentication_flow": null, "liability_shift": null, "failure_reason": "Card not enrolled in 3DS", "amount": 12990, "currency": "BRL", "expires_at": "2026-02-25T20:35:00.000Z", "created_at": "2026-02-25T19:35:00.000Z", "updated_at": "2026-02-25T19:35:00.000Z" } ``` In practice, sessions come back as `ACTION_REQUIRED` or `FAILED` at creation — even for frictionless flows, authentication completes through the SDK element, not at session creation time. The API contract does allow `AUTHENTICATED` as a creation-time response for forward compatibility, so always branch on all three statuses rather than assuming only two. When `auth_status` is `ACTION_REQUIRED`, `authentication_flow` and `liability_shift` are null. They are populated only after the 3DS element completes the authentication flow. Sessions expire 1 hour after creation. If the user takes too long to complete a challenge, you will need to create a new session and restart the authentication step. Always branch on `auth_status` before deciding whether to mount: * **`FAILED`**: the card was rejected before the flow could start. Do not mount — surface the error and let the user retry with a different card. * **`AUTHENTICATED`**: frictionless authentication completed at session creation time. This is rare but allowed by the API contract. Skip mounting and proceed directly to transaction creation. * **`ACTION_REQUIRED`**: authentication has not run yet. Mount the element with `tds_session_id`; the SDK runs the full 3DS flow internally. When the element is mounted, the issuer decides the authentication path: * **Frictionless**: the issuer authenticates the card silently in the background with no visible UI. `onSuccess` fires automatically. * **Challenge**: the issuer presents a verification step (OTP, app approval, biometric) inside the element. Even when that screen tells the cardholder to approve the purchase in their banking app, the screen is issuer-hosted, rendered by the element — which also detects the approval. `onSuccess` fires after the user completes it. Your code handles both identically — `onSuccess` is the signal to proceed in either case. ```typescript theme={null} if (session.auth_status === 'FAILED') { showError(session.failure_reason ?? 'Authentication failed') return } if (session.auth_status === 'AUTHENTICATED') { continueCheckout() return } const threeDS = await rinne.elements.threeDSecure({ target: '#three-ds-container', onSuccess: () => continueCheckout(), onFailure: () => showError('Authentication failed'), onError: (error) => showError(error.message) }) await threeDS.mount(session.tds_session_id) // onSuccess/onFailure/onError callbacks drive the next step ``` Use session `id` as `three_d_secure_session_id`. ```bash cURL theme={null} curl -X POST 'https://api-sandbox.rinne.com.br/core/v1/transactions' \ -H 'x-api-key: YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "provider": "RINNE", "request_id": "order-3ds-0001", "amount": 12990, "currency": "BRL", "capture_method": "ECOMMERCE", "payment_method": "CREDIT_CARD", "card_data": { "number": "ev:encrypted:card_number", "cvv": "ev:encrypted:cvc", "expiry_month": "12", "expiry_year": "2028", "cardholder_name": "Maria Silva", "last_digits": "1111" }, "three_d_secure_session_id": "550e8400-e29b-41d4-a716-446655440000" }' ``` ```bash cURL theme={null} curl -X POST 'https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/transactions' \ -H 'x-api-key: YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "provider": "RINNE", "request_id": "order-3ds-0001", "amount": 12990, "currency": "BRL", "capture_method": "ECOMMERCE", "payment_method": "CREDIT_CARD", "card_data": { "number": "ev:encrypted:card_number", "cvv": "ev:encrypted:cvc", "expiry_month": "12", "expiry_year": "2028", "cardholder_name": "Maria Silva", "last_digits": "1111" }, "three_d_secure_session_id": "550e8400-e29b-41d4-a716-446655440000" }' ``` Card transactions typically resolve synchronously — the response already contains the final status (`APPROVED`, `AUTHORIZED`, or `REFUSED`). Use this status to drive your UI immediately. A successful 3DS authentication does not guarantee the transaction will be approved. The issuer can still decline the transaction for reasons unrelated to authentication (insufficient funds, card blocked, etc.). ## Flow B: Transaction-first Use this flow when transaction orchestration starts before 3DS authentication and your backend owns the payment lifecycle from the first call. In transaction-first, transaction status is the source of truth for when 3DS is required. Call `/authenticate` only for transactions currently in `AWAITING_3DS`. ### Transaction-first status contract | Transaction status | Meaning in this flow | Next action | | ----------------------------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------- | | `AWAITING_3DS` | 3DS is required now | Create 3DS session, complete challenge when needed, then call `/authenticate` | | `PROCESSING` | Provider processing continues without immediate challenge | Wait for webhook/status update; do not call `/authenticate` yet | | `AUTHORIZED` or `APPROVED` | Payment completed without additional challenge | Continue fulfillment/capture flow | | `REFUSED` + `CHALLENGE_NOT_ALLOWED` | Challenge would be required, but policy blocked it | Fail fast path, retry with different policy only if desired | | `REFUSED` (other reasons) | Regular decline | Follow your normal decline handling | Without `require_3ds: true`, some transactions can enter `AWAITING_3DS` later through two paths: * **Risk assessment**: Rinne's anti-fraud rules determine the transaction needs cardholder verification. * **Soft-decline recovery**: the payment provider initially declines the transaction with a response code indicating 3DS authentication could resolve the decline (e.g., issuer requires authentication). Rinne automatically retries via the 3DS path instead of refusing immediately. In both cases the transition is `PROCESSING` → `AWAITING_3DS`. Handle this either synchronously or asynchronously through webhooks or transaction polling, then run the same 3DS session/challenge/authenticate steps described below. For deterministic transaction-first 3DS entry, set `require_3ds: true`. ```bash cURL theme={null} curl -X POST 'https://api-sandbox.rinne.com.br/core/v1/transactions' \ -H 'x-api-key: YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "provider": "RINNE", "request_id": "order-3ds-0002", "amount": 12990, "currency": "BRL", "capture_method": "ECOMMERCE", "payment_method": "CREDIT_CARD", "card_data": { "number": "ev:encrypted:card_number", "cvv": "ev:encrypted:cvc", "expiry_month": "12", "expiry_year": "2028", "cardholder_name": "Maria Silva", "last_digits": "1111" }, "require_3ds": true }' ``` ```bash cURL theme={null} curl -X POST 'https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/transactions' \ -H 'x-api-key: YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "provider": "RINNE", "request_id": "order-3ds-0002", "amount": 12990, "currency": "BRL", "capture_method": "ECOMMERCE", "payment_method": "CREDIT_CARD", "card_data": { "number": "ev:encrypted:card_number", "cvv": "ev:encrypted:cvc", "expiry_month": "12", "expiry_year": "2028", "cardholder_name": "Maria Silva", "last_digits": "1111" }, "require_3ds": true }' ``` ```json Example response theme={null} { "id": "tx_123456789", "status": "AWAITING_3DS", "amount": 12990, "currency": "BRL" } ``` `/authenticate` only accepts `three_d_secure_session_id`. CVV is not sent again in this step. * If status is `AWAITING_3DS`, continue to 3DS session creation. * If status is `PROCESSING`, `AUTHORIZED`, `APPROVED`, or `REFUSED`, do not call `/authenticate`. * If your asynchronous update later moves transaction to `AWAITING_3DS`, start 3DS at that point. Create a 3DS session for the same payment attempt: * Same company/merchant context as the transaction. * Same `amount` and `currency` as the transaction. * Encrypted card values from rinne-js. * Optionally include `payer_email`, `payer_name`, `payer_document`, and `billing_address` to improve authentication rates. Use `tds_session_id` for frontend challenge mount and keep `id` for backend `/authenticate`. Branch on `auth_status`: * **`FAILED`**: do not mount — surface the error and let the user retry with a different card. * **`AUTHENTICATED`**: skip mounting and proceed directly to the `/authenticate` call. * **`ACTION_REQUIRED`**: mount the element. The SDK handles frictionless and challenge flows internally; `onSuccess` fires in both cases. Call `/authenticate` from `onSuccess` using the session `id`. ```bash cURL theme={null} curl -X POST 'https://api-sandbox.rinne.com.br/core/v1/transactions/TRANSACTION_ID/authenticate' \ -H 'x-api-key: YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "three_d_secure_session_id": "550e8400-e29b-41d4-a716-446655440000" }' ``` ```bash cURL theme={null} curl -X POST 'https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/transactions/TRANSACTION_ID/authenticate' \ -H 'x-api-key: YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "three_d_secure_session_id": "550e8400-e29b-41d4-a716-446655440000" }' ``` ```json Example response theme={null} { "id": "tx_123456789", "status": "APPROVED", "amount": 12990, "currency": "BRL" } ``` Card transactions typically resolve synchronously — the `/authenticate` response already contains the final status (`APPROVED`, `AUTHORIZED`, or `REFUSED`). Use this status to drive your UI immediately. If the response still shows `AWAITING_3DS`, the session failed validation and was not consumed. Check the error response for details and create a new session to retry. ### Transaction-first authenticate failures and recovery | Failure condition | Typical API behavior | Recovery action | | --------------------------------------------- | --------------------------------- | --------------------------------------------------------------------------------------------------------- | | Transaction not in `AWAITING_3DS` | `400` validation error | Fetch latest transaction state and branch from actual status; do not keep calling `/authenticate` blindly | | Session not authenticated/failed | `400` validation error | Create a new 3DS session and complete challenge again | | Session expired/already consumed | `400` validation error | Create a new session; never reuse previous session | | Session scope/amount/currency mismatch | `400` validation error | Recreate session in correct context with exact transaction amount and currency | | Duplicate or concurrent authenticate attempts | One attempt succeeds, others fail | Treat `authenticate` as single-attempt per session and rely on transaction status as source of truth | ## Session validation before consumption Rinne validates all checks below before linking a 3DS session to a transaction: 1. Same company/merchant scope. 2. Same amount. 3. Same currency. 4. `auth_status = AUTHENTICATED`. 5. Session has cryptogram. 6. Session is not expired (sessions expire 1 hour after creation). 7. `consumption_status = NOT_CONSUMED`. Sessions are single-use and become unavailable after consumption. If any check fails, the request is rejected with a `400` validation error identifying which condition was not met. ## Session create request fields Amount in cents. Must exactly match the transaction amount you will use this session for. ISO 4217 currency code (e.g. `BRL`). Must exactly match the transaction currency. Encrypted card data from the rinne-js Card Element. Encrypted card number (`ev:` prefix) from the Card Element. Card numbers that reach the API unencrypted are rejected with `400 VALIDATION_ERROR`. Only Visa, Mastercard, and American Express can be 3DS-authenticated. A card from any other network (Elo, Hipercard, Discover, Diners, JCB) is rejected with a `400 VALIDATION_ERROR` on `card.number` — no session is created, so there is no `auth_status` to branch on. Route these cards through a card transaction without 3DS instead. Two-digit expiry month (`MM` format). Four-digit expiry year (`YYYY` format). Support URL shown to the cardholder during the challenge screen. Falls back to the website configured on your company or parent organization. The request fails if no URL can be resolved from any source. Cardholder email. Optional but helps issuers assess risk and reduces unnecessary challenges. Cardholder name as it appears on the card. Cardholder document number (e.g. CPF for Brazilian cardholders). Cardholder billing address. Street address. City. State or province code. Postal or ZIP code. Two-letter ISO 3166 country code (e.g. `BR`). ## Session response fields Internal session UUID. Use this as `three_d_secure_session_id` in transaction create or `/authenticate` requests. Provider session ID. Pass this to `rinne.elements.threeDSecure().mount(tds_session_id)`. Authentication status. `ACTION_REQUIRED` means authentication has not run yet — mount the 3DS element to run it. `AUTHENTICATED` and `FAILED` are outcomes. Whether the session has been linked to a transaction. Sessions are single-use. | Value | Meaning | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | `NOT_CONSUMED` | Available for use — not yet linked to any transaction | | `PROCESSING` | Currently being claimed by a transaction request. This is a transient internal state that lasts only during the atomic link operation | | `CONSUMED` | Already used — cannot be reused. Create a new session if needed | How authentication completed. Null at session creation — populated after the 3DS element finishes the authentication flow. | Value | Meaning | | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `frictionless` | Issuer authenticated the cardholder silently with no user interaction | | `challenge` | Issuer presented a verification step (OTP, app notification, biometric) that the cardholder completed | | `attempt` | Issuer's directory server responded but the ACS was unavailable or the cardholder was not enrolled in interactive authentication. A proof-of-attempt is recorded | Whether liability for fraud chargebacks shifts away from the merchant. Null at session creation — populated after authentication completes. * `true`: fraud liability shifts away from the merchant. For `frictionless` and `challenge` flows the **issuer** accepts liability. For `attempt` flows the **card network** stands in and accepts liability when the issuer's ACS is unavailable. * `false`: the merchant retains fraud liability. You can still proceed with a transaction when `liability_shift` is `false`. The 3DS cryptogram is still valid — the difference is only in who bears the chargeback risk. Human-readable failure reason when `auth_status` is `FAILED`. Null otherwise. ISO 8601 timestamp. Sessions expire 1 hour after creation. After this time, the session cannot be used and a new one must be created. ISO 8601 timestamp of when the session was created. ISO 8601 timestamp of the last session update. Equals `created_at` initially — updated when the 3DS element completes authentication. ## Idempotency and retry * Transaction create remains idempotent via `request_id`. * 3DS session create is non-idempotent by design. * If session is expired/consumed/mismatched, create a new session and retry. * If `/authenticate` result is uncertain (timeout/network failure), fetch transaction first. Retry only if transaction is still `AWAITING_3DS` and use a new session. ## Related docs * [Card transactions](/guides/card-transactions) * [Wallet transactions](/guides/wallet-transactions) * [Testing](/guides/testing) * [Guide: Card + 3DS Checkout (rinne-js)](/rinne-js/guides/card-3ds-checkout) — complete frontend example (HTML + code) for both flows # User management Source: https://docs.rinne.com.br/guides/user-management Manage users, roles, and permissions Rinne provides a comprehensive user management system with role-based access control (RBAC). Organizations and merchants can create users with specific permissions. ## User basics Users are individuals who can access the Rinne platform through your organization or merchant account. Each user: * Has one or more identifiers (email, phone) * Can belong to multiple companies * Has roles that define their permissions * Can authenticate using password or OAuth ## Creating users ### For your organization ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/users \ -H "Authorization: Bearer JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "identifiers": [ { "type": "EMAIL", "value": "user@company.com" } ], "first_name": "John", "last_name": "Doe", "auth_methods": ["PASSWORD"], "roles": ["admin"] }' ``` ### For a specific merchant ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/users \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "identifiers": [ { "type": "EMAIL", "value": "merchant.user@store.com" } ], "first_name": "Jane", "last_name": "Smith", "auth_methods": ["PASSWORD"], "roles": ["merchant-admin"] }' ``` ## User identifiers Users can have multiple identifiers for authentication: ### Email identifier ```json theme={null} { "type": "EMAIL", "value": "user@company.com" } ``` ### Phone identifier ```json theme={null} { "type": "PHONE", "value": "+5511999999999" } ``` Users must verify their identifiers before they can log in. ## Authentication methods Users can authenticate using: * `PASSWORD`: Email/phone and password * `GOOGLE`: Google OAuth * `CLIENT_PLATFORM`: Platform-specific authentication ## Roles and permissions Rinne uses a hierarchical permission system with role-based access control (RBAC). Permissions are enforced on all protected API endpoints for both JWT-authenticated users and API key requests. API keys carry their own set of permissions, configured at creation time. See the [Authentication guide](/guides/authentication#managing-api-keys) for details on creating keys with specific permissions. ### Built-in roles Rinne provides default roles: * **admin**: Full access to all resources (uses `*.*` wildcard) * **user**: Basic access to view resources * **merchant-admin**: Full access to merchant resources ### Custom roles Create custom roles with specific permissions: ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/roles \ -H "Authorization: Bearer JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "finance-manager", "description": "Can view transactions and banking", "permissions": [ "transaction.list", "transaction.view", "banking.list", "banking.view" ] }' ``` ### Permission format Permissions follow a hierarchical dot-separated format: `resource[.subresource].action` **Basic permissions:** * `transaction.list`: List transactions * `transaction.create`: Create transactions * `user.edit`: Edit users **Nested resource permissions (for merchant-scoped operations):** * `merchant.company.create`: Create merchant companies * `merchant.transaction.list`: List merchant transactions * `merchant.banking.view`: View merchant banking data ### Wildcard permissions Wildcards grant access to multiple actions at once: | Wildcard | Description | | ------------------------ | ----------------------------------------------------------------------------------- | | `resource.*` | All actions for a resource (e.g., `user.*` grants `user.create`, `user.list`, etc.) | | `resource.subresource.*` | All actions for a sub-resource (e.g., `merchant.company.*`) | | `merchant.*` | All merchant-scoped operations (cascades to all sub-resources) | | `*.*` | All permissions (used for organization admin roles) | ### Permission categories | Category | Permissions | Description | | ----------- | ------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | | User | `user.create`, `user.list`, `user.view`, `user.edit`, `user.delete`, `user.*` | User management | | Role | `role.create`, `role.list`, `role.edit`, `role.delete`, `role.*` | Role management | | Company | `company.create`, `company.list`, `company.edit`, `company.delete`, `company.*` | Company profile | | Transaction | `transaction.create`, `transaction.list`, `transaction.view`, `transaction.refund`, `transaction.cancel`, `transaction.*` | Organization transactions | | 3DS | `3ds.create`, `3ds.*` | 3D Secure session operations | | Banking | `banking.list`, `banking.create`, `banking.edit`, `banking.delete`, `banking.view`, `banking.*` | Balance, statement, cashouts, transfers | | Affiliation | `affiliation.create`, `affiliation.list`, `affiliation.view`, `affiliation.edit`, `affiliation.*` | Organization affiliations | | PIX | `pix.create`, `pix.list`, `pix.view`, `pix.delete`, `pix.*` | PIX keys | | Card | `card.create`, `card.list`, `card.view`, `card.delete`, `card.*` | Stored cards | | Ledger | `ledger.list`, `ledger.view`, `ledger.*` | Ledger entries | | Fee Policy | `fee_policy.create`, `fee_policy.list`, `fee_policy.edit`, `fee_policy.delete`, `fee_policy.*` | Pricing policies | | Cost Policy | `cost_policy.create`, `cost_policy.list`, `cost_policy.edit`, `cost_policy.delete`, `cost_policy.*` | System cost policies | | Webhook | `webhook.list`, `webhook.*` | Webhook dashboard | | API Key | `api_key.create`, `api_key.list`, `api_key.delete`, `api_key.*` | API key management | | Merchant | `merchant.*` | All merchant-scoped operations | **Merchant sub-resources:** * `merchant.company.*`: Merchant CRUD * `merchant.transaction.*`: Merchant transactions * `merchant.3ds.*`: Merchant 3D Secure sessions * `merchant.banking.*`: Merchant balance, cashouts, bank accounts * `merchant.affiliation.*`: Merchant affiliations * `merchant.pix.*`: Merchant PIX keys * `merchant.card.*`: Merchant stored cards * `merchant.ledger.*`: Merchant ledger entries ### Listing available permissions ```bash theme={null} curl https://api-sandbox.rinne.com.br/core/v1/permissions \ -H "Authorization: Bearer JWT_TOKEN" ``` Filter by resource: ```bash theme={null} curl "https://api-sandbox.rinne.com.br/core/v1/permissions?resource=transaction" \ -H "Authorization: Bearer JWT_TOKEN" ``` ## User lifecycle ### 1. User creation Admin creates user with email/phone identifier. User receives verification code. ### 2. Verification User verifies their identifier and sets password: ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/auth/verify \ -H "Content-Type: application/json" \ -d '{ "identifier": "user@company.com", "code": "123456", "password": "SecurePassword123" }' ``` ### 3. Active user User can now log in and access resources based on their roles. ### 4. Suspension Admins can suspend users temporarily: ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/users/USER_ID/suspend \ -H "Authorization: Bearer JWT_TOKEN" ``` ### 5. Reactivation Suspended users can be reactivated: ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/users/USER_ID/activate \ -H "Authorization: Bearer JWT_TOKEN" ``` ## Updating users Update user information and roles: ```bash theme={null} curl -X PATCH https://api-sandbox.rinne.com.br/core/v1/users/USER_ID \ -H "Authorization: Bearer JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "first_name": "John", "last_name": "Updated", "roles": ["admin", "finance-manager"] }' ``` Providing the `roles` array replaces all existing roles. Pass an empty array `[]` to remove all roles. ## Multi-company access Users can belong to multiple companies. When logging in, they select which company context to use: ```bash theme={null} # Login returns available companies POST /v1/auth/login { "requires_company_selection": true, "available_companies": [ { "id": "company-1", "name": "Company A" }, { "id": "company-2", "name": "Company B" } ] } # Select company POST /v1/auth/select-company { "company_id": "company-1" } ``` ## Listing users ### Organization users ```bash theme={null} curl "https://api-sandbox.rinne.com.br/core/v1/users?page=1&limit=20" \ -H "Authorization: Bearer JWT_TOKEN" ``` ### Merchant users ```bash theme={null} curl "https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/users" \ -H "x-api-key: YOUR_API_KEY" ``` ## Next steps Learn about authentication methods Explore user management endpoints # Wallet transactions Source: https://docs.rinne.com.br/guides/wallet-transactions Process Apple Pay and Google Pay transactions through your backend using rinne-js payloads. Wallet transactions use the same transaction endpoint as card payments, but `card_data` is tokenized and captured by wallet elements. Wallet and 3DS are complementary. If a wallet transaction returns `AWAITING_3DS`, continue with either official 3DS flow (session-first or transaction-first), based on your organization architecture. ## Architecture 1. Frontend creates a wallet transaction object with rinne-js. 2. Frontend mounts Apple Pay and Google Pay elements. 3. `onCapture` returns encrypted `payload.card_data` and `payload.payment_method`. 4. Frontend sends payload data to your backend. 5. Backend calls transaction create endpoint in self or merchant context. Do not call the Core API directly from the browser. Keep `x-api-key` on your backend only. Treat `payload.card_data` as a secure encrypted payload. Forward it to your backend without decrypting, reformatting, or storing sensitive values in logs. `network_token`, `cryptogram`, and `number` are accepted only as encrypted values (`ev:` prefix), exactly as the wallet elements emit them. Plaintext values are rejected with `400 VALIDATION_ERROR`. ## Transaction endpoint context | Context | Endpoint | | ---------------------------------- | ---------------------------------------------- | | Self (company) | `POST /v1/transactions` | | Organization on behalf of merchant | `POST /v1/merchants/{merchantId}/transactions` | ## Map rinne-js payload to API fields | rinne-js payload | Rinne Core API field | Notes | | -------------------------------------- | -------------------- | ------------------------------------- | | `payload.card_data` | `card_data` | Send as-is from frontend to backend | | `payload.payment_method` | `payment_method` | Usually `CREDIT_CARD` or `DEBIT_CARD` | | `payload.transaction.details.amount` | `amount` | Amount in cents | | `payload.transaction.details.currency` | `currency` | Usually `BRL` | | Backend-generated UUID | `request_id` | Idempotency key | ## Backend transaction request example ```bash cURL theme={null} curl -X POST 'https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/transactions' \ -H 'x-api-key: YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "provider": "RINNE", "request_id": "wallet-order-0001", "amount": 3990, "currency": "BRL", "capture_method": "ECOMMERCE", "payment_method": "CREDIT_CARD", "installments": 1, "card_data": { "network_token": "ev:encrypted:network_token", "cryptogram": "ev:encrypted:cryptogram", "expiry_month": "12", "expiry_year": "2028", "cardholder_name": "Maria Santos", "last_digits": "1111", "wallet_type": "APPLE_PAY", "display_name": "Visa 1111" } }' ``` ```json 201 Created theme={null} { "id": "tx_123456789", "status": "PROCESSING", "payment_method": "CREDIT_CARD", "amount": 3990, "currency": "BRL" } ``` ## Backend handler example (Node.js) ```typescript server.ts theme={null} app.post('/api/checkout/wallet', async (req, res) => { const { cardData, paymentMethod, amount, currency } = req.body try { const response = await fetch( `https://api-sandbox.rinne.com.br/core/v1/merchants/${process.env.RINNE_MERCHANT_ID}/transactions`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': process.env.RINNE_API_KEY as string }, body: JSON.stringify({ provider: 'RINNE', request_id: crypto.randomUUID(), amount, currency, capture_method: 'ECOMMERCE', payment_method: paymentMethod, installments: 1, card_data: cardData }) } ) const body = await response.json() if (!response.ok) { return res.status(response.status).json(body) } return res.status(200).json(body) } catch { return res.status(500).json({ message: 'Wallet processing failed' }) } }) ``` ## Frontend capture handling In `onCapture`, always call `fail()` when backend processing fails so wallet UI can recover. ```typescript theme={null} const walletHandlers = { onCapture: async (payload, fail) => { try { const response = await fetch('/api/checkout/wallet', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ cardData: payload.card_data, paymentMethod: payload.payment_method, amount: payload.transaction.details.amount, currency: payload.transaction.details.currency }) }) if (!response.ok) { const body = await response.json() throw new Error(body?.error?.message ?? 'Wallet authorization failed') } } catch (error) { fail({ message: error instanceof Error ? error.message : 'Wallet authorization failed' }) } } } ``` ## 3DS behavior in wallet flows Most wallet payloads already contain tokenized authentication data (`network_token` + `cryptogram`), so authentication happened on the device and liability already shifted. The exception is a Google Pay capture made under `allowedAuthMethods: ['UNAUTHENTICATED']`. That payload is an encrypted PAN in `card_data.number`, with no `cryptogram` and no `authentication_type`, and it carries **no liability shift** — treat it exactly like a keyed card and run 3DS on it. See [`allowedAuthMethods`](/rinne-js/wallet-elements#allowedauthmethods-google-pay). If the transaction returns `AWAITING_3DS` (or you want to authenticate it regardless), run your regular 3DS flow: 1. Create a 3DS session. 2. Complete challenge in frontend with `tds_session_id`. 3. Call authenticate endpoint in your context: * Self: `/v1/transactions/{transactionId}/authenticate` * Merchant: `/v1/merchants/{merchantId}/transactions/{transactionId}/authenticate` You can also run session-first 3DS before transaction creation if that better matches your checkout orchestration. ### Optional policy flags for wallet card transactions The same card transaction flags are available in wallet-backed card requests: * `require_3ds: true`: use this when you want wallet transactions to deterministically enter `AWAITING_3DS` in your transaction-first flow. * `refuse_on_challenge: true`: use this when you want challenge-triggered wallet transactions to fail fast as `REFUSED` (`status_reason = CHALLENGE_NOT_ALLOWED`) instead of requiring challenge handling. `require_3ds` and `refuse_on_challenge` cannot both be `true` in the same transaction request. ## Production checklist Wallet checkout is ready for production when: * You send wallet `card_data` only to your backend. * You create transactions with idempotent `request_id` values. * You call `fail()` in `onCapture` for all backend failures. * You handle `PROCESSING`, `APPROVED`, `REFUSED`, and `AWAITING_3DS` outcomes. * You use webhooks for final transaction state updates. ## Related docs * [Wallet Elements (rinne-js)](/rinne-js/wallet-elements) * [Guide: Wallet Checkout (rinne-js)](/rinne-js/guides/wallet-checkout) * [3D Secure authentication](/guides/three-d-secure-authentication) # Webhooks Source: https://docs.rinne.com.br/guides/webhooks Receive real-time event notifications from Rinne Webhooks allow you to receive real-time notifications when events occur in your Rinne account. Instead of polling the API, Rinne sends HTTP POST requests to your server when important events happen. ## Webhook events Rinne sends webhooks for the following events: ### Transaction events * `transaction.created`: New transaction created * `transaction.status-changed`: Transaction status updated ### Dispute events * `dispute.created`: Dispute opened against a settled payment * `dispute.status-changed`: Dispute status updated ### Affiliation events * `affiliation.created`: New affiliation created * `affiliation.status-changed`: Affiliation status updated * `affiliation-bank-account.operational-status-changed`: Affiliation bank account operational status updated ### Company events * `company.created`: New company created * `company.status-changed`: Company status updated ### KYC events * `kyc.completed`: KYC check finished for a merchant company ### Banking events * `cashout.created`: New cashout request created * `cashout.status-changed`: Cashout status updated * `internal-transfer.created`: New internal transfer created * `internal-transfer.status-changed`: Internal transfer status updated * `judicial-blockage.created`: Judicial block or release movement recorded ### Ledger entry events * `ledger-entry.settled`: Ledger entry settled ## Webhook payload structure All webhooks follow a consistent structure: ```json theme={null} { "id": "evt_123456789", "type": "transaction.status-changed", "version": 1, "timestamp": "2025-01-21T10:30:00.000Z", "source": "transaction-service", "company_id": "company-123", "correlation_id": "corr-987654321", "payload": { "transaction_id": "tx_123456789", "old_status": "WAITING_PAYMENT", "new_status": "APPROVED" } } ``` ### Common fields * `id`: Unique event identifier * `type`: Event type (e.g., `transaction.status-changed`) * `version`: Event schema version * `timestamp`: When the event occurred (ISO 8601) * `source`: Service that generated the event * `company_id`: Company associated with the event * `correlation_id`: ID for tracking related events * `payload`: Event-specific data ## Setting up webhooks Contact Rinne support to configure your webhook endpoint URL. We'll create a webhook configuration for your organization. ### Webhook endpoint requirements Your endpoint must: * Accept POST requests * Respond with 2xx status code within 5 seconds * Use HTTPS (required for production) * Handle duplicate events (idempotency) ## Webhook security Rinne uses Svix for webhook delivery, which provides: * Request signing for verification * Automatic retries with exponential backoff * Webhook dashboard for monitoring * Replay functionality for testing ### Verifying webhook signatures Verify that webhooks are from Rinne by checking the signature: ```javascript theme={null} const svix = require('svix'); app.post('/webhooks/rinne', (req, res) => { const payload = req.body; const headers = req.headers; const wh = new svix.Webhook(WEBHOOK_SECRET); try { const verified = wh.verify(payload, headers); // Process the webhook res.status(200).send('OK'); } catch (err) { res.status(400).send('Invalid signature'); } }); ``` ## Handling webhook events ### Transaction status changed ```javascript theme={null} if (event.type === 'transaction.status-changed') { const { transaction_id, old_status, new_status, refund_id } = event.payload; if (new_status === 'APPROVED') { // Payment approved - fulfill order await fulfillOrder(transaction_id); } else if (new_status === 'REFUSED') { // Payment declined - notify customer await notifyPaymentFailed(transaction_id); } else if (new_status === 'PENDING_REFUND') { // Refund initiated - notify customer await notifyRefundInitiated(transaction_id); } else if (new_status === 'REFUNDED' || new_status === 'PARTIALLY_REFUNDED') { // Refund completed - update order status await updateOrderRefundStatus(transaction_id, new_status); } else if (old_status === 'PENDING_REFUND' && new_status === 'APPROVED') { // Refund failed - transaction returned to approved await notifyRefundFailed(transaction_id); } } ``` #### Refund failure handling When a refund fails, you'll receive a `transaction.status-changed` webhook where: * `old_status` is `PENDING_REFUND` * `new_status` is `APPROVED` (if no other refunds exist) or remains `PENDING_REFUND` (if other refunds are still processing) This allows you to handle refund failures appropriately in your system and notify customers when a refund could not be processed. ### Dispute created ```javascript theme={null} if (event.type === 'dispute.created') { const { dispute_id, transaction_id, type, status, amount, entity } = event.payload; await recordDispute(dispute_id, transaction_id, type, amount, entity.deadline_at); if (status === 'CLOSED') { // Created from an already finished claim - no status-change event follows await resolveDispute(dispute_id, event.payload.outcome); await notifyDisputeClosure(dispute_id, event.payload.outcome); } } ``` ### Dispute status changed ```javascript theme={null} if (event.type === 'dispute.status-changed') { const { dispute_id, new_status, outcome } = event.payload; if (new_status === 'UNDER_REVIEW') { // The claim is being analyzed await markDisputeUnderReview(dispute_id); } else if (new_status === 'CLOSED') { // Terminal - outcome is always set here await resolveDispute(dispute_id, outcome); await notifyDisputeClosure(dispute_id, outcome); } } async function notifyDisputeClosure(dispute_id, outcome) { if (outcome === 'UPHELD') { // The claim succeeded and funds are returned await notifyDisputeLost(dispute_id); } else { // DISMISSED or WITHDRAWN - no financial movement await notifyDisputeClosedWithoutLoss(dispute_id, outcome); } } ``` Disputes only move forward and never reopen, so `old_status` is always strictly earlier than `new_status`. Deliveries can repeat or arrive out of order, so make these handlers idempotent and apply an update only when it moves the dispute forward from the status you stored, ideally as a single conditional write. See [Best practices](#best-practices). Group events belonging to one dispute by `dispute_id`—`correlation_id` is unique per event here, not per dispute. See [Disputes](/concepts/disputes) for the full lifecycle, outcomes, and payload fields. ### Affiliation status changed ```javascript theme={null} if (event.type === 'affiliation.status-changed') { const { affiliation_id, new_status, onboarding_url, provider_status_message } = event.payload; if (new_status === 'ACTIVE') { // Affiliation activated - merchant can process transactions await enableMerchantPayments(affiliation_id); } else if (new_status === 'WAITING_DOCUMENTS') { // Provider needs documents - send onboarding URL to merchant await sendOnboardingLink(affiliation_id, onboarding_url); } else if (new_status === 'BLOCKED' && provider_status_message) { // Affiliation blocked - log provider message for debugging await logAffiliationBlocked(affiliation_id, provider_status_message); } else if (new_status === 'INVALID_SETTLEMENT_BANK_ACCOUNT') { // Settlement bank account rejected by provider — notify and prompt a replacement await notifyInvalidSettlementBank(affiliation_id, provider_status_message); } } ``` The `provider_status_message` field contains additional context from the payment provider when available, including possible error messages. ### Affiliation bank account operational status changed ```javascript theme={null} if (event.type === 'affiliation-bank-account.operational-status-changed') { const { bank_account_id, affiliation_id, old_status, new_status, entity } = event.payload; if (new_status === 'JUDICIAL_BLOCK') { // Account under court-ordered restriction — funds may be unavailable await notifyJudicialBlock(affiliation_id, bank_account_id, entity); } else if (new_status === 'REGULAR' && old_status === 'JUDICIAL_BLOCK') { // Judicial block lifted — account operates normally again await notifyJudicialBlockReleased(affiliation_id, bank_account_id, entity); } } ``` The `entity` field is a full write-time snapshot of the affiliation bank account (including `operational_status`). You do not need a follow-up API call to inspect the account. ### Cashout created ```javascript theme={null} if (event.type === 'cashout.created') { const { cashout_id, amount, currency, status } = event.payload; // Cashout request created await logCashoutCreated(cashout_id, amount, currency); } ``` ### Cashout status changed ```javascript theme={null} if (event.type === 'cashout.status-changed') { const { cashout_id, old_status, new_status, error_message } = event.payload; if (new_status === 'COMPLETED') { // Cashout completed successfully await notifyCashoutCompleted(cashout_id); } else if (new_status === 'PARTIALLY_RETURNED') { // Part of the cashout was returned const cashout = await getCashoutDetails(cashout_id); await notifyPartialReturn(cashout_id, cashout.returned_amount); } else if (new_status === 'RETURNED') { // Full cashout amount was returned await notifyFullReturn(cashout_id); } else if (new_status === 'FAILED') { // Cashout failed - error_message contains details when available await notifyCashoutFailed(cashout_id, error_message); } } ``` ### KYC completed ```javascript theme={null} if (event.type === 'kyc.completed') { const { kyc_check_id, company_id, kyc_type, rule_executions } = event.payload; const issues = rule_executions.filter((rule) => rule.status === 'ISSUES_FOUND'); const inconclusive = rule_executions.filter((rule) => rule.status === 'FAILED'); if (issues.length > 0) { // Discrepancies found - review the merchant's data await flagMerchantForReview(company_id, kyc_check_id, issues); } else if (inconclusive.length === 0) { // All rules passed await markMerchantVerified(company_id, kyc_check_id); } // FAILED rules are inconclusive (data source could not evaluate them), // not a problem with the merchant } ``` See [KYC Verification](/concepts/kyc) for rule names, statuses, and the full payload. ### Internal transfer status changed ```javascript theme={null} if (event.type === 'internal-transfer.status-changed') { const { internal_transfer_id, old_status, new_status, error_message } = event.payload; if (new_status === 'COMPLETED') { // Transfer completed successfully await notifyTransferCompleted(internal_transfer_id); } else if (new_status === 'FAILED') { // Transfer failed - error_message contains details when available await notifyTransferFailed(internal_transfer_id, error_message); } } ``` ### Judicial blockage created ```javascript theme={null} if (event.type === 'judicial-blockage.created') { const { judicial_blockage_id, affiliation_bank_account_id, affiliation_id, type, entity, } = event.payload; if (type === 'BLOCK') { // Funds frozen by court order — amount is in entity.amount (cents) await notifyJudicialMovementBlocked(affiliation_id, affiliation_bank_account_id, entity); } else if (type === 'RELEASE') { // Previously blocked funds were released await notifyJudicialMovementReleased(affiliation_id, affiliation_bank_account_id, entity); } } ``` The `entity` field is a full write-time snapshot of the judicial blockage movement. Use it for amount, process details, and `occurred_at` without listing movements from the API. Account-level restriction changes arrive separately as `affiliation-bank-account.operational-status-changed`. ## Webhook dashboard Access your webhook dashboard to: * View webhook delivery history * Replay failed webhooks * Test webhook endpoints * Monitor delivery success rates Get your dashboard URL: ```bash theme={null} curl https://api-sandbox.rinne.com.br/core/v1/webhooks/dashboard \ -H "x-api-key: YOUR_API_KEY" ``` ## Retry behavior If your endpoint returns an error or times out, Rinne automatically retries: * Immediate retry * After 5 seconds * After 5 minutes * After 30 minutes * After 2 hours * After 5 hours * After 10 hours * After 24 hours After all retries fail, the webhook is marked as failed in your dashboard. ## Best practices Return a 200 status code immediately, then process the webhook asynchronously. Don't perform long-running operations in the webhook handler. Use the event `id` to track processed events and ignore duplicates. Webhooks may be delivered more than once. Always verify webhook signatures to ensure requests are from Rinne. Use `correlation_id` to track related events across different webhook types. Regularly check your webhook dashboard for failed deliveries and fix issues promptly. ## Testing webhooks ### Local testing with ngrok Use ngrok to expose your local server for webhook testing: ```bash theme={null} ngrok http 3000 ``` Provide the ngrok URL to Rinne support for webhook configuration. ### Webhook replay Use the webhook dashboard to replay events to your endpoint for testing. ## Example webhook handler ```javascript theme={null} const express = require('express'); const { Webhook } = require('svix'); const app = express(); app.use(express.json()); app.post('/webhooks/rinne', async (req, res) => { const payload = JSON.stringify(req.body); const headers = req.headers; const wh = new Webhook(process.env.WEBHOOK_SECRET); let event; try { event = wh.verify(payload, headers); } catch (err) { return res.status(400).send('Invalid signature'); } // Respond immediately res.status(200).send('OK'); // Process asynchronously processWebhook(event).catch(console.error); }); async function processWebhook(event) { switch (event.type) { case 'transaction.status-changed': await handleTransactionStatusChanged(event.payload); break; case 'affiliation.status-changed': await handleAffiliationStatusChanged(event.payload); break; case 'affiliation-bank-account.operational-status-changed': await handleAffiliationBankAccountOperationalStatusChanged(event.payload); break; case 'dispute.created': await handleDisputeCreated(event.payload); break; case 'dispute.status-changed': await handleDisputeStatusChanged(event.payload); break; case 'judicial-blockage.created': await handleJudicialBlockageCreated(event.payload); break; // Handle other event types } } app.listen(3000); ``` ## Next steps Explore all webhook event schemas Handle errors and edge cases # Introduction Source: https://docs.rinne.com.br/index Complete payment infrastructure platform for managing merchants, processing transactions, and handling banking operations through a unified API. ## Welcome to Rinne Rinne provides a complete payment infrastructure for organizations to manage merchants, process transactions, and handle banking operations through a unified API. Get started with Rinne in minutes Accept Apple Pay & Google Pay on the web Explore our API endpoints Receive real-time event notifications ## What you can build with Rinne Create a complete payment processing solution for your merchants with support for PIX, credit cards, and other payment methods. Build a B2B2B marketplace where organizations manage multiple merchants with isolated operations and custom pricing. Enable merchants to manage bank accounts, process cashouts, and handle internal transfers through integrated providers. Implement flexible pricing strategies with rule-based fee and cost policies tailored to different merchant categories. ## Key features * **Multi-tenant architecture**: Organizations manage multiple merchants with complete data isolation * **Provider integration**: Seamless integration with payment providers like Celcoin and Cappta * **PIX payments**: Full support for Brazil's instant payment system * **Apple Pay & Google Pay**: Accept wallet payments with [rinne-js SDK](/rinne-js) * **Flexible pricing**: Rule-based fee and cost policies with dynamic calculation * **Banking operations**: Balance checking, statements, cashouts, and internal transfers * **Real-time webhooks**: Event-driven notifications for transaction and status updates * **Role-based access**: Granular permission system for user management # Quickstart Source: https://docs.rinne.com.br/quickstart Get started with Rinne API in minutes This guide will help you create your first transaction using the Rinne API. ## Prerequisites Before you begin, you need: * A Rinne organization account (contact [sales@rinne.com.br](mailto:sales@rinne.com.br)) * An API key for your organization — see the [Authentication guide](/guides/authentication#managing-api-keys) to create one * A merchant created under your organization ## Base URLs ```bash Sandbox theme={null} https://api-sandbox.rinne.com.br/core ``` ```bash Production theme={null} https://api.rinne.com.br/core ``` Use the sandbox environment for testing. All examples in this guide use the sandbox URL. ## Step 1: Authenticate your requests All API requests require authentication using your API key in the `x-api-key` header. ```bash theme={null} curl https://api-sandbox.rinne.com.br/core/v1/companies/me \ -H "x-api-key: YOUR_API_KEY" ``` For more details on authentication methods and best practices, see the [Authentication guide](/guides/authentication). ## Step 2: Create a fee policy Before creating merchants, set up a fee policy to define how much you'll charge for processing transactions. ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/pricing/fee-policies \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "default-fees", "description": "Default fee structure for merchants", "is_active": true, "cashout_price": 350, "rules": [ { "conditions": [ { "field": "transaction.payment_method", "operator": "EQUALS", "value": "PIX" } ], "price": { "percentage": 0.99 }, "priority": 1 }, { "conditions": [], "price": { "percentage": 3.0 }, "priority": 99 } ] }' ``` This creates a simple fee policy that charges 0.99% for PIX transactions and 3.0% for all other payment methods. Save the policy `id` from the response. Learn more about fee policies and pricing rules in the [Pricing concept guide](/concepts/pricing). ## Step 3: Create a merchant Organizations can create merchants to process payments on their behalf. ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/merchants \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "full_name": "My Store Ltda.", "name": "My Store", "document_number": "16525269000121", "document_type": "CNPJ", "document_tax_type": "PJ", "mcc": "5912", "contact": { "first_name": "John", "last_name": "Silva", "phone": "+5511999999991", "email": "contact@mystore.com", "mother_name": "Maria Silva", "birth_date": "15-08-1990", "document_number": "81146431023", "politically_exposed": false, "declared_income": 15000, "occupation": "SOFTWARE_DEVELOPER", "net_worth": 100000, "address": { "street": "Rua das Flores", "street_number": "123", "neighborhood": "Centro", "zipcode": "01234567", "country": "076", "state": "SP", "city": "São Paulo" } }, "address": { "street": "Av Paulista", "street_number": "1000", "neighborhood": "Bela Vista", "zipcode": "01310100", "country": "076", "state": "SP", "city": "São Paulo" }, "transfer_configurations": { "automatic_transfer_enabled": true, "transfer_frequency": "DAILY", "transfer_date": 1, "rail": "PIX" }, "declared_revenue": 500000, "bank_account": { "branch_number": "0001", "account_number": "123456", "account_type": "CHECKING", "account_holder_name": "My Store Ltda.", "account_holder_document_number": "16525269000121", "ispb": "00000000" } }' ``` Save the merchant `id` from the response. You'll need it for the next steps. **Sandbox testing**: In sandbox, the contact phone number's last digit controls affiliation behavior: * Ending with **1**: Affiliation approved after document processing * Ending with **2**: Affiliation rejected before requesting documents * Ending with **3**: Affiliation rejected after document processing See the [Affiliations guide](/concepts/affiliations#sandbox-testing-behavior) for details. For more details on merchants and the company hierarchy, see the [Organizations & Merchants guide](/concepts/organizations-and-merchants). ## Step 4: Create an affiliation Before processing transactions, create an affiliation with a payment provider. ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/affiliations \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "provider": "CELCOIN", "allowed_capture_methods": ["ECOMMERCE"], "allowed_payment_methods": ["PIX"] }' ``` The affiliation will be created with status `PENDING_APPROVAL`. Once approved by the provider, it will change to `ACTIVE`. Learn more about affiliations and payment providers in the [Affiliations concept guide](/concepts/affiliations). ## Step 5: Create a PIX transaction Once your affiliation is active, you can create transactions. ```bash theme={null} curl -X POST https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/transactions \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "provider": "CELCOIN", "request_id": "unique-request-123", "amount": 10000, "currency": "BRL", "capture_method": "ECOMMERCE", "payment_method": "PIX", "pix_data": { "description": "Payment for order #123", "expiration_in_seconds": 3600 }, "consumer": { "full_name": "João Silva", "email": "joao@example.com", "document_type": "CPF", "document_number": "81146431023" } }' ``` The response includes a PIX QR code that your customer can scan to complete the payment: ```json theme={null} { "id": "tx_123456789", "status": "WAITING_PAYMENT", "amount": 10000, "pix_data": { "qr_code": "00020126580014br.gov.bcb.pix...", "expires_at": "2025-01-21T11:00:00Z" } } ``` For more details on PIX payments and transaction flows, see the [PIX Payments guide](/guides/pix-payments) and [Transactions concept guide](/concepts/transactions). ## Step 6: Monitor transaction status You can check the transaction status at any time: ```bash theme={null} curl https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/transactions/TRANSACTION_ID \ -H "x-api-key: YOUR_API_KEY" ``` Or set up webhooks to receive real-time status updates: ```json theme={null} { "type": "transaction.status-changed", "payload": { "transaction_id": "tx_123456789", "old_status": "WAITING_PAYMENT", "new_status": "APPROVED" } } ``` Learn how to set up webhooks for real-time notifications in the [Webhooks guide](/guides/webhooks). ## Next steps Accept Apple Pay & Google Pay on the web Learn about API authentication Process instant PIX payments Set up event notifications # Card Element Source: https://docs.rinne.com.br/rinne-js/card-element Collect encrypted card data with validation state, field events, and theme customization. Card Element is the secure card entry component in rinne-js. It renders sensitive fields in an isolated context and returns encrypted values and validation state for your checkout UI. Use Card Element for all card transactions. Rinne expects encrypted values from `mountedCard.values` and does not accept plain PAN/CVC. ## Create a Card Element ```typescript theme={null} const cardElement = await rinne.elements.card({ theme: 'material', locale: 'pt', fields: ['name', 'number', 'expiry', 'cvc'], icons: true }) const mountedCard = await cardElement.mount('#card-element') ``` The Core API requires `cardholder_name` and `last_digits` (4 digits) when creating card transactions. Include `'name'` in your `fields` array so the cardholder name is captured by the Card Element. If you omit `'name'`, you must collect the cardholder name through your own form input and pass it as `cardholder_name` in `card_data` when calling the API. To process real card transactions, the merchant or organization must have an active Rinne affiliation for card processing. ## Capture Payload Contract `cardElement.mount()` returns a `mountedCard` object. In production checkout, send `mountedCard.values.card` to your backend. ```typescript theme={null} interface MountedCard { isValid: boolean isComplete: boolean values: { card: { number: string // encrypted cvv: string // encrypted expiry_month: string // zero-padded, e.g. '01' expiry_year: string // four digits, e.g. '2026' cardholder_name?: string brand?: string last_digits?: string } } unmount(): void } ``` Before the user finishes input, fields in `mountedCard.values.card` can still be empty strings. `number` and `cvv` are encrypted in `mountedCard.values.card`. `expiry_month` and `expiry_year` are plain strings. ## Options ```typescript theme={null} interface CardElementOptions { theme?: 'clean' | 'minimal' | 'material' | ThemeConfig | ThemeFunction colorScheme?: 'light' | 'dark' // iframe root color-scheme; match your page theme locale?: 'pt' | 'en' | 'es' // default: 'pt' icons?: boolean fields?: ('name' | 'number' | 'expiry' | 'cvc')[] onReady?: () => void onChange?: (state: CardState) => void onComplete?: (state: CardState) => void onFocus?: (event: CardFieldEvent) => void onBlur?: (event: CardFieldEvent) => void } ``` ## Event Lifecycle | Event | When it fires | Recommended use | | ------------------- | -------------------------------------- | --------------------------------------- | | `onReady()` | Card iframe is mounted and interactive | Hide loading state | | `onChange(state)` | Any input/value/validation change | Keep submit button and UI state in sync | | `onComplete(state)` | All configured fields are filled | Trigger pre-submit checks | | `onFocus(event)` | A field receives focus | Clear stale field-level errors | | `onBlur(event)` | A field loses focus | Show field-level validation message | `onComplete` means all fields are filled. It does not guarantee the values are valid. ## colorScheme Set `colorScheme` to the same value as your page theme so the card iframe blends correctly with light or dark surfaces. ```typescript theme={null} const colorScheme = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light' const cardElement = await rinne.elements.card({ theme: 'material', colorScheme, locale: 'pt', fields: ['number', 'expiry', 'cvc'] }) ``` ## Field Control ```typescript theme={null} // Recommended: include cardholder name (required by Core API) await rinne.elements.card({ fields: ['name', 'number', 'expiry', 'cvc'] }) // Without cardholder name (you must provide cardholder_name separately in card_data) await rinne.elements.card({ fields: ['number', 'expiry', 'cvc'] }) ``` ## Validation State ```typescript theme={null} interface CardState { isValid: boolean isComplete: boolean errors: { number?: string expiry?: string cvc?: string name?: string } } ``` Use `onChange` to enable/disable your submit button. ```typescript theme={null} const cardElement = await rinne.elements.card({ onChange: (state) => { submitButton.disabled = !state.isComplete || !state.isValid } }) ``` ## Read Mounted Card Values After mount, you can read encrypted values from `mountedCard.values`. ```typescript theme={null} if (mountedCard.isComplete && mountedCard.isValid) { const payload = mountedCard.values // Encrypted values console.log(payload.card.number) console.log(payload.card.cvv) // Additional metadata console.log(payload.card.brand) console.log(payload.card.last_digits) } ``` Card number and CVV are encrypted before exposure in `mountedCard.values`. ## Submit Pattern ```typescript theme={null} const mountedCard = await cardElement.mount('#card-element') async function submitCardCheckout() { if (!mountedCard.isComplete || !mountedCard.isValid) { return } await fetch('/api/checkout/card', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ cardData: mountedCard.values.card }) }) } ``` Never collect raw PAN/CVC in your own inputs. Use only encrypted values from `mountedCard.values`. ## Live Demo This sandbox demo mounts the card element directly in Mintlify, applies `colorScheme` based on the current docs theme, and shows real-time state plus encrypted values. ## Theming `theme` accepts: * A preset: `'clean' | 'minimal' | 'material'` * A config object: `{ preset?, styles?, fonts? }` * A function theme: `(utils) => ({ styles, fonts })` ## Preset Preview This live sandbox preview uses tabs to switch between built-in presets while keeping the same card configuration. The iframe window stays on a light surface for easier visual comparison. ## Custom Styling Theme styles are CSS-in-JS rules injected into the secure iframe. You can style CSS selectors, but you cannot change iframe HTML structure. ### Full Theme Example ```typescript rinne-theme.ts theme={null} export const checkoutTheme = { preset: 'minimal', fonts: ['https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap'], styles: { ':root': { colorScheme: 'light' }, fieldset: { border: '1px solid #dbe2ea', borderRadius: '14px', backgroundColor: '#ffffff', padding: '14px' }, label: { color: '#475569', fontSize: 12, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.04em' }, '.field input': { marginTop: 6, height: '48px', padding: '10px 12px', border: '1px solid #cbd5e1', borderRadius: '10px', backgroundColor: '#ffffff', color: '#0f172a', fontSize: 16 }, '.field[ev-name="number"] input': { letterSpacing: '0.08em', fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace' }, '.field:focus-within input': { borderColor: '#2563eb', boxShadow: '0 0 0 3px rgba(37, 99, 235, 0.15)' }, '.field[ev-valid="false"] input': { borderColor: '#dc2626' }, '.field[ev-has-value="true"] input': { backgroundColor: '#f8fafc' }, '.error': { marginTop: 6, fontSize: 12, color: '#dc2626' } } } ``` ```typescript theme={null} const cardElement = await rinne.elements.card({ colorScheme: 'light', theme: checkoutTheme, locale: 'pt', fields: ['number', 'expiry', 'cvc'] }) ``` ### State Attributes for Selectors Use these iframe attributes for state-aware styling: | Scope | Attribute | Values | | ---------------------- | -------------- | --------------------------------- | | Root `fieldset` | `ev-component` | `Card` | | Root `fieldset` | `ev-valid` | `true` or `false` | | Field wrapper `.field` | `ev-name` | `name`, `number`, `expiry`, `cvc` | | Field wrapper `.field` | `ev-valid` | `true` or `false` | | Field wrapper `.field` | `ev-has-value` | `true` or `false` | ## Responsive Styling Use a function theme and `utils.media()` so breakpoints match the parent document viewport. ```typescript theme={null} const responsiveTheme = (utils) => ({ fonts: ['https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap'], styles: { label: { fontSize: 12 }, '.field input': { height: '48px', padding: '10px 12px', fontSize: 16 }, '.field[ev-name="number"] input': { letterSpacing: '0.08em' }, ...utils.media('(max-width: 768px)', { '.field input': { height: '44px', fontSize: 15 } }), ...utils.media('(max-width: 480px)', { label: { fontSize: 11 }, '.field input': { height: '40px', padding: '8px 10px', fontSize: 14 }, '.field[ev-name="number"] input': { letterSpacing: '0.05em' } }) } }) ``` ```typescript theme={null} const cardElement = await rinne.elements.card({ colorScheme: document.documentElement.classList.contains('dark') ? 'dark' : 'light', theme: responsiveTheme }) ``` Set both `colorScheme` and `theme` using the same page theme source so iframe and host container stay visually consistent. ## Mount Errors `mount()` throws when the target selector is invalid or the provider cannot render the component. ```typescript theme={null} try { const card = await rinne.elements.card() await card.mount('#missing-card-slot') } catch (error) { console.error('Card mount failed', error) } ``` ## Unmount ```typescript theme={null} mountedCard.unmount() ``` # Errors and Debugging Source: https://docs.rinne.com.br/rinne-js/errors-and-debugging Handle SDK errors consistently and troubleshoot wallet, card, and 3DS integrations. This page explains how to handle runtime and integration errors across wallet, card, and 3DS components. Use these patterns to show clear user messages, keep checkout recoverable, and capture diagnostics for backend and support teams. ## Wallet Error Handling Wallet elements expose `onError` and `onCancel` handlers. ```typescript theme={null} onError: (error) => { console.error('Wallet error', error.code, error.message) }, onCancel: () => { console.log('User cancelled wallet flow') } ``` Possible error codes: * `PROVIDER_ERROR` * `VALIDATION_ERROR` * `CONFIGURATION_ERROR` * `UNKNOWN` ## Card and 3DS Mount Errors `mount()` throws if the target is invalid or if the provider component fails to mount. ```typescript theme={null} try { const card = await rinne.elements.card() await card.mount('#missing-container') } catch (error) { console.error('Mount failed:', error) } ``` ```typescript theme={null} try { const threeDSecure = await rinne.elements.threeDSecure() await threeDSecure.mount('') } catch (error) { console.error('3DS mount failed:', error) } ``` ## Common Failure Points The selector passed to `mount()` does not match a DOM node. Ensure the element exists before mounting. Check HTTPS, browser/device compatibility, and Apple Pay domain verification. On the default `allowedAuthMethods: ['THREE_DS']`, a wallet holding only cards saved to the shopper's Google account has nothing eligible to offer, and no SDK event fires. Enable `UNAUTHENTICATED` to accept those cards — at the cost of the liability shift. See [`allowedAuthMethods`](/rinne-js/wallet-elements#allowedauthmethods-google-pay). `allowedAuthMethods` was empty, or carried a value other than `UNAUTHENTICATED` and `THREE_DS`. Your `onCapture` handler likely failed without calling `fail()`. Always call `fail()` when backend charge creation fails. Verify that sessions are not expired. Sessions expire 1 hour after creation. Request a new session and remount on retry. Always check `auth_status` from your backend session response before mounting the 3DS element. A `FAILED` status means the card was rejected before the authentication flow could start — do not mount and do not proceed with payment. Only mount for `ACTION_REQUIRED`; the SDK handles frictionless and challenge flows internally from there. ## Debug Logging Pattern ```typescript theme={null} const log = (event: string, payload?: unknown) => { console.log(`[rinne-checkout] ${event}`, payload) } const walletHandlers = { onCapture: async (payload, fail) => { log('wallet.capture', payload) try { await charge(payload) log('wallet.capture.success') } catch (error) { log('wallet.capture.failure', error) fail({ message: error instanceof Error ? error.message : 'Charge failed' }) } }, onError: (error) => log('wallet.error', error), onCancel: () => log('wallet.cancel') } ``` # Guide: Card + 3DS Checkout Source: https://docs.rinne.com.br/rinne-js/guides/card-3ds-checkout Implement card payments with 3D Secure using session-first or transaction-first flows. Use this guide to run card payments that may require 3D Secure, using either the session-first or transaction-first architecture. Both flows are officially supported. Choose the one that matches how your backend orchestrates transaction state and authentication. In both flows, mounting the [3D Secure Element](/rinne-js/three-d-secure-element) is what runs the authentication. Just creating a session is not enough for the cardholder or the bank to complete the authorization — mount the element for every session with `auth_status: ACTION_REQUIRED` and let `onSuccess`/`onFailure` drive the next step. ## Choose a Flow | Flow | Sequence | When to use | | ----------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------- | | Session-first | Create 3DS session -> complete 3DS -> create transaction | Use when your backend authenticates before transaction creation | | Transaction-first | Create transaction (`AWAITING_3DS`) -> complete 3DS -> authenticate transaction | Use when your backend starts transaction orchestration first | Both flows are official in Rinne. There is no platform-level preferred flow. ## Backend Endpoint Mapping | Context | Create session | Create transaction | Authenticate transaction | | --------------------------------- | ---------------------------------------------- | ---------------------------------------------- | --------------------------------------------------------------------------- | | Self (company) | `POST /v1/3ds-sessions` | `POST /v1/transactions` | `POST /v1/transactions/{transactionId}/authenticate` | | Merchant (organization on behalf) | `POST /v1/merchants/{merchantId}/3ds-sessions` | `POST /v1/merchants/{merchantId}/transactions` | `POST /v1/merchants/{merchantId}/transactions/{transactionId}/authenticate` | ## Shared Frontend Setup Before processing card transactions, your merchant or organization must have an active Rinne card affiliation. Use encrypted values from `mountedCard.values` only. Do not collect raw PAN/CVC in custom inputs. ```html checkout.html theme={null}
``` ```typescript checkout.ts theme={null} import { Rinne } from '@rinnebr/js' const rinne = new Rinne({ merchantId: 'your-merchant-id', environment: 'sandbox' // 'production' when going live }) const submitButton = document.querySelector('#submit-button')! const cardElement = await rinne.elements.card({ theme: 'material', colorScheme: 'dark', locale: 'pt', fields: ['number', 'expiry', 'cvc'], onChange: (state) => { submitButton.disabled = !state.isComplete || !state.isValid } }) const mountedCard = await cardElement.mount('#card-element') ``` ## Session-First Flow Send the encrypted card number and plain expiry (month/year) from `mountedCard.values` to your backend. The Card Element encrypts the card number client-side; expiry values are not encrypted. Create `threeDSecure` once and call `mount(tdsSessionId)`. Mounting starts the authentication — the element resolves frictionless flows silently and renders the issuer's challenge when one is required. On `onSuccess`, call your backend and include the authenticated session ID. ### Backend session request example ```bash cURL theme={null} curl -X POST 'https://your-api.example.com/api/3ds-sessions' \ -H 'Content-Type: application/json' \ -d '{ "amount": 1090, "currency": "BRL", "card": { "number": "ev:encrypted:...", "expiry": { "month": "04", "year": "2028" } } }' ``` ```json Example response theme={null} { "id": "550e8400-e29b-41d4-a716-446655440000", "tds_session_id": "tds_visa_e7c2yfa3867c", "auth_status": "ACTION_REQUIRED" } ``` ```typescript theme={null} function runThreeDSAuthentication(tdsSessionId: string): Promise { return new Promise((resolve, reject) => { rinne.elements .threeDSecure({ target: '#three-ds-container', colorScheme: 'dark', onSuccess: () => resolve(), // authenticated — the awaiting flow continues to the transaction step onFailure: (error) => reject(new Error(error?.message ?? '3DS authentication failed')), onError: (error) => reject(new Error(error.message)) }) .then((threeDS) => threeDS.mount(tdsSessionId)) .catch(reject) // element creation or mount failed — authentication never started }) } async function post(url: string, body: object) { const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }) if (!res.ok) { const error = await res.json().catch(() => ({})) throw new Error(error.message ?? `Request failed with status ${res.status}`) } return res.json() } async function runSessionFirst(amountInCents: number) { if (!mountedCard.isComplete || !mountedCard.isValid) { throw new Error('Card form is not ready') } const cardValues = mountedCard.values const session = await post('/api/3ds-sessions', { amount: amountInCents, currency: 'BRL', card: { number: cardValues.card.number, expiry: { month: cardValues.card.expiry_month, year: cardValues.card.expiry_year } } }) if (session.auth_status === 'FAILED') { throw new Error(session.failure_reason ?? '3DS authentication failed') } if (session.auth_status === 'ACTION_REQUIRED') { await runThreeDSAuthentication(session.tds_session_id) } // AUTHENTICATED — authentication completed, either at creation time or after challenge. Continue to transaction. const payment = await post('/api/transactions', { amount: amountInCents, currency: 'BRL', // must match the session exactly — amount and currency are validated on link cardData: { number: cardValues.card.number, cvv: cardValues.card.cvv, expiry_month: cardValues.card.expiry_month, expiry_year: cardValues.card.expiry_year, cardholder_name: cardValues.card.cardholder_name, last_digits: cardValues.card.last_digits }, three_d_secure_session_id: session.id }) return payment } document.querySelector('#checkout-form')!.addEventListener('submit', async (event) => { event.preventDefault() submitButton.disabled = true try { const payment = await runSessionFirst(12990) if (payment.status === 'APPROVED' || payment.status === 'AUTHORIZED') { showSuccess() // your UI } else { showDeclined(payment.status) // your UI — REFUSED, etc. } } catch (error) { showError(error) // your UI — session failures and 3DS failures land here } finally { submitButton.disabled = false } }) ``` The `/api/...` routes are your backend: they forward these payloads to the Rinne endpoints in [Backend Endpoint Mapping](#backend-endpoint-mapping) with your API key. See [3D Secure authentication](/guides/three-d-secure-authentication) for the backend contract. ## Transaction-First Flow In this flow, your backend transaction status determines whether frontend should run 3DS. Backend creates the transaction and returns the initial status. Use `require_3ds: true` when you want deterministic `AWAITING_3DS` entry in transaction-first. If transaction is `AWAITING_3DS`, create a 3DS session with the same amount/currency and encrypted card number/expiry. Branch on `auth_status`: mount only for `ACTION_REQUIRED`, skip for `AUTHENTICATED`, abort for `FAILED`. After challenge success (or if already `AUTHENTICATED`), backend authenticates the pending transaction with session `id` using: * Self: `POST /v1/transactions/{transactionId}/authenticate` * Merchant: `POST /v1/merchants/{merchantId}/transactions/{transactionId}/authenticate` Card transactions typically resolve synchronously — the `/authenticate` response already contains the final status (`APPROVED`, `AUTHORIZED`, or `REFUSED`). Use this status to drive your UI immediately. ### Challenge strategy flags (backend) * `require_3ds: true`: create directly in `AWAITING_3DS`, so you can enforce 3DS while staying on one transaction-first backend flow. * `refuse_on_challenge: true`: fail fast with `REFUSED` + `status_reason=CHALLENGE_NOT_ALLOWED` when a challenge-triggered path would occur. * Use `refuse_on_challenge` when you intentionally do not support challenge UX for a merchant, channel, or transaction segment. * The two flags cannot both be `true`. ### Transaction-first status branching | Transaction status | Frontend/backend action | | ----------------------------------- | ---------------------------------------------------------------- | | `AWAITING_3DS` | Run 3DS session/challenge and call authenticate | | `PROCESSING` | Wait for webhook or status refresh; do not call authenticate yet | | `AUTHORIZED` / `APPROVED` | Payment already completed; continue normal success flow | | `REFUSED` + `CHALLENGE_NOT_ALLOWED` | Fail fast path triggered; no 3DS handling required | | `REFUSED` (other) | Regular decline flow | If a transaction is initially `PROCESSING` and later becomes `AWAITING_3DS` (for example, soft-decline recovery), run the same transaction-first 3DS steps at that point. ### Transaction-first orchestration example ```typescript theme={null} type TransactionResult = { id: string status: 'PROCESSING' | 'AWAITING_3DS' | 'AUTHORIZED' | 'APPROVED' | 'REFUSED' status_reason?: string } async function runTransactionFirst(amountInCents: number): Promise { if (!mountedCard.isComplete || !mountedCard.isValid) { throw new Error('Card form is not ready') } const cardValues = mountedCard.values const created: TransactionResult = await post('/api/transactions', { amount: amountInCents, currency: 'BRL', cardData: { number: cardValues.card.number, cvv: cardValues.card.cvv, expiry_month: cardValues.card.expiry_month, expiry_year: cardValues.card.expiry_year, cardholder_name: cardValues.card.cardholder_name, last_digits: cardValues.card.last_digits }, require_3ds: true }) // If you set refuse_on_challenge instead of require_3ds, you will need the following branch: // if (created.status === 'REFUSED' && created.status_reason === 'CHALLENGE_NOT_ALLOWED') { // throw new Error('3DS challenge is disabled by transaction policy') // } if (created.status !== 'AWAITING_3DS') { return created } const session = await post('/api/3ds-sessions', { amount: amountInCents, currency: 'BRL', card: { number: cardValues.card.number, expiry: { month: cardValues.card.expiry_month, year: cardValues.card.expiry_year } } }) if (session.auth_status === 'FAILED') { throw new Error(session.failure_reason ?? '3DS authentication failed') } if (session.auth_status === 'ACTION_REQUIRED') { await runThreeDSAuthentication(session.tds_session_id) } const authenticated: TransactionResult = await post( `/api/transactions/${created.id}/authenticate`, { three_d_secure_session_id: session.id } ) return authenticated } ``` Authenticate requests must use session `id` as `three_d_secure_session_id`. Do not send `tds_session_id` to your backend authenticate route. Wire `runTransactionFirst` to your submit handler the same way as `runSessionFirst` in the session-first flow. ## Session ID Mapping * `tds_session_id`: use in `threeDSecure.mount(tds_session_id)` on frontend. * `id`: use as `three_d_secure_session_id` in backend transaction create/authenticate calls. ## Handle 3DS Statuses In practice, sessions come back as `ACTION_REQUIRED` or `FAILED` at creation — even frictionless authentication completes through the SDK element. The API contract allows `AUTHENTICATED` as a creation-time response for forward compatibility, so always branch on all three statuses. | `auth_status` at creation | Meaning | Frontend action | | ------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------------- | | `ACTION_REQUIRED` | Authentication has not run yet — the required action is your frontend's | Mount the 3DS element; the SDK handles frictionless and challenge internally | | `FAILED` | Card was rejected before the flow could start | Surface the error; let the user retry with a different card or payment method | | `AUTHENTICATED` | Frictionless authentication completed at creation time (rare) | Skip mounting; proceed directly to transaction creation or `/authenticate` | ### Frictionless vs challenge When the element is mounted, the issuer decides whether the transaction can be authenticated silently (frictionless) or needs cardholder interaction (challenge): * **Frictionless**: authentication completes in the background with no visible UI. `onSuccess` fires automatically, often within seconds. * **Challenge**: an issuer-hosted verification step appears inside the element (OTP, app approval, biometric). Even when that screen tells the cardholder to approve the purchase in their banking app, it is rendered by the element, which also detects the approval — do not replace it with a waiting screen of your own. `onSuccess` fires after the user completes it. Your frontend code handles both identically — `onSuccess` is the signal to proceed in either case. You can inspect `authentication_flow` on the backend after the session is authenticated to see which path was taken. ## Retry Strategy Sessions are single-use: after a failure, an expired session, or a rejected `/authenticate` call, the attempt must restart with a **new** session. In this guide's architecture that happens naturally — the submit handler re-enables the button, and `runSessionFirst`/`runTransactionFirst` create a fresh session on every attempt. ```typescript theme={null} // Each retry creates a fresh session — never remount a failed or consumed one const session = await post('/api/3ds-sessions', { /* same payload as the first attempt */ }) await runThreeDSAuthentication(session.tds_session_id) ``` The SDK automatically cleans up the previous element instance when a new one mounts. # Guide: Wallet Checkout Source: https://docs.rinne.com.br/rinne-js/guides/wallet-checkout Implement a production-ready Apple Pay and Google Pay checkout flow with backend processing. Use this guide to implement a production wallet flow where the frontend handles wallet UX and your backend performs Core API calls. The core contract is simple: capture encrypted `payload.card_data` in `onCapture` and forward it unchanged to your server. ## Flow Overview 1. Initialize SDK in the frontend 2. Create a transaction 3. Mount wallet buttons 4. On capture, send encrypted data to backend 5. Backend creates transaction in Rinne API Forward `payload.card_data` as encrypted secure data to your backend. Avoid logging sensitive payload fields in plaintext logs. ## Frontend Implementation ```typescript checkout.ts theme={null} import { Rinne } from '@rinnebr/js' const rinne = new Rinne({ merchantId: 'your-merchant-id', environment: 'sandbox' }) const transaction = await rinne.transaction.create({ amount: 3990, currency: 'BRL', country: 'BR', lineItems: [{ label: 'Order Total', amount: 3990 }] }) const handlers = { onCapture: async (payload, fail) => { try { const response = await fetch('/api/checkout/wallet', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ cardData: payload.card_data, paymentMethod: payload.payment_method, amount: payload.transaction.details.amount, currency: payload.transaction.details.currency, requestId: crypto.randomUUID() }) }) if (!response.ok) { const body = await response.json() throw new Error(body.message ?? 'Wallet payment failed') } } catch (error) { fail({ message: error instanceof Error ? error.message : 'Wallet payment failed' }) } }, onError: (error) => console.error('Wallet error:', error), onCancel: () => console.log('Wallet cancelled') } const applePay = await rinne.elements.applePay(transaction, { button: { type: 'pay', color: 'black', locale: 'pt' }, ...handlers }) await applePay.mount('#apple-pay') const googlePay = await rinne.elements.googlePay(transaction, { button: { type: 'pay', color: 'black', locale: 'pt' }, colorScheme: 'dark', // Optional. Defaults to ['THREE_DS'] — device-tokenized cards only. // allowedAuthMethods: ['UNAUTHENTICATED', 'THREE_DS'], ...handlers }) await googlePay.mount('#google-pay') ``` ## Google Pay Authentication Methods By default Google Pay only returns device-tokenized cards (`network_token` + `cryptogram`), which carry a liability shift and need no further authentication. Passing `allowedAuthMethods: ['UNAUTHENTICATED', 'THREE_DS']` also accepts cards saved to the shopper's Google account, which arrive as an encrypted PAN with **no liability shift**. An `UNAUTHENTICATED` capture behaves like a keyed card: it has no cryptogram, and you can — and generally should — run 3DS on it using the same session-first or transaction-first flow as the Card element. Branch on the discriminant before deciding: ```typescript theme={null} onCapture: async ({ card_data, transaction }, fail) => { if (card_data.wallet_type === 'GOOGLE_PAY' && card_data.authentication_type !== '3DS') { // Encrypted PAN, no liability shift — run 3DS with card_data.number } // Otherwise the wallet already authenticated on-device } ``` See [`allowedAuthMethods`](/rinne-js/wallet-elements#allowedauthmethods-google-pay) for the full contract. ## Backend Example (Node.js) ```typescript server.ts theme={null} app.post('/api/checkout/wallet', async (req, res) => { const { cardData, paymentMethod, amount, currency, requestId } = req.body try { const rinneResponse = await fetch( `https://api-sandbox.rinne.com.br/core/v1/merchants/${process.env.RINNE_MERCHANT_ID}/transactions`, { method: 'POST', headers: { 'content-type': 'application/json', 'x-api-key': process.env.RINNE_API_KEY }, body: JSON.stringify({ amount, currency, provider: 'RINNE', payment_method: paymentMethod, capture_method: 'ECOMMERCE', request_id: requestId, card_data: cardData, installments: 1 }) } ) const body = await rinneResponse.json() if (!rinneResponse.ok) { return res.status(rinneResponse.status).json({ message: body.message ?? 'Charge failed' }) } return res.status(200).json(body) } catch (error) { return res.status(500).json({ message: 'Internal wallet processing error' }) } }) ``` Never call Rinne transaction APIs directly from the browser. Keep your API key server-side. ## Verification Checklist Wallet checkout is correctly implemented when: * `onCapture` sends encrypted payloads to your backend * Backend creates transaction with your API key * `fail()` is called whenever backend processing fails * If transaction status is `AWAITING_3DS`, your checkout continues with the 3DS flow # rinne-js Source: https://docs.rinne.com.br/rinne-js/index Browser SDK for Apple Pay, Google Pay, card collection, and 3D Secure authentication. Closed Beta rinne-js helps you accept **wallet** and **card** payments with a single browser SDK. It gives you secure payment elements, encrypted card payloads, and a consistent API for Apple Pay, Google Pay, Card, and 3D Secure. Mount Apple Pay and Google Pay with shared callbacks and button customization. Collect encrypted card details with validation state, events, and theme control. Run issuer authentication challenges and remount with new session IDs on retry. Follow production flows for wallet checkout or card + 3DS checkout. ## How It Works Create a `Rinne` instance with your `merchantId` and environment. Use `rinne.transaction.create()` for wallet flows, or mount the card element to collect encrypted card data. Mount a wallet button or a 3DS challenge element based on your checkout flow. Send encrypted payloads or 3DS session IDs to your backend and finalize the transaction server-side. ## PCI Scope and Encryption rinne-js helps you reduce PCI scope by collecting and encrypting sensitive payment data inside a secure environment. * Plaintext card number and CVC are not exposed to your frontend business logic * Your backend receives encrypted payloads, not raw PAN/CVC * Rinne API is designed to receive and process this encrypted data flow With the card and wallet elements, sensitive card data is encrypted before it leaves the secure component context. ## Start Here Install with npm, yarn, pnpm, or CDN. Build your first wallet checkout in minutes. Try rinne-js in an interactive sandbox. # Installation Source: https://docs.rinne.com.br/rinne-js/installation Install and initialize rinne-js with package managers or CDN. Learn how to set up rinne-js in your frontend and establish the base SDK configuration. rinne-js handles secure payment UI and encrypted payload generation in the browser, while your backend remains responsible for API keys and transaction finalization. ## Prerequisites Before you integrate rinne-js, make sure you have: * A valid Rinne `merchantId` * A frontend running on HTTPS (required for wallet payments) * A backend endpoint that processes encrypted card data Do not expose your Rinne API key in frontend code. Only the `merchantId` belongs in the browser. ## Install the Package ```bash npm theme={null} npm install @rinnebr/js ``` ```bash yarn theme={null} yarn add @rinnebr/js ``` ```bash pnpm theme={null} pnpm add @rinnebr/js ``` ## Initialize the SDK ```typescript checkout.ts theme={null} import { Rinne } from '@rinnebr/js' const rinne = new Rinne({ merchantId: 'your-merchant-id', environment: 'sandbox', // optional: defaults to 'production' // rinneUrl: 'https://api-sandbox.rinne.com.br/core/v1' // optional custom base URL }) ``` Initialization is lazy. The SDK fetches merchant configuration when you first create a transaction or element. ## Use via CDN (UMD) Use UMD when you need script-tag integration or a quick prototype. ```html index.html theme={null} ``` The URL above always points to the latest version. You can also pin a specific version: ```html theme={null} ``` ## Next Step After installation, continue with the [quickstart](/rinne-js/quickstart). # Live Demo Source: https://docs.rinne.com.br/rinne-js/live-demo Try Apple Pay, Google Pay, and card collection in an interactive sandbox checkout. See rinne-js in action. This interactive demo renders the same payment elements you'll use in production — Apple Pay, Google Pay, and card collection — all powered by a single SDK instance running in sandbox mode. Use it to explore the checkout experience, inspect element behavior, and understand the flow before you start integrating. This demo runs in sandbox mode and is intended for integration testing only. No data or payments are processed. Use `environment: 'sandbox'` while developing and switch to `production` only after wallet, card, and 3DS paths are fully tested. # Quickstart Source: https://docs.rinne.com.br/rinne-js/quickstart Accept wallet and card payments with 3D Secure using rinne-js. This quickstart builds a checkout that supports Apple Pay, Google Pay, and card entry with 3D Secure — all from one SDK instance. ## Goal In this guide, you will: * Initialize the SDK * Mount wallet buttons and card fields * Run 3D Secure when required * Forward encrypted payment data to your backend ## 1. Initialize Rinne ```typescript checkout.ts theme={null} import { Rinne } from '@rinnebr/js' const rinne = new Rinne({ merchantId: 'your-merchant-id', environment: 'sandbox' }) ``` ## 2. Mount Payment Elements Mount wallet buttons for Apple Pay and Google Pay, and a card element for manual entry. ```typescript checkout.ts theme={null} const transaction = await rinne.transaction.create({ amount: 2500, lineItems: [ { label: 'Premium Plan', amount: 2000 }, { label: 'Tax', amount: 500 } ] }) const walletHandlers = { onCapture: async (payload, fail) => { try { await submitToBackend(payload) } catch (error) { fail({ message: error instanceof Error ? error.message : 'Payment failed' }) } } } const applePay = await rinne.elements.applePay(transaction, { button: { type: 'pay', color: 'black', locale: 'pt' }, ...walletHandlers }) await applePay.mount('#apple-pay-button') const googlePay = await rinne.elements.googlePay(transaction, { button: { type: 'pay', color: 'black', locale: 'pt' }, ...walletHandlers }) await googlePay.mount('#google-pay-button') const submitButton = document.querySelector('#submit-button')! const cardElement = await rinne.elements.card({ theme: 'material', locale: 'pt', fields: ['number', 'expiry', 'cvc'], onChange: (state) => { submitButton.disabled = !state.isComplete || !state.isValid } }) const mountedCard = await cardElement.mount('#card-element') ``` If you omit `currency` and `country`, the SDK defaults to `BRL` and `BR`. Always call `fail()` inside `onCapture` when backend processing fails. This lets the wallet UI show a proper failure state and allows retry. ## 3. Handle Card Submission with 3DS This example follows the **session-first** pattern: create a 3DS session, authenticate, then create the transaction. When the customer submits the card form, read the encrypted values, run authentication by mounting the 3DS element, and create the transaction after authentication succeeds. Mounting is what runs the authentication — the element resolves frictionless flows silently and shows the issuer's challenge only when one is required. ```typescript checkout.ts theme={null} async function submitCardPayment(amountInCents: number) { if (!mountedCard.isComplete || !mountedCard.isValid) return const cardValues = mountedCard.values const session = await create3DSSession({ amount: amountInCents, currency: 'BRL', card: { number: cardValues.card.number, expiry: { month: cardValues.card.expiry_month, year: cardValues.card.expiry_year } } }) if (session.auth_status === 'FAILED') { throw new Error(session.failure_reason ?? '3DS authentication failed') } if (session.auth_status === 'ACTION_REQUIRED') { await runThreeDSAuthentication(session.tds_session_id) } return await createTransaction({ amount: amountInCents, cardData: cardValues.card, three_d_secure_session_id: session.id }) } async function runThreeDSAuthentication(tdsSessionId: string) { return new Promise((resolve, reject) => { rinne.elements.threeDSecure({ target: '#three-ds-container', onSuccess: () => resolve(), // authenticated — submitCardPayment continues to createTransaction onFailure: (error) => reject(new Error(error?.message ?? 'Authentication failed')), onError: (error) => reject(new Error(error.message)) }).then((threeDS) => threeDS.mount(tdsSessionId)) .catch(reject) // creation/mount failures must also reject — never leave the promise hanging }) } ``` `create3DSSession` and `createTransaction` represent calls to your backend. Rinne also supports a **transaction-first** pattern where you create the transaction before authentication. See the [Card + 3DS Guide](/rinne-js/guides/card-3ds-checkout) for both flows with full backend examples. Never collect raw PAN/CVC in your own inputs. Use only encrypted values from `mountedCard.values`. ## 4. Verify Success Your checkout is correctly wired when: * Wallet authorization triggers `onCapture` and your backend receives an encrypted payload * Card submission creates a 3DS session, mounts the 3DS element to authenticate, and creates the transaction with the authenticated session ID after `onSuccess` * `fail()` is called whenever wallet backend processing fails ## Next Steps Explore session-first and transaction-first flows for production card payments. Build the backend endpoint that processes wallet capture payloads. Configure themes, validation events, and field options. Handle modal vs embedded challenges, retries, and session expiry. # Requirements and Support Source: https://docs.rinne.com.br/rinne-js/requirements-and-support Production requirements, CSP, browser support, and operational guidance for rinne-js. Use this checklist before you go live with wallet, card, or 3DS flows. ## Requirements Wallet payments do not work on insecure HTTP origins. Serve all checkout pages over `https://`. Before you enable Apple Pay in production, download the Apple Pay domain association file and host it at `/.well-known/apple-developer-merchantid-domain-association` on every domain and subdomain where Apple Pay runs, then share the list of domains with Rinne so they can be submitted to Apple. The file is the same for every merchant. See [Apple Pay domain verification](/rinne-js/wallet-elements#apple-pay-domain-verification) for details. If your application uses CSP headers, allow these origins: ```text theme={null} script-src https://js.evervault.com; connect-src https://keys.evervault.com https://api.evervault.com https://api.rinne.com.br https://api-sandbox.rinne.com.br; frame-src https://ui-components.evervault.com; ``` For a successful checkout, users need: * A supported browser and device * A configured wallet with at least one eligible card Keep API keys server-side and process encrypted payloads from frontend elements on your backend. ## Browser support | Browser | Apple Pay | Google Pay | | ------------------------------ | --------- | ---------- | | Safari (iOS 10+, macOS 10.12+) | ✅ | ✅ | | Chrome | ✅ | ✅ | | Firefox | ❌ | ✅ | | Edge | ✅ | ✅ | Wallet availability also depends on device capabilities and wallet setup. Apple Pay requires Safari configuration, and Google Pay requires at least one registered card. # 3D Secure Element Source: https://docs.rinne.com.br/rinne-js/three-d-secure-element Mount the element that runs 3DS authentication — frictionless and challenge — and handle success, failure, and retry flows. The 3D Secure Element runs 3DS authentication for a session created on your backend. Mounting it with a `tds_session_id` is what starts authentication: frictionless flows complete invisibly, and when the issuer requires a challenge the element renders the issuer's verification UI (OTP, approve-in-app instructions, biometric) and detects completion. You create the element once, mount it with a `tds_session_id`, and react to success, failure, or technical errors through callbacks. The element is not optional challenge UI — it is the component that executes authentication. Until it is mounted, nothing happens: the cardholder's bank is not notified and the session stays `ACTION_REQUIRED` until it expires. Never replace it with your own "confirm with your bank" screen — when an in-app approval is needed, the element shows that instruction and detects the approval. ## Create a 3DS Element Create the element once with callbacks, then call `mount(sessionId)` whenever you need to authenticate. ```typescript theme={null} const threeDSecure = await rinne.elements.threeDSecure({ target: '#three-ds-container', // requires
in your page size: { width: '100%', height: '450px' }, colorScheme: 'dark', locale: 'pt', onReady: () => hideLoadingIndicator(), onSuccess: () => finalizePayment(), // authenticated — create/authenticate the transaction on your backend onFailure: (error) => offerRetry(error), // authentication failed — a new session is required to retry onError: (error) => showFallback(error) // technical error — log it and offer another payment method }) // tds_session_id from the 3DS session your backend created — mounting runs the authentication await threeDSecure.mount('tds_visa_e7c2yfa3867c') ``` ## Options ```typescript theme={null} interface ThreeDSecureElementOptions { target?: string | HTMLElement // default: modal overlay size?: { width: string; height: string } colorScheme?: 'light' | 'dark' // iframe root color-scheme; match your page theme theme?: ThemeOption locale?: 'pt' | 'en' | 'es' // default: 'pt' onReady?: () => void onSuccess?: () => void onFailure?: (error?: { code?: string; message?: string }) => void onError?: (error: { code: string; message: string }) => void } ``` ## Callback Semantics | Callback | Meaning | Typical action | | ----------- | ------------------------------------------- | ---------------------------------------- | | `onReady` | Challenge UI is rendered and ready | Hide loading placeholders | | `onSuccess` | Authentication completed successfully | Call backend to confirm/finalize payment | | `onFailure` | Authentication was attempted but failed | Show retry CTA and create a new session | | `onError` | Technical error (component/network/runtime) | Show fallback path and log diagnostics | ### Awaiting the Outcome Checkout flows usually need to `await` the authentication before continuing. Wrap creation and mount in a promise that the callbacks settle — and forward creation/mount rejections with `.catch(reject)` so the promise can never hang: ```typescript theme={null} function runThreeDSAuthentication(tdsSessionId: string): Promise { return new Promise((resolve, reject) => { rinne.elements .threeDSecure({ target: '#three-ds-container', onSuccess: () => resolve(), // authenticated — the awaiting checkout flow continues below onFailure: (error) => reject(new Error(error?.message ?? 'Authentication failed')), onError: (error) => reject(new Error(error.message)) }) .then((threeDS) => threeDS.mount(tdsSessionId)) .catch(reject) // element creation or mount failed — authentication never started }) } // In your checkout flow: if (session.auth_status === 'ACTION_REQUIRED') { await runThreeDSAuthentication(session.tds_session_id) } // Authenticated — create or authenticate the transaction on your backend, linked by session `id` const payment = await processTransaction({ three_d_secure_session_id: session.id }) ``` See [Guide: Card + 3DS Checkout](/rinne-js/guides/card-3ds-checkout) for this pattern inside a complete session-first and transaction-first checkout. ## colorScheme Use `colorScheme` to align the 3DS iframe root with your page theme. ```typescript theme={null} const colorScheme = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light' const threeDSecure = await rinne.elements.threeDSecure({ target: '#three-ds-container', colorScheme, onSuccess: () => confirmPayment() }) ``` ## Theme `theme` supports preset strings (`clean`, `minimal`, `material`) and function themes. ```typescript theme={null} const threeDSecure = await rinne.elements.threeDSecure({ theme: 'material' }) ``` If you pass `theme` as an object, the 3DS component currently uses the `preset` value. ### Full Theme Example ```typescript theme={null} const isDark = document.documentElement.classList.contains('dark') const threeDSTheme = (utils) => ({ styles: { ':root': { colorScheme: isDark ? 'dark' : 'light', backgroundColor: isDark ? '#020617' : '#ffffff' }, body: { margin: 0, fontFamily: 'Inter, ui-sans-serif, system-ui, sans-serif' }, ...utils.media('(max-width: 640px)', { body: { fontSize: 14 } }) } }) const threeDSecure = await rinne.elements.threeDSecure({ target: '#three-ds-container', size: { width: '100%', height: '450px' }, colorScheme: isDark ? 'dark' : 'light', theme: threeDSTheme }) ``` 3DS challenge copy and issuer-controlled challenge UI cannot be fully restyled. Use `theme`, `size`, and `colorScheme` to style the container experience around it. ## Mount Behavior * `mount(sessionId)` requires a non-empty session ID. * If you mount again with a new session ID, the SDK cleans up the previous instance automatically. * Callbacks from options are automatically wired for each mount. 3DS sessions expire 1 hour after creation. When a session expires, request a new session ID from your backend and call `mount(newSessionId)`. The SDK automatically attempts to unmount the previous instance, but you should unmount manually for safety. ## Backend Contract Use the frontend 3DS element as an authentication step between your backend session creation and transaction confirmation. Return both session IDs from your backend: * `tds_session_id` for `threeDSecure.mount(...)` in the frontend * `id` for `three_d_secure_session_id` in backend transaction create/authenticate calls Use one of the supported create-session endpoints based on your context: * `POST /v1/3ds-sessions` (self) * `POST /v1/merchants/{merchantId}/3ds-sessions` (organization on behalf of merchant) In practice, sessions come back as `ACTION_REQUIRED` or `FAILED` at creation — even frictionless authentication completes through the SDK element. The API contract allows `AUTHENTICATED` as a creation-time response for forward compatibility, so always branch on all three statuses. * **`FAILED`**: the card was rejected before the flow could start — surface the error and let the user retry. * **`AUTHENTICATED`**: authentication already completed — skip mounting and proceed to payment confirmation. * **`ACTION_REQUIRED`**: mount with `tds_session_id`. The SDK runs the full 3DS flow internally. When mounted, the issuer decides the path: * **Frictionless**: the issuer authenticates the transaction silently in the background. No challenge UI is shown. `onSuccess` fires automatically. * **Challenge**: the issuer presents a verification step inside the element. `onSuccess` fires after the user completes it. Your code handles both identically. ```typescript theme={null} if (session.auth_status === 'FAILED') { showError(session.failure_reason ?? 'Authentication failed') return } if (session.auth_status === 'AUTHENTICATED') { confirmPayment() return } await threeDSecure.mount(session.tds_session_id) // onSuccess/onFailure/onError callbacks drive the next step ``` On `onSuccess`, create/authenticate the transaction on your backend using the internal session `id`. On `onFailure`, create a new session before retrying. ## Modal vs Embedded Without `target`, the element renders as a modal overlay on top of your page. With `target`, it renders inline inside your container — use this to keep the challenge within your checkout layout. ```typescript theme={null} // No target — the element renders its own overlay; no container markup needed const threeDSecure = await rinne.elements.threeDSecure({ onSuccess: () => confirmPayment() }) await threeDSecure.mount(sessionId) ``` ```html theme={null}
``` ```typescript theme={null} const threeDSecure = await rinne.elements.threeDSecure({ target: '#three-ds-container', size: { width: '100%', height: '450px' }, onSuccess: () => confirmPayment() }) await threeDSecure.mount(sessionId) ```
## Mount Errors `mount()` rejects when the element cannot start — for example, an empty or invalid session ID. Authentication outcomes never reject the mount promise: they arrive through `onSuccess`/`onFailure`/`onError` after mounting succeeds. ```typescript theme={null} try { await threeDSecure.mount(session.tds_session_id) } catch (error) { // The element could not start — authentication never ran showError('Could not start authentication. Please try again.') } ``` ## Unmount ```typescript theme={null} const mounted = await threeDSecure.mount(sessionId) // Later — for example, the customer abandons checkout mid-challenge mounted.unmount() ``` ## Retrying After Failure 3DS sessions are single-use: after `onFailure`, an expired session, or a failed `/authenticate` call, request a **new** session from your backend and mount again — remounting cleans up the previous instance and rewires the callbacks. ```typescript theme={null} async function retryAuthentication() { const session = await create3DSSession() // your backend — never reuse a failed or consumed session if (session.auth_status !== 'ACTION_REQUIRED') return handleStatus(session) await threeDSecure.mount(session.tds_session_id) } ``` `locale` applies to SDK/component messages. Issuer-hosted challenge copy is controlled by the card issuer. # Transactions Source: https://docs.rinne.com.br/rinne-js/transactions Create payment transactions and understand defaults and payload structure. Transactions in rinne-js represent the checkout amount and context used by wallet elements during authorization. Create a new transaction whenever totals change so wallet sheets and backend charge requests stay aligned. ## Create a Transaction Use `rinne.transaction.create()` before mounting Apple Pay or Google Pay. ```typescript theme={null} const transaction = await rinne.transaction.create({ amount: 5000, currency: 'BRL', country: 'BR', lineItems: [ { label: 'Subtotal', amount: 4500 }, { label: 'Shipping', amount: 500 } ] }) ``` Amount in cents. ISO 4217 currency code. ISO 3166 country code. Optional line item breakdown used by wallet UIs. ## Behavior Notes * The SDK initializes lazily on first transaction/element call. * `currency` defaults to `BRL` and `country` defaults to `BR`. * Returned transactions are intended to be passed into wallet elements. Create a new transaction whenever checkout totals change. Keep the transaction amount aligned with what you charge on your backend. ## Example with Dynamic Cart ```typescript theme={null} const total = cartItems.reduce((sum, item) => sum + item.amount, 0) const transaction = await rinne.transaction.create({ amount: total, lineItems: cartItems.map((item) => ({ label: item.label, amount: item.amount })) }) ``` # Wallet Elements Source: https://docs.rinne.com.br/rinne-js/wallet-elements Configure and mount Apple Pay and Google Pay elements with shared handlers. Wallet Elements provide Apple Pay and Google Pay buttons backed by the same transaction object and callback contract. Use them to collect tokenized wallet payment data and forward encrypted payloads to your backend for transaction processing. ## Supported Wallet Elements ```typescript theme={null} const applePay = await rinne.elements.applePay(transaction, options) const googlePay = await rinne.elements.googlePay(transaction, options) ``` Both elements support the same core options (`button`, `onCapture`, `onError`, `onCancel`). `googlePay()` also supports `colorScheme` and `allowedAuthMethods`. ## Options ```typescript theme={null} interface WalletMountOptions { button?: { color?: 'black' | 'white' // default: 'black' locale?: 'pt' | 'en' | 'es' // default: 'pt' type?: 'book' | 'buy' | 'checkout' | 'donate' | 'order' | 'pay' | 'plain' | 'subscribe' // default: 'plain' borderRadius?: number size?: { width: string | number // default: '100%' height: string | number // default: '40px' } } colorScheme?: 'light' | 'dark' // Google Pay only: iframe root color-scheme allowedAuthMethods?: [GooglePayAuthMethod, ...GooglePayAuthMethod[]] // Google Pay only // default: ['THREE_DS'] — never empty onCapture?: (payload, fail) => Promise | void onError?: (error) => void onCancel?: () => void } ``` ## Event Lifecycle | Stage | Callback | What you should do | | ------------------------------ | -------------------------- | --------------------------------------------------------------------------- | | User authorizes wallet payment | `onCapture(payload, fail)` | Send `payload.card_data` to your backend and create/confirm the transaction | | Backend processing fails | `fail({ message })` | Call `fail()` so the wallet UI exits processing state and can retry | | User closes wallet sheet | `onCancel()` | Restore your checkout UI and keep cart state | | Provider or SDK issue | `onError(error)` | Show a fallback payment method and log details for debugging | ## Button Behavior | Capability | Apple Pay | Google Pay | | ------------ | ------------------------------------------------------------------------- | ----------------- | | Button color | `black`, `white` | `black`, `white` | | Button types | `book`, `buy`, `checkout`, `donate`, `order`, `pay`, `plain`, `subscribe` | Same as Apple Pay | | Locale input | `pt`, `en`, `es` | `pt`, `en`, `es` | ## Wallet Availability Apple Pay and Google Pay buttons are not rendered when the environment does not support them. This happens silently — `mount()` resolves without rendering any visible element. Conditions that prevent rendering include: * Non-HTTPS origin * Browser or device without wallet support * No card in the wallet that is eligible under your configuration * Apple Pay domain not verified (Apple Pay only) * Google Pay wallet holding only account-saved cards, on the default `allowedAuthMethods: ['THREE_DS']` — enabling `UNAUTHENTICATED` removes this case Design your checkout layout so it gracefully handles an empty wallet button slot, or check support before mounting. ## Apple Pay Domain Verification Apple Pay only appears on domains that are verified with Apple. If your domain is not verified, the Apple Pay button will not render. Before going live: 1. Download the Apple Pay domain association file. Keep the filename exactly as it downloads — `apple-developer-merchantid-domain-association` with no extension. 2. Host it on every domain and subdomain where Apple Pay is used, at the exact path `/.well-known/apple-developer-merchantid-domain-association`, served over HTTPS. 3. Let Rinne know which domains and subdomains to register so they can be submitted to Apple for verification. If the file is removed, replaced, or becomes unavailable, Apple can invalidate the domain and Apple Pay will stop working until the domain is verified again. ## onCapture Contract `onCapture` gives you encrypted wallet card data, payment method, and the transaction object you created. ```typescript theme={null} interface WalletSuccessPayload { card_data: CardData payment_method: 'CREDIT_CARD' | 'DEBIT_CARD' transaction: EvTransaction } ``` `card_data` is a **discriminated union**, not a single flat interface. `wallet_type` separates the wallets, and for Google Pay `authentication_type` separates its two shapes: ```typescript theme={null} interface CardDataCommon { expiry_month: string // "MM" expiry_year: string // "YYYY" brand?: string last_digits?: string issuer?: string segment?: string country?: string display_name?: string } // Apple Pay always authenticates on-device, so it has a single shape. interface ApplePayCardData extends CardDataCommon { wallet_type: 'APPLE_PAY' network_token: string cryptogram: string token_provider: string eci?: string | null device_id?: string // Apple Pay only authentication_type?: '3DS' } // Google Pay under THREE_DS — device-tokenized, liability shifts to the issuer. interface GooglePayThreeDsCardData extends CardDataCommon { wallet_type: 'GOOGLE_PAY' authentication_type: '3DS' network_token: string cryptogram: string token_provider: string eci?: string | null } // Google Pay under UNAUTHENTICATED — encrypted PAN, no liability shift. interface GooglePayUnauthenticatedCardData extends CardDataCommon { wallet_type: 'GOOGLE_PAY' number: string authentication_type?: never } type CardData = | ApplePayCardData | GooglePayThreeDsCardData | GooglePayUnauthenticatedCardData ``` Fields absent from a member are left off instead of typed as optional, so reading one before narrowing is a compile error rather than a silent `undefined`: ```typescript theme={null} onCapture: ({ card_data }) => { if (card_data.wallet_type === 'APPLE_PAY') { card_data.cryptogram // string } else if (card_data.authentication_type === '3DS') { card_data.cryptogram // string — device-tokenized Google Pay } else { card_data.number // string — encrypted PAN, no liability shift } } ``` Narrow on `wallet_type` before `authentication_type`. Apple Pay sets `authentication_type` to `'3DS'` only on some captures, so one that arrives without it would otherwise fall into the branch you wrote for the unauthenticated Google Pay shape. `GooglePayMountOptions` is generic in the methods you allow, so `onCapture` receives only the shapes your selection can produce. With `allowedAuthMethods` omitted or set to `['THREE_DS']`, you get `GooglePayThreeDsCardData` directly and no narrowing is needed. `payment_method` is inferred from wallet funding data and defaults to `CREDIT_CARD` when funding type is not provided by the provider. ```typescript theme={null} onCapture: async (payload, fail) => { try { await sendToBackend(payload.card_data, payload.payment_method, payload.transaction) } catch (error) { fail({ message: error instanceof Error ? error.message : 'Payment failed' }) } } ``` If backend processing fails, call `fail()` inside `onCapture`. Without it, the wallet may stay in a processing state. ## allowedAuthMethods (Google Pay) `allowedAuthMethods` selects which card credentials Google Pay may return. It defaults to `['THREE_DS']` — only device-tokenized cards carrying a 3DS cryptogram. Leave the option out and that is what you get, so nothing widens what you accept unless you ask for it. | Value | Credential | Liability shift | | -------------------- | ------------------------------------------------------------ | -------------------------- | | `THREE_DS` (default) | Card tokenized on the device: `network_token` + `cryptogram` | Yes — shifts to the issuer | | `UNAUTHENTICATED` | Card saved to the shopper's Google account: encrypted PAN | **No** | Enable `UNAUTHENTICATED` only if your organization has made that call, and consider running 3DS on those payments. ```typescript theme={null} const googlePay = await rinne.elements.googlePay(transaction, { allowedAuthMethods: ['UNAUTHENTICATED', 'THREE_DS'], ...walletHandlers }) ``` Accepting `UNAUTHENTICATED` means **no liability shift** — the same risk profile as a keyed card without 3DS. The upside is that shoppers whose wallet holds only account-saved cards (common on desktop) can pay instead of seeing an empty Google Pay sheet. `THREE_DS` and `UNAUTHENTICATED` are the only values the option accepts. Note that the `THREE_DS` option value is not the same string as `authentication_type: '3DS'` in the captured `card_data`, which is the API's wire value. ### Payload differences The two methods return different `card_data` shapes: | `card_data` field | `THREE_DS` | `UNAUTHENTICATED` | | ----------------------------------------------------- | -------------- | ----------------- | | `network_token` | `token.number` | — | | `number` | — | encrypted PAN | | `expiry_month` / `expiry_year` | present | present | | `cryptogram` / `eci` / `token_provider` | present | — | | `authentication_type` | `'3DS'` | — | | `brand`, `last_digits`, `display_name`, `wallet_type` | present | present | The missing `authentication_type` is the marker to branch on server-side if you apply different risk rules to unauthenticated payments. Both shapes go to the standard API host — the PAN is already encrypted before it reaches the SDK, so the [PCI endpoints](/pci-endpoints) are not involved. A malformed payload now fails the payment instead of being downgraded. A Google Pay token missing `tokenServiceProvider` throws `Google Pay network token is missing tokenServiceProvider` rather than falling back to the PAN shape, and a payload with no card at all throws `Google Pay response must contain either a network token or a card number`. Both surface through `fail()` like any other capture error. ## colorScheme (Google Pay) Use `colorScheme` to align the Google Pay iframe with your page theme. ```typescript theme={null} const colorScheme = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light' const googlePay = await rinne.elements.googlePay(transaction, { button: { color: 'black', type: 'pay', locale: 'pt' }, colorScheme, ...walletHandlers }) ``` ## Mount and Unmount ```typescript theme={null} const element = await rinne.elements.applePay(transaction, options) const mounted = await element.mount('#apple-pay-container') // Later mounted.unmount() ``` `mount()` accepts either a CSS selector or an `HTMLElement`. ## Mount Errors `mount()` throws if the target is missing or the provider cannot render the wallet button. ```typescript theme={null} try { const applePay = await rinne.elements.applePay(transaction, options) await applePay.mount('#missing-wallet-slot') } catch (error) { console.error('Wallet mount failed', error) } ``` ## Full Wallet Example ```typescript theme={null} const transaction = await rinne.transaction.create({ amount: 1999, lineItems: [{ label: 'Demo Product', amount: 1999 }] }) const colorScheme = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light' const walletHandlers = { onCapture: async (payload, fail) => { try { const response = await fetch('/api/wallet/charge', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ cardData: payload.card_data, paymentMethod: payload.payment_method, amount: payload.transaction.details.amount }) }) if (!response.ok) { const body = await response.json() throw new Error(body.message ?? 'Charge failed') } } catch (error) { fail({ message: error instanceof Error ? error.message : 'Charge failed' }) } }, onError: (error) => console.error(error), onCancel: () => console.log('Cancelled') } const applePay = await rinne.elements.applePay(transaction, { button: { color: 'black', type: 'pay', locale: 'pt', borderRadius: 0 }, ...walletHandlers }) await applePay.mount('#apple-pay') const googlePay = await rinne.elements.googlePay(transaction, { button: { color: 'black', type: 'pay', locale: 'pt', borderRadius: 0 }, colorScheme, ...walletHandlers }) await googlePay.mount('#google-pay') ```