---
title: "Bulk upsert billing charges for an appointment"
method: PUT
path: "/billingCharges"
tags: ["billingCharges"]
---

# Bulk upsert billing charges for an appointment

`PUT /billingCharges`

## Request

- Base URL: `https://api.lumahealth.io/api/v2`
- URL: `https://api.lumahealth.io/api/v2/billingCharges`
- Auth: HTTP bearer
- TypeScript: <https://skmtc.dev/lumahealth/apis/rest-service/docs/billingCharges/put.ts> (a function with typed inputs; its response is checked against the schema)

Creates or updates the set of billing charges tied to a single appointment in one call. Only one `copay` type charge is allowed per appointment, and any existing `custom-appointment-charge` not present in the request will be aborted.

## Request body

- BillingChargesUpsertRequest
  - `appointmentId` string, required: ID of the appointment these charges belong to.
  - `charges` object[], required: Charges to upsert for this appointment. Include `_id` to update an existing charge, or omit it to create a new one. Only one `copay` type charge is allowed per appointment. Any existing custom-appointment-charge not present in this array will be aborted.
    - `_id` string: ID of an existing charge to update. Omit to create a new charge.
    - `amount` number: The charge amount.
    - `type` 'copay' | 'balance' | 'custom-appointment-charge' | 'convenience-fee': The kind of charge.
    - `description` string: Public description of the charge, visible to the patient.

## Response `200`

The upserted billing charges

- BillingChargeResponse[]
  - `_id` string: Luma's internal ID of an object.
  - `user` string: The ID of the root account user.
  - `deleted` 0 | 1: Flag for logical deletion where 1 means deleted.
  - `createdBy` string: The ID of the user who created this object.
  - `updatedBy` string: The ID of the user who updated this object.
  - `createdAt` string, date-time: The date/time when this object was created.
  - `updatedAt` string, date-time: The date/time when this object was updated.
  - `amount` number: The charge amount.
  - `currency` string: Currency code for the charge.
  - `status` 'planned' | 'billable' | 'billed' | 'aborted': Lifecycle status of the charge. `planned` = planned but not yet billed, `billable` = confirmed by staff and ready to bill, `billed` = sent to the patient for collection, `aborted` = wrongfully added or aborted.
  - `lastStatusUpdatedAt` string, date-time: When `status` was last changed.
  - `lastStatusUpdatedBy` string: ID of the user who last changed `status`.
  - `type` 'copay' | 'balance' | 'custom-appointment-charge' | 'convenience-fee': The kind of charge. `copay` = copay amount for the appointment, `balance` = patient balance tied to the appointment, `custom-appointment-charge` = custom value added by staff, `convenience-fee` = a convenience fee charge.
  - `source` 'manual' | 'integrator' | 'insurance-verification' | 'fixedAmount': Where the charge originated from. `manual` = added by staff, `integrator` = from the EHR, `insurance-verification` = from the insurance verification process, `fixedAmount` = created from a fixed copay rule.
  - `description` string: Public description of the charge, visible to the patient.
  - `reason` string: Internal, staff-only note about the charge. Omitted from patient-facing responses.
  - `appointment` string: ID of the appointment associated with this charge.
  - `patient` string: ID of the patient associated with this charge.
  - `insuranceVerification` string: ID of the insurance verification this charge's copay was derived from, if any.
  - `insurance` string: ID of the patient's insurance saved alongside the insurance verification.
  - `paymentStatus` 'pending' | 'not-paid' | 'paid' | 'partially-paid' | 'refunded': How much of the charge has been collected.
  - `amountPaid` number: The amount paid toward this charge so far.
  - `billingTransactions` string[]: IDs of the billing transactions associated with this charge.
  - `isCreatedByAI` boolean: Indicates whether this charge was created or suggested by AI.
  - `discountResolution` object: The prompt-pay discount as it currently stands for this charge, resolved at read time. Distinct from `discount`, which is only present once a discount has actually been honoured by a payment.
    - `effectiveAmount` number: The amount actually owed, after any prompt-pay discount currently in effect. Equal to `amount` when no discount applies.
    - `originalAmount` number: The full, undiscounted amount. Equal to `amount` unless a prompt-pay discount is in effect.
    - `isDiscountApplied` boolean: Whether a prompt-pay discount is currently applied to this charge.
    - `percentage` number: The prompt-pay discount percentage currently applied to this charge. `0` when no discount applies.
    - `windowEndsAt` string, date-time, nullable: When the prompt-pay discount window closes for this charge, if it was ever eligible for one. `null` when the charge could never be discounted.
    - `reason` string: Why a prompt-pay discount is or isn't currently applied to this charge (e.g. the account setting is off, the discount window closed, or the charge type is excluded). Not present once the discount was honoured by an actual payment.
  - `hasDiscount` boolean: Whether a prompt-pay discount was actually honoured (money captured under it) for this charge. `false` for a discount that was only ever offered but never paid.

## Other responses

- `400`: Missing appointmentId or charges
- `401`: Not authenticated
- `403`: Access token does not have the required scope

## Changes

- **2026-10-17** `f19aeba84a26`: 2 info
  - added the optional property `items/discountResolution` to the response with the `200` status
  - added the optional property `items/hasDiscount` to the response with the `200` status
- **2026-08-23** `d3c75c2a2f1d`: 2 warning
  - removed the optional property `items/discountResolution` from the response with the `200` status
  - removed the optional property `items/hasDiscount` from the response with the `200` status
- **2026-08-21** `45839e3280c8`: 2 info
  - added the optional property `items/discountResolution` to the response with the `200` status
  - added the optional property `items/hasDiscount` to the response with the `200` status

[Operation history](https://skmtc.dev/lumahealth/apis/rest-service/changes/billingCharges/put.md)

---

[API](https://skmtc.dev/lumahealth/apis/rest-service.md) - [All operations](https://skmtc.dev/lumahealth/apis/rest-service/llms.txt) - [OpenAPI document](https://skmtc.dev/lumahealth/apis/rest-service/revisions/f19aeba84a26?raw)
