RPG For Developers
On this page
RPG FOR DEVELOPERS

Build your first financial integration.

The complete RPG Connecta guide: API keys, sandbox, bank consent, data, webhooks and production. Public documentation for developers and agents.

https://www.rpgcapital.com.br/api/connecta/v1

01

From account to first response

Sign in with Google in the console. Choose Sandbox, create a named API key and save its full value immediately: it is shown only once. No application registration, website URL or Malvo account is required.

Store the key as RPG_API_KEY on your backend. Create one synthetic connection, save data.id as RPG_CONNECTION_ID, then read accounts and transactions. The examples below run outside the console; console queries instead use your signed-in session.

  • 1. Create an RPG sandbox key.
  • 2. Run a quickstart and retain its connection ID.
  • 3. Explore JSON, switch scenarios and inspect events.
cURL + jq
# Requires bash, curl and jq. Store your sandbox key in RPG_API_KEY.
set -euo pipefail
: "${RPG_API_KEY:?Set RPG_API_KEY in your shell}"
BASE='https://www.rpgcapital.com.br/api/connecta/v1'
CONNECTION_ID=$(curl --fail-with-body -sS "$BASE/connections" \
  -H "Authorization: Bearer $RPG_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"provider":"rpg_simulator","scenario":"success"}' | jq -er '.data.id')
curl --fail-with-body -sS "$BASE/connections/$CONNECTION_ID/accounts" \
  -H "Authorization: Bearer $RPG_API_KEY"
curl --fail-with-body -sS "$BASE/connections/$CONNECTION_ID/transactions?offset=0" \
  -H "Authorization: Bearer $RPG_API_KEY"
02

Keys, permissions and environments

Send Authorization: Bearer followed by your RPG key on every API call. rpg_test_ identifies sandbox; rpg_live_ identifies production. Optional X-RPG-Environment must match the key. A credential can access only its owner’s connections in its own environment.

Keep secrets in server environment variables or a secret manager. Never place them in browser bundles, public prompts, logs or Git. Rename keys in the console; rotate by creating a replacement, updating your backend and revoking the old key. Revocation takes effect immediately.

  • Limit: 10 active keys per user per environment.
  • Limit: 60 requests per minute per key. Respect Retry-After on HTTP 429.
  • Limit: 20 active connections per user per environment.
03

A complete sandbox inside your workspace

The RPG simulator persists synthetic accounts and transactions per connection. Use provider=rpg_simulator, then POST /connections/{id}/simulate with a scenario. It does not contact a bank, require a card or create a real payment.

success produces a complete fixture; pending awaits authorization; expired requires renewed consent; unavailable simulates an institution outage; partial exposes an incomplete snapshot. Previously valid data can remain visible during an outage or pending state: inspect status and last_synced_at before using it.

The dedicated Malvo sandbox is a separate adapter that requires RPG-side sandbox credentials. Its readiness is displayed in advanced options. The anonymous public preview at /developers/sandbox/data is a separate small fixture, not your persistent workspace.

  • Use success after another scenario to restore the complete synthetic fixture.
  • Reuse connection IDs when rerunning examples; disconnect unused fixtures.
04

Production and bank consent

Explicitly activate Production in the console, create a production key and POST /connections with {"provider":"malvo"}. RPG authenticates with its own Malvo account server-side. The response includes an ephemeral connect_url for the account holder to authorize bank access. Do not log it or let agents automatically open or preview it.

A bank connection belongs to the developer’s RPG account. In an end-user integration, your backend must map that connection to the correct signed-in customer and enforce access before returning data. Never give an RPG API key to the customer’s browser.

After authorization, your backend calls POST /connections/{id}/complete with {"item_id":"returned item ID"}. RPG verifies provider ownership. The hosted /developers/connect return page can complete the flow for a signed-in developer; an end-user journey must use your own authenticated backend completion flow.

Read GET /connections/{id} for status. POST /sync reads the latest data available at the provider; it does not guarantee a new bank refresh. POST /reconnect returns a new consent URL. DELETE /connections/{id} disconnects and removes the local snapshot.

  • Production uses real data and requires the account holder’s consent.
  • Gateway billing is not enabled; activation does not charge a card.
  • Bank availability and financial coverage depend on the provider and institution.
05

A predictable data contract

Responses use JSON. Resource lists have data, pagination and meta. Monetary values are integer minor units: 185000 BRL means R$ 1,850.00. Preserve currency when displaying or aggregating amounts. Positive transaction amounts represent incoming funds; negative amounts represent outgoing funds.

IDs are source identifiers, not labels; retain them for reconciliation. Categories can be a string, provider object or null. Tags may be absent upstream; never infer that missing classification means zero activity. Dates and missing fields should be handled explicitly.

The response below is illustrative. Your actual IDs, dates, categories, records and balances may differ. Current resources cover accounts and transactions; investments, credit reports, cards and payments are not additional published Connecta resources.

    JSON
    {
      "data": [{
        "id": "CONNECTION_ID_tx_1",
        "account_id": "CONNECTION_ID_checking",
        "date": "2026-10-31",
        "description": "Recebimento de cliente",
        "amount_minor": 185000,
        "currency": "BRL",
        "category": "Receitas",
        "tags": ["synthetic", "sandbox"]
      }],
      "pagination": {"offset": 0, "limit": 100, "total": 1, "next_offset": null},
      "meta": {"environment":"sandbox", "provider":"rpg_simulator", "synthetic":true, "status":"connected", "last_synced_at":"2026-10-31T12:00:00Z", "partial":false, "note":null}
    }
    06

    Pagination, partial results and freshness

    Accounts and transactions accept offset (a non-negative integer). Each page contains up to 100 records. Follow pagination.next_offset until null; transactions also accepts account_id. Do not assume the first page is the entire history.

    meta.partial and meta.status describe the snapshot. Always expose last_synced_at and an incomplete-data indicator in your product. A successful HTTP response can contain stale or partial data. Bank refresh latency is not a real-time balance guarantee.

    Provider ingestion is bounded; large histories may be partial. Polling sync repeatedly does not remove those limits. Events and request feeds expose only the latest 100 entries; the console shows a smaller recent history. Persist the history your own product needs.

    07

    Complete examples you can run

    Use Node.js 18+ or Python 3 with the standard library; no SDK installation is required. Export RPG_API_KEY in your terminal using your own sandbox key. Save either example as the filename in its first comment, then run it.

    Set RPG_CONNECTION_ID to reuse a fixture on subsequent runs. Both examples create a synthetic connection only when that variable is absent, then read accounts and paginate transactions. A failed call includes server error details for diagnosis.

      Node.js 18+
      // Node.js 18+. Save as quickstart.mjs; run: node quickstart.mjs
      const base = 'https://www.rpgcapital.com.br/api/connecta/v1';
      const key = process.env.RPG_API_KEY;
      if (!key?.startsWith('rpg_test_')) throw new Error('Use a sandbox RPG_API_KEY');
      async function api(path, body) {
        const response = await fetch(base + path, {
          method: body ? 'POST' : 'GET',
          signal: AbortSignal.timeout(30000),
          headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
          ...(body ? { body: JSON.stringify(body) } : {})
        });
        const payload = await response.json();
        if (!response.ok) throw new Error(`${response.status} ${payload.error?.code}: ${payload.error?.message} [${payload.request_id}]`);
        return payload;
      }
      // Reuse RPG_CONNECTION_ID on subsequent runs to avoid creating duplicate fixtures.
      const id = process.env.RPG_CONNECTION_ID || (await api('/connections', {
        provider: 'rpg_simulator', scenario: 'success'
      })).data.id;
      console.log('Connection:', id);
      console.log('Accounts:', (await api(`/connections/${id}/accounts`)).data);
      const transactions = [];
      let offset = 0;
      do {
        const page = await api(`/connections/${id}/transactions?offset=${offset}`);
        transactions.push(...page.data);
        offset = page.pagination.next_offset;
      } while (offset !== null);
      console.log('Transactions:', transactions);
      Python 3
      # Python 3, standard library only. Save as quickstart.py.
      import json, os, urllib.request, urllib.error
      BASE = 'https://www.rpgcapital.com.br/api/connecta/v1'
      KEY = os.environ.get('RPG_API_KEY', '')
      if not KEY.startswith('rpg_test_'):
          raise RuntimeError('Use a sandbox RPG_API_KEY')
      def api(path, body=None):
          request = urllib.request.Request(BASE + path,
              data=json.dumps(body).encode() if body is not None else None,
              headers={'Authorization': 'Bearer ' + KEY, 'Content-Type': 'application/json'})
          try:
              with urllib.request.urlopen(request, timeout=30) as response:
                  return json.load(response)
          except urllib.error.HTTPError as error:
              details = json.load(error)
              raise RuntimeError(f'{error.code}: {details}') from error
      connection_id = os.environ.get('RPG_CONNECTION_ID') or api('/connections',
          {'provider': 'rpg_simulator', 'scenario': 'success'})['data']['id']
      print('Connection:', connection_id)
      print('Accounts:', api(f'/connections/{connection_id}/accounts')['data'])
      transactions, offset = [], 0
      while offset is not None:
          page = api(f'/connections/{connection_id}/transactions?offset={offset}')
          transactions.extend(page['data'])
          offset = page['pagination']['next_offset']
      print('Transactions:', transactions)
      08

      Signed webhooks and safe processing

      Configure a public HTTPS endpoint in the console for each environment. Copy the signing secret when it is displayed. The header RPG-Signature has t=<Unix timestamp>,v1=<hex HMAC>. Compute HMAC-SHA256(secret, timestamp + "." + raw request body). Verify the signature before parsing or processing data and reject timestamps more than five minutes away.

      Use the event ID as a durable unique key. Persist receipt and enqueue business work atomically; repeated delivery must not repeat financial side effects. The example validates signatures only and intentionally performs no business operation; add durable storage before processing events in production.

      Delivery status and manual resend are available in the console. Automatic retries are not enabled. Upstream Malvo automatic events additionally require RPG-side signing-secret configuration; when unavailable, use completion and explicit sync. Treat an event as a signal to read the canonical connection state.

        Node.js · Webhook
        // Node.js 18+, no framework. Terminate HTTPS at your public hosting provider.
        import http from 'node:http';
        import { createHmac, timingSafeEqual } from 'node:crypto';
        const secret = process.env.RPG_WEBHOOK_SECRET;
        if (!secret) throw new Error('Set RPG_WEBHOOK_SECRET');
        http.createServer(async (req, res) => {
          if (req.method !== 'POST' || req.url !== '/webhooks/rpg') { res.writeHead(404).end(); return; }
          const chunks = []; let size = 0;
          for await (const chunk of req) {
            size += chunk.length;
            if (size > 1_000_000) { res.writeHead(413).end(); return; }
            chunks.push(chunk);
          }
          const raw = Buffer.concat(chunks);
          const parts = String(req.headers['rpg-signature'] || '').split(',');
          const t = parts.find(p => p.startsWith('t='))?.slice(2) || '';
          if (!/^\d+$/.test(t) || Math.abs(Date.now()/1000 - Number(t)) > 300) { res.writeHead(401).end(); return; }
          const expected = createHmac('sha256', secret).update(t + '.').update(raw).digest();
          const valid = parts.filter(p => p.startsWith('v1=')).some(p => {
            const hex = p.slice(3); if (!/^[a-f0-9]{64}$/.test(hex)) return false;
            return timingSafeEqual(Buffer.from(hex, 'hex'), expected);
          });
          if (!valid) { res.writeHead(401).end(); return; }
          try {
            const event = JSON.parse(raw.toString('utf8'));
            // REQUIRED: persist event.id with a UNIQUE constraint in your database,
            // and enqueue the business effect in the same transaction (outbox pattern).
            // Duplicate event IDs must become a no-op, including after restarts.
            // This signature example intentionally performs no business effect.
            res.writeHead(200, {'Content-Type':'application/json'}).end(JSON.stringify({received:true,id:event.id}));
          } catch { res.writeHead(400).end(); }
        }).listen(Number(process.env.PORT || 3000));
        09

        Errors with a clear recovery path

        Errors contain error.code, error.message and request_id; X-Request-ID is also returned. Share the request ID when requesting support, never the API key or a financial payload.

        Retry transient failures with bounded exponential backoff and jitter. Respect Retry-After. Do not blindly retry POST /connections after an ambiguous timeout: list existing connections first to avoid duplicates. There is no published idempotency-key contract.

          JSON
          {
            "error": {
              "code": "environment_mismatch",
              "message": "Esta chave pertence a outro ambiente."
            },
            "request_id": "uuid"
          }
          10

          Build a useful dashboard

          Start with a server route that reads the authenticated customer’s mapped connection. Fetch all account pages and transaction pages, retain IDs and show balances in the correct currency. Separate incoming and outgoing transactions; group by month and category while keeping unknown categories visible.

          Show connection state, latest synchronization time, partial-data status and a reconnect action beside the data. A 401 should offer sign-in or credential repair; a bank outage should preserve the latest valid snapshot with its timestamp. Test all five sandbox states before switching to production.

          11

          Instructions for coding agents

          Read this guide and the OpenAPI contract before generating code. Use the English Markdown URL as the compact entry point. Keep placeholders for credentials; ask the user to create a key in the console and place it in the backend secret store.

          Generate a server integration, map user ownership, paginate, validate webhook signatures and expose errors with request IDs. Use the sandbox first. Do not invent MCP endpoints, installable skills, SDK packages, bank coverage, pricing or payment capabilities.

          An agent may help create code and explain the authorization steps. Google sign-in, bank consent and activation must remain visible to the user and respect the authentication tools and permissions available in the agent’s environment.

          12

          Production readiness and current boundaries

          Before launch: rotate development credentials, isolate sandbox and production secrets, verify customer ownership checks, test expired consent and partial results, validate raw-body signatures and durable deduplication, and confirm your monitoring and support workflow.

          Current functionality: Google sign-in, RPG keys, persistent simulator, Malvo production adapter, accounts, transactions, events, request diagnostics and manually resent signed webhooks. The dedicated Malvo sandbox and upstream event readiness depend on server configuration.

          No published MCP server, packaged SDK, automatic webhook retries or billing gateway is available. The legacy merchant API at /api/public/v1 and /developers/docs has a different contract and must not be used with Connecta keys. Contact: comercial@rpgcapital.com.br.

          HTTP API

          MethodEndpointPurpose
          GET/connectionsList your connections
          POST/connectionsCreate a synthetic or bank-consent connection
          GET/connections/{id}Read connection status
          GET/connections/{id}/accountsRead account balances; offset pagination
          GET/connections/{id}/transactionsRead transactions; offset and account_id filters
          POST/connections/{id}/simulateChange the RPG simulator scenario
          POST/connections/{id}/syncRead the latest provider snapshot
          POST/connections/{id}/completeVerify and bind the authorized provider item
          POST/connections/{id}/reconnectGenerate a new bank-consent URL
          DELETE/connections/{id}Disconnect and delete the local snapshot
          GET/eventsRead the latest 100 events
          GET/requestsRead the latest 100 request summaries

          Data dictionary

          idStable source identifier; keep it for reconciliation
          account_idAccount associated with the transaction
          name / typeAccount display name and account type
          balance_minorBalance in integer minor currency units
          amount_minorSigned transaction amount in integer minor units
          currencyExplicit ISO currency code; do not mix currencies
          dateSource transaction date; handle missing values
          descriptionSource transaction description
          categoryString, object or null; handle all shapes
          tagsClassification metadata; source coverage varies
          pagination.next_offsetNext offset, or null when there are no more pages
          meta.synthetic / providerWhether data is fictitious and which provider produced it
          meta.status / partialConnection state and whether the snapshot is incomplete
          meta.last_synced_atSnapshot time; expose it in your UI

          Status

          StatusMeaningNext action
          401 invalid_api_keyMissing, invalid or revoked keyReplace the server credential; never use a session cookie as an API key
          403 environment_mismatchHeader and key disagreeUse the matching environment or remove the optional header
          403 production_inactiveProduction has not been activatedActivate it explicitly in the console
          404 not_foundConnection not found for this account and environmentCheck the ID, owner and key environment
          409 authorization_pendingBank authorization is incompleteComplete consent before synchronizing
          409 connection_limit / key_limitActive resource limit reachedRevoke an unused key or disconnect an unused connection
          429 rate_limitedPer-key request limit reachedWait for Retry-After; reduce polling
          502 provider_error / provider_auth_errorProvider failed or rejected authenticationRetry transient failures; persistent failures require RPG support
          503 provider_not_configuredProvider credentials are unavailableUse the RPG simulator or contact RPG support
          400 invalid_scenario / invalid_offsetInvalid request parameterUse a supported scenario and a non-negative integer offset
          BALCÃO · v12.0 · 480bb05