Spend Templates
Create spend templates (AllocatableTypes) for white label programs
Overview
A spend template defines a category of spend: for example travel, meals, or field equipment. In the API it is the AllocatableType model. Each template has a stable code (activity type key), behavior flags (such as supportsMerchants or supportsSingleCreation), and defaults used when users or integrations create spend permissions.
Typical flow:
- Get an available activity type code (recommended):
POST /md/api/AllocatableTypes/availableCode - Create a spend template:
POST /md/api/AllocatableTypes - Set the display name:
PUT /md/api/TenantDictionaries(see Set display name) - Link expense categories:
POST /md/api/ActivityExpenseTypeMappings(see Link expense categories) - Configure optional behavior: update flags via
PATCH /md/api/AllocatableTypes/{id}(see Optional fields) - Create spend permissions from the template:
POST /md/api/AllocatableTypes/{id}/createActivity(see Create spend permission)
Authentication: All endpoints require Authorization: Bearer {access_token} and x-appid: {clientId}. Send companyId in the request body for availableCode, create spend template, display name, and expense category calls. See Authentication.
code must be a valid ActivityType dictionary value for your tenant (for example Other/Activity03). Each code must be unique within your company. Use availableCode to obtain an unused platform code slot when creating custom templates.
Get Available Activity Type Code
Returns the next unused ActivityType dictionary code your company can use when creating a custom spend template (for example Other/Activity03). Call this before create spend template if you need the platform to assign a code slot rather than supplying your own.
Request Body: mandatory fields
| Parameter | Type | Required | Description |
|---|---|---|---|
companyId |
string | Yes | Company UUID to reserve a code for. |
Send the body as application/x-www-form-urlencoded (form fields), not JSON.
Example Request
Using cURL:
curl --location 'https://sandbox.custodia-tech.com/md/api/AllocatableTypes/availableCode' \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
--header 'x-appid: YOUR_CLIENT_ID' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'companyId=2a8f6c14-9e7b-4d32-a816-5b3f0d8e72c1'
Using JavaScript (fetch):
const body = new URLSearchParams({
companyId: '2a8f6c14-9e7b-4d32-a816-5b3f0d8e72c1'
});
const response = await fetch('https://sandbox.custodia-tech.com/md/api/AllocatableTypes/availableCode', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'x-appid': clientId,
'Content-Type': 'application/x-www-form-urlencoded'
},
body: body.toString()
});
const code = await response.json();
Response
Success Response (200 OK)
"Other/Activity03"
Returns a JSON string: the activity type code to use in your create request when calling POST /md/api/AllocatableTypes.
Create Spend Template
Creates a new spend template. The platform sets id and createdOn automatically. After create, set the display name and expense categories in separate calls.
Request Body: mandatory fields
| Parameter | Type | Required | Description |
|---|---|---|---|
code |
string | Yes | Activity type key (max 36 characters). Must exist in your tenant ActivityType dictionary: for example Other/Activity025 from availableCode. This is the value used as associatedType when creating activities. |
companyId |
string | Yes | Company UUID the template belongs to. |
Optional fields (create or PATCH)
Send only the flags you need at create time; configure the rest via PATCH after the template exists. See Support config reference for nested object shapes.
| Parameter | Type | Default | Description |
|---|---|---|---|
type |
string | "spend" |
Template category: spend or benefit. |
creatableByUser |
boolean | true |
Whether non-admin users can create activities from this template. |
canSelfCreate |
boolean | true |
Whether a user can create this activity type for themselves. |
supportsSingleCreation |
boolean | false |
Whether a single-user activity can be created from this template. |
supportsBulkCreation |
boolean | false |
Whether bulk activities can be created from this template. |
private |
boolean | false |
Whether this activity type is for private usage only. |
rolloverBudget |
boolean | - | Whether this activity type supports a rollover budget. |
keywords |
Keyword[] | - | Default keyword list for activities created from this template. |
supportsMerchants |
MerchantSupportConfig | - | Merchant restriction support. See Support config reference. |
supportsRecurring |
RecurringSupportConfig | - | Recurring activity support. Set defaultRecurringType to single, weekly, bi-weekly, or monthly. |
supportsReceiptRequired |
ActivityPropertySupportConfig | - | Receipt-required support. |
supportsBoundToCard |
BoundToCardSupportConfig | - | Bound-to-card support. |
supportsTransactionsRestrictions |
TransactionsRestrictionsSupportConfig | - | Transaction count limit restrictions. |
supportsEditable |
ActivityPropertySupportConfig | - | Whether activities are editable after creation. |
supportsAutoExtend |
AutoExtendSupportConfig | - | Auto-extend activity end date support. |
supportsATM |
ActivityPropertySupportConfig | - | ATM transaction support. |
supportsOutOfPocket |
ActivityPropertySupportConfig | - | Out-of-pocket expense support. |
supportsLockedByDefault |
ActivityPropertySupportConfig | - | Whether new activities are locked by default. |
supportsAutoLockOnBufferUsage |
ActivityPropertySupportConfig | - | Auto-lock when buffer is used. |
supportsGroupPay |
GroupPaySupportConfig | - | Group pay send/receive support. |
supportsRecurringPaymentAllowed |
ActivityPropertySupportConfig | - | Recurring payment allowed support. |
supportsAllocationSplit |
ActivityPropertySupportConfig | - | Allocation split support. |
supportsAllocationMerge |
ActivityPropertySupportConfig | - | Allocation merge support. |
supportsHideLimits, supportsHideDailyLimit, supportsHideTransactionLimit |
ActivityPropertySupportConfig | - | Hide limit fields in the activity UI. |
supportsActiveForCurrencies, supportsActiveForCountryCodes |
ActivityForceValuesConfig | - | Force specific currencies or country codes. Includes value (string array) plus base config fields. |
supportsDailyLimit, supportsTransactionLimit, supportsDailyCountLimit |
ActivityLimitConfig | - | Limit support with defaultValue (number). |
defaultAmount, defaultCurrency |
BasicActivityPropertySupportConfig | - | Default amount or currency. Object has editable and defaultValue. |
Support config reference
Most supports* fields use a shared config shape. Send them as nested JSON objects on create or PATCH.
ActivityPropertySupportConfig (base)
| Field | Type | Description |
|---|---|---|
supported | boolean | Whether this capability is available for activities of this type. |
editable | boolean | Whether the value can be changed when creating or editing an activity. |
enabledByDefault | boolean | Whether the capability is on by default for new activities. |
visibleAtCreation | boolean | Whether the field is shown during activity creation. |
Extended config types
| Config type | Used on | Additional fields |
|---|---|---|
MerchantSupportConfig |
supportsMerchants |
supported, editable, defaultMerchants (Keyword array) |
RecurringSupportConfig |
supportsRecurring |
supported, editable, defaultRecurringType: one of single, weekly, bi-weekly, monthly |
BoundToCardSupportConfig |
supportsBoundToCard |
supported, required |
GroupPaySupportConfig |
supportsGroupPay |
canReceive, canSend (booleans) |
TransactionsRestrictionsSupportConfig |
supportsTransactionsRestrictions |
supported, supportsTxCountLimit, defaultTxCountLimit, canEditTxCountLimit, txCountLimitVisible |
AutoExtendSupportConfig |
supportsAutoExtend |
Base fields plus autoExtendUntilAmountPercentage (0–100) and extendByDays |
ActivityForceValuesConfig |
supportsActiveForCurrencies, supportsActiveForCountryCodes |
Base fields plus value (string array) |
ActivityLimitConfig |
supportsDailyLimit, supportsTransactionLimit, supportsDailyCountLimit |
supported, editable, visibleAtCreation, defaultValue (number) |
BasicActivityPropertySupportConfig |
defaultAmount, defaultCurrency |
editable, defaultValue (any: amount number or currency code string) |
JSON examples
Send config objects as nested JSON on POST or PATCH /md/api/AllocatableTypes. Omit fields you do not need.
ActivityPropertySupportConfig: used by supportsReceiptRequired, supportsEditable, supportsATM, supportsOutOfPocket, supportsLockedByDefault, supportsAutoLockOnBufferUsage, supportsRecurringPaymentAllowed, supportsAllocationSplit, supportsAllocationMerge, supportsHideLimits, supportsHideDailyLimit, and supportsHideTransactionLimit:
"supportsReceiptRequired": {
"supported": true,
"editable": true,
"enabledByDefault": true,
"visibleAtCreation": true
}
MerchantSupportConfig: supportsMerchants:
"supportsMerchants": {
"supported": true,
"editable": true,
"defaultMerchants": [
{
"type": "Merchant",
"value": "5812",
"name": "Restaurants"
}
]
}
RecurringSupportConfig: supportsRecurring:
defaultRecurringType | Description |
|---|---|
single | One-time activity (not recurring). Default when omitted. |
weekly | Recurring weekly activity. |
bi-weekly | Recurring bi-weekly activity. |
monthly | Recurring monthly activity. |
"supportsRecurring": {
"supported": true,
"editable": true,
"defaultRecurringType": "monthly"
}
BoundToCardSupportConfig: supportsBoundToCard:
"supportsBoundToCard": {
"supported": true,
"required": false
}
GroupPaySupportConfig: supportsGroupPay:
"supportsGroupPay": {
"canReceive": true,
"canSend": true
}
TransactionsRestrictionsSupportConfig: supportsTransactionsRestrictions:
"supportsTransactionsRestrictions": {
"supported": true,
"supportsTxCountLimit": true,
"defaultTxCountLimit": 10,
"canEditTxCountLimit": true,
"txCountLimitVisible": true
}
AutoExtendSupportConfig: supportsAutoExtend:
"supportsAutoExtend": {
"supported": true,
"editable": true,
"enabledByDefault": false,
"visibleAtCreation": true,
"autoExtendUntilAmountPercentage": 80,
"extendByDays": 7
}
ActivityForceValuesConfig: supportsActiveForCurrencies:
"supportsActiveForCurrencies": {
"supported": true,
"editable": false,
"enabledByDefault": true,
"visibleAtCreation": false,
"value": ["USD", "EUR"]
}
ActivityForceValuesConfig: supportsActiveForCountryCodes:
"supportsActiveForCountryCodes": {
"supported": true,
"editable": false,
"value": ["US", "CA"]
}
ActivityLimitConfig: supportsDailyLimit, supportsTransactionLimit, or supportsDailyCountLimit:
"supportsDailyLimit": {
"supported": true,
"editable": true,
"visibleAtCreation": true,
"defaultValue": 500
}
BasicActivityPropertySupportConfig: defaultAmount and defaultCurrency:
"defaultAmount": {
"editable": true,
"defaultValue": 1000
},
"defaultCurrency": {
"editable": false,
"defaultValue": "USD"
}
Combined PATCH example: update several configs on an existing template:
curl -X PATCH https://sandbox.custodia-tech.com/md/api/AllocatableTypes/668aa37d-6243-46d0-9373-1c859435f89f \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "x-appid: YOUR_CLIENT_ID" \
-H "Content-Type: application/json" \
-d '{
"supportsMerchants": {
"supported": true,
"editable": true,
"defaultMerchants": []
},
"supportsReceiptRequired": {
"supported": true,
"editable": true,
"enabledByDefault": true,
"visibleAtCreation": true
},
"defaultCurrency": {
"editable": false,
"defaultValue": "USD"
},
"supportsDailyLimit": {
"supported": true,
"editable": true,
"visibleAtCreation": true,
"defaultValue": 500
}
}'
Example Request
Using cURL:
curl -X POST https://sandbox.custodia-tech.com/md/api/AllocatableTypes \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "x-appid: YOUR_CLIENT_ID" \
-H "Content-Type: application/json" \
-d '{
"code": "Other/Activity025",
"companyId": "7332ad13-d460-457d-b73f-0e1681f060fc",
"type": "spend",
"creatableByUser": true,
"supportsSingleCreation": true
}'
Using JavaScript (fetch):
const response = await fetch('https://sandbox.custodia-tech.com/md/api/AllocatableTypes', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'x-appid': clientId,
'Content-Type': 'application/json'
},
body: JSON.stringify({
code: 'Other/Activity025',
companyId: '7332ad13-d460-457d-b73f-0e1681f060fc',
type: 'spend',
creatableByUser: true,
supportsSingleCreation: true
})
});
const activityTemplate = await response.json();
Response
Success Response (200 OK)
{
"id": "668aa37d-6243-46d0-9373-1c859435f89f",
"companyId": "7332ad13-d460-457d-b73f-0e1681f060fc",
"partnerId": null,
"code": "Other/Activity025",
"createdOn": "2026-07-08T01:40:32.204Z",
"enabled": true,
"type": "spend",
"creatableByUser": true,
"supportsBulkCreation": false,
"bulkOnly": false,
"supportsSingleCreation": true,
"canSelfCreate": true,
"hasPartialAuth": false,
"private": false,
"skipAdvancedSettings": "false",
"status": "active",
"version": 0
}
Save the returned id: it is the AllocatableType UUID used in POST /md/api/AllocatableTypes/{id}/createActivity. The code is the activity type key referenced elsewhere (for example budget mappings, expense category links, or associatedType).
Set Display Name
The user-facing template name (for example Travel Meals) is stored in TenantDictionary, not on AllocatableType itself. Upsert a dictionary entry after creating the template.
Request Body: mandatory fields
| Parameter | Type | Required | Description |
|---|---|---|---|
code | string | Yes | Must match the template code (for example Other/Activity025). |
category | string | Yes | Always ActivityType. |
label | string | Yes | Display name shown in apps and admin UI. |
companyId | string | Yes | Company UUID. |
locale | string | Yes | Locale code (for example en-US). |
Example Request
curl -X PUT https://sandbox.custodia-tech.com/md/api/TenantDictionaries \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "x-appid: YOUR_CLIENT_ID" \
-H "Content-Type: application/json" \
-d '{
"code": "Other/Activity025",
"category": "ActivityType",
"label": "Travel Meals",
"companyId": "7332ad13-d460-457d-b73f-0e1681f060fc",
"locale": "en-US",
"scope": "default",
"status": "active"
}'
Link Expense Categories
Expense categories allowed for a template are linked via ActivityExpenseTypeMapping records: one per expense type. Call this after create to allow transactions in specific expense categories (for example Meals) or all categories (*).
Request Body: mandatory fields
| Parameter | Type | Required | Description |
|---|---|---|---|
activityType | string | Yes | Template code (for example Other/Activity025). |
expenseType | string | Yes | ExpenseType dictionary code (for example Meals, Travel, or * for any expense). |
companyId | string | Yes | Company UUID. |
Optional fields
| Parameter | Type | Default | Description |
|---|---|---|---|
status | string | active | active, disabled, or deleted. |
scope | string | default | App scope for the mapping. Use the same scope as the template when not default. |
Use expenseType: "*" to allow any expense category in a single mapping. To restrict to specific categories, create one mapping per expense type (for example Meals or Travel).
Example Request
curl -X POST https://sandbox.custodia-tech.com/md/api/ActivityExpenseTypeMappings \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "x-appid: YOUR_CLIENT_ID" \
-H "Content-Type: application/json" \
-d '{
"activityType": "Other/Activity025",
"expenseType": "*",
"companyId": "7332ad13-d460-457d-b73f-0e1681f060fc",
"status": "active"
}'
Success Response (200 OK)
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"activityType": "Other/Activity025",
"expenseType": "*",
"companyId": "7332ad13-d460-457d-b73f-0e1681f060fc",
"status": "active",
"createdOn": "2026-07-08T01:41:00.000Z"
}
Complete initial setup example
Typical sequence after availableCode returns Other/Activity025:
POST /md/api/AllocatableTypes: create template withcodeandcompanyIdPUT /md/api/TenantDictionaries: setlabeltoTravel MealsPOST /md/api/ActivityExpenseTypeMappings: linkexpenseType: "*"to allow any expense category
Spend Template Fields (summary)
| Parameter | Type | On create | Description |
|---|---|---|---|
id |
string | Set by platform | UUID of the spend template. Used in createActivity URL paths. |
code |
string | Required | Activity type key (ActivityType dictionary). |
companyId |
string | Required | Company UUID. |
type |
string | Optional (default spend) |
spend or benefit. |
status |
string | Set by platform | Lifecycle status (for example active). |
label (display name) |
string | Via TenantDictionary | User-facing name: not stored on AllocatableType. Set with category: ActivityType. |
expenseType (categories) |
string | Via ActivityExpenseTypeMapping | Expense categories allowed for this template: one mapping per category. |
Full optional field and support config definitions are in Optional fields and Support config reference.
Error Responses
| Status Code | Description | Solution |
|---|---|---|
400 Bad Request |
Missing code or companyId, missing companyId on availableCode, invalid ActivityType dictionary value, or duplicate code |
Send required fields; use a valid unused code from availableCode or your tenant dictionary |
401 Unauthorized |
Invalid, missing, or expired access token (jwt expired) |
Re-authenticate via POST /md/api/Application/authenticate and use a fresh Bearer token |
403 Forbidden |
Insufficient permissions | Spend template create requires admin-level access for your program |
422 Unprocessable Entity |
Validation failed: for example code is not a valid ActivityType dictionary value |
Use a code from availableCode (for example Other/Activity025), not a free-form name like TravelMeals |
Related documentation
- Spend permission: create permissions from this template’s
id - Glossary: Spend template
- Budget mapping: route spend by activity type
- Authentication