Skip to main content

Payments Integration

The Payments integration is a provider-agnostic REST API for the core financial operations every backoffice needs:
  1. Issue an invoice (charge)POST /payments/invoices. An Invoice here is a payment request — a charge collected via PIX, boleto, card, ACH or SEPA depending on region.
  2. Cancel an invoicePOST /payments/invoices/{id}/cancel (voids unpaid charge).
  3. Issue a tax document (NFS-e/NF-e/PEPPOL)POST /payments/invoices/{id}/tax-documents. Optional, region-specific (Brazil today, Europe later). Can also be auto-issued from the Invoice creation call.
  4. Pay a beneficiary (a “prize” or any payout)POST /payments/payouts.
  5. Read a bank statementGET /payments/balance-transactions.
Every transaction carries first-class control fields used by your finance team for reconciliation: cost_center and contractor_reference you provide on the request; our_number is generated by SalesOS and returned in the response (and on every statement entry). Behind the scenes, SalesOS routes the request to the right provider — you do not pick the provider unless you want to.
Charge ≠ tax document. An Invoice is the request for payment (boleto, PIX, card). The fiscal document (NFS-e in BR, PEPPOL in EU) is a separate, optional resource attached to the Invoice. This split keeps the API portable across BR / US / EU.
The API follows accepted market conventions: REST resource model, integer minor-unit money, ISO-8601 timestamps, RFC 9457 Problem Details for errors, Idempotency-Key header for safe retries, cursor pagination, signed webhooks. If you have integrated with any major payments API you will feel at home.

Region routing

SalesOS picks the right backend automatically from the customer’s country and currency. You do not need to choose.
Customer countryCurrencyStatusMethods
BRBRLlivepix, boleto, bolepix, card (card-as-PIX)
USUSDcoming sooncard, ach_debit, bank_transfer
EU member statesEUR / localcoming sooncard, sepa_debit, bank_transfer
RegionTax document typeStatus
BRnfse (services)live
BRnfe (products)coming soon
EUpeppol (e-invoice)coming soon
USn/asales tax via Invoice line metadata; no fiscal document
Outbound payouts route by destination: BRL → PIX cashout; other currencies → international transfer.

How It Works

  1. You get an API Key in the SalesOS Dashboard (Admin > Integrations > API Keys) with the right scopes (payments:write, payments:read).
  2. You issue invoices (charges). SalesOS creates the payment request for the customer’s region (BR live; US/EU coming soon). You receive a payable artifact: PIX QR-code/copy-paste, boleto barcode, card payment URL or hosted checkout link.
  3. (Optional) A tax document is attached. When currency = BRL and you set tax_document.auto_issue: true, SalesOS issues an NFS-e automatically after the charge is confirmed paid. You can also call POST /invoices/{id}/tax-documents later to issue or retry.
  4. You pay beneficiaries (employees, partners, prize winners). SalesOS routes BRL via PIX and other currencies via international transfer.
  5. You read a unified statement — every invoice, payout, fee and tax-document fee in one ledger, filterable by cost_center, our_number, contractor_reference, period and provider.
  6. You listen to webhooks for terminal events (invoice.paid, invoice.canceled, tax_document.issued, payout.paid, …) instead of polling.

Quick Start

1

Get your API Key

Go to Admin > Integrations > API Keys in the SalesOS Dashboard. Create a new key with the scopes payments:write and payments:read. Copy the key — it will only be shown once.Your key looks like: sk_live_a1b2c3d4e5f6g7h8i9j0...
2

Issue your first invoice (BR — PIX charge with auto NFS-e)

Create a PIX charge for a Brazilian customer. The optional tax_document block tells SalesOS to issue the NFS-e automatically after the charge is paid.
curl -X POST https://api.play2sell.com/functions/v1/payments/invoices \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Idempotency-Key: inv-2026-04-27-001" \
  -H "Content-Type: application/json" \
  -d '{
    "customer": {
      "country": "BR",
      "tax_id": "12345678000190",
      "name": "Acme Corp Ltda",
      "email": "billing@acme.com.br"
    },
    "amount": 12345,
    "currency": "BRL",
    "description": "Consulting services — April/2026",
    "payment_method": { "type": "pix", "expires_in_seconds": 3600 },
    "tax_document": {
      "auto_issue": true,
      "type": "nfse",
      "service_code": "01.05"
    },
    "cost_center": "CC-OPERATIONS",
    "contractor_reference": "PO-9981",
    "metadata": { "campaign": "spring-2026", "owner_user_id": "usr-7782" }
  }'
