Guides

    Rent and property management

    Sync residents, every charge, and every payment from a property management app into HelloLedger.

    How it maps

    Use your own primary keys as external IDs. Retries and nightly re-syncs then never create duplicates.

    Your appHelloLedgerEndpoint
    Resident or studentCustomerPUT /customers/by-external-id/{resident_id}
    Each charge (rent, late fee, utilities, parking)Invoice with one or more linesPUT /invoices/by-external-id/{charge_id}
    Each paymentCustomer payment applied to the charges it paidPUT /customer-payments/by-external-id/{payment_id}
    Special, waived balance, write-off, kept depositCustomer credit applied to the charge it coversPUT /customer-credits/by-external-id/{credit_id}
    Deposit return or refundCustomer refundPUT /customer-refunds/by-external-id/{refund_id}
    Processor payoutPayout with its feesPUT /payouts/by-external-id/{payout_id}
    Charge typesProducts and servicesGET /items

    One-time setup (bookkeeper)

    • Companies with the Property Management business type get a chart of accounts with Tenant Security Deposits, Payment Processor Clearing, Rent Concessions, Bad Debt, and Bank and Payment Processing Fees, plus items for Rent, Room Rent, Late Fee, Application Fee, Move-in Fee, Pet Fee, Parking, and Utility Billback.
    • Otherwise, create items for each charge type, each pointing at its income account.
    • Use a liability account for security deposits. Deposits are owed back to the resident, so they are not income.
    • Use a clearing asset account (or Undeposited Funds) when your processor pays out in batches.
    • Connect the company. If your app serves many landlords or managers, use Connect HelloLedger: they approve from your app and your server gets the key. Otherwise a company admin creates a developer key under Profile → Developers with the Property management / receivables sync template and sets integration to your app's name. The secret is shown once; store it in your secret manager.
    • Save the account IDs your app needs (deposit liability, clearing, concessions, bad debt, fees) from GET /coa in your app's settings for that company.

    1. New residents the same day

    When a resident is added, PUT the customer. If your sync might send a charge first, include the customer object on the invoice; HelloLedger creates the customer automatically.

    PUT
    curl -X PUT "https://api.helloledger.ai/v1/companies/{company_id}/customers/by-external-id/stu_1001" \
      -H "Authorization: Bearer $HL_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
      "name": "Jamie Rivera",
      "email": "jamie@example.com",
      "metadata": {
        "unit": "4B"
      }
    }'

    2. Every charge is its own invoice

    Post each charge when it's created. Use an item line for income and an account line for a security deposit, so the deposit lands in the liability account.

    Rent · PUT
    curl -X PUT "https://api.helloledger.ai/v1/companies/{company_id}/invoices/by-external-id/chg_2026_10_rent_1001" \
      -H "Authorization: Bearer $HL_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
      "customer_external_id": "stu_1001",
      "date": "2026-10-01",
      "due_date": "2026-10-05",
      "lines": [
        {
          "item_id": "66f1a2b3c4d5e6f708091a2b",
          "description": "October rent",
          "amount": "850.00"
        }
      ]
    }'
    Security deposit · PUT
    curl -X PUT "https://api.helloledger.ai/v1/companies/{company_id}/invoices/by-external-id/chg_deposit_1001" \
      -H "Authorization: Bearer $HL_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
      "customer_external_id": "stu_1001",
      "date": "2026-08-15",
      "terms": "due_on_receipt",
      "lines": [
        {
          "account_id": "66f1a2b3c4d5e6f708091d4e",
          "description": "Security deposit",
          "amount": "500.00"
        }
      ]
    }'

    3. Every payment applied to what it paid

    Record each payment individually and list the charges it paid. Deposit it to the clearing account; when the processor's payout reaches the bank, the bookkeeper matches the bank deposit against the clearing account.

    PUT
    curl -X PUT "https://api.helloledger.ai/v1/companies/{company_id}/customer-payments/by-external-id/pay_pi_3Q8x" \
      -H "Authorization: Bearer $HL_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
      "customer_external_id": "stu_1001",
      "date": "2026-10-03",
      "amount": "850.00",
      "deposit_account_id": "66f1a2b3c4d5e6f708091c3d",
      "method": "card",
      "reference": "pi_3Q8x",
      "applications": [
        {
          "invoice_external_id": "chg_2026_10_rent_1001",
          "amount": "850.00"
        }
      ]
    }'

    4. Specials, waived balances, and write-offs

    Record each as a credit applied to the charge it covers. A move-in special goes to Rent Concessions, so gross rent and the discount both show on the P&L. A balance you give up on goes to Bad Debt with reason write_off.

    PUT
    curl -X PUT "https://api.helloledger.ai/v1/companies/{company_id}/customer-credits/by-external-id/cr_2026_10_special_1001" \
      -H "Authorization: Bearer $HL_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
      "customer_external_id": "stu_1001",
      "date": "2026-10-01",
      "reason": "concession",
      "lines": [
        {
          "account_id": "66f1a2b3c4d5e6f708091f60",
          "description": "Move-in special",
          "amount": "100.00"
        }
      ],
      "applications": [
        {
          "invoice_external_id": "chg_2026_10_rent_1001",
          "amount": "100.00"
        }
      ]
    }'

    5. Move-out: deposits kept and returned

    • Post any move-out charges (cleaning, damage, final utilities) as invoices.
    • Apply the deposit to them with a credit: reason deposit_applied, line account the deposit liability.
    • Return the rest with a refund: source deposit, paid from the bank account.
    • PUT the customer with status inactive.
    Deposit kept for cleaning · PUT
    curl -X PUT "https://api.helloledger.ai/v1/companies/{company_id}/customer-credits/by-external-id/cr_deposit_kept_1001" \
      -H "Authorization: Bearer $HL_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
      "customer_external_id": "stu_1001",
      "date": "2027-05-20",
      "reason": "deposit_applied",
      "lines": [
        {
          "account_id": "66f1a2b3c4d5e6f708091d4e",
          "description": "Deposit applied",
          "amount": "150.00"
        }
      ],
      "applications": [
        {
          "invoice_external_id": "chg_2027_05_cleaning_1001",
          "amount": "150.00"
        }
      ]
    }'
    Rest of the deposit returned · PUT
    curl -X PUT "https://api.helloledger.ai/v1/companies/{company_id}/customer-refunds/by-external-id/rf_deposit_1001" \
      -H "Authorization: Bearer $HL_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
      "customer_external_id": "stu_1001",
      "date": "2027-06-01",
      "amount": "350.00",
      "paid_from_account_id": "66f1a2b3c4d5e6f708091e5f",
      "source": "deposit",
      "deposit_account_id": "66f1a2b3c4d5e6f708091d4e",
      "method": "ach"
    }'

    6. Payouts and fees

    Keep recording each payment at its full amount into the clearing account. When the processor pays out, send the payout with its gross, fee, and net. HelloLedger posts the fee to the fees expense account, leaving exactly the net in clearing for the bookkeeper to match against the bank deposit.

    Don't use the daily revenue journal for this: it bypasses invoices, so resident balances would be wrong.

    PUT
    curl -X PUT "https://api.helloledger.ai/v1/companies/{company_id}/payouts/by-external-id/po_1QxYz" \
      -H "Authorization: Bearer $HL_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
      "date": "2026-10-06",
      "clearing_account_id": "66f1a2b3c4d5e6f708091c3d",
      "gross": "2550.00",
      "fee": "74.25",
      "net": "2475.75",
      "fee_account_id": "66f1a2b3c4d5e6f708092182",
      "bank_account_id": "66f1a2b3c4d5e6f708091e5f",
      "reference": "po_1QxYz"
    }'

    7. Corrections

    • Charge amount changed before it was paid: PUT the invoice again with the new lines. HelloLedger replaces it and keeps the invoice number.
    • Charge waived before anything was applied: POST /invoices/by-external-id/{id}/void. After a payment, use a credit instead.
    • Charge changed after payment: void the payment, update the invoice, then record the payment again under a new external ID.
    • Returned payment (ACH return, chargeback): POST /customer-payments/by-external-id/{id}/void. The charges reopen.
    • Overpayment sent back: refund with source payment.
    • A voided invoice stays voided. If the charge comes back, send it under a new external ID.

    8. Never miss a record

    Send records in real time, and also run a nightly job that re-sends the last few days of residents, charges, and payments. Records already in HelloLedger return result unchanged, so the job is safe to run as often as you like and fills any gap left by an outage.

    Process residents first, then charges, then payments, then credits, refunds, and payouts, since each step refers to the ones before. Stay under 120 requests per minute.

    Send your own X-Request-Id header (for example your sync batch and record ID). HelloLedger echoes it, puts it in errors, and shows it in the key's activity.

    9. Check the totals

    Compare your app's open balances against HelloLedger's A/R aging (reports:read) at month end.

    GET
    curl "https://api.helloledger.ai/v1/companies/{company_id}/reports/ar-aging?as_of=2026-10-31" \
      -H "Authorization: Bearer $HL_API_KEY"