Donutwork Docs

Partners

Manage partner channels, commission settlement cycles, and partner-customer associations.

Partners API

The Partners API enables organizations to build and scale distribution networks through agencies, resellers, and affiliates. It provides a framework for managing partner identities, commission calculations (fees), and financial settlements (payouts), while maintaining clear attribution for every customer acquired through the channel.

Partner records and all related financial resources are isolated to the company associated with the API token. Use partners:read|write for partner profiles and partner_payouts:read|write for payouts. Monetary snapshots created by the current payout flow use integer minor units together with an ISO 4217 currency code; decimal fields remain available for backward compatibility.


Channel Management

List Partners

Retrieve a paginated directory of all registered partners. This list includes high-level identification data and current status for each collaborative entity.

GET
/2026-02-01/partners.json
Required permissionpartners:read

Query Parameters

sizeinteger
Maximum records per page (max 100).
pageinteger
Target page index.

Responses

Partner directory successfully retrieved.

{
  "entities": "Partner",
  "count": 24,
  "per_page": 100,
  "pages": {
    "current": 1,
    "max": 1
  },
  "elements": [
    {
      "id": "675d...",
      "name": "Global Solutions Agency",
      "external_id": "GSA-9921",
      "partner_code": "GOLD-PARTNER-X"
    }
  ]
}

Provision New Partner

Onboard a new partner into the ecosystem. This initializes their profile and associates them with a governance group (Partner Group) to define their commission rules.

POST
/2026-02-01/partners.json
Required permissionpartners:write

Query Parameters

No query parameters required.

Request Body

JSON
{
  "partner": {
    "name": "Apex Marketing Group",
    "email": "channel@apex-marketing.com",
    "external_id": "APEX_001",
    "code": "APEXPROMO",
    "partner_group_id": "66bf22b0...",
    "address": {
      "name": "Apex Marketing Group Ltd",
      "tax_number": "GB123456789",
      "country": "GB",
      "city": "London",
      "address": "1 Example Street",
      "email": "billing@apex-marketing.com",
      "legal_email": "legal@apex-marketing.com"
    }
  }
}
partner.namestringRequired
Legal entity name of the partner.
partner.emailstringRequired
Primary contact for financial and operational notifications.
partner.partner_group_idstring
The ID of the Partner Group define rules. If empty, the default group is used.
partner.address.namestring
Legal or billing name. Defaults to the partner name when omitted.
partner.address.vat_numberstring
VAT or indirect-tax identifier, when applicable.
partner.address.tax_numberstring
National or business tax identifier, when applicable.
partner.address.countrystring
ISO 3166-1 alpha-2 country code. Partner profiles are not limited to Italy.
partner.address.statestring
State, province, or region. Optional for countries that do not use it.
partner.address.citystring
City or locality.
partner.address.postal_codestring
Postal code. Optional where it is not applicable.
partner.address.addressstring
Street address.
partner.address.emailstring
Administrative or billing email. Defaults to the partner email.
partner.address.legal_emailstring
Optional legal or certified-delivery email.

Responses

Partner successfully provisioned.

{
  "partner": {
    "id": "6768..."
  }
}

Creating a partner through this endpoint does not grant Partner Portal access and does not add anyone to the company backoffice. Portal access remains a separate, user-bound onboarding flow.

portal_access_mode is returned by partner read/update responses with one of disabled, active, or read_only. It is read-only in this Public API: company administrators change it from the Partner Portal settings in the backoffice, so profile integrations cannot implicitly grant or broaden portal access.

Retrieve Partner Profile

Fetch the comprehensive profile of a specific partner, including their fee structure, whitelist configuration, and association metadata.

GET
/2026-02-01/partners/{partnerId}.json
Required permissionpartners:read

Query Parameters

partnerIdstringRequired
The unique identifier of the partner.

Responses

Partner profile retrieved.

{
  "id": "66b136b9...",
  "name": "Apex Marketing Group",
  "status": "active",
  "tags": [
    "tier-1",
    "mfa-enabled"
  ],
  "email": "channel@apex-marketing.com",
  "legal_address": {
    "city": "London",
    "country": "GB",
    "tax_number": "GB123..."
  },
  "portal_access_mode": "active"
}