Response (201 Created):
{
  "id": "inv_01HW1Z3K8C7G6Y9PQ4M5R2X0NA",
  "status": "open",
  "amount": 12345,
  "currency": "BRL",
  "region": "br",
  "payment_method": {
    "type": "pix",
    "pix": {
      "qr_code": "00020126580014br.gov.bcb.pix...",
      "qr_code_image_url": "https://api.play2sell.com/.../qr.png",
      "expires_at": "2026-04-27T15:00:00Z"
    }
  },
  "tax_document": {
    "id": "txd_01HW1Z3K8C7G6Y9PQ4M5R2X0NX",
    "type": "nfse",
    "status": "pending",
    "auto_issue": true
  },
  "cost_center": "CC-OPERATIONS",
  "our_number": "INV-2026-000123",
  "contractor_reference": "PO-9981",
  "metadata": { "campaign": "spring-2026", "owner_user_id": "usr-7782" },
  "created": "2026-04-27T14:00:00Z"
}
Status flows from openpaid (on PIX confirmation, ~seconds) → tax_document.status: issued (NFS-e emission, seconds to minutes). Listen to webhooks invoice.paid and tax_document.issued instead of polling.
3

(Optional) Issue an invoice for a US customer (coming soon)

For US/EU customers SalesOS routes the charge to its international backend. This path is queued behind the Play2Sell LLC onboarding — calls today return 501 provider_not_configured.
curl -X POST https://api.play2sell.com/functions/v1/payments/invoices \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Idempotency-Key: inv-us-2026-04-27-001" \
  -H "Content-Type: application/json" \
  -d '{
    "customer": {
      "country": "US",
      "name": "Acme Inc",
      "email": "billing@acme.com"
    },
    "amount": 12345,
    "currency": "USD",
    "description": "Consulting services — April/2026",
    "payment_method": { "type": "card" },
    "cost_center": "CC-OPERATIONS",
    "contractor_reference": "PO-9981"
  }'
No tax_document block — US has no national fiscal document. Sales tax goes inside metadata or future line_items. The customer’s SSN is not collected by this endpoint: regular B2C/B2B charges in the US do not require it. SSN/EIN handling for 1099 reporting (when paying out US contractors) is a separate flow under Payouts, not Invoices.
4

Pay a beneficiary

Pay a prize winner via PIX (BRL — routed automatically):
curl -X POST https://api.play2sell.com/functions/v1/payments/payouts \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Idempotency-Key: payout-2026-04-27-001" \
  -H "Content-Type: application/json" \
  -d '{
    "beneficiary": {
      "name": "Maria Santos",
      "document": "12345678901",
      "phone": "+5511999000111",
      "email": "maria@example.com",
      "bank_account": { "type": "pix", "key_type": "cpf", "key": "12345678901" }
    },
    "amount": 50000,
    "currency": "BRL",
    "purpose": "prize",
    "cost_center": "CC-AWARDS",
    "contractor_reference": "EVENT-Q2-WINNER-12",
    "metadata": { "event_id": "evt-2026-q2", "user_id": "usr-1101" }
  }'
Response (201 Created):
{
  "id": "po_01HW1Z9P7B5F2X8KQ4M5R2X0NB",
  "status": "processing",
  "amount": 50000,
  "currency": "BRL",
  "region": "br",
  "cost_center": "CC-AWARDS",
  "our_number": "PO-2026-000045",
  "contractor_reference": "EVENT-Q2-WINNER-12",
  "created": "2026-04-27T14:05:00Z"
}
You will receive a payout.paid webhook within seconds for sandbox or under a minute for production PIX.
5

Read your statement

