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:

  1. Get an available activity type code (recommended): POST /md/api/AllocatableTypes/availableCode
  2. Create a spend template: POST /md/api/AllocatableTypes
  3. Set the display name: PUT /md/api/TenantDictionaries (see Set display name)
  4. Link expense categories: POST /md/api/ActivityExpenseTypeMappings (see Link expense categories)
  5. Configure optional behavior: update flags via PATCH /md/api/AllocatableTypes/{id} (see Optional fields)
  6. 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.

Activity type code: 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

POST /md/api/AllocatableTypes/availableCode

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

POST /md/api/AllocatableTypes

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)

FieldTypeDescription
supportedbooleanWhether this capability is available for activities of this type.
editablebooleanWhether the value can be changed when creating or editing an activity.
enabledByDefaultbooleanWhether the capability is on by default for new activities.
visibleAtCreationbooleanWhether the field is shown during activity creation.

Extended config types

Config typeUsed onAdditional 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:

defaultRecurringTypeDescription
singleOne-time activity (not recurring). Default when omitted.
weeklyRecurring weekly activity.
bi-weeklyRecurring bi-weekly activity.
monthlyRecurring 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

PUT /md/api/TenantDictionaries

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

ParameterTypeRequiredDescription
codestringYesMust match the template code (for example Other/Activity025).
categorystringYesAlways ActivityType.
labelstringYesDisplay name shown in apps and admin UI.
companyIdstringYesCompany UUID.
localestringYesLocale 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"
  }'
POST /md/api/ActivityExpenseTypeMappings

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

ParameterTypeRequiredDescription
activityTypestringYesTemplate code (for example Other/Activity025).
expenseTypestringYesExpenseType dictionary code (for example Meals, Travel, or * for any expense).
companyIdstringYesCompany UUID.

Optional fields

ParameterTypeDefaultDescription
statusstringactiveactive, disabled, or deleted.
scopestringdefaultApp 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:

  1. POST /md/api/AllocatableTypes: create template with code and companyId
  2. PUT /md/api/TenantDictionaries: set label to Travel Meals
  3. POST /md/api/ActivityExpenseTypeMappings: link expenseType: "*" 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