Skip to main content
This document covers advancedmd_unapply_payment, the AdvancedMD payment unapply tool exposed through the Agents Platform. It detaches an applied payment from the charge it was applied to, leaving it as an unapplied credit on the patient account. Optionally (move_balance_to_insurance=true) it then moves the charge’s remaining patient responsibility to the insurance balance. For locating the payment row and its charge first, see AdvancedMD Find Charge; for posting payments, see AdvancedMD Post Payment.

Authentication

AdvancedMD payment unapply uses the existing AdvancedMD connector:
  • office_code
  • username
  • password
The tool uses the same audited AdvancedMD client as the other AdvancedMD tools. It does not accept raw AdvancedMD tokens or direct PM REST URLs — the client derives the tenant REST base from the login-returned URL and manages the security token itself (including one transparent re-authentication retry if AdvancedMD returns HTTP 401). If AdvancedMD is not connected, the tool returns a structured advancedmd_not_connected result with suggested actions:
  • Connect AdvancedMD and try again
  • Stop task

Safety

advancedmd_unapply_payment is a mutating billing tool. It calls the AdvancedMD PM REST endpoint POST {rest}/transaction/Payments/{payment_id}/unapply, which immediately detaches the payment from its charge. dry_run defaults to false (the tool unapplies immediately, matching advancedmd_create_claim). Set dry_run=true to preview the exact endpoint call without executing it. The unapply call is a non-idempotent financial write: the client never replays it on HTTP 429 or transient 5xx errors. The only automatic retry is a single re-authentication replay on HTTP 401, which is safe because AdvancedMD rejects unauthenticated requests before processing them. The optional balance move is a second financial write (updvisitwithcharges), shared with AdvancedMD Transfer Balance. It reads the charge’s live balances from AdvancedMD after the unapply commits, then moves the patient balance and portion together onto insurance, setting the patient side to 0.00. The write echoes the charge’s FID references and line fields (proccode/diagcodes/modcodes/respparty, fee/allowed/etc.) so AdvancedMD’s CD_FIDReferences constraint is satisfied, and it refuses any move that would drive a balance negative (CD_Balances). It skips the write entirely when the charge has no patient responsibility to move.

Provider profile & facility preservation

updvisitwithcharges rewrites the visit record as well as the charge, and clears any attribute it is not given. Per the AdvancedMD API docs (June 2025, UpdateVisitWithNewCharges) the billing provider and facility are @profile and @facility on the <visit> element — not on the charge. Our payload sent visit with only its @id, so every successful write reset the visit’s provider and facility to AdvancedMD’s blank records (prof1 / fac1), which surfaces in reports as “Billing Provider Profile: N/A” and “Facility: N/A - N/A”. 58 charges were affected between 27–30 Jul 2026. Verified live on charge 350485 (2026-07-31): the same attributes on the charge element were accepted and ignored; on the visit element they took effect and restored provcode/faccode, with no other field disturbed. The write now:
  1. Reads the visit record from getchargedetaildataicd10profile, facility, date, insorder, refplan — the same call that already supplies the balances, so no extra round trip.
  2. Echoes them on the visit element as bare ids (prof179@profile="179"), matching the docs’ write sample.
  3. Fails closed. If the visit’s provider cannot be read, the write is refused (billing_identity_unresolved) rather than run without it. No balance is moved. This is the real protection, and it is why a flat (non-nested) charge-detail response now blocks the write.
  4. Verifies after the write. The visit’s provider is re-read and compared: unchanged, changed/cleared (provider_cleared: true plus the codes to restore and a ⚠️ Billing Provider — CLEARED BY THIS WRITE summary row), or the read-back itself failed (provider_verification_failed: true, reported as unknown and never as intact). The transfer is still reported as successful in all cases, because the money did move.
ADVANCEDMD_CHARGE_WRITE_ENABLED is an ops kill switch for every updvisitwithcharges write — this tool’s balance move, AdvancedMD Transfer Balance and AdvancedMD Add Codes To Visit. Writes are enabled by default; set it to false/0/no/off to stop them without a deploy.

Inputs

Required:
  • payment_id: AdvancedMD payment detail ID of the applied payment to unapply (numeric, e.g. 9225081). A leading pay prefix is stripped automatically; any other non-numeric value is rejected.
Optional:
  • dry_run: Defaults to false. When true, returns the unapply call that would be made without executing it.
  • move_balance_to_insurance: Defaults to true — after the unapply succeeds, the charge’s remaining patient balance and patient portion are moved to the insurance balance/portion. Set to false to only unapply.
  • charge_id: AdvancedMD charge ID the payment was applied to (from advancedmd_find_charge’s ehr_charge_id). Required unless move_balance_to_insurance=false (the tool errors with guidance when missing).
  • patient_id / visit_id: Only used for the balance move, and only needed when AdvancedMD’s charge detail response doesn’t identify the patient/visit itself (the tool errors asking for them if so). advancedmd_find_charge returns both (ehr_patient_id, ehr_visit_id).
Note that payment_id is the payment detail ID of the applied payment row (the per-charge application record), not the parent payment ID. Both appear in the output: the detail ID is what you pass in, and paymentId in the response is the parent payment it belongs to.

Standard Unapply Flow

  1. Use advancedmd_find_charge to locate the charge — its match.applied_payments lists every payment applied to the charge, and each entry’s payment_id is what this tool takes (ehr_charge_id is the charge_id). Confirm the amount and check number match the payment that should be unapplied, and skip entries with void != "0".
  2. Call advancedmd_unapply_payment with dry_run=true and review the endpoint preview.
  3. Call again with dry_run=false (or omit dry_run) to unapply.
  4. Confirm unapplied is true and the returned payment details (amount, check number, carrier) match expectations.

