Skip to content
English
  • There are no suggestions because the search field is empty.

Import External Expense Claims to brioHR Payroll via API Integration

This article provides complete technical specifications, validation rules, operational behaviors, and error handling details for integrating 3rd-party claims expense systems with the brioHR Payroll Module via the Expense Claim Import API.

Audience & Scope

Audience: HR Admin / Payroll & Claims Manager / IT Admin

Module: Payroll

Country: Global

Pre-requisites: HR Module permission for the Payroll Module

 Note: For a comprehensive overview of the BrioHR API integration flow, please refer to the article here and and click here for the guide on the Claim Report (API Integration) 

IMPORTANT:

To enable this integration, you will need to contact our support team via email or live chat so we can provide the necessary access. Please contact us via:

Note: If you are still in the implementation stage, please reach out to your implementation manager for support.

Prerequisites & Setup

Before issuing API requests, ensure the following prerequisites are completed:

  1. IP Whitelisting: The client system's outbound IP address must be whitelisted by brioHR.
  2. API Credentials: Request username, password, and company ID from brioHR.

 

Overview & Operational Behavior

The Claim Import API enables external systems to push pre-approved expense claim reports directly into brioHR Payroll's Claims to Payroll > To process queue.

  • Bypasses Approval Workflow: Claims pushed via this API must already be approved in the source system. brioHR does not trigger an in-app approval workflow; claims land directly in Payroll queue.
  • Cycle Independence: Pushing claims does not require a payroll cycle to be open. Claims remain parked in the queue until a Payroll Admin processes them.
  • Report Creation vs. Payslip Aggregation: Each API push creates its own report record in the Claims to Payroll UI. When added to payroll, claims mapped to the same reimbursement item are automatically aggregated into a single line item on the employee's final payslip.
  • Internal ID Transformation: brioHR generates a deterministic internal MongoDB ObjectId derived from the client's externalClaimReportId for database storage. The original client ID is preserved in brioHR audit logs.

 

API Specification & Request Headers 

  • Endpoint URL: https://static.api.briohr.com/v2/api/external/claims/import
  • HTTP Method: POST
  • Content-Type: application/json
Header Type Value/Description
Authorization String Basic <base64_encode(username:password)> 
x-api-context-company String <company id> (Target brioHR Company ID) 
x-resource-type  String Must strictly equal claim-import
Content-Type  String application/json

Security Guardrail:

The x-resource-type header is route-pinned. Passing any other resource module (e.g., leaves or timelogs), even if assigned to the API key, returns HTTP 403 Forbidden

 

Request Payload Schema

Each request should contain a reports array with 1 to 100 report objects per batch. Unknown extra fields are automatically stripped and ignored (HTTP 201). 

Schema Tree

reports [] (Array, Required, 1–100 elements)
 ├── externalClaimReportId (String, Required) - Unique report ID / Idempotency key
 ├── employeeEmail (String, Required) - Claimant's work email address
 ├── reportName    (String, Required) - Report label shown in brioHR
 ├── currency      (String, Required) - ISO 4217 uppercase code (e.g., MYR, SGD)
 └── claims []     (Array, Required, Non-empty) - Claim line items array
      ├── externalClaimId  (String, Required) - Unique claim line item ID
      ├── payrollItem      (String, Required) - Target reimbursement item label
      ├── claimableAmount  (Number, Required) - Min 0.01, max 2 decimal places
      ├── taxAmount        (Number, Optional) - Min 0, max 2 decimal places (Default: 0)
      ├── description      (String, Optional) - Line item description note
      └── transactAt       (String, Required) - Date incurred (ISO 8601 date string) 

Field Rules & Data Types

Field Data Type Constraint/Validation Rule
 reports  Array Must contain between 1 and 100 elements.  