List every entry tagged with cost_center=CC-AWARDS for the month:
curl -X GET "https://api.play2sell.com/functions/v1/payments/balance-transactions?cost_center=CC-AWARDS&created[gte]=2026-04-01&created[lte]=2026-04-30&limit=50" \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response (200 OK):
{
  "data": [
    {
      "id": "btxn_01HW1Z9P7B5F2X8KQ4M5R2X0NC",
      "type": "payout",
      "amount": -50000,
      "currency": "BRL",
      "net": -50100,
      "fee": 100,
      "available_on": "2026-04-27",
      "created": "2026-04-27T14:05:30Z",
      "source": { "resource": "payout", "id": "po_01HW1Z9P7B5F2X8KQ4M5R2X0NB" },
      "cost_center": "CC-AWARDS",
      "our_number": "PO-2026-000045",
      "contractor_reference": "EVENT-Q2-WINNER-12"
    }
  ],
  "has_more": false
}

Authentication

All requests require an API Key in the Authorization header:
Authorization: Bearer sk_live_YOUR_API_KEY
See the Authentication page for details on creating and managing API Keys.
PropertyDetails
HeaderAuthorization: Bearer sk_live_xxx
Scopespayments:write (create/cancel), payments:read (list/retrieve)
Rate limitConfigurable per key (default: 1000 requests/hour)
Key formatsk_live_ (production) or sk_test_ (testing)

Environments

Base URL: https://api.play2sell.comDashboard: https://dashboard.play2sell.com

Endpoint Reference

Base path: /functions/v1/payments. All endpoints accept and return JSON.

Invoices

An Invoice is a payment request — a charge that will be collected from your customer via PIX, boleto, card or another method depending on region. The Invoice does not include a fiscal document by default; see Tax Documents below.

POST /functions/v1/payments/invoices — create a charge

customer
object
required
Customer that will be billed.
customer.country
string
required
ISO-3166-1 alpha-2 (BR, US, DE, …). Drives charge-provider routing.
customer.tax_id
string
CPF (11 digits) or CNPJ (14 digits) for BR (required when tax_document is set). For US/EU charges this field is not required — leave it out unless your accounting workflow specifically needs it. SSN/EIN are not collected here for normal charges.
customer.name
string
required
Full legal name (max 255 chars).
customer.email
string
required
Valid email — used for receipts and (when applicable) fiscal-document delivery.
amount
integer
required
Total amount in minor units (e.g. 12345 = R$ 123,45). Must be positive.
currency
string
required
ISO-4217 code. Drives charge-provider routing alongside customer.country.
description
string
required
Human description shown on the payment artifact and (when issued) the fiscal document body (max 1000 chars).
The description field is shown to the payer. For PIX charges it appears in the payer’s bank/wallet app next to the QR code; for boleto it prints on the slip; for fiscal documents it goes into the document body. Pick a value that reads well in all three contexts (e.g. "Consulting — April/2026", not "INV-2026-000123 internal-billing-row").
payment_method
object
required
How the charge will be collected.
payment_method.type
string
required
One of pix, boleto, bolepix, card (BR); card, ach_debit, bank_transfer (US — coming soon); card, sepa_debit, bank_transfer (EU — coming soon).
payment_method.expires_in_seconds
integer
For pix / boleto / bolepix: how long the payment artifact stays valid. Defaults to 3600 (PIX) / 3 days (boleto).
payment_method.return_url
string
For card: where to send the customer after card-checkout completion.
tax_document
object
Optional. When set, SalesOS will issue a fiscal document automatically after the charge is paid. BR-only today.
tax_document.auto_issue
boolean
When true, the fiscal document is issued the moment the charge transitions to paid. When false (or the field is omitted), use POST /v1/invoices/{id}/tax-documents later.
tax_document.type
string
"nfse" (BR services — live), "nfe" (BR products — coming soon), "peppol" (EU — coming soon).
tax_document.service_code
string
Municipal service code, required when type="nfse" (e.g. "01.05" for São Paulo consulting).
cost_center
string
required
Your internal cost center code (max 64 chars). See Common Control Fields.
contractor_reference
string
External customer/contract reference (max 64 chars).
metadata
object
Free-form key-value pairs (max 50 keys, value max 500 chars).
our_number is not part of the request — SalesOS generates it on creation and returns it on the response (format: INV-<YYYY>-<sequence>). Store it for reconciliation.

