Customers
Manage customer lifecycle, professional profiles, and organizational associations.
Customers
The Customer API is the central hub for managing your client base. It allows you to synchronize your internal CRM data, manage billing profiles, and monitor subscription health across your organization.
List All Customers
Retrieve a paginated collection of customers associated with your organization. This endpoint is ideal for dashboard overviews and batch synchronization processes.
customers:readQuery Parameters
sizeintegerpageintegerResponses
A paginated list of customers.
{
"entities": "Customer",
"count": 150,
"per_page": 100,
"pages": {
"current": 1,
"max": 2
},
"elements": [
{
"id": "67248941cbe2b1ff8b099a6c",
"external_id": "CRM-UID-9921",
"company_name": "Global Logistics S.A.",
"email": "billing@globallogistics.com"
}
]
}Technical Implementation
curl --location --request GET \
'https://api.hub.donutwork.com/2026-02-01/customers.json?size=20' \
--header 'Authorization: Bearer YOUR_API_KEY'const customers = await sdk.customers.list({ size: 20 });
console.table(customers.elements);Create a New Customer
Initialize a new customer profile. This is the prerequisite step for attaching subscriptions or processing one-time charges. High-quality data at this stage ensures accurate tax calculation and communication delivery.
customers:writeQuery Parameters
No query parameters required.
Request Body
{
"customer": {
"company_name": "Acme Corporation",
"first_name": "Jane",
"last_name": "Doe",
"email": "jane.doe@acme.com",
"externalId": "CRM-UID-9921",
"address": {
"country": "US",
"city": "San Francisco",
"address": "123 Market St"
}
}
}customer.company_namestringRequiredcustomer.emailstringRequiredcustomer.first_namestringRequiredcustomer.last_namestringRequiredcustomer.address.countrystringRequiredcustomer.externalIdstringcustomer.address.citystringcustomer.address.addressstringcustomer.address.postal_codestringcustomer.address.statestringcustomer.address.vat_numberstringcustomer.propertiesarraycustomer.partner.idstringResponses
Customer profile successfully created.
{
"customer": {
"id": "67c9...",
"partner": null
}
}A profile with this email or externalId already exists.
{
"error": "A customer already exists with this email"
}Retrieve Customer Details
Fetch the complete profile for a specific customer. This includes organizational metadata, contact information, and summarized subscription status.
customers:readQuery Parameters
customerIdstringRequiredResponses
Complete customer profile retrieved.
{
"id": "672489...",
"email": "jane.doe@acme.com",
"company_name": "Acme Corporation",
"externalId": "CRM-UID-9921"
}Search by External Identifier
Locate a customer profile using your internal system's identifier (externalId). This is essential for maintaining synchronization between Donutwork and your primary CRM/ERP.
customers:readQuery Parameters
externalIdstringRequiredResponses
Customer profile found.
{
"id": "672489...",
"email": "jane.doe@acme.com",
"externalId": "CRM-UID-9921"
}No customer matches the provided externalId.
{
"error": "This customer does not exist"
}Update Customer Profile
Modify an existing customer record. This endpoint supports partial updates (PATCH-style behavior); only the fields included in the request body will be modified. This is useful for updating contact information or adding organizational tags.
customers:writeQuery Parameters
customerIdstringRequiredRequest Body
{
"customer": {
"email": "updated.contact@acme.com",
"tags": [
"enterprise",
"priority-support"
]
}
}customer.emailstringcustomer.first_namestringcustomer.last_namestringcustomer.company_namestringcustomer.tagsarraycustomer.address.countrystringResponses
Customer profile successfully updated.
{
"customer": {
"id": "6724..."
}
}Decommission Customer Profile
Permanently remove a customer profile and all associated metadata. This action is destructive and irreversible. Ensure that all active subscriptions are terminated before choosing to decommission a profile.
customers:writeQuery Parameters
customerIdstringRequiredResponses
Deletion request accepted and queued for processing.
{
"id": "67c9...",
"deleted": true
}Customer Properties (Metadata)
Extend the customer profile with custom key-value pairs. Properties are ideal for storing domain-specific data, integration identifiers, or specialized configuration flags that are not captured by standard profile fields.
Retrieve All Properties
Fetch the complete set of custom metadata associated with a customer.
customer_properties:readQuery Parameters
customerIdstringRequiredResponses
Collection of customer-specific properties retrieved.
{
"count": 2,
"type": "CustomerProperties",
"elements": {
"crm_segment": "enterprise",
"onboarding_status": "completed"
}
}Atomic Update/Insert (Upsert) Properties
Update existing property values or append new keys to the customer's property collection. This endpoint performs an "upsert" operation; keys that do not exist will be created, and existing keys will have their values overwritten.
customer_properties:writeQuery Parameters
customerIdstringRequiredRequest Body
{
"CustomerProperties": {
"support_tier": "platinum",
"preferred_language": "en"
}
}CustomerPropertiesobjectRequiredResponses
Properties successfully updated.
{
"customer": {
"id": "6724..."
},
"CustomerProperties": {
"support_tier": "platinum",
"preferred_language": "en"
}
}Secure Hosted Payment Link
Generate a unique, short-lived URL for the Donutwork Hosted Payment Page. This secure interface allow customers to update their sensitive payment information (e.g., Credit Card, SEPA Mandate) without sensitive data passing through your servers.
customers:readQuery Parameters
customerIdstringRequiredResponses
Secure payment link generated.
{
"url": "https://pay.donutwork.com/l/xxxxxxxxxx"
}Remove Stored Payment Method
Remove a saved payment method from a customer billing profile. The paymentId path value must be the stored payment record id.
customers:writeQuery Parameters
customerIdstringRequiredpaymentIdstringRequiredResponses
Payment method removed from the customer billing profile.
{
"id": "seti_123",
"deleted": true
}The customer or payment method does not exist.
{
"error": "This payment method does not exists for this customer"
}Subscription Management
Manage recurring billing cycles and plan associations for your customers. Subscriptions automate the generation of invoices and payment processing based on predefined logic.
List Active and Historical Subscriptions
Retrieve a collection of all subscription entities associated with a customer, including active, canceled, and past-due states.
customer_subscriptions:readQuery Parameters
customerIdstringRequiredResponses
A list of customer subscriptions.
{
"type": "CustomerSubscriptions",
"elements": [
{
"id": "sub_123",
"name": "Enterprise SaaS Plan",
"status": "success",
"next_renew": "2026-04-01",
"price": {
"amount": 499,
"vat": {
"percentage": 22,
"name": "IVA"
}
}
}
]
}Attach New Subscription
Provision a new subscription for a customer. This endpoint allows for runtime overrides of plan defaults, such as custom discounts and initial addon quantities.
customer_subscriptions:writeQuery Parameters
customerIdstringRequiredRequest Body
{
"subscription": {
"id": "PLAN_PREMIUM_V2",
"taxes": "TAX_STANDARD_22",
"global_discount": {
"value": 15,
"type": "percentage"
},
"addons": [
{
"element": "api_quota",
"quantity": 10000
}
]
}
}subscription.idstringRequiredsubscription.taxesstringRequiredsubscription.global_discount.valuenumbersubscription.global_discount.typestringsubscription.addonsarrayResponses
Subscription successfully provisioned.
{
"id": "sub_67c9...",
"name": "Starter Monthly",
"status": "success",
"next_renew": "2026-04-28",
"price": {
"amount": 39,
"vat": {
"percentage": 22,
"name": "VAT Italy"
}
},
"metering": {
"active": false,
"balance": {
"amount": 0,
"vat": {
"percentage": 22,
"name": "VAT Italy"
}
}
},
"limits": [],
"addons": [],
"discount": {
"value": 0,
"type": "fixed",
"until": null
},
"lifecycle_status": "active",
"cancel_at_period_end": false,
"jobId": "123456"
}jobId is the processing identifier returned by the platform for the subscription request.
If an identifier cannot be assigned, jobId is returned as false.
Detach Subscription
Detach a subscription from the customer profile.
customer_subscriptions:writeQuery Parameters
customerIdstringRequiredsubscriptionIdstringRequiredResponses
Subscription detached from customer profile.
{
"id": "sub_67c9...",
"deleted": true
}Retrieve Subscription Status
Read the operational status of a specific customer subscription.
customer_subscriptions:readQuery Parameters
customerIdstringRequiredsubscriptionIdstringRequiredResponses
Current status for the selected subscription.
{
"id": "sub_123",
"status": "active",
"next_renew": "2026-05-01"
}Subscription does not exist for the specified customer.
{
"error": "This subscription does not exists for this customer"
}Modify Subscription Status
Manually transition a subscription between operational states. This is typically used for administrative overrides or manual churn management.
customer_subscriptions:writeQuery Parameters
customerIdstringRequiredsubscriptionIdstringRequiredRequest Body
{
"subscription": {
"status": "dismiss"
}
}subscription.statusstringRequiredResponses
Status successfully updated.
{
"id": "sub_123",
"status": "dismiss",
"next_renew": null
}Update Subscription Next Renewal
Move the next renewal boundary for an active customer subscription.
customer_subscriptions:writeQuery Parameters
customerIdstringRequiredsubscriptionIdstringRequiredRequest Body
{
"subscription": {
"next_renew": "2026-08-01",
"reason": "Manual billing alignment"
}
}subscription.next_renewstringRequiredsubscription.reasonstringsubscription.idempotency_keystringResponses
Next renewal date updated and audit log emitted.
{
"id": "sub_123",
"status": "success",
"previous_next_renew": "2026-05-01",
"next_renew": "2026-08-01"
}Invalid date format, invalid date, date in the past, or date too far in the future.
Subscription state does not allow manual renewal-date updates, or date is unchanged.
Advanced Subscription Lifecycle (Additive)
These endpoints extend subscription lifecycle management without replacing existing legacy routes. Use them when you need pause/resume, plan amendments, scheduled changes, and proration-aware updates.
Read Extended Lifecycle State
customer_subscriptions:readQuery Parameters
customerIdstringRequiredsubscriptionIdstringRequiredResponses
Extended lifecycle metadata for the subscription.
{
"id": "sub_123",
"status": "success",
"lifecycle_status": "active",
"cancel_at_period_end": false,
"scheduled_change": null,
"carryover_credit": 0
}Pause, Resume, Reactivate, Cancel-at-Period-End
customer_subscriptions:writeQuery Parameters
customerIdstringRequiredsubscriptionIdstringRequiredRequest Body
{
"lifecycle": {
"idempotency_key": "lifecycle-evt-001"
}
}lifecycle.idempotency_keystringResponses
Lifecycle transition applied.
{
"id": "sub_123",
"lifecycle_status": "paused",
"amendment": {
"action": "pause",
"status": "applied"
}
}Use the same payload model with:
.../lifecycle/resume.json.../lifecycle/cancel-at-period-end.json.../lifecycle/undo-cancel-at-period-end.json
Manual renewal retry and expired-subscription reactivation are documented separately below.
Pause/resume billing behavior:
- No retroactive automatic billing is created when resuming after a long pause.
- If resumed two months later (for example pause on May 1, 2026 and resume on July 1, 2026), renewal is re-anchored to the resume date (or future
resume_atif provided).
Undo-cancel behavior:
- If a subscription is
cancel_pending, you can revert it toactivebefore renewal boundary. - Plan/addons remain the current ones on the subscription; cancellation flag is removed.
Retry a Past-Due Renewal
Queue one immediate manual payment attempt for a subscription that is still past_due and inside its three-day recovery window. The retry keeps the original billing anchor. A successful payment moves the subscription to active; a failure leaves it past_due, or closes it as cancelled when the final recovery day is reached.
Only one manual retry charge can be created for the same subscription, billing cycle, and calendar day. Repeated requests are therefore safe from duplicate charges.
customer_subscriptions:writeQuery Parameters
customerIdstringRequiredsubscriptionIdstringRequiredRequest Body
{
"lifecycle": {}
}Responses
The idempotent renewal retry was queued.
{
"id": "sub_123",
"status": "success",
"lifecycle_status": "past_due",
"job_id": "123456",
"queued_at": 1788602400
}The subscription is not past due, the recovery window expired, a charge is pending, or today's manual retry was already attempted.
The retry could not be queued. No payment attempt was started.
Reactivate an Expired Subscription
Use reactivation for a cancelled subscription or a legacy subscription whose renewal date is older than the three-day recovery window. Reactivation is different from resume: it immediately charges a full new billing period and anchors future renewals to the reactivation day. It never creates automatic retroactive charges for the expired interval.
Each request creates a normal, independently traceable charge. If a payment fails, the subscription remains expired and an operator can submit another reactivation later; that request creates a separate charge. A new request is rejected only while another charge for the same subscription is still pending.
customer_subscriptions:writeQuery Parameters
customerIdstringRequiredsubscriptionIdstringRequiredRequest Body
{
"lifecycle": {}
}Responses
The new period was paid and the subscription was reactivated from today.
{
"id": "sub_123",
"status": "success",
"lifecycle_status": "active",
"charge_id": "68c123...",
"next_renew": "2026-10-20",
"renewal_recovery": {
"status": "reactivated",
"reactivated_on": "2026-09-20"
}
}The subscription is not expired/cancelled or another renewal charge is still pending.
The reactivation charge could not be created or captured. A failed charge remains traceable and the subscription remains unchanged.
Failed-renewal lifecycle:
- The first failed renewal moves the lifecycle from
activetopast_dueand emitsCustomer.subscription.past_dueonce. - The platform retries once per billing day while preserving the original billing anchor.
- A successful retry returns the lifecycle to
activeand emitsCustomer.subscription.recovered. - The third failed billing day sets both lifecycle and legacy status to
cancelled, clearsnext_renew, and emitsCustomer.subscription.cancelled. - If the renewal job misses the final retry day, its next run closes the expired
past_duecycle without creating a fourth charge. - A successful manual reactivation emits
Customer.subscription.reactivated.
Change Plan or Addons with Immediate/Scheduled Application
customer_subscriptions:writeQuery Parameters
No query parameters required.
Request Body
{
"lifecycle": {
"plan_id": "PLAN_ENTERPRISE_V3",
"taxes": "TAX_STANDARD_22",
"when": "period_end",
"proration": true
}
}lifecycle.plan_idstringRequiredlifecycle.taxesstringlifecycle.whenstringlifecycle.prorationbooleanResponses
Plan change accepted (immediate or scheduled).
{
"id": "sub_123",
"scheduled_change": {
"type": "plan",
"apply_on": "2026-06-01"
},
"amendment": {
"action": "change_plan",
"status": "applied"
}
}customer_subscriptions:writeQuery Parameters
No query parameters required.
Request Body
{
"lifecycle": {
"addons": [
{
"element": "workspace_seat",
"quantity": 25
}
],
"when": "immediate",
"proration": true
}
}Responses
Addon amendment applied with optional proration.
{
"id": "sub_123",
"carryover_credit": 18.5,
"amendment": {
"action": "change_addons",
"status": "applied"
}
}Proration Preview (No Mutation)
customer_subscriptions:readQuery Parameters
No query parameters required.
Request Body
{
"lifecycle": {
"action": "change_addons",
"addons": [
{
"element": "workspace_seat",
"quantity": 40
}
]
}
}Responses
Economic preview of the amendment without persisting changes.
{
"action": "change_addons",
"proration": {
"old_due": 499,
"new_due": 679,
"delta": 180,
"direction": "debit"
}
}Amendment History
customer_subscriptions:readQuery Parameters
sizeintegerpageintegerResponses
Paginated amendment stream for audit and support operations.
{
"type": "CustomerSubscriptionAmendments",
"elements": [
{
"action": "change_addons",
"when": "immediate",
"status": "applied"
}
]
}Backward Compatibility
Lifecycle endpoints are additive and opt-in. Existing integrations can keep using current routes unchanged.
| Area | Legacy Contract | Lifecycle Extension |
|---|---|---|
| Attach/Detach | Unchanged | Still valid |
| Status route | Unchanged payload keys | Additional lifecycle visibility |
| Addons route | Unchanged | Optional proration-aware lifecycle change-addons |
| Usage/metering | Unchanged | Unchanged |
| Existing panel/API flows | Unchanged | New actions available when explicitly called |
Subscription Addons
Manage the quantity of modular features (addons) attached to a specific subscription. Addons allow for flexible, feature-based pricing.
Audit Addon Quantities
Retrieve a list of all addons currently active on a subscription along with their provisioned quantities.
customer_subscriptions:readQuery Parameters
customerIdstringRequiredsubscriptionIdstringRequiredResponses
List of provisioned addons.
[
{
"element": "compute_units",
"quantity": 10
},
{
"element": "secondary_domains",
"quantity": 2
}
]Reconcile Addon Quantities
Update the provisioned quantities for one or more addons. This operation is additive or disruptive based on the provided quantity values.
customer_subscriptions:writeQuery Parameters
customerIdstringRequiredsubscriptionIdstringRequiredRequest Body
{
"addons": [
{
"element": "compute_units",
"quantity": 15
}
]
}addonsarrayRequiredaddons[].elementstringRequiredaddons[].quantityintegerRequiredResponses
Addon quantities successfully updated.
{
"status": "updated"
}Simulation Interface (Sandbox Only)
Accelerate development cycles by simulating subscription lifecycle events in non-production environments.
Simulate Success Event
Force a successful renewal or provisioning event for a subscription.
customer_subscriptions:writeQuery Parameters
customerIdstringRequiredsubscriptionIdstringRequiredResponses
{
"status": "simulated_success"
}Simulate Failure Event
Force a failed renewal or payment rejection event.
customer_subscriptions:writeQuery Parameters
customerIdstringRequiredsubscriptionIdstringRequiredResponses
{
"status": "simulated_failure"
}Metered Usage Tracking
For subscriptions with metering: true, consumption must be reported via these endpoints. Usage is aggregated and billed at the end of the current billing cycle.
Report Usage Event
Record a new consumption event (e.g., API requests, data processed, seats occupied). Each record requires a unique externalId to prevent idempotent replay errors.
customer_subscriptions:writeQuery Parameters
customerIdstringRequiredsubscriptionIdstringRequiredRequest Body
{
"usage": {
"amount": 10.5,
"value": 100,
"value_type": "api_requests",
"externalId": "evt_unique_uuid_123"
}
}usage.amountnumberRequiredusage.valuenumberRequiredusage.value_typestringRequiredusage.externalIdstringRequiredResponses
Usage successfully recorded.
{
"id": "usage_67c9...",
"amount": 10.5
}Audit Usage History
Retrieve a paginated history of all usage events reported for a specific subscription.
customer_subscriptions:readQuery Parameters
customerIdstringRequiredsubscriptionIdstringRequiredResponses
Paginated usage history retrieved.
{
"type": "CustomerSubscriptionUsages",
"elements": []
}Ad-hoc Charges
Charges represent immediate, non-recurring billable events created independently of a subscription's billing cycle (e.g., setup fees, professional services).
Create One-Time Charge
Generate an ad-hoc charge. If the capture flag is set to true, the system will immediately attempt to authorize and capture the funds using the customer's primary payment method.
charges:writeQuery Parameters
customerIdstringRequiredRequest Body
{
"charge": {
"capture": true,
"services": [
{
"name": "Technical Consulting",
"amount": 250,
"taxProfile": "TAX_SERVICES_22"
}
]
}
}charge.capturebooleancharge.servicesarrayRequiredcharge.instructionstringResponses
Charge created and payment status returned.
{
"status": "success",
"id": "ch_777",
"captured": true
}List Organizational Charges
Retrieve all charges (including pending and failed attempts) associated with a specific customer profile.
charges:readQuery Parameters
customerIdstringRequiredResponses
List of customer-specific charges.
{
"type": "CustomerCharges",
"elements": []
}Charge Details and Reconciliation
Retrieve detailed metadata for a specific charge, including its internal lifecycle state and tax calculations.
charges:readQuery Parameters
customerIdstringRequiredchargeIdstringRequiredResponses
Full charge metadata.
{
"id": "ch_777",
"customer_id": "cust_123",
"total": 305,
"type": "one-shot",
"status": "paid",
"statuses": {
"pending": false,
"paid": true,
"refunded": false
},
"date": {
"date": "2026-03-01 10:00:00.000000",
"timezone": "+00:00"
}
}External Invoice Synchronization
Manage links to external accounting systems (e.g., Fatture in Cloud) for each charge.
Retrieve Invoice Metadata
charges:readQuery Parameters
customerIdstringRequiredchargeIdstringRequiredResponses
Invoice synchronization details.
{
"number": "INV-2026-001",
"date": "2026-03-01",
"source": "accounting-system",
"link": "https://docs.acme.com/inv/123.pdf"
}Persist Invoice Metadata
charges:writeQuery Parameters
customerIdstringRequiredchargeIdstringRequiredRequest Body
{
"invoice": {
"number": "INV-2026-001",
"date": "2026-03-01",
"source": "erp-system",
"link": "https://erp.example.com/invoices/991.pdf"
}
}invoice.numberstringRequiredinvoice.datestringRequiredinvoice.sourcestringinvoice.linkstringResponses
{
"status": "success"
}Global Charge Resolution
Resolve charge details using only the global unique identifier. This is optimized for callback handlers and webhook reconciliation modules.
charges:readQuery Parameters
chargeIdstringRequiredResponses
Charge metadata resolved globally.
{
"id": "ch_777",
"customer_id": "cust_123"
}