Attribution & Performance

Audit Attributed Customers

Retrieve a paginated list of all customers currently attributed to a specific partner. This is used to verify channel performance and revenue attribution.

GET
/2026-02-01/partners/{partnerId}/customers.json
Required permissionpartners:read

Query Parameters

partnerIdstringRequired
The partner identifier.

Responses

Attributed customer list retrieved.

{
  "entities": "PartnerCustomers",
  "count": 142,
  "elements": [
    {
      "id": "68d2...",
      "company_name": "Acme Corp",
      "email": "procurement@acme.com",
      "status": "active"
    }
  ]
}

Performance Analytics

Retrieve high-level performance metrics for a partner, including total attributed revenue and cumulative commissions earned.

GET
/2026-02-01/partners/{partnerId}/stats.json
Required permissionpartners:read

Query Parameters

partnerIdstringRequired
The partner identifier.

Responses

Aggregated performance statistics retrieved.

{
  "total_revenue": "45200.00",
  "commission_earned": "4520.00",
  "currency": "EUR"
}

Financial Settlement (Payouts)

Audit Commission Ledger (Fees)

Retrieve individual commission entries (fees) generated by attributed customer transactions. These entries serve as the basis for payout generation.

GET
/2026-02-01/partners/{partnerId}/fees.json
Required permissionpartners:read

Query Parameters

partnerIdstringRequired
The partner identifier.

Responses

Commission ledger retrieved.

{
  "entities": "PartnerFees",
  "elements": [
    {
      "id": "fee_991",
      "fee_value": 15.5,
      "calculated_fee_minor": 1550,
      "currency": "EUR",
      "currency_config_version": 3,
      "status": "pending"
    }
  ]
}

Create an Offline Payout

Consolidate available commission fees into the operational payout record used by the backoffice and Partner Portal. The partner supplies the actual invoice details, while invoice delivery and payment take place offline. This endpoint does not generate a self-billing or fiscal document.

POST
/2026-02-01/partners/payouts.json
Required permissionpartner_payouts:write

Query Parameters

Idempotency-Keystring
Recommended unique key (16-200 characters) for this exact payout payload. Reuse it only when retrying the same request. Requests without the header receive a deterministic compatibility key derived from the payload.

Request Body

JSON
{
  "payout": {
    "partner_id": "675d...",
    "transactions": [
      "fee_991",
      "fee_992"
    ],
    "invoice": {
      "number": "INV-2026-001",
      "date": "2026-03-01"
    },
    "vat_settings": {
      "tax_id": "tax_vat_22"
    }
  }
}
payout.partner_idstringRequired
The partner identifier.
payout.transactionsarrayRequired
Between 1 and 500 fee entry IDs. Every fee must belong to this company and partner, be available, and match the configured company currency.
payout.invoice.numberstringRequired
Number of the actual invoice supplied by the partner.
payout.invoice.datestringRequired
Invoice date in YYYY-MM-DD format.
payout.vat_settings.tax_idstringRequired
Company tax-profile ID used to calculate the payout tax snapshot.

Responses

Payout created and all selected fees reserved atomically.

{
  "id": "pay_5521",
  "total": 1250,
  "subtotal_minor": 102459,
  "tax_minor": 22541,
  "total_minor": 125000,
  "currency": "EUR",
  "status": "pending"
}

The idempotency key was reused with a different payload, the selected fees changed concurrently, or their currencies do not match the persisted company currency.

{
  "error": "The idempotency key was used with a different payload."
}

A selected fee has already been reserved or processed by another payout.

{
  "error": "A selected fee is already assigned to a payout."
}

Invoice data is invalid, a fee amount is invalid, or the configured minimum payout was not reached.

{
  "error": "The selected fees do not reach the minimum payout amount."
}

The company currency has not been persisted in Partner Portal settings.

{
  "error": "The company currency must be persisted before financial operations are enabled."
}

The server derives the currency and its configuration version from the company configuration. Mixed-currency requests are rejected. Replaying the same request with the same idempotency key returns the existing payout and never claims the same fee twice. After a payout is refused, use a new idempotency key when intentionally creating a new payout from the released fees.