GET /functions/v1/payments/invoices/{id} — retrieve

curl -X GET https://api.play2sell.com/functions/v1/payments/invoices/inv_01HW1Z3K8C7G6Y9PQ4M5R2X0NA \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY"
The response always includes the current payment_method artifact (PIX QR, boleto barcode, card checkout URL) and the inline tax_document summary when present.

GET /functions/v1/payments/invoices — list

Filters: status (open|paid|canceled|failed), payment_method.type, cost_center, our_number, contractor_reference, created[gte], created[lte]. Cursor pagination via limit (≤ 100), starting_after, ending_before.
curl -X GET "https://api.play2sell.com/functions/v1/payments/invoices?status=paid&cost_center=CC-OPERATIONS&limit=50" \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY"

POST /functions/v1/payments/invoices/{id}/cancel — void unpaid charge

curl -X POST https://api.play2sell.com/functions/v1/payments/invoices/inv_01HW1Z3K8C7G6Y9PQ4M5R2X0NA/cancel \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Idempotency-Key: cancel-inv-001" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "customer_request" }'
If the Invoice has an attached tax_document already in issued state, the cancellation cascades into the fiscal document when the issuing municipality is still inside its cancellation window. A cancellation_window_expired error is returned otherwise.
Refunding a paid charge is not exposed in v1. If you need to reverse a settled payment, contact support — the team can process it manually. A future version of the API may expose a programmatic refund flow once the use-cases stabilize.

Tax Documents

A TaxDocument is the fiscal artifact attached to an Invoice — currently NFS-e (BR services). It has its own lifecycle and can be issued, retrieved or canceled independently of the underlying charge. Use this resource when you opted out of auto_issue at Invoice creation, when an auto-issue attempt failed and you want to retry, or when you need to cancel only the fiscal document.

POST /functions/v1/payments/invoices/{id}/tax-documents — issue/retry

type
string
required
"nfse" today. Future: "nfe", "peppol".
service_code
string
Required when type="nfse".
metadata
object
Optional free-form metadata, persisted on the document row.
curl -X POST https://api.play2sell.com/functions/v1/payments/invoices/inv_01HW1Z3K8C7G6Y9PQ4M5R2X0NA/tax-documents \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Idempotency-Key: txd-2026-04-27-001" \
  -H "Content-Type: application/json" \
  -d '{ "type": "nfse", "service_code": "01.05" }'
Response (202 Accepted):
{
  "id": "txd_01HW1Z3K8C7G6Y9PQ4M5R2X0NX",
  "invoice_id": "inv_01HW1Z3K8C7G6Y9PQ4M5R2X0NA",
  "type": "nfse",
  "status": "pending",
  "created": "2026-04-27T14:10:00Z"
}
Issuance is asynchronous. Listen for tax_document.issued (or tax_document.failed) webhooks. On success the document carries document_number, xml_url, pdf_url and issued_at.

GET /functions/v1/payments/invoices/{id}/tax-documents/{doc_id} — retrieve

POST /functions/v1/payments/invoices/{id}/tax-documents/{doc_id}/cancel — cancel

curl -X POST "https://api.play2sell.com/functions/v1/payments/invoices/inv_.../tax-documents/txd_.../cancel" \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Idempotency-Key: cancel-txd-001" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "billing_correction" }'
Subject to the issuing municipality’s cancellation window (typically the day of issuance for NFS-e). A cancellation_window_expired error is returned otherwise.

Payouts

POST /functions/v1/payments/payouts — pay a beneficiary