externalClaimReportId String Must be unique per company. Serves as idempotency key.  
employeeEmail String Must resolve to exactly one active employee in the company.  
currency String Uppercase ISO 4217 code (e.g., MYR, SGD). Lowercase values are rejected. Must match the employee's Payroll Center base currency. 
payrollItem String Label of a Reimbursement item in the employee's Payroll Center. 
claimableAmount Number Minimum 0.01, maximum 2 decimal places. Rejects 0, negative numbers, strings, or sub-cent decimals. 
taxAmount Number Minimum 0, maximum 2 decimal places. Defaults to 0 
transactAt String Valid ISO 8601 date string. 

 

Payroll Item Label Resolution Rules

The payrollItem string maps claim lines to active Reimbursement Items in the employee's assigned Payroll Center.

  1. Reimbursement Eligibility Only: Claims can only map to Reimbursement-type items (e.g., Expense Claims, Medical Reimbursement). Allowances, Deductions, and Basic Salary items are ineligible.
  2. Flexible Label Matching: Matching ignores cosmetic variations:
    • Casing: eXpEnSe cLaImS resolves to Expense Claims.
    • Whitespace: Extra, leading, trailing, or repeated spaces are stripped.
    • Singular/Plural Forms: Expense Claim, Expense Claims, and Expenses claims resolve to the same item.
  3. Ambiguity Prevention: If two items in a single payroll center normalize to the exact same string, the request is rejected with CLAIM_IMPORT_API.PAYROLL_ITEM_AMBIGUOUS.
  4. Discovering Valid Items: There is no standalone lookup endpoint (calling GET /v2/api/external/payroll-items returns 404 Not Found). Failed label matches return CLAIM_IMPORT_API.PAYROLL_ITEM_NOT_FOUND along with an availableLabels array in the response metadata listing all valid labels for that Payroll Center.

 

Idempotency & Batch Atomicity Rules

Idempotency Matrix

Condition HTTP Status Response Status / Code System Behavior
New Report ID 201 Created status: "parked"

Report is parked in Claims to PayrollTo process queue.

Same ID, Identical Payload 201 Created status: "already-parked"

Safe retry. Evaluated on canonical payload content (ignores JSON formatting/key order). Nothing is duplicated.

Same ID, Modified Payload 400 Bad Request CLAIM_IMPORT_API.CONFLICT

Request rejected due to conflicting payload values for an existing ID.

Duplicate ID within Batch 400 Bad Request CLAIM_IMPORT_API.DUPLICATE_REPORT_ID_IN_BATCH

Entire batch is rejected before importing any items.

Batch Atomicity & Upstream Error Handling

Scenario HTTP Status Behavior & Recovery Strategy 
Batch Atomicity (Validation/Business Error)  400 Bad Request  All-or-Nothing. If any report in a batch (1–100) fails validation or database lookup, zero reports are parked. Fix reported errors and resend. 
Upstream / System Failure  5xx Server Error  Read 5xx as a server issue. Resending the unchanged batch is safe. If a failure occurs mid-transfer across multiple payroll centers, already transferred centers stay saved; a retry returns already-parked for completed ones and parks the remainder. 

 

Critical Operational Behaviors & Potential Blockers

Blocker 1: Excluded Claims Cannot Be Re-Pushed Under the Same ID

When a Payroll Officer clicks Exclude on an imported claim in brioHR Payroll process, the claim is permanently removed from payroll processing.

  • Behavior: Subsequent API pushes using the same externalClaimReportId will return already-parked and will not recreate the claim.

  • Workaround: To resubmit an excluded claim, the third-party system must assign a new externalClaimReportId.

Blocker 2: Queue Visibility Requirements

Claims parked via the API only appear in the Validate Payroll items tab if an active payroll cycle exists that includes the claimant employee. If the API returns 201 status: parked but the claim is invisible in the web portal, verify that a running payroll cycle covers the employee.

Blocker 3: Skip vs. Exclude UI Actions

  • Skip: Deferring a claim moves it to the Skipped tab (with an Undo option), allowing it to be included in a future cycle.


  • Exclude: Permanently excludes the claim and displays: "This claim was sent via API and cannot be sent back to payroll once excluded".

