Spend Permission
Create and manage spend permissions for white label programs
Overview
Spend permissions define spending parameters and budgets for white label programs: purpose, amount, time window, and limits. They are created through the same allocation APIs as agentic flows, but this guide uses spend permission terminology. Each permission goes through an approval process before it becomes active.
Create spend permissions against a spend template (AllocatableType). Configure the template first, then call createActivity with that template’s id.
Authorization: Bearer {access_token} header and the x-appid: {clientId} header in your requests.
See also Merchant restriction to limit a spend permission to specific merchants via POST /md/api/Allocations/controlledSave.
Create Spend Permission
Creates a new spend permission for a specific spend template (AllocatableType: category such as Travel, IT, or Meals). The type id in the path selects the spend template. The body defines budget, dates, owner, and optional limits for spending under that permission.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | The unique identifier of the AllocatableType (spend permission category) you want to create a spend permission for |
Request Body
The request body must contain the following required fields and can include optional fields for additional configuration.
| Parameter | Type | Required | Description |
|---|---|---|---|
amount |
number | Yes | The budget amount allocated for this spend permission |
start |
string (date) | Yes | The start date when the spend permission becomes active in YYYY-MM-DD format (UTC) |
end |
string (date) | Yes | The end date when the spend permission expires in YYYY-MM-DD format (UTC) |
ownerId |
string | Yes | The ID of the user who will own this spend permission |
budgetCategoryType |
string | No | The expense category type. If not provided and only one option is available, it will be used automatically. Must match available categories for the AllocatableType. |
currency |
string | No | The currency code (e.g., "USD", "EUR", "ILS"). Defaults to the user's default currency if not provided. |
name |
string | No | A custom name for the spend permission. If not provided, a name will be auto-generated based on the category and amount. |
purpose |
string | No | A description or purpose for the spend permission |
costCenterIds |
array | No | Array of cost center IDs to associate with this spend permission |
purchaseOrder |
string | No | Purchase order number associated with this spend permission |
boundToCardId |
string | No | ID of a specific card to bind this spend permission to |
dailyLimit |
number | No | Maximum amount that can be spent per day |
txLimit |
number | No | Maximum amount per transaction |
weekDaysLimit |
string | No | A 7-character string representing which days of the week are enabled. Each character is either 0 (disabled) or 1 (enabled). Position 0 = Sunday, 1 = Monday, 2 = Tuesday, 3 = Wednesday, 4 = Thursday, 5 = Friday, 6 = Saturday. Example: "1111100" enables Monday-Friday only, "1111111" enables all days. |
hoursLimit |
string | No | A 24-character string representing which hours of the day are enabled. Each character is either 0 (disabled) or 1 (enabled). Position 0 = 12:00 AM (midnight), 1 = 1:00 AM, ..., 23 = 11:00 PM. Example: "000000000000111111110000" enables 12:00 PM - 10:00 PM only (business hours), "111111111111111111111111" enables all hours. |
txCountLimit |
number | No | Maximum number of transactions allowed |
txDailyCountLimit |
number | No | Maximum number of transactions per day |
Example Request
Using cURL:
curl -X POST https://sandbox.custodia-tech.com/md/api/AllocatableTypes/550e8400-e29b-41d4-a716-446655440000/createActivity \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "x-appid: YOUR_CLIENT_ID" \
-H "Content-Type: application/json" \
-d '{
"amount": 1000,
"start": "2024-01-15",
"end": "2024-01-25",
"ownerId": "user-id-123",
"currency": "USD",
"purpose": "Business trip to New York",
"name": "NYC Business Trip"
}'
Using JavaScript (fetch):
const allocatableTypeId = '550e8400-e29b-41d4-a716-446655440000';
const response = await fetch(`https://sandbox.custodia-tech.com/md/api/AllocatableTypes/${allocatableTypeId}/createActivity`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'x-appid': clientId,
'Content-Type': 'application/json'
},
body: JSON.stringify({
amount: 1000,
start: '2024-01-15',
end: '2024-01-25',
ownerId: 'user-id-123',
currency: 'USD',
purpose: 'Business trip to New York',
name: 'NYC Business Trip'
})
});
const spendPermission = await response.json();
Creating a spend permission with spending limits:
Using JavaScript (fetch):
const allocatableTypeId = '550e8400-e29b-41d4-a716-446655440000';
const response = await fetch(`https://sandbox.custodia-tech.com/md/api/AllocatableTypes/${allocatableTypeId}/createActivity`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'x-appid': clientId,
'Content-Type': 'application/json'
},
body: JSON.stringify({
amount: 5000,
start: '2024-02-01',
end: '2024-02-28',
ownerId: 'user-id-123',
currency: 'USD',
purpose: 'Monthly IT equipment budget',
dailyLimit: 500,
txLimit: 200,
weekDaysLimit: '1111100', // Monday-Friday only
hoursLimit: '000000000000111111110000', // 12:00 PM - 10:00 PM only
txCountLimit: 50
})
});
const spendPermission = await response.json();
Response
Success Response (200 OK)
{
"id": "7f3e2a1b-9c4d-4e8f-a5b6-1c2d3e4f5a6b",
"subsidiaryId": null,
"when": "2026-05-13T15:55:24.899Z",
"status": "new",
"companyId": "c8f46be5-2dd4-4f2e-8b91-3e7a5d1c9f20",
"requesterId": "550e8400-e29b-41d4-a716-446655440001",
"createdById": "550e8400-e29b-41d4-a716-446655440002",
"workflowId": "default-other-request-approval",
"associatedId": "25ba8b40-6a52-4a69-ac52-d6d43ac6c5da",
"associatedType": "restaurant",
"createdOn": "2026-05-13T15:55:24.899Z",
"silent": false,
"budgetId": "d290f1ee-6c54-4b01-9e3e-0242ac130002",
"budgetIds": [
"d290f1ee-6c54-4b01-9e3e-0242ac130002"
]
}
Note: The create call returns an activity request record, not the full allocation (spend permission) document.
Key fields
id: activity request idstatus: often"new"until the approval workflow runsassociatedId: same value as{allocationId}on other endpoints (spend permission / allocation id)associatedType: AllocatableType category key (for example"restaurant")workflowId: approval flow identifierrequesterId,createdById: user idsbudgetId/budgetIds: linked budgetwhen,createdOn: timestamps
Next steps
- Use
associatedIdas{allocationId}when callingGET /md/api/Allocations/{allocationId}after the permission exists.
Get Spend Permissions (Allocations)
Retrieves a list of spend permissions (allocations) that are currently available. You can filter, sort, and paginate the results using query parameters.
Query Parameters
Use the filter query parameter to filter, sort, and paginate results. The filter should be a JSON object passed as a URL-encoded string.
| Parameter | Type | Description |
|---|---|---|
filter |
object (JSON) | Filter, sorting, and pagination options. See filter options below. |
Filter Options
The filter object supports the following properties:
| Property | Type | Description |
|---|---|---|
where |
object | Filter conditions. See where clause examples below. |
fields |
array | Array of field names to include in the response |
order |
string | Sort order (e.g., "activationDate DESC" or "title ASC") |
limit |
number | Maximum number of results to return |
skip |
number | Number of results to skip (for pagination) |
include |
array | Related models to include (e.g., ["allocatedTo", "budgetAllocations"]) |
utilization |
boolean | Include utilization metrics (used, pending, remaining amounts) |
Where Clause Examples
Common filter conditions for the where clause:
| Field | Example | Description |
|---|---|---|
ownerId |
{"ownerId": "user-id-123"} |
Filter by user who owns the spend permission |
companyId |
{"companyId": "company-id-123"} |
Filter by company ID |
status |
{"status": "active"} |
Filter by status: "new", "active", "expired", "ended", "used", "archived", "locked" |
status (multiple) |
{"status": {"inq": ["active", "new"]}} |
Filter by multiple status values |
activationDate |
{"activationDate": {"gte": "2024-01-01"}} |
Filter by activation date (greater than or equal) |
expirationDate |
{"expirationDate": {"lte": "2024-12-31"}} |
Filter by expiration date (less than or equal) |
title |
{"title": {"like": "%Business Trip%"}} |
Search by title (case-insensitive partial match) |
boundToCardId |
{"boundToCardId": "card-id-123"} |
Filter by card id when the spend permission is bound to a specific card |
Example Requests
Get all active spend permissions for a user:
Using JavaScript (fetch):
const filter = {
where: {
ownerId: 'user-id-123',
status: 'active'
},
utilization: true,
order: 'activationDate DESC'
};
const filterParam = encodeURIComponent(JSON.stringify(filter));
const response = await fetch(`https://sandbox.custodia-tech.com/md/api/Allocations?filter=${filterParam}`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${accessToken}`,
'x-appid': clientId
}
});
const spendPermissions = await response.json();
Get active and new spend permissions with utilization metrics:
Using JavaScript (fetch):
const filter = {
where: {
ownerId: 'user-id-123',
status: {inq: ['active', 'new']}
},
utilization: true,
include: ['allocatedTo'],
order: 'expirationDate ASC',
limit: 50
};
const filterParam = encodeURIComponent(JSON.stringify(filter));
const response = await fetch(`https://sandbox.custodia-tech.com/md/api/Allocations?filter=${filterParam}`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${accessToken}`,
'x-appid': clientId
}
});
const spendPermissions = await response.json();
Search spend permissions by title or purpose:
Using JavaScript (fetch):
const filter = {
where: {
ownerId: 'user-id-123',
or: [
{title: {like: '%Business Trip%'}},
{purpose: {like: '%Conference%'}}
]
},
utilization: true
};
const filterParam = encodeURIComponent(JSON.stringify(filter));
const response = await fetch(`https://sandbox.custodia-tech.com/md/api/Allocations?filter=${filterParam}`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${accessToken}`,
'x-appid': clientId
}
});
const spendPermissions = await response.json();
Response
Success Response (200 OK)
[
{
"id": "allocation-id-123",
"title": "NYC Business Trip",
"purpose": "Business trip to New York",
"status": "active",
"ownerId": "user-id-123",
"companyId": "company-id-123",
"amount": 1000,
"currency": "USD",
"used": 250,
"pending": 50,
"amountLeft": 700,
"activationDate": "2024-01-15T00:00:00.000Z",
"expirationDate": "2024-01-25T00:00:00.000Z",
"activityStart": "2024-01-15",
"activityEnd": "2024-01-25",
"boundToCardId": null,
"allocatedToType": "Travel",
"created": "2024-01-10T10:00:00.000Z",
"modified": "2024-01-15T00:00:00.000Z"
},
{
"id": "allocation-id-456",
"title": "Monthly IT Budget",
"purpose": "IT equipment and software",
"status": "active",
"ownerId": "user-id-123",
"companyId": "company-id-123",
"amount": 5000,
"currency": "USD",
"used": 1200,
"pending": 300,
"amountLeft": 3500,
"activationDate": "2024-02-01T00:00:00.000Z",
"expirationDate": "2024-02-28T23:59:59.000Z",
"activityStart": "2024-02-01",
"activityEnd": "2024-02-28",
"boundToCardId": "card-id-789",
"allocatedToType": "IT",
"created": "2024-01-25T10:00:00.000Z",
"modified": "2024-02-01T00:00:00.000Z"
}
]
Get Spend Permission by ID
Retrieves detailed information about a specific spend permission (allocation) by its ID. After you create a spend permission, use the associatedId from that response as this path’s allocationId (they are the same value).
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
allocationId |
string | Yes | Same value as associatedId returned by createActivity: the unique id of the spend permission (allocation) |
Query Parameters
| Parameter | Type | Description |
|---|---|---|
filter |
object (JSON) | Optional filter to specify which fields to include or related models (e.g., {"fields": ["id", "title", "status", "amount"], "utilization": true}) |
Example Request
Using cURL:
curl -X GET "https://sandbox.custodia-tech.com/md/api/Allocations/allocation-id-123?filter=%7B%22utilization%22%3Atrue%7D" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "x-appid: YOUR_CLIENT_ID"
Using JavaScript (fetch):
const allocationId = 'allocation-id-123';
const filter = {
utilization: true,
include: ['allocatedTo', 'budgetAllocations']
};
const filterParam = encodeURIComponent(JSON.stringify(filter));
const response = await fetch(`https://sandbox.custodia-tech.com/md/api/Allocations/${allocationId}?filter=${filterParam}`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${accessToken}`,
'x-appid': clientId
}
});
const spendPermission = await response.json();
Response
Success Response (200 OK)
{
"id": "allocation-id-123",
"title": "NYC Business Trip",
"description": null,
"purpose": "Business trip to New York",
"status": "active",
"ownerId": "user-id-123",
"companyId": "company-id-123",
"amount": 1000,
"currency": "USD",
"used": 250,
"pending": 50,
"amountLeft": 700,
"activationDate": "2024-01-15T00:00:00.000Z",
"expirationDate": "2024-01-25T00:00:00.000Z",
"activityStart": "2024-01-15",
"activityEnd": "2024-01-25",
"boundToCardId": null,
"allocatedToType": "Travel",
"allocatedToId": "allocatable-type-id",
"dailyLimit": null,
"txLimit": null,
"weekDaysLimit": "1111111",
"hoursLimit": "111111111111111111111111",
"txCountLimit": null,
"merchants": [],
"keywords": [],
"created": "2024-01-10T10:00:00.000Z",
"modified": "2024-01-15T00:00:00.000Z"
}
Spend Permission Fields
The following fields are available in spend permission (allocation) responses:
| Field | Type | Description |
|---|---|---|
id |
string | Unique identifier for the spend permission |
title |
string | Title/name of the spend permission |
purpose |
string | Purpose or description of the spend permission |
status |
string | Status: "new", "active", "expired", "ended", "used", "archived", "locked" |
ownerId |
string | ID of the user who owns this spend permission |
companyId |
string | ID of the company this spend permission belongs to |
amount |
number | Total budget amount allocated |
currency |
string | Currency code (e.g., "USD", "EUR", "ILS") |
used |
number | Amount already used (included when utilization: true) |
pending |
number | Amount pending in transactions (included when utilization: true) |
amountLeft |
number | Remaining available amount (included when utilization: true) |
activationDate |
date | Date when the spend permission becomes active |
expirationDate |
date | Date when the spend permission expires |
activityStart |
string | Start date in YYYY-MM-DD format |
activityEnd |
string | End date in YYYY-MM-DD format |
boundToCardId |
string | ID of card this spend permission is bound to (null if not bound to a specific card) |
allocatedToType |
string | Spend category label (for example "Travel", "IT", "Meals") |
dailyLimit |
number | Maximum amount per day (if set) |
txLimit |
number | Maximum amount per transaction (if set) |
weekDaysLimit |
string | A 7-character string representing which days of the week are enabled. Each character is either 0 (disabled) or 1 (enabled). Position 0 = Sunday, 1 = Monday, 2 = Tuesday, 3 = Wednesday, 4 = Thursday, 5 = Friday, 6 = Saturday. Example: "1111100" enables Monday-Friday only, "1111111" enables all days. |
merchants |
array | List of allowed merchants (if restricted) |
keywords |
array | Keywords/tags associated with the spend permission |
utilization: true in your filter to get real-time spending metrics (used, pending, amountLeft) for each spend permission. This helps check remaining budget before authorizing spend.
Balance Update
Adjust the available budget on an existing spend permission (allocation) by posting a manual credit or debit. This uses the allocation updateBalance API. The path id is the spend permission’s allocation id, the same value as associatedId from create activity, or id on GET /md/api/Allocations/{allocationId}.
DEFAULT and balance-update in the scope array when obtaining the access token. Tokens without balance-update cannot call this endpoint.
Headers
Send Content-Type: application/x-www-form-urlencoded. Include Authorization: Bearer {access_token} and x-appid: {clientId} as for other MD API calls.
Form Fields
| Field | Type | Required | Description |
|---|---|---|---|
amount |
number | Yes | Change in budget in the allocation’s currency. Use a positive value to increase available balance (credit). Use a negative value to reduce available balance (debit), for example -15 to subtract 15. Zero is ignored. |
sequence |
string | Yes | Client-supplied identifier for this balance movement. The platform derives an internal balance expense key from your budget line and the first 10 characters of sequence. Use a new sequence for each distinct adjustment you intend to record. Reusing the same sequence for the same allocation is appropriate for retries (same logical update): a matching record is returned instead of creating a duplicate. Digits in the string also influence the synthetic time used for the balance expense’s date within the permission’s activation window. Any unique string (for example a monotonic id or UUID) is acceptable if the leading portion differs per new posting. |
comment |
string | No | Optional note stored on the balance movement. |
type |
string | No | Optional movement type; if omitted, the API uses default balance credit/debit types. |
Example (cURL)
curl -X POST https://sandbox.custodia-tech.com/md/api/Allocations/25ba8b40-6a52-4a69-ac52-d6d43ac6c5da/updateBalance \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "x-appid: YOUR_CLIENT_ID" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d 'amount=15' \
-d 'sequence=57547456363'
Using JavaScript (fetch):
const allocationId = '25ba8b40-6a52-4a69-ac52-d6d43ac6c5da';
const body = new URLSearchParams({
amount: '15',
sequence: '57547456363'
});
const response = await fetch(`https://sandbox.custodia-tech.com/md/api/Allocations/${allocationId}/updateBalance`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'x-appid': clientId,
'Content-Type': 'application/x-www-form-urlencoded'
},
body
});
const delta = await response.json();
To reduce the balance by 15 in the same currency, send a negative amount (use a new sequence for a new debit, not a retry):
curl -X POST https://sandbox.custodia-tech.com/md/api/Allocations/25ba8b40-6a52-4a69-ac52-d6d43ac6c5da/updateBalance \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "x-appid: YOUR_CLIENT_ID" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d 'amount=-15' \
-d 'sequence=57547456364'
Using JavaScript (fetch):
const allocationId = '25ba8b40-6a52-4a69-ac52-d6d43ac6c5da';
const bodyDebit = new URLSearchParams({
amount: '-15',
sequence: '57547456364'
});
const response = await fetch(`https://sandbox.custodia-tech.com/md/api/Allocations/${allocationId}/updateBalance`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'x-appid': clientId,
'Content-Type': 'application/x-www-form-urlencoded'
},
body: bodyDebit
});
const delta = await response.json();
Response
Success returns a numeric delta (root body): the amount applied after internal rules (for example debits are capped by remaining unused budget).
Lock and unlock spend permission
Lock or unlock an existing spend permission (allocation) by id. Use lock to set the allocation to locked so spending against it is blocked until you call unlock. The path {allocationId} is the spend permission id (same as associatedId from create activity, or id from GET /md/api/Allocations/{allocationId}).
POST .../updateBalance, lock and unlock do not declare companion OAuth scope strings such as balance-update on the remote method. The platform still enforces who may call them: execute permission is limited to principals tied to that allocation (for example owner, delegate, budget-owner, support, or card-issuer-admin, per tenant configuration). Your access token must be issued for a context that satisfies those rules-do not assume the same scope list as balance update; confirm the exact scope and role mapping with Custodia for your program. If the allocation is in locked/admin status, unlock requires feature scope f:Activity/LockByAdmin on the token.
Both calls are POST with no required body. Send Authorization: Bearer {access_token} and x-appid: {clientId} like other MD API calls.
Lock
curl -X POST https://sandbox.custodia-tech.com/md/api/Allocations/25ba8b40-6a52-4a69-ac52-d6d43ac6c5da/lock \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "x-appid: YOUR_CLIENT_ID"
Using JavaScript (fetch):
const allocationId = '25ba8b40-6a52-4a69-ac52-d6d43ac6c5da';
const response = await fetch(`https://sandbox.custodia-tech.com/md/api/Allocations/${allocationId}/lock`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'x-appid': clientId
}
});
const allocation = await response.json();
Unlock
curl -X POST https://sandbox.custodia-tech.com/md/api/Allocations/25ba8b40-6a52-4a69-ac52-d6d43ac6c5da/unlock \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "x-appid: YOUR_CLIENT_ID"
Using JavaScript (fetch):
const allocationId = '25ba8b40-6a52-4a69-ac52-d6d43ac6c5da';
const response = await fetch(`https://sandbox.custodia-tech.com/md/api/Allocations/${allocationId}/unlock`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'x-appid': clientId
}
});
const allocation = await response.json();
Response (success)
On success the API returns the updated allocation as JSON (root object). The payload is large in production; partners typically care about identity, status, dates, owner, amounts, and category fields. Example shape (ids and values are illustrative):
{
"id": "25ba8b40-6a52-4a69-ac52-d6d43ac6c5da",
"title": "August restaurant meals",
"description": "USD 100 budget for approved restaurant spend",
"purpose": null,
"status": "active",
"activationDate": "2026-08-21T00:00:00.000Z",
"expirationDate": "2026-08-31T23:59:59.999Z",
"activityStart": "2026-08-21",
"activityEnd": "2026-08-31",
"companyId": "c8f46be5-2dd4-4f2e-8b91-3e7a5d1c9f20",
"ownerId": "550e8400-e29b-41d4-a716-446655440001",
"allocatedToType": "restaurant",
"activityType": "restaurant",
"amount": 100,
"currency": "USD",
"originalAmount": 100,
"originalCurrency": "USD",
"boundToCardId": null,
"version": 2,
"activityStatus": "approved",
"createdOn": "2026-05-13T15:55:24.000Z",
"updatedOn": "2026-05-14T13:12:52.464Z"
}
After lock, expect "status": "locked" when the operation succeeds. After unlock, expect "status": "active" (or the prior non-locked state your workflow allows). Other fields (for example statusChangedById, costCenterIds, actualBudgetId, _history) are returned as in a full allocation record; omit them from integrations unless you need them.
Error Responses
All endpoints may return the following error responses:
| Status Code | Description | Solution |
|---|---|---|
401 Unauthorized |
Invalid or missing access token | Verify your access token is valid and included in the Authorization header |
400 Bad Request |
Invalid request parameters or missing required fields | Check that all required fields (amount, start, end, ownerId) are included and have valid values |
404 Not Found |
AllocatableType or Allocation not found | Verify the ID is correct and exists for your company |
400 Bad Request |
Invalid budgetCategoryType | Ensure the budgetCategoryType matches one of the available categories for the AllocatableType |
400 Bad Request |
Currency must be set | Provide a currency code or ensure the user has a default currency configured |
403 Forbidden |
Insufficient permissions | Verify your access token has the required scopes and permissions |