Routing is automatic: currency = "BRL" → PIX cashout; any other currency → international transfer.
beneficiary
object
required
Recipient of the payout.
beneficiary.name
string
required
Full legal name (max 255 chars).
beneficiary.document
string
required
Tax/identity document of the beneficiary. CPF (11 digits) or CNPJ (14 digits) for BR; passport or local tax ID for international. Mandatory.
beneficiary.phone
string
required
Phone number in E.164 format (e.g. "+5511999000111"). Mandatory — used for AML/KYC matching and payout-status notifications.
beneficiary.email
string
Optional email address. When provided, payout receipts are sent to this address.
beneficiary.bank_account
object
required
Destination account. For BR PIX, set type: "pix" and provide key_type (cpf|cnpj|email|phone|evp) and key. For international, set type: "bank_transfer" and provide country, iban or account_number + routing_number per the destination country’s banking requirements.
amount
integer
required
Amount in minor units. Must be positive.
currency
string
required
ISO-4217. Drives backend routing.
purpose
string
required
One of "prize", "commission", "vendor_payment", "other".
cost_center
string
required
Internal cost center code (max 64 chars).
contractor_reference
string
External reference (max 64 chars).
metadata
object
Free-form key-value (max 50 keys, value max 500 chars).
our_number is not part of the request — SalesOS generates it on creation and returns it on the response (format: PO-<YYYY>-<sequence>). Store it for reconciliation.

GET /functions/v1/payments/payouts/{id} — retrieve

GET /functions/v1/payments/payouts — list

Filters: status, provider, cost_center, our_number, contractor_reference, created[gte], created[lte]. Cursor pagination as above.

POST /functions/v1/payments/payouts/{id}/cancel — cancel

Only available for international transfers in created/incoming_payment_waiting state. PIX cashout is sync-final; once accepted, it cannot be cancelled — issue a payout in the opposite direction to compensate.

Balance Transactions

A unified ledger across providers. Read-only.

GET /functions/v1/payments/balance-transactions — list

Filters:
FilterValues
accountomie, rinne, wise
typecharge, payout, fee, adjustment
cost_centerany string
our_numberany string
contractor_referenceany string
currencyISO-4217 code
created[gte] / created[lte]ISO-8601 timestamps
curl -X GET "https://api.play2sell.com/functions/v1/payments/balance-transactions?type=payout&created[gte]=2026-04-01&limit=100" \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY"

GET /functions/v1/payments/balance-transactions/{id} — retrieve

Each row exposes:
id
string
Stable ledger-entry id (btxn_...).
type
string
charge | payout | fee | adjustment.
amount
integer
Signed minor units. Negative = money out, positive = money in.
currency
string
ISO-4217.
net
integer
amount - fee for outgoing; amount + fee ignored for incoming.
fee
integer
Provider fee in minor units, always positive.
available_on
string
ISO-8601 date the funds settle.
created
string
ISO-8601 timestamp of the entry.
source
object
{ resource, id } — points back to the originating invoice/payout.
cost_center
string
Inherited from the source resource.
our_number
string
Inherited from the source resource.
contractor_reference
string
Inherited from the source resource.

Common Control Fields

Every Invoice, Payout and Balance Transaction carries the same control fields. They round-trip on GET and are filterable on LIST.
FieldDirectionFormatMaxIndexedUse
cost_centerinput (required)string, FK to your cost-centers table64yesFile every transaction against a budget line.
contractor_referenceinput (optional)string64yesExternal customer / contract reference (PO, contract id, event id).
metadatainput (optional)Record<string, string>50 keys, value 500 charsnoFree-form. Use sparingly; not searchable.
our_numberoutput onlystring, format <PREFIX>-<YYYY>-<sequence>64yesSalesOS-generated reference returned at creation. Distinct from id (ULID for API use). Stable, sequential per resource type per tenant. Use it as the bank-side reconciliation key.
Why our_number is server-generated. In Brazilian banking the cedente assigns “nosso número” — but in this API SalesOS is the issuance gateway, so we assign the value and hand it back. If you need to carry your own internal id, put it in contractor_reference (top-level, indexed) or metadata.* (free-form).

Idempotency