Unapply + Move Balance to Insurance Flow (default)

This is the default behavior: after unapplying, the charge’s entire patient responsibility moves to insurance (e.g. the payment is being clawed back and the claim goes back to the carrier).
  1. Use advancedmd_find_charge to get the charge (ehr_charge_id, plus ehr_patient_id/ehr_visit_id) and the applied payment’s payment_id.
  2. Call advancedmd_unapply_payment with payment_id and charge_id.
  3. The tool unapplies first, then re-reads the charge’s live balances (which now include the unapplied amount back on the charge) and moves patient balance/portion to insurance.
  4. Confirm balance_transfer.transferred in the output and the before/after amounts in the summary table.
A payment that spans multiple charges has one payment detail row per charge — run this flow once per (payment detail, charge) pair. Partial failure: if the unapply succeeds but the balance move fails, the result is still a success (unapplied: true) with balance_transfer.status = "failed" — an error result would wrongly suggest nothing happened. Whether to re-run depends on balance_transfer.retryable:
  • retryable: true — re-run the tool with the same arguments. The already-done unapply comes back from AdvancedMD as either a 409 “payment is not applied” or a 400 “Payment cannot be unapplied”, both of which the tool resolves to already_unapplied: true, and it then retries only the balance move.
  • retryable: false (reason: "payment_detail_fid_constraint") — AdvancedMD rejected the move with its PD_FIDReferences constraint on actv_PaymentDetails. This was caused by the same visit-element omission described above and is fixed: when the write omitted the visit’s @profile, the payment-detail row AdvancedMD derives had no valid provider to reference, so its own CHECK constraint rejected it. Confirmed on charges 301828 and 257186, both stuck on this constraint since 30 Jul 2026 and both transferred cleanly once the visit’s provider was echoed. The classification is kept as a defensive net: if the constraint ever fires again it is a property of the charge, so the tool marks it non-retryable and asks for a manual transfer instead of looping.
With move_balance_to_insurance=false there is nothing to chain, so both rejections stay plain errors.

Repost / Correction Flow

Use this when a payment was applied to the wrong charge or with the wrong amounts.
  1. Unapply the payment with advancedmd_unapply_payment.
  2. The payment becomes an unapplied credit on the patient account.
  3. Re-apply it to the correct charge with AdvancedMD Post Payment, passing the correct charge_id and amounts.

Missing AdvancedMD Connector Flow

Use this when the tool reports advancedmd_not_connected.
  1. Do not retry with the same inputs immediately.
  2. Ask the user to either connect AdvancedMD and try again, or stop the task.
  3. After AdvancedMD is connected, call the tool again.

Dry-Run Example

Unapply + Move Balance Example (default)

Unapply-Only Example

Error Handling

The tool returns a structured tool error for invalid inputs (missing or non-numeric payment_id). If AdvancedMD returns a non-2xx status, the tool surfaces the HTTP status and AdvancedMD’s response body as a tool error. It does not retry. Two rejections are handled specially, and only when the balance move is enabled:
  • 409 “payment is not applied” — unambiguous, so the tool treats the unapply as already done and proceeds to the balance move.
  • 400 “Payment cannot be unapplied” — AdvancedMD’s description lumps four states together (“transferred, protected, voided, or unapplied payment detail”), and only the last means the work is done. The tool therefore re-reads the charge’s applied payments and branches on what it finds:
    • payment no longer applied → treated as already unapplied (unapply_state: "already_unapplied"), balance move proceeds;
    • payment still applied → it is transferred, protected, or voided and AdvancedMD will not release it through the API. This is a tool error and no balance is moved; resolve the payment in AdvancedMD or pass the correct payment detail id;
    • state unresolvable (no patient_id, or the charge is beyond the scanned history pages) → unapply_state: "already_unapplied_unverified". Nothing was unapplied by the call, the requested balance move still runs, and the summary says the state is unconfirmed.
Passing patient_id alongside charge_id is what enables that read-back, so include both. A 2xx response with an unexpected body is still reported as a successful unapply — the structured output then carries the raw response under amd_response instead of the parsed payment object.

Output

The structured output includes:
  • unapplied: true only when dry_run=false and AdvancedMD accepted the unapply.
  • dry_run: whether the call was a dry run.
  • payment_id: the normalized payment detail ID that was targeted.
  • payment: the payment detail returned by AdvancedMD after unapplying — paymentDetailId, paymentId, paymentAmount, checkNumber, carrierCode, paymentCode, profileCode, depositDate, note, and related fields.
  • endpoint / method: on dry runs, the call that would be made.
  • already_unapplied: true when the unapply step was skipped because AdvancedMD reported the payment as not currently applied (only when the balance move is enabled).
  • unapply_state: "already_unapplied" or "already_unapplied_unverified" — set only when the unapply step was skipped, distinguishing a confirmed-detached payment from one whose state could not be read back.
  • balance_transfer (absent with move_balance_to_insurance=false): the transfer outcome — transferred, charge_id, direction, amount, the previous patient/insurance balance and portion, and the new patient/insurance balance and portion; or {status: "failed", error, retryable} when the move failed after a committed unapply (plus reason and suggested_actions when retryable is false); or {status: "planned"} on dry runs.
The markdown summary renders an “Unapplied” status table with the payment detail ID, parent payment ID, amount (normalized to two decimals), payment code, carrier, check number, profile, deposit date, and note, followed by a balance-transfer table (before → after amounts) when the move ran.