Blocker 4: Office Country Validation Scope

The API validates only payload-present fields. A separate office-country vs. payroll-center-country check is not performed because currency matching (TRANSFER_CLAIM_REPORT.UNMATCH_CURRENCY_BASE) inherently blocks cross-country mismatches.

 

Error Code & Troubleshooting Reference 

All business rule failures return HTTP 400 Bad Request with per-report details in metadata.errors

Error Code HTTP Status Cause Resolution
CLAIM_IMPORT_API.EMPLOYEE_NOT_FOUND 400

No employee matches employeeEmail in the authenticated company[cite: 1].

Verify employee email spelling in brioHR[cite: 1].

CLAIM_IMPORT_API.EMPLOYEE_AMBIGUOUS 400

Multiple employees share the same email address[cite: 1].

Resolve duplicate emails in brioHR Core HR[cite: 1].

CLAIM_IMPORT_API.NO_PAYROLL_CENTER 400

Employee exists but is not assigned to a Payroll Center[cite: 1].

Assign employee to a Payroll Center in brioHR[cite: 1].

CLAIM_IMPORT_API.PAYROLL_ITEM_NOT_FOUND 400

No matching Reimbursement item in the Payroll Center[cite: 1].

Use availableLabels from error metadata to set a valid label[cite: 1].

CLAIM_IMPORT_API.PAYROLL_ITEM_AMBIGUOUS 400

Multiple items normalize to the same name in the center[cite: 1].

Rename duplicate items in Payroll Center settings[cite: 1].

TRANSFER_CLAIM_REPORT.UNMATCH_CURRENCY_BASE 400

Report currency differs from Payroll Center base currency.

Provide currency matching the employee's Payroll Center base currency.

CLAIM_IMPORT_API.CONFLICT 400

Re-sent externalClaimReportId with modified payload contents.

Resend exact payload for retries, or use a new ID for updates.

CLAIM_IMPORT_API.DUPLICATE_REPORT_ID_IN_BATCH 400

Duplicate externalClaimReportId values within one batch payload.

Ensure report IDs are unique within the batch array.

CLAIM_IMPORT_API.INTEGRATION_NOT_CONNECTED 400

Claim-to-payroll integration is disabled for the Payroll Center[cite: 1].

Contact brioHR Support to enable integration[cite: 1].

 

Code Examples & Response Payloads

Sample cURL Request

Bash
curl --location 'https://static.api.briohr.com/v2/api/external/claims/import' \
--header 'Authorization: Basic YWRtaW51c2VyOnBhc3N3b3JkMTIz' \
--header 'x-api-context-company: 627c63693329540009b1d0ff' \
--header 'x-resource-type: claim-import' \
--header 'Content-Type: application/json' \
--data '{
    "reports": [
        {
            "externalClaimReportId": "MESI-2026-07-001",
            "employeeEmail": "employee5active@yopmail.com",
            "reportName": "August Travel & Meal Claims",
            "currency": "MYR",
            "claims": [
                {
                    "externalClaimId": "MESI-2026-07-001-L1",
                    "payrollItem": "Expense Claims",
                    "claimableAmount": 120.50,
                    "taxAmount": 0,
                    "description": "Grab to client site",
                    "transactAt": "2026-07-15"
                },
                {
                    "externalClaimId": "MESI-2026-07-001-L2",
                    "payrollItem": "Expense Claims",
                    "claimableAmount": 45.00,
                    "taxAmount": 0,
                    "description": "Client lunch",
                    "transactAt": "2026-07-16"
                }
            ]
        }
    ]
}'


---

### Response Examples

#### 1. Success Response (`HTTP 201 Created`)

```json
[
    {
        "externalClaimReportId": "MESI-2026-07-001",
        "status": "parked"
    }
]


#### 2. Safe Retry Response (`HTTP 201 Created`)

```json
[
    {
        "externalClaimReportId": "MESI-2026-07-001",
        "status": "already-parked"
    }
]