All POST endpoints require an Idempotency-Key header — a unique string of your choosing (max 255 chars; we recommend a UUIDv4 or a deterministic key derived from your domain).
  • The first call with a given key processes normally and the response is stored for 24 h.
  • Replays with the same key and the same body return the same response, with a Idempotent-Replay: true response header.
  • Replays with the same key but a different body return 409 idempotency_key_reused.
# First call — processes
curl -X POST https://api.play2sell.com/functions/v1/payments/invoices \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Idempotency-Key: inv-2026-04-27-001" \
  -H "Content-Type: application/json" \
  -d '{ "amount": 12345, "currency": "BRL", "payment_method": { "type": "pix" }, "...": "..." }'

# Second call (network retry) — returns same response, no duplicate
curl -X POST https://api.play2sell.com/functions/v1/payments/invoices \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Idempotency-Key: inv-2026-04-27-001" \
  -H "Content-Type: application/json" \
  -d '{ "amount": 12345, "currency": "BRL", "payment_method": { "type": "pix" }, "...": "..." }'

Money & Currency

Amounts are integers in minor units to avoid floating-point errors:
CurrencyMinor unit12345 means
BRLcentavoR$ 123,45
USDcent$123.45
EURcent€123.45
Currencies follow ISO-4217 (3 uppercase letters). Always pair amount with currency. Reject any payload that mixes scales (e.g. sending decimals).

Error Handling

All errors follow RFC 9457 Problem Details:
{
  "type": "https://docs.play2sell.com/errors/validation_error",
  "title": "Invalid request",
  "status": 422,
  "detail": "amount must be a positive integer",
  "instance": "req_01HW1Z3K8C7G6Y9PQ4M5R2X0NA",
  "code": "validation_error",
  "errors": [
    { "field": "amount", "message": "must be a positive integer" }
  ]
}
Malformed JSON or missing required headers. Fix the request and resend.
API key missing, invalid or expired. Check the Authorization header. Generate a new key if expired.
Key is valid but lacks the required scope (payments:write or payments:read). Edit the key in the Dashboard.
The resource id does not exist or belongs to another tenant.
Same Idempotency-Key used with a different body. Either use a fresh key or send the original body.
Body validation failed. The errors[] array lists field-level problems.
Too many requests. The Retry-After response header tells you how many seconds to wait.
The downstream backend returned an error. The detail field contains a sanitized message. Retry safely with the same Idempotency-Key.
Internal error. Retry with exponential backoff (2s, 4s, 8s). Contact support if it persists.

Webhooks

Configure a webhook endpoint per environment in Admin > Integrations > Webhooks. SalesOS will POST signed JSON for every terminal event:
EventResourceTriggered when
invoice.paidinvoiceCharge confirmed paid by the provider
invoice.canceledinvoiceUnpaid charge voided
invoice.failedinvoiceCharge failed permanently (e.g. card declined, boleto expired)
tax_document.issuedtax_documentFiscal document accepted by the municipality
tax_document.canceledtax_documentFiscal document cancellation accepted
tax_document.failedtax_documentIssuance failed permanently
payout.paidpayoutFunds confirmed delivered
payout.failedpayoutProvider rejected the transfer
payout.canceledpayoutCancel request accepted (international transfers only)
Each request includes:
  • X-Pay-Event — event type (e.g. payout.paid).
  • X-Pay-Signaturet=<unix>,v1=<hex-hmac-sha256>. Verify with the secret you set in the Dashboard.
  • X-Pay-Delivery — unique delivery id, useful for deduplication.
Signature verification (Node.js):
import crypto from 'node:crypto';

