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.

Note: All endpoints require proper authentication. Make sure to include the 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

POST /md/api/AllocatableTypes/{id}/createActivity

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 id
  • status: often "new" until the approval workflow runs
  • associatedId: same value as {allocationId} on other endpoints (spend permission / allocation id)
  • associatedType: AllocatableType category key (for example "restaurant")
  • workflowId: approval flow identifier
  • requesterId, createdById: user ids
  • budgetId / budgetIds: linked budget
  • when, createdOn: timestamps

Next steps

  • Use associatedId as {allocationId} when calling GET /md/api/Allocations/{allocationId} after the permission exists.

Get Spend Permissions (Allocations)

GET /md/api/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

GET /md/api/Allocations/{allocationId}

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
Tip: Use 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}.

OAuth scopes: Request DEFAULT and balance-update in the scope array when obtaining the access token. Tokens without balance-update cannot call this endpoint.
POST /md/api/Allocations/{allocationId}/updateBalance

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}).

OAuth scopes and privileges: Unlike 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

POST /md/api/Allocations/{allocationId}/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

POST /md/api/Allocations/{allocationId}/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
Important: Spend permissions are subject to approval workflows. A permission is not active until approved. Check the request status for pending steps. Once active, spending must stay within the configured limits and dates.