#### 3. Schema Validation Error Response (`HTTP 400 Bad Request`)

```json
{
    "id": "0c2e7320-8a55-11f1-869b-15639df20a9d",
    "code": null,
    "type": "unhandled",
    "message": "Bad Request Exception",
    "metadata": [
        "0.currency must be one of the following values: MYR, SGD, EUR, USD, ...",
        "0.claims.0.claimableAmount must be a number with at most 2 decimal places",
        "0.claims.0.transactAt must be a valid ISO 8601 date string"
    ],
    "isBrioError": true
}


#### 4. Business Rule Error Response (`HTTP 400 Bad Request`)

```json
{
    "id": "ce3ff901-beb2-4130-ab9b-42bf78a19f95",
    "code": "U",
    "type": "user",
    "message": "",
    "metadata": {
        "errors": [
            {
                "resourceId": "MESI-2026-07-004",
                "resourceType": "claim-report",
                "error": [
                    {
                        "code": "CLAIM_IMPORT_API.EMPLOYEE_NOT_FOUND",
                        "variables": {
                            "metadata": {
                                "value": {
                                    "email": "nobody@nowhere.com"
                                },
                                "type": "object"
                            }
                        }
                    }
                ]
            },
            {
                "resourceId": "MESI-2026-07-005",
                "resourceType": "claim-report",
                "error": [
                    {
                        "code": "CLAIM_IMPORT_API.PAYROLL_ITEM_NOT_FOUND",
                        "variables": {
                            "metadata": {
                                "value": {
                                    "label": "Does Not Exist",
                                    "payrollCenterId": "627c636aed34fa000816bf84",
                                    "availableLabels": [
                                        "Expense Claims"
                                    ]
                                },
                                "type": "object"
                            }
                        }
                    }
                ]
            }
        ],
        "isSilent": true
    },
    "isBrioError": true
}


#### 5. Idempotency Conflict Error Response (`HTTP 400 Bad Request`)

```json
{
    "id": "2b6daa64-8cd9-48ff-99e5-ed0bca428430",
    "code": "CLAIM_IMPORT_API.CONFLICT",
    "type": "user",
    "message": "externalClaimReportId was already ingested with a different payload",
    "metadata": {
        "externalClaimReportId": "MESI-2026-07-002"
    },
    "isBrioError": true
}

 

Common Issues/FAQ

Q1: What should I do if an imported claim was excluded by mistake in brioHR Payroll?

Once a Payroll Officer excludes an imported claim in brioHR, it is permanently removed from processing and cannot be moved back to payroll. Re-pushing the request using the same externalClaimReportId will return an HTTP 201 status: already-parked response and will not recreate the claim in the queue. To resubmit an excluded claim, your system must send the request using a new, unique externalClaimReportId.

Q2: How can I find the correct payrollItem label to map claims? Is there an API endpoint to list them?

There is no separate endpoint for listing payroll items; issuing a GET request to /v2/api/external/payroll-items will return 404 Not Found. brioHR automatically tolerates minor variations in casing, leading/trailing whitespace, and singular/plural wording when matching labels. If an item label fails to match, the API returns an HTTP 400 error with the code CLAIM_IMPORT_API.PAYROLL_ITEM_NOT_FOUND, which includes an availableLabels array in the response metadata listing all valid reimbursement labels for that employee's Payroll Center.  

Q3: The API returned HTTP 201 status: parked, but the claim is not visible in the Claims to Payroll queue. Why?

Pushed claims only appear in the Claims to Payroll queue for a payroll cycle that covers the claimant employee. If a claim was successfully accepted by the API but does not appear in the brioHR web interface, verify that an active payroll cycle including that employee is currently running.

Q4: What happens to a claim item if it was 'added to payroll' then deleted (under the employee's pay item list)?

A4: The claim item will go back to the 'Validate payroll items'> Claims tab. You can choose to add it back to payroll, skip, or exclude the item.

 

Related Articles

Managing Approved Claims in the Payroll Module 

 

Ownership

By: Aqilah