function verifyWebhook(rawBody, signatureHeader, secret) {
  const parts = Object.fromEntries(
    signatureHeader.split(',').map(kv => kv.split('=')),
  );
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${parts.t}.${rawBody}`)
    .digest('hex');
  const ok = crypto.timingSafeEqual(
    Buffer.from(parts.v1, 'hex'),
    Buffer.from(expected, 'hex'),
  );
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300; // 5 min
  return ok && fresh;
}
Sample payload:
{
  "id": "evt_01HW1Z9P7B5F2X8KQ4M5R2X0ND",
  "type": "payout.paid",
  "created": "2026-04-27T14:05:30Z",
  "data": {
    "id": "po_01HW1Z9P7B5F2X8KQ4M5R2X0NB",
    "status": "paid",
    "amount": 50000,
    "currency": "BRL",
    "cost_center": "CC-AWARDS",
    "our_number": "PO-2026-000045",
    "contractor_reference": "EVENT-Q2-WINNER-12"
  }
}

Complete Code Examples

export SALESOS_KEY="sk_live_YOUR_API_KEY"
export SALESOS_URL="https://api.play2sell.com/functions/v1/payments"

# 1. Issue an invoice (BR PIX charge with auto NFS-e)
curl -s -X POST "$SALESOS_URL/invoices" \
  -H "Authorization: Bearer $SALESOS_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "customer": { "country": "BR", "tax_id": "12345678000190", "name": "Acme Corp", "email": "billing@acme.com" },
    "amount": 12345, "currency": "BRL",
    "description": "Consulting — April/2026",
    "payment_method": { "type": "pix", "expires_in_seconds": 3600 },
    "tax_document": { "auto_issue": true, "type": "nfse", "service_code": "01.05" },
    "cost_center": "CC-OPERATIONS", "contractor_reference": "PO-9981"
  }' | jq .

# 2. Pay a prize via PIX
curl -s -X POST "$SALESOS_URL/payouts" \
  -H "Authorization: Bearer $SALESOS_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "beneficiary": {
      "name": "Maria Santos", "document": "12345678901", "phone": "+5511999000111", "email": "maria@example.com",
      "bank_account": { "type": "pix", "key_type": "cpf", "key": "12345678901" }
    },
    "amount": 50000, "currency": "BRL", "purpose": "prize",
    "cost_center": "CC-AWARDS", "contractor_reference": "EVENT-Q2-WINNER-12"
  }' | jq .

# 3. Read the statement for the cost center
curl -s -X GET "$SALESOS_URL/balance-transactions?cost_center=CC-AWARDS&created[gte]=2026-04-01&limit=100" \
  -H "Authorization: Bearer $SALESOS_KEY" | jq .

Best Practices

Cost-center hygiene

Pick a small, stable set of cost-center codes (≤ 50). Document them in your finance wiki. Reject API calls server-side that reference an unknown code — better to fail loudly than to silently miscategorize.

Use both keys together for reconciliation

  • our_number (server-generated) is your bank-side key — printed on the boleto / sent to the provider, present in your bank statement file.
  • contractor_reference (you provide) is your contract-side key — the PO, vendor agreement id or event id you owe against; present in your ERP.
Joining the two at month-end gives you a one-click reconciliation between the bank, the SalesOS Payments API and your ERP.

Region routing

SalesOS auto-routes by customer.country and currency. You do not need to pick a backend; trust the routing.

Webhook reliability

Treat webhooks as the source of truth for terminal status. Polling works but burns rate-limit budget. Always verify X-Pay-Signature and dedupe by X-Pay-Delivery.

Handling failures

  • 422: fix the data and resend with a fresh Idempotency-Key.
  • 429: back off as Retry-After says.
  • 502 (provider_error): retry with the same Idempotency-Key — the original request did not commit, so retrying is safe.
  • 5xx: exponential backoff (2s, 4s, 8s). Contact support if it persists.

Rate Limits

Each API key has a configurable limit (default: 1000 requests per hour). The counter resets every hour.
LimitValue
Default requests per hour1000
Max payload size1 MB
Max list page size100
Timeout per request60 seconds
Rate-limited responses include a Retry-After header (seconds).

Security

  • API keys are hashed with bcrypt — never stored in plaintext.
  • Each key is scoped to a single tenant — no cross-tenant access.
  • IP allowlists can be configured per key.
  • All requests are logged for audit purposes (immutable, 7-year retention).
  • Keys can be revoked instantly from the Dashboard.
Never expose your API key in client-side code (JavaScript running in the browser, mobile apps, or public repos). The Payments API must be called only from your backend server.

Next Steps

Authentication

Create and manage API Keys

Activities Integration

Send CRM activities to SalesOS

Support

Need help? Contact our support team