Set up Expense Management with the API
Configure merchant settings, approval workflows, and rules, then submit a test expense with the API.
Set up Expense Management with the API
Use the API when you want to configure Expense Management from your own system or test the full flow without the Dashboard.
Expense Management uses two services:
| Service | Base URL | What it controls |
|---|---|---|
| Expense API | https://expense-api.paymongo.com | Merchant settings, expense requests, receipts, and expense status |
| Approvals API | https://approvals-api.paymongo.com | Approval templates, approvers, and the rules that choose a template |
Both services use HTTP Basic authentication. Pass your PayMongo secret key as the username and leave the password blank.
Keep your secret key on the server. Do not put it in browser code, mobile apps, logs, or a Git repository.
Before you start
Set these values in your terminal:
export PAYMONGO_SECRET_KEY="sk_live_..."
export EXPENSE_API_BASE_URL="https://expense-api.paymongo.com"
export APPROVALS_API_BASE_URL="https://approvals-api.paymongo.com"Use a test key and test accounts while you build your integration.
Configure the feature in this order:
- Add the merchant settings that your expense form will use.
- Set the source account for expense transfers.
- Create an approval template.
- Create at least one active workflow rule.
- Turn on approvals.
- Submit a test expense.
Do not turn on approvals before an active rule can match the expense. Otherwise, the approval service cannot choose a workflow.
1. Add merchant settings
Merchant settings define the values available on an expense request, such as categories and budget codes.
| Setting | Method | Purpose |
|---|---|---|
| nature_expense | POST /v1/config_items | Adds one expense category |
| nature_expense_enabled | POST /v1/config_items | Shows the category field |
| budget_code | POST /v1/config_items | Adds one budget code |
| budget_code_enabled | POST /v1/config_items | Shows the budget code field |
| transfer_source_account_number | PUT /v1/config_items | Sets the account that funds expense transfers |
| expense_approvals_enabled | PUT /v1/config_items | Turns approval workflows on or off |
Add an expense category
Call this endpoint once for each category:
curl --request POST --url "$EXPENSE_API_BASE_URL/v1/config_items" --user "$PAYMONGO_SECRET_KEY:" --header "Content-Type: application/json" --data '{
"config_key": "nature_expense",
"value": "Travel"
}'Enable the category field:
curl --request POST --url "$EXPENSE_API_BASE_URL/v1/config_items" --user "$PAYMONGO_SECRET_KEY:" --header "Content-Type: application/json" --data '{
"config_key": "nature_expense_enabled",
"value": "true"
}'Add a budget code
The value is the code sent with an expense. The label is the name shown to the user.
curl --request POST --url "$EXPENSE_API_BASE_URL/v1/config_items" --user "$PAYMONGO_SECRET_KEY:" --header "Content-Type: application/json" --data '{
"config_key": "budget_code",
"value": "MKT-001",
"label": "Marketing"
}'Enable the budget code field:
curl --request POST --url "$EXPENSE_API_BASE_URL/v1/config_items" --user "$PAYMONGO_SECRET_KEY:" --header "Content-Type: application/json" --data '{
"config_key": "budget_code_enabled",
"value": "true"
}'Set the transfer source account
This is the account that will fund an approved expense.
curl --request PUT --url "$EXPENSE_API_BASE_URL/v1/config_items" --user "$PAYMONGO_SECRET_KEY:" --header "Content-Type: application/json" --data '{
"config_key": "transfer_source_account_number",
"value": "<SOURCE_ACCOUNT_NUMBER>"
}'Make sure the account belongs to the correct organization and has enough available balance. If your PayMongo setup uses parent and child organizations, contact your PayMongo representative to confirm which source account to use.
Check or remove a setting
List settings for a key:
curl --request GET --url "$EXPENSE_API_BASE_URL/v1/config_items?config_key=budget_code" --user "$PAYMONGO_SECRET_KEY:"Each item in the response has an id. Use that id to remove a category, budget code, or enabled flag:
curl --request DELETE --url "$EXPENSE_API_BASE_URL/v1/config_items/<CONFIG_ITEM_ID>" --user "$PAYMONGO_SECRET_KEY:"2. Create an approval template
A template lists the approval stages and the people who can approve each stage.
curl --request POST --url "$APPROVALS_API_BASE_URL/v1/workflow_templates" --user "$PAYMONGO_SECRET_KEY:" --header "Content-Type: application/json" --data '{
"resource": "expense",
"operation": "ResolveWorkflow",
"name": "Expense approval",
"stages": [
{
"key": "manager",
"order": 1,
"mode": "single",
"assignees": ["[email protected]"]
},
{
"key": "finance",
"order": 2,
"mode": "any_of",
"assignees": [
"[email protected]",
"[email protected]"
]
}
]
}'Stage modes control how approval works:
| Mode | Meaning |
|---|---|
| single | One named assignee approves the stage |
| any_of | Any one assignee can approve the stage |
| all_of | Every assignee must approve the stage |
Save the template_id and version from the response. You will use both values in the rule.
To list active expense templates:
curl --request GET --url "$APPROVALS_API_BASE_URL/v1/workflow_templates?resource=expense&operation=ResolveWorkflow&status=active" --user "$PAYMONGO_SECRET_KEY:"3. Create a workflow rule
A rule decides which template applies to an expense. This catch-all rule matches every expense amount:
curl --request POST --url "$APPROVALS_API_BASE_URL/v1/workflow_rules" --user "$PAYMONGO_SECRET_KEY:" --header "Content-Type: application/json" --data '{
"resource": "expense",
"operation": "ResolveWorkflow",
"name": "All expenses",
"priority": 100,
"status": "active",
"conditions": {
"all": [
{
"field": "subject.amount",
"operator": "gte",
"value": 0
}
]
},
"workflow_template_id": "<TEMPLATE_ID>",
"workflow_template_version": 1
}'Rules are checked from the lowest priority number to the highest. The first active rule that matches wins.
For example, you can send expenses worth PHP 5,000 or more to a stricter template by using subject.amount with a value of 500000. Amounts are in centavos. Give that rule a lower priority number than the catch-all rule.
Common condition fields include:
- subject.amount
- subject.category
- subject.metadata.business_code
- subject.employee_email
Supported operators are exists, not_exists, eq, neq, gt, gte, lt, lte, in, and not_in.
Keep one catch-all rule at the highest priority number so every valid expense can find a workflow. Avoid overlapping catch-all rules because only the first match is used.
To check active rules:
curl --request GET --url "$APPROVALS_API_BASE_URL/v1/workflow_rules?resource=expense&operation=ResolveWorkflow&status=active" --user "$PAYMONGO_SECRET_KEY:"4. Turn on approvals
Turn on approvals only after you have confirmed that the template and rule are active:
curl --request PUT --url "$EXPENSE_API_BASE_URL/v1/config_items" --user "$PAYMONGO_SECRET_KEY:" --header "Content-Type: application/json" --data '{
"config_key": "expense_approvals_enabled",
"value": "true",
"label": "Approvals"
}'To turn approvals off, send the same request with value set to false.
When approvals are off or not configured, a new expense is approved immediately and the service tries to create the transfer. Include valid destination details even while testing this mode.
5. Submit a test expense
This example submits a PHP 1,250 expense. The API expects amounts in centavos, so 125000 means PHP 1,250.00.
curl --request POST --url "$EXPENSE_API_BASE_URL/v1/expenses" --user "$PAYMONGO_SECRET_KEY:" --header "Content-Type: application/json" --data '{
"amount": 125000,
"currency": "PHP",
"description": "Client meeting lunch",
"created": 1787222400,
"employee_email": "[email protected]",
"category": "Meals",
"destination_account_number": "<DESTINATION_ACCOUNT_NUMBER>",
"destination_account_name": "<DESTINATION_ACCOUNT_NAME>",
"destination_bic": "<DESTINATION_BIC>",
"items": [
{
"description": "Lunch",
"quantity": 1,
"amount": 125000,
"tax_amount": 0
}
],
"metadata": {
"business_code": "MKT-001"
}
}'The total must match the item amounts, item tax amounts, and top-level tax amount. Use quantity 1 in your first test so the calculation is easy to check.
When approvals are on and a rule matches, the response includes approval details in metadata, such as approval_id and an approval_status of pending.
Save the expense id from the response and check it later:
export EXPENSE_ID="<EXPENSE_ID>"
curl --request GET --url "$EXPENSE_API_BASE_URL/v1/expenses/$EXPENSE_ID" --user "$PAYMONGO_SECRET_KEY:"Troubleshooting
| Problem | What to check |
|---|---|
| The request cannot find a workflow | Confirm that the rule is active, uses resource expense and operation ResolveWorkflow, and matches the request |
| The wrong template is used | Check rule priorities. The lowest priority number is checked first |
| The expense total is rejected | Confirm that the top-level amount equals all item amounts and tax amounts |
| A transfer starts before review | Check expense_approvals_enabled. A false or missing value causes immediate approval |
| A category or budget code is missing | Confirm that its config item exists and its enabled flag is present |
| Authentication fails | Confirm that the secret key is passed as the Basic Auth username with a blank password |
| A transfer fails | Check the source account, destination details, currency, and available balance |
Test the complete setup
Before using real company funds, run the request, approval, rejection, and transfer checks in Test your Expense Management setup.
Updated 6 days ago