Retrieve Payout Documentation (PDF)

Retrieve a base64-encoded operational payout report. It is an account statement for the payout and is not the partner invoice or a platform-generated fiscal document.

GET
/2026-02-01/partners/{partnerId}/payouts/{payoutId}/report.json
Required permissionpartner_payouts:read

Query Parameters

partnerIdstringRequired
The partner identifier.
payoutIdstringRequired
The payout identifier.

Responses

PDF documentation retrieved.

{
  "stream": {
    "type": "application/pdf",
    "base64_content": "JVBER..."
  }
}

Technical Implementation (Payout Flow)

const payoutData = {
  payout: {
    partner_id: "PARTNER_6621",
    transactions: ["FEE_001", "FEE_002"],
    invoice: { number: "REF-9921", date: "2026-03-03" },
    vat_settings: { tax_id: "VAT_22_IT" }
  }
};

try {
  const response = await fetch("https://api.example.com/2026-02-01/partners/payouts.json", {
    method: "POST",
    headers: {
      "Authorization": "Bearer YOUR_API_TOKEN",
      "Content-Type": "application/json",
      "Idempotency-Key": "payout-PARTNER_6621-2026-03-03-001"
    },
    body: JSON.stringify(payoutData)
  });
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  const settlement = await response.json();
  console.log(`Settlement Processed. ID: ${settlement.id}, Total: ${settlement.total}`);
} catch (error) {
  console.error(`Settlement Failed: ${error.message}`);
}

Additional Partner Operations

Search Partner by External ID or Email

GET
/2026-02-01/partners/partner.json
Required permissionpartners:read

Query Parameters

externalIdstring
Partner external identifier.
emailstring
Partner email address.

Responses

Partner record retrieved.

{
  "id": "675d...",
  "name": "Apex Marketing Group",
  "email": "channel@apex-marketing.com"
}

Neither `externalId` nor `email` was provided.

{
  "error": "A valid \"externalId\" or \"email\" must be specified in the request"
}

Update Partner Profile

PUT
/2026-02-01/partners/{partnerId}.json
Required permissionpartners:write

Query Parameters

partnerIdstringRequired
Partner identifier.

Request Body

JSON
{
  "partner": {
    "name": "Apex Marketing Group - Enterprise",
    "email": "enterprise@apex-marketing.com",
    "description": "Updated profile",
    "external_id": "APEX_001",
    "code": "APEXPROMO",
    "partner_group_id": "66bf22b0...",
    "tags": [
      "tier-1",
      "priority"
    ],
    "address": {
      "name": "Apex Marketing Group Ltd",
      "tax_number": "GB123456789",
      "country": "GB",
      "city": "London",
      "state": "",
      "postal_code": "SW1A 1AA",
      "address": "1 Example Street",
      "email": "billing@apex-marketing.com",
      "legal_email": "legal@apex-marketing.com"
    }
  }
}
partner.namestring
Updated partner legal name.
partner.emailstring
Updated partner contact email.
partner.descriptionstring
Updated profile description.
partner.external_idstring
Updated external identifier.
partner.codestring
Updated partner code.
partner.partner_group_idstring
Partner group assignment (empty string removes current group).
partner.tagsarray|string
Updated tags list.
partner.addressobject
Partial worldwide Address update. Supported fields: name, vat_number, tax_number, country, state, city, postal_code, address, email, and legal_email.

Responses

Partner updated.

{
  "id": "675d...",
  "name": "Apex Marketing Group - Enterprise",
  "portal_access_mode": "active"
}

state and postal_code are optional. A billing profile needs at least one of vat_number or tax_number before the company can approve it. Updating Address data changes its canonical review hash, so any previous billing approval no longer applies to the modified data. The API never updates encrypted payment coordinates.

portal_access_mode in the response reflects the company-controlled Portal policy. Supplying it in this update payload has no effect; use the dedicated backoffice controls for disabled, active, and read_only transitions.

Delete Partner

DELETE
/2026-02-01/partners/{partnerId}.json
Required permissionpartners:write

Query Parameters

partnerIdstringRequired
Partner identifier.

Responses

Partner deleted.

{
  "id": "675d...",
  "deleted": true
}

Partner has still attached customers.

{
  "error": "Unable to remove this partner. Customer attached to partner: ..."
}

Get Partner Fee by ID

GET
/2026-02-01/partners/{partnerId}/fees/{feeId}.json
Required permissionpartners:read

Query Parameters

partnerIdstringRequired
Partner identifier.
feeIdstringRequired
Fee entry identifier.

Responses

Partner fee entry retrieved.

{
  "id": "fee_991",
  "partner_id": "675d...",
  "fee_value": 15.5,
  "status": "pending"
}

List Global Payouts

GET
/2026-02-01/partners/payouts.json
Required permissionpartner_payouts:read

Query Parameters

sizeinteger
Maximum records per page (1-100).
pageinteger
Page index (1-based).

Responses

Paginated payout list across all partners.

{
  "entities": "PartnerPayout",
  "count": 3,
  "elements": [
    {
      "id": "pay_5521",
      "total": 1250,
      "total_minor": 125000,
      "currency": "EUR",
      "status": "pending",
      "partner_id": "675d..."
    }
  ]
}

List Payouts for One Partner

GET
/2026-02-01/partners/{partnerId}/payouts.json
Required permissionpartner_payouts:read

Query Parameters

partnerIdstringRequired
Partner identifier.
sizeinteger
Maximum records per page (1-100).
pageinteger
Page index (1-based).

Responses

Paginated payout list for the specified partner.

{
  "entities": "PartnerPayout",
  "count": 2,
  "elements": [
    {
      "id": "pay_5521",
      "total": 1250,
      "total_minor": 125000,
      "currency": "EUR",
      "status": "pending",
      "partner_id": "675d..."
    }
  ]
}

Get Payout by Partner and ID

GET
/2026-02-01/partners/{partnerId}/payouts/{payoutId}.json
Required permissionpartner_payouts:read

Query Parameters

partnerIdstringRequired
Partner identifier.
payoutIdstringRequired
Payout identifier.

Responses

Payout details retrieved.

{
  "id": "pay_5521",
  "partner_id": "675d...",
  "status": "pending",
  "total": 1250,
  "total_minor": 125000,
  "currency": "EUR",
  "currency_config_version": 3
}

Get Payout by ID

GET
/2026-02-01/partners/payouts/{payoutId}.json
Required permissionpartner_payouts:read

Query Parameters

payoutIdstringRequired
Payout identifier.

Responses

Payout details retrieved.

{
  "id": "pay_5521",
  "partner_id": "675d...",
  "status": "pending",
  "total": 1250,
  "total_minor": 125000,
  "currency": "EUR",
  "currency_config_version": 3
}

Update Payout Status

PUT
/2026-02-01/partners/payouts/{payoutId}/status.json
Required permissionpartner_payouts:write

Query Parameters

payoutIdstringRequired
Payout identifier.

Request Body

JSON
{
  "payout": {
    "status": "paid",
    "metadata": {
      "reference": "BANK-TRX-2026-8891"
    }
  }
}
payout.statusstringRequired
Next payout status. Allowed values: pending, paid, refused. paid is terminal.
payout.metadataobject
Optional metadata merged into payout metadata.

Responses

Payout status updated and related fee entries synchronized.

{
  "payout": {
    "id": "pay_5521",
    "status": "paid"
  }
}

Invalid status transition.

{
  "error": "Paid payouts cannot be updated"
}

Setting a payout to refused releases all associated fees idempotently, making them available for a later payout. Setting it to paid marks the fees as paid and is terminal: a paid payout cannot return to pending or refused.

Stream Payout PDF

GET
/2026-02-01/partners/{partnerId}/payouts/{payoutId}/report.pdf
Required permissionpartner_payouts:read

Query Parameters

partnerIdstringRequired
Partner identifier.
payoutIdstringRequired
Payout identifier.

Responses

Inline PDF stream response.

{
  "note": "Response is streamed as application/pdf."
}

On this page