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:

ServiceBase URLWhat it controls
Expense APIhttps://expense-api.paymongo.comMerchant settings, expense requests, receipts, and expense status
Approvals APIhttps://approvals-api.paymongo.comApproval 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:

  1. Add the merchant settings that your expense form will use.
  2. Set the source account for expense transfers.
  3. Create an approval template.
  4. Create at least one active workflow rule.
  5. Turn on approvals.
  6. 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.

SettingMethodPurpose
nature_expensePOST /v1/config_itemsAdds one expense category
nature_expense_enabledPOST /v1/config_itemsShows the category field
budget_codePOST /v1/config_itemsAdds one budget code
budget_code_enabledPOST /v1/config_itemsShows the budget code field
transfer_source_account_numberPUT /v1/config_itemsSets the account that funds expense transfers
expense_approvals_enabledPUT /v1/config_itemsTurns 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:

ModeMeaning
singleOne named assignee approves the stage
any_ofAny one assignee can approve the stage
all_ofEvery 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

ProblemWhat to check
The request cannot find a workflowConfirm that the rule is active, uses resource expense and operation ResolveWorkflow, and matches the request
The wrong template is usedCheck rule priorities. The lowest priority number is checked first
The expense total is rejectedConfirm that the top-level amount equals all item amounts and tax amounts
A transfer starts before reviewCheck expense_approvals_enabled. A false or missing value causes immediate approval
A category or budget code is missingConfirm that its config item exists and its enabled flag is present
Authentication failsConfirm that the secret key is passed as the Basic Auth username with a blank password
A transfer failsCheck 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.


Did this page help you?