Provisioning from a CRM

When a deal closes in your CRM, one call to POST /api/v2/provision sets up everything Paid needs to bill it: the customer, an optional contact, the products on the deal, and the order with its lines. Each record carries the ID it has in your CRM, so you can send the same deal again when it changes, and Paid updates the records it already has instead of creating new ones.

Use this endpoint when you build your own integration between a CRM or quoting tool and Paid.

Prerequisites

  • A Paid API key with the write:billing scope. Full-access keys have it.
  • A stable ID in your CRM for the account, the deal, each line item, and each product. Paid calls these externalId.

How Paid matches records

Paid never asks for its own IDs in a provision request. It finds each record by the externalId you send, and creates the record if none exists.

In the requestMatched byResult
customercustomer.externalIdThe customer is created or updated.
contactcontact.externalIdThe contact is created or updated. Optional.
order.lines[].productproduct.externalIdThe product is created or updated.
Product attributesnameEach attribute is created or updated on its product.
orderorder.externalIdThe order is created, or a draft order is updated.

An order line is one product on the order, with its prices. An attribute is one priced part of a product, such as a monthly platform fee or a usage charge.

Make a provision request

This request provisions a customer with one order line: a platform fee of $499.00 per month, billed in advance.

curl https://api.agentpaid.io/api/v2/provision \
-H "Authorization: Bearer $PAID_API_KEY" \
-H "Content-Type: application/json" \
-H "idempotency-key: deal_88213-2026-09-30T14:05:00Z" \
-d '{
"externalReference": {
"source": "standard",
"metadata": { "crmDealId": "deal_88213" }
},
"customer": {
"externalId": "acct_4410",
"name": "Northwind Logistics",
"email": "billing@northwind.example"
},
"order": {
"externalId": "deal_88213",
"currency": "USD",
"startDate": "2026-10-01T00:00:00.000Z",
"lines": [
{
"externalId": "deal_88213_platform",
"startDate": "2026-10-01T00:00:00.000Z",
"product": {
"externalId": "growth_plan",
"name": "Growth plan",
"attributes": [
{
"name": "Platform fee",
"pricing": {
"chargeType": "recurring",
"pricingModel": "perUnit",
"billingFrequency": "monthly",
"billingType": "advance",
"pricePoints": { "currency": "USD", "unitPrice": 49900 }
}
}
]
}
}
]
}
}'

The response gives the Paid ID of each record and says whether it was created or updated:

{
"status": "created",
"createdAt": "2026-09-30T14:05:12.000Z",
"customer": {
"id": "cus_7Qm2Lx9Pa",
"provisionStatus": "created",
"createdAt": "2026-09-30T14:05:12.000Z"
},
"order": {
"id": "ord_3Vk8Ht1Zc",
"provisionStatus": "created",
"createdAt": "2026-09-30T14:05:12.000Z",
"lines": [
{
"id": "ordl_5Nw4Rb2Ye",
"externalId": "deal_88213_platform",
"provisionStatus": "created",
"createdAt": "2026-09-30T14:05:12.000Z",
"lineType": "STANDARD"
}
]
}
}

The order in this request is active, because order.status defaults to active, so Paid activates it and starts billing. To stage an order without billing it, set order.status to draft. When the deal closes, provision the order again with status set to active, or with status left out.

Create, update, or conflict

The status code tells you what happened to the order:

Order with this externalIdResponseWhat Paid does
None201 Created, status: "created"Creates the order.
A draft order200 OK, status: "updated"Updates the order and replaces its lines with the lines you send.
An active order409, code ORDER_ALREADY_ACTIVEChanges nothing.

Treat both 200 and 201 as success. The customer, contact, and products are created or updated in both cases.

Paid does not change an active order through a provision request, because the order may already be invoiced. To change an active order, for example to add a product or change a quantity, use the amendments API. Amending orders explains what an amendment can change.

Retries and idempotency keys

Every request needs an idempotency-key header. The key lets you retry a request safely after a timeout or a network error: Paid runs a request once for each key. Build the key from the deal and its version, such as the deal ID plus its last-modified time. When the deal changes, the key changes too.

When you send a key thatPaid returns
Is newThe result of the request.
Was used before with the same bodyThe stored response, with the header Idempotent-Replayed: true.
Was used before with a different body409, code IDEMPOTENCY_KEY_CONFLICT.
Belongs to a request that is still running409, code IDEMPOTENCY_KEY_IN_PROGRESS. Retry after a short wait.
Belongs to a request that saved its changes, but whose result Paid did not record409, code PROVISION_OUTCOME_UNKNOWN.

After PROVISION_OUTCOME_UNKNOWN, look up the order with List orders, filtered by its externalId, before you send the deal again with a new key.

If a request fails before Paid saves anything, for example with a 400 validation error, Paid does not store a response for its key. Fix the request and send it again with the same key.

Keys are scoped to your organization.

Amounts and discounts

All prices are whole numbers in minor currency units, such as cents for USD. 49900 is $499.00. Paid rejects decimal and negative prices.

To give a discount or a credit, add a separate order line with lineType set to CREDIT, and send its prices as positive amounts. Paid subtracts a credit line from the invoice. For example, a credit line with a monthly price of 10000 takes $100.00 off each monthly invoice until the line’s endDate.

Credit amounts in creditBenefits are numbers of credits, not money.

Choose a source

externalReference.source names the system that sends the provision request. Use standard for an integration you build yourself.

SourceBehaviour
standardNo source-specific behaviour. Prices are per billing period.
hubspotSame as standard.
salesbricksSame as standard.
salesforceA recurring or seat-based line price is the value for the whole line term. Paid bills it once, as one billing period that spans the line. You cannot combine it with order.billingFrequencyOverride.

Where the external reference is stored

Paid stores the whole externalReference object on the order’s metadata, under the externalReference key. Put the IDs you need to trace an order back to your CRM in externalReference.metadata. For the request above, the order’s metadata contains:

{
"externalReference": {
"source": "standard",
"metadata": { "crmDealId": "deal_88213" }
}
}

Each provision request for the order replaces externalReference. Other keys in the order’s metadata stay as they are. You can read the metadata with Get order.