ReferenceReference by module

Finance API

Every Finance action (42) and data model (13): endpoints, scopes, input, preconditions, events and examples.

What customers owe and pay, and what the business spends.

Module code finance · 42 actions · 13 data models · 30 events. A key or token never does more here than the person behind it can; row-level security applies exactly as in the app. Scopes: finance (everything), finance:read (read only) or finance:<capability>.

Resources

ResourceTableList and readPOSTPATCHDELETE
agreement_linesagreement_line/api/v1/agreement_lines
agreementsagreement/api/v1/agreementsfinance.create_agreementfinance.update_agreement
commission_payoutscommission_payout/api/v1/commission_payouts
commissionscommission/api/v1/commissions
discount_linesdiscount_line/api/v1/discount_lines
expense_categoriesexpense_category/api/v1/expense_categoriesfinance.save_expense_category
expensesexpense/api/v1/expensesfinance.create_expensefinance.update_expense
finance_provider_eventsfinance_provider_event/api/v1/finance_provider_events
installmentsinstallment/api/v1/installmentsfinance.update_installment
payment_allocationspayment_allocation/api/v1/payment_allocations
payment_eventspayment_event/api/v1/payment_events
payment_provider_configspayment_provider_config/api/v1/payment_provider_configs
paymentspayment/api/v1/payments

Lists take limit (up to 200), cursor, order (e.g. -created_at), fields and q; every other query parameter is a column filter: ?status=paid, ?total[gte]=100, ?id[in]=a,b, ?x=null.

Actions

ActionNeedsWhat it does
finance.accrue_commissionfinance:writeAccrue a commission
finance.allocate_paymentfinance:writeAllocate a collection
finance.apply_discountfinance:writeApply a discount
finance.approve_expensefinance:approveApprove an expense
finance.approve_refundfinance:approveApprove a refund
finance.cancel_agreementfinance:manageCancel an agreement
finance.cancel_commissionfinance:manageCancel a commission
finance.cancel_installmentfinance:manageCancel an instalment
finance.cancel_paymentfinance:writeRelease a payment
finance.capture_paymentfinance:writeCapture a payment
finance.configure_payment_appfinance:manageConfigure a payment app
finance.create_agreementfinance:writeOpen an agreement
finance.create_expensefinance:writeRecord an expense
finance.deactivate_expense_categoryfinance:manageDeactivate an expense category
finance.mark_expense_paidfinance:writeMark an expense paid
finance.open_draft_for_sourcefinance:writeDraft an agreement for a record
finance.pay_site_agreementfinance:writePay by link (site)
finance.pay_site_orderfinance:writePay for an order (site)
finance.raise_installment_noticesfinance:writeRaise instalment reminders
finance.recalculate_commissionfinance:writeRecalculate the commission
finance.record_commission_payoutfinance:approveRecord a commission payout
finance.record_gift_card_paymentfinance:writeRecord a gift card collection
finance.record_paymentfinance:writeRecord a collection
finance.record_provider_webhookfinance:writeRecord a payment webhook
finance.record_source_discountsfinance:writeRecord the shop’s discounts
finance.refund_paymentfinance:manageRefund a collection
finance.reject_expensefinance:approveReject an expense
finance.reject_refundfinance:approveReject a refund
finance.remove_discountfinance:manageRemove a discount
finance.request_refundfinance:writeRequest a refund
finance.request_refunds_for_sourcefinance:writeAnswer a cancellation
finance.return_gift_card_paymentfinance:writeReturn a gift card collection
finance.run_recurring_expensesfinance:writeRecord recurring expenses
finance.save_expense_categoryfinance:manageSave an expense category
finance.schedule_installmentsfinance:writeSchedule instalments
finance.set_commission_statusfinance:manageMove a commission
finance.stop_recurring_expensefinance:writeStop repeating
finance.take_paymentfinance:writeTake a payment
finance.update_agreementfinance:writeEdit an agreement
finance.update_expensefinance:writeEdit an expense
finance.update_installmentfinance:writeEdit an instalment
finance.void_expensefinance:manageVoid an expense

finance.accrue_commission

Accrue a commission — Opens what the affiliate is owed, at the rate frozen on the day of the agreement.

Needs finance:write · changes data

POSThttps://app.getera.io/api/v1/commissions/actions/accrue_commission
API key or OAuth token · finance:write
  • Canonical address: POST /api/v1/actions/finance.accrue_commission

Input

FieldTypeRequiredNotes
agreementIduuidyesAgreement · id of agreement
ratenumberRate (%) · min 0 · max 100
amountnumber (money)Amount · min 0

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
agreement_not_foundThe agreement must exist.
commission_already_existsOne commission per agreement.
agreement_has_no_affiliateThe agreement must name an affiliate.
affiliate_not_activeThe affiliate must be active.

Events it publishes: finance.commission_accrued

What it writes: commission (insert) — Opens the commission

curl -X POST https://app.getera.io/api/v1/commissions/actions/accrue_commission \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"agreementId":"AGREEMENT_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.commission_accrued"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.commissions.accrueCommission({
  agreementId: "AGREEMENT_ID"
})
era commissions accrue_commission \
  --agreement-id "AGREEMENT_ID"
tools/call
{
  "name": "era__finance__accrue_commission",
  "arguments": {
    "agreementId": "AGREEMENT_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 agreement_not_found, commission_already_exists, agreement_has_no_affiliate, affiliate_not_active · 429 slow down

finance.allocate_payment

Allocate a collection — Spreads one collection across the instalments it paid off.

Needs finance:write · changes data

POSThttps://app.getera.io/api/v1/payment_allocations/actions/allocate_payment
API key or OAuth token · finance:write
  • Canonical address: POST /api/v1/actions/finance.allocate_payment

Input

FieldTypeRequiredNotes
paymentIduuidyesCollection · id of payment
allocationsobject / arrayyesAllocations

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
payment_not_foundThe collection must exist.
no_allocationsAt least one allocation is needed.
unknown_allocation_targetThe target must be a known kind of record.
invalid_allocation_amountEvery allocation must be greater than zero.
duplicate_allocation_targetA target appears only once in one allocation request.
allocation_exceeds_paymentAllocations may not add up to more than was collected.
allocation_target_not_foundThe target must exist and remain available.
allocation_currency_mismatchThe collection and target must use the same currency.
allocation_customer_mismatchA customer collection cannot settle somebody else’s balance.
allocation_exceeds_targetAn allocation cannot exceed the target’s outstanding balance.

Events it publishes: finance.payment_allocated

What it writes: payment_allocation (insert) — Writes the allocations

curl -X POST https://app.getera.io/api/v1/payment_allocations/actions/allocate_payment \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"paymentId":"PAYMENT_ID","allocations":{}}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.payment_allocated"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.paymentAllocations.allocatePayment({
  paymentId: "PAYMENT_ID",
  allocations: {}
})
era payment_allocations allocate_payment --data '{"paymentId":"PAYMENT_ID","allocations":{}}'
tools/call
{
  "name": "era__finance__allocate_payment",
  "arguments": {
    "paymentId": "PAYMENT_ID",
    "allocations": {}
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 payment_not_found, no_allocations, unknown_allocation_target, invalid_allocation_amount, duplicate_allocation_target, allocation_exceeds_payment, allocation_target_not_found, allocation_currency_mismatch, allocation_customer_mismatch, allocation_exceeds_target · 429 slow down

finance.apply_discount

Apply a discount — Reduces an agreement, and freezes the reduced amount on the row.

Needs finance:write · changes data

POSThttps://app.getera.io/api/v1/agreements/{id}/actions/apply_discount
API key or OAuth token · finance:write
  • Canonical address: POST /api/v1/actions/finance.apply_discount

Input

FieldTypeRequiredNotes
agreementIduuidyesFilled from the path {id} · Agreement · id of agreement
valueTypestring (enum)Kind · Allowed values: percentage, fixed
valuenumberyesValue
namestringLabel · max 255
reasonstringyesReason · max 2000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
agreement_not_foundThe agreement must exist.
agreement_closedA closed agreement is not discounted.
discount_value_negativeA discount is not negative.
discount_percentage_over_100A percentage discount stops at 100.
discount_after_collectionA discount may not take the agreement below what has been collected.
discount_over_capThe workspace discount cap must not be exceeded.
discount_actor_requiredA manual discount records who applied it.

Events it publishes: finance.discount_applied

What it writes: discount_line (insert) — Writes the discount; agreement (update) — Freezes the discounted total

curl -X POST https://app.getera.io/api/v1/agreements/AGREEMENT_ID/actions/apply_discount \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"value":1,"reason":"Reason"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.discount_applied"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.agreements.applyDiscount("AGREEMENT_ID", {
  value: 1,
  reason: "Reason"
})
era agreements apply_discount AGREEMENT_ID \
  --value 1 \
  --reason "Reason"
tools/call
{
  "name": "era__finance__apply_discount",
  "arguments": {
    "agreementId": "AGREEMENT_ID",
    "value": 1,
    "reason": "Reason"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 agreement_not_found, agreement_closed, discount_value_negative, discount_percentage_over_100, discount_after_collection, discount_over_cap, discount_actor_required · 429 slow down

finance.approve_expense

Approve an expense — Signs an expense off. Never your own.

Needs finance:approve · changes data

POSThttps://app.getera.io/api/v1/expenses/{id}/actions/approve_expense
API key or OAuth token · finance:approve
  • Canonical address: POST /api/v1/actions/finance.approve_expense

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Expense · id of expense

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
expense_not_foundThe expense must exist.
expense_already_decidedThe expense must still be pending.
cannot_approve_own_expenseNobody approves their own spending.

Events it publishes: finance.expense_approved

What it writes: expense (update) — Stamps the approval

curl -X POST https://app.getera.io/api/v1/expenses/EXPENSE_ID/actions/approve_expense \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.expense_approved"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.expenses.approveExpense("EXPENSE_ID")
era expenses approve_expense EXPENSE_ID
tools/call
{
  "name": "era__finance__approve_expense",
  "arguments": {
    "id": "EXPENSE_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:approve · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 expense_not_found, expense_already_decided, cannot_approve_own_expense · 429 slow down

finance.approve_refund

Approve a refund — Approves a request and executes it: the money is recorded as gone back.

Needs finance:approve · removes or cannot be undone · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/payment_events/{id}/actions/approve_refund
API key or OAuth token · finance:approve
  • Canonical address: POST /api/v1/actions/finance.approve_refund

Input

FieldTypeRequiredNotes
requestIduuidyesFilled from the path {id} · Request · id of payment_event

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
refund_request_not_foundThe request must exist.
refund_request_resolvedA request is approved or rejected once.
refund_not_availableThe provider must still allow a refund.
payment_app_not_connectedThe payment app must still be enabled.
payment_credentials_invalidThe payment app credentials must open from the vault.

Events it publishes: finance.refund_approved, finance.payment_refunded

What it writes: payment_event (insert) — Records the refund step; payment (update) — Moves the pending and refunded amounts; payment_allocation (insert) — Writes the negative allocation; installment (update) — Reopens an instalment it no longer covers

curl -X POST https://app.getera.io/api/v1/payment_events/PAYMENT_EVENT_ID/actions/approve_refund \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.refund_approved",
    "finance.payment_refunded"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.paymentEvents.approveRefund("PAYMENT_EVENT_ID")
era payment_events approve_refund PAYMENT_EVENT_ID
tools/call
{
  "name": "era__finance__approve_refund",
  "arguments": {
    "requestId": "PAYMENT_EVENT_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:approve · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 refund_request_not_found, refund_request_resolved, refund_not_available, payment_app_not_connected, payment_credentials_invalid · 429 slow down

finance.cancel_agreement

Cancel an agreement — Closes the agreement and its pending instalments. Nothing is deleted.

Needs finance:manage · removes or cannot be undone · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/agreements/{id}/actions/cancel_agreement
API key or OAuth token · finance:manage
  • Canonical address: POST /api/v1/actions/finance.cancel_agreement

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Agreement · id of agreement
reasonstringReason · max 2000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
agreement_not_foundThe agreement must exist.
already_cancelledAn agreement is cancelled once.
agreement_has_collectionMoney already collected must be refunded before cancelling.

Events it publishes: finance.agreement_cancelled

What it writes: agreement (update) — Marks it cancelled; installment (update) — Cancels the pending instalments

curl -X POST https://app.getera.io/api/v1/agreements/AGREEMENT_ID/actions/cancel_agreement \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"reason":"Reason"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.agreement_cancelled"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.agreements.cancelAgreement("AGREEMENT_ID", {
  reason: "Reason"
})
era agreements cancel_agreement AGREEMENT_ID \
  --reason "Reason"
tools/call
{
  "name": "era__finance__cancel_agreement",
  "arguments": {
    "id": "AGREEMENT_ID",
    "reason": "Reason"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 agreement_not_found, already_cancelled, agreement_has_collection · 429 slow down

finance.cancel_commission

Cancel a commission — Zeroes a commission that has not been paid. A paid one is never removed.

Needs finance:manage · removes or cannot be undone · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/commissions/{id}/actions/cancel_commission
API key or OAuth token · finance:manage
  • Canonical address: POST /api/v1/actions/finance.cancel_commission

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Commission · id of commission

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
commission_not_foundThe commission must exist.
paid_commission_not_deletableA paid commission is reversed, never removed.

Events it publishes: finance.commission_cancelled

What it writes: commission (update) — Cancels it and zeroes the amount

curl -X POST https://app.getera.io/api/v1/commissions/COMMISSION_ID/actions/cancel_commission \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.commission_cancelled"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.commissions.cancelCommission("COMMISSION_ID")
era commissions cancel_commission COMMISSION_ID
tools/call
{
  "name": "era__finance__cancel_commission",
  "arguments": {
    "id": "COMMISSION_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 commission_not_found, paid_commission_not_deletable · 429 slow down

finance.cancel_installment

Cancel an instalment — Takes an instalment out of the plan without deleting it.

Needs finance:manage · removes or cannot be undone · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/installments/{id}/actions/cancel_installment
API key or OAuth token · finance:manage
  • Canonical address: POST /api/v1/actions/finance.cancel_installment

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Instalment · id of installment

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
installment_not_foundThe instalment must exist.
installment_has_collectionAn instalment with money against it is refunded, not cancelled.

Events it publishes: finance.installment_cancelled

What it writes: installment (update) — Marks it cancelled

curl -X POST https://app.getera.io/api/v1/installments/INSTALLMENT_ID/actions/cancel_installment \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.installment_cancelled"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.installments.cancelInstallment("INSTALLMENT_ID")
era installments cancel_installment INSTALLMENT_ID
tools/call
{
  "name": "era__finance__cancel_installment",
  "arguments": {
    "id": "INSTALLMENT_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 installment_not_found, installment_has_collection · 429 slow down

finance.cancel_payment

Release a payment — Cancels what is authorized and not charged.

Needs finance:write · removes or cannot be undone · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/payments/{id}/actions/cancel_payment
API key or OAuth token · finance:write
  • Canonical address: POST /api/v1/actions/finance.cancel_payment

Input

FieldTypeRequiredNotes
paymentIduuidyesFilled from the path {id} · Payment · id of payment
outcomestring (enum)Test outcome · Only the dummy gateway reads this. · Allowed values: success, failure, pending

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
payment_not_foundThe payment must exist.
cancel_not_availableThe provider must still allow a release.
payment_app_not_connectedThe payment app must still be enabled.
payment_credentials_invalidThe payment app credentials must open from the vault.

Events it publishes: finance.payment_provider_event

What it writes: payment (update) — Moves the amounts and stores what the provider now allows; payment_event (insert) — Records the provider’s answer

curl -X POST https://app.getera.io/api/v1/payments/PAYMENT_ID/actions/cancel_payment \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"outcome":"success"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.payment_provider_event"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.payments.cancelPayment("PAYMENT_ID", {
  outcome: "success"
})
era payments cancel_payment PAYMENT_ID \
  --outcome "success"
tools/call
{
  "name": "era__finance__cancel_payment",
  "arguments": {
    "paymentId": "PAYMENT_ID",
    "outcome": "success"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 payment_not_found, cancel_not_available, payment_app_not_connected, payment_credentials_invalid · 429 slow down

finance.capture_payment

Capture a payment — Charges money that was only authorized.

Needs finance:write · reaches outside (sends, publishes or charges) · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/payments/{id}/actions/capture_payment
API key or OAuth token · finance:write
  • Canonical address: POST /api/v1/actions/finance.capture_payment

Input

FieldTypeRequiredNotes
paymentIduuidyesFilled from the path {id} · Payment · id of payment
amountnumber (money)Amount
outcomestring (enum)Test outcome · Only the dummy gateway reads this. · Allowed values: success, failure, pending

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
payment_not_foundThe payment must exist.
charge_not_availableThe provider must still allow a capture.
capture_exceeds_authorizedA capture takes at most what is held.
payment_app_not_connectedThe payment app must still be enabled.
payment_credentials_invalidThe payment app credentials must open from the vault.

Events it publishes: finance.payment_provider_event, finance.payment_recorded

What it writes: payment (update) — Moves the amounts and stores what the provider now allows; payment_event (insert) — Records the provider’s answer; payment_allocation (insert) — Puts charged money on the instalment; installment (update) — Closes the instalment once covered

curl -X POST https://app.getera.io/api/v1/payments/PAYMENT_ID/actions/capture_payment \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"amount":1}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.payment_provider_event",
    "finance.payment_recorded"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.payments.capturePayment("PAYMENT_ID", {
  amount: 1
})
era payments capture_payment PAYMENT_ID \
  --amount 1
tools/call
{
  "name": "era__finance__capture_payment",
  "arguments": {
    "paymentId": "PAYMENT_ID",
    "amount": 1
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 payment_not_found, charge_not_available, capture_exceeds_authorized, payment_app_not_connected, payment_credentials_invalid · 429 slow down

finance.configure_payment_app

Configure a payment app — Enables, orders and selects test mode for a connected payment app.

Needs finance:manage · changes data

POSThttps://app.getera.io/api/v1/payment_provider_configs/actions/configure_payment_app
API key or OAuth token · finance:manage
  • Canonical address: POST /api/v1/actions/finance.configure_payment_app

Input

FieldTypeRequiredNotes
providerstringyesProvider · max 40
credentialIduuidyesConnected app · id of credential
enabledbooleanEnabled
testModebooleanTest mode
positionnumberOrder · min 0
displayNamestringCheckout label · max 120

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
unsupported_payment_appOnly a known online payment app can be configured.
payment_credential_mismatchThe connected app must belong to the selected provider.
payment_app_unavailableThe connected app must be active.

Events it publishes: finance.payment_method_changed

What it writes: payment_provider_config (upsert) — Saves checkout enablement, order and test mode

curl -X POST https://app.getera.io/api/v1/payment_provider_configs/actions/configure_payment_app \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"provider":"Provider","credentialId":"CREDENTIAL_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.payment_method_changed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.paymentProviderConfigs.configurePaymentApp({
  provider: "Provider",
  credentialId: "CREDENTIAL_ID"
})
era payment_provider_configs configure_payment_app \
  --provider "Provider" \
  --credential-id "CREDENTIAL_ID"
tools/call
{
  "name": "era__finance__configure_payment_app",
  "arguments": {
    "provider": "Provider",
    "credentialId": "CREDENTIAL_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 unsupported_payment_app, payment_credential_mismatch, payment_app_unavailable · 429 slow down

finance.create_agreement

Open an agreement — Records what somebody owes, in the workspace currency unless told otherwise.

Needs finance:write · changes data

POSThttps://app.getera.io/api/v1/agreements/actions/create_agreement
API key or OAuth token · finance:write
  • REST alias: POST /api/v1/agreements
  • Canonical address: POST /api/v1/actions/finance.create_agreement

Input

FieldTypeRequiredNotes
customerIduuidyesPerson · id of customer
kindstring (enum)Kind · Allowed values: contract, order, booking, subscription_term
descriptionstringWhat it is for · max 4000
notestringInternal note · max 4000
currencystringCurrency · Prefilled from the workspace default. Never typed from scratch. · max 3
totalAmountnumber (money)Subtotal · min 0
taxTotalAmountnumber (money)Tax · min 0
linesobject / arrayLines
sourceModulestringCame from · max 40
sourceRecordIduuidSource record
affiliateIduuidReferred by · id of affiliate

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
customer_not_foundThe person must already exist as a customer.
customer_archivedAn archived person cannot take on an agreement.
invalid_currencyThe currency must be an ISO-4217 code.
affiliate_not_activeThe referring affiliate must be active.
agreement_total_not_positiveAn agreement must be worth more than zero.

Events it publishes: finance.agreement_created

What it writes: agreement (insert) — Opens the agreement; agreement_line (insert) — Writes its lines

curl -X POST https://app.getera.io/api/v1/agreements/actions/create_agreement \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"customerId":"CUSTOMER_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.agreement_created"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.agreements.create({
  customerId: "CUSTOMER_ID"
})
era agreements create \
  --customer-id "CUSTOMER_ID"
tools/call
{
  "name": "era__finance__create_agreement",
  "arguments": {
    "customerId": "CUSTOMER_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 customer_not_found, customer_archived, invalid_currency, affiliate_not_active, agreement_total_not_positive · 429 slow down

finance.create_expense

Record an expense — Money going out, against a category, awaiting approval.

Needs finance:write · changes data

POSThttps://app.getera.io/api/v1/expenses/actions/create_expense
API key or OAuth token · finance:write
  • REST alias: POST /api/v1/expenses
  • Canonical address: POST /api/v1/actions/finance.create_expense

Input

FieldTypeRequiredNotes
categoryIduuidyesCategory · id of expense_category
titlestringyesWhat it was · max 255
descriptionstringDescription · max 4000
amountnumber (money)yesAmount
currencystringCurrency · max 3
expenseDatedateyesDate
methodstring (enum)Paid by · Allowed values: transfer, card, cash, other
vendorNamestringSupplier · max 255
vendorContactstringSupplier contact · max 255
receiptFileIduuidReceipt · id of file
isRecurringbooleanRecurring
recurringPeriodstring (enum)Every · Allowed values: weekly, monthly, quarterly, yearly
nextOccurrencedateNext due
recurringUntildateRepeat until

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
expense_category_not_availableThe category must exist and be active.
invalid_currencyThe currency must be an ISO-4217 code.
expense_amount_negativeAn expense is not negative.
expense_amount_over_capThe workspace expense limit applies.
expense_date_in_futureAn expense date cannot be in the future.
expense_recurring_period_requiredA recurring expense needs a period.

Events it publishes: finance.expense_created

What it writes: expense (insert) — Records the expense

curl -X POST https://app.getera.io/api/v1/expenses/actions/create_expense \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"categoryId":"EXPENSE_CATEGORY_ID","title":"What it was","amount":1,"expenseDate":"2026-11-02"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.expense_created"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.expenses.create({
  categoryId: "EXPENSE_CATEGORY_ID",
  title: "What it was",
  amount: 1,
  expenseDate: "2026-11-02"
})
era expenses create \
  --category-id "EXPENSE_CATEGORY_ID" \
  --title "What it was" \
  --amount 1 \
  --expense-date "2026-11-02"
tools/call
{
  "name": "era__finance__create_expense",
  "arguments": {
    "categoryId": "EXPENSE_CATEGORY_ID",
    "title": "What it was",
    "amount": 1,
    "expenseDate": "2026-11-02"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 expense_category_not_available, invalid_currency, expense_amount_negative, expense_amount_over_cap, expense_date_in_future, expense_recurring_period_required · 429 slow down

finance.deactivate_expense_category

Deactivate an expense category — Stops new expenses landing on it. Existing ones keep theirs.

Needs finance:manage · changes data

POSThttps://app.getera.io/api/v1/expense_categories/{id}/actions/deactivate_expense_category
API key or OAuth token · finance:manage
  • Canonical address: POST /api/v1/actions/finance.deactivate_expense_category

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Category · id of expense_category

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
expense_category_not_foundThe category must exist.

Events it publishes: finance.expense_category_changed

What it writes: expense_category (update) — Clears is_active

curl -X POST https://app.getera.io/api/v1/expense_categories/EXPENSE_CATEGORY_ID/actions/deactivate_expense_category \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.expense_category_changed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.expenseCategories.deactivateExpenseCategory("EXPENSE_CATEGORY_ID")
era expense_categories deactivate_expense_category EXPENSE_CATEGORY_ID
tools/call
{
  "name": "era__finance__deactivate_expense_category",
  "arguments": {
    "id": "EXPENSE_CATEGORY_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 expense_category_not_found · 429 slow down

finance.mark_expense_paid

Mark an expense paid — Stamps the day the money left. The date is what "paid" means here.

Needs finance:write · changes data

POSThttps://app.getera.io/api/v1/expenses/{id}/actions/mark_expense_paid
API key or OAuth token · finance:write
  • Canonical address: POST /api/v1/actions/finance.mark_expense_paid

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Expense · id of expense
paymentDatedatePaid on

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
expense_not_foundThe expense must exist.
expense_not_approvedAn expense is approved before it is paid.
expense_paid_without_dateMarking an expense paid needs the date.

Events it publishes: finance.expense_paid

What it writes: expense (update) — Stamps the payment date

curl -X POST https://app.getera.io/api/v1/expenses/EXPENSE_ID/actions/mark_expense_paid \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"paymentDate":"2026-11-02"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.expense_paid"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.expenses.markExpensePaid("EXPENSE_ID", {
  paymentDate: "2026-11-02"
})
era expenses mark_expense_paid EXPENSE_ID \
  --payment-date "2026-11-02"
tools/call
{
  "name": "era__finance__mark_expense_paid",
  "arguments": {
    "id": "EXPENSE_ID",
    "paymentDate": "2026-11-02"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 expense_not_found, expense_not_approved, expense_paid_without_date · 429 slow down

finance.open_draft_for_source

Draft an agreement for a record — Machine-only: the agreement a won lead, a booking, a package, an order or a subscription period implies.

Needs finance:write · changes data

Run by Era itself (machine-only). Listed so the reference is complete; an API key or a token is refused (403 machine_only).
POSThttps://app.getera.io/api/v1/agreements/actions/open_draft_for_source
API key or OAuth token · finance:write
  • Canonical address: POST /api/v1/actions/finance.open_draft_for_source

Input

FieldTypeRequiredNotes
sourceModulestringyesCame from · max 40
sourceRecordIduuidyesSource record
customerIduuidyesPerson · id of customer
kindstring (enum)Kind · Allowed values: contract, order, booking, subscription_term
totalAmountnumber (money)Total · min 0
currencystringCurrency · max 3
descriptionstringWhat it is for · max 4000
activatebooleanOpen as active
linesobject / arrayWhat was sold
taxTotalAmountnumber (money)Tax on top · min 0
discountsobject / arrayDiscounts applied at the source
affiliateIduuidCame through affiliate · id of affiliate
giftCardPaymentsobject / arrayPaid by gift cards

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
agreement_exists_for_sourceOne open agreement per source record.
customer_not_foundThe person must exist.
customer_archivedAn archived person takes on nothing.
invalid_currencyThe currency must be an ISO-4217 code.
payment_amount_not_positiveA gift card collection is more than zero.
allocation_exceeds_targetGift cards cannot pay more than the agreement is worth.

Events it publishes: finance.agreement_created, finance.payment_recorded

What it writes: agreement (insert) — Drafts the agreement; agreement (update) — Totals the discounts it came with; agreement_line (insert) — Writes what was sold, with its frozen tax, when the source says; discount_line (insert) — Writes the promotions and coupons the source applied; payment (insert) — Records what gift cards already paid, as collections; payment_event (insert) — The gift card charge; payment_allocation (insert) — Allocates it to the agreement

Errors: 401 no or expired credential · 403 the credential does not hold finance:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 agreement_exists_for_source, customer_not_found, customer_archived, invalid_currency, payment_amount_not_positive, allocation_exceeds_target · 429 slow down

finance.pay_site_agreement

Pay by link (site) — A visitor pays what a booking deposit, ticket, package or open instalment still owes, by its payment link, through a connected payment app or the test method.

Needs finance:write · reaches outside (sends, publishes or charges) · Also callable from a published site (visitor)

A website action: your site calls it through the site API (/api/site/v1/{siteId}, e.g. with @getera/storefront). With an API key or a token it is refused (403 site_only).
POSThttps://app.getera.io/api/v1/installments/actions/pay_site_agreement
API key or OAuth token · finance:write
  • Canonical address: POST /api/v1/actions/finance.pay_site_agreement

Input

FieldTypeRequiredNotes
tokenuuidyesPayment link
providerstringPayment method · Not needed when a gift card on the checkout pays it all. · max 40
returnUrlstringReturn URL · max 2000
cancelUrlstringCancel URL · max 2000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
pay_link_invalidThe link names an open agreement of this business.
payment_method_unavailableThe method is a connected payment app, or the test method outside production.
gift_card_hold_expiredA gift card put on the checkout is still held (fifteen minutes) when it pays.
nothing_dueSomething is still owed on the link.

Events it publishes: finance.payment_provider_event, finance.payment_recorded, finance.installments_scheduled

What it writes: installment (insert) — The whole agreement as one instalment due today, when it has none; payment (insert) — Opens the transaction; payment_event (insert) — Records the provider’s answer; payment_allocation (insert) — Puts what was charged, and what a gift card paid, on the instalment; installment (update) — Closes the instalment when card and method cover it

Errors: 401 no or expired credential · 403 the credential does not hold finance:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 pay_link_invalid, payment_method_unavailable, gift_card_hold_expired, nothing_due · 429 slow down

finance.pay_site_order

Pay for an order (site) — A visitor pays what their website order still owes, through a connected payment app or the test method.

Needs finance:write · reaches outside (sends, publishes or charges) · Also callable from a published site (visitor)

A website action: your site calls it through the site API (/api/site/v1/{siteId}, e.g. with @getera/storefront). With an API key or a token it is refused (403 site_only).
POSThttps://app.getera.io/api/v1/installments/actions/pay_site_order
API key or OAuth token · finance:write
  • Canonical address: POST /api/v1/actions/finance.pay_site_order

Input

FieldTypeRequiredNotes
tokenuuidyesBasket
providerstringPayment method · Not needed when a gift card on the checkout pays it all. · max 40
returnUrlstringReturn URL · max 2000
cancelUrlstringCancel URL · max 2000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
order_not_foundThe basket token names a placed order.
payment_method_unavailableThe method is a connected payment app, or the test method outside production.
gift_card_hold_expiredA gift card put on the checkout is still held (fifteen minutes) when it pays.
payment_not_readyFinance has opened the order’s agreement.

Events it publishes: finance.payment_provider_event, finance.payment_recorded, finance.installments_scheduled

What it writes: installment (insert) — The whole order as one instalment due today, when it has none; payment (insert) — Opens the transaction; payment_event (insert) — Records the provider’s answer; payment_allocation (insert) — Puts what was charged, and what a gift card paid, on the instalment; installment (update) — Closes the instalment when card and method cover it

Errors: 401 no or expired credential · 403 the credential does not hold finance:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 order_not_found, payment_method_unavailable, gift_card_hold_expired, payment_not_ready · 429 slow down

finance.raise_installment_notices

Raise instalment reminders — Machine-only (the daily finance routine): flags every pending instalment falling due within the reminder window, and every one past due, once each.

Needs finance:write · changes data

Run by Era itself (machine-only). Listed so the reference is complete; an API key or a token is refused (403 machine_only).
POSThttps://app.getera.io/api/v1/installments/actions/raise_installment_notices
API key or OAuth token · finance:write
  • Canonical address: POST /api/v1/actions/finance.raise_installment_notices

Input

FieldTypeRequiredNotes
asOfdateAs of

Events it publishes: finance.installment_due, finance.installment_overdue

What it writes: installment (update) — Records that the customer was told (due soon / overdue)

Errors: 401 no or expired credential · 403 the credential does not hold finance:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 429 slow down

finance.recalculate_commission

Recalculate the commission — K-09: brings a pending commission in line with what has been collected (calculate_commission_amount_v3). Runs on every collection and refund; the button runs it by hand.

Needs finance:write · changes data

POSThttps://app.getera.io/api/v1/commissions/actions/recalculate_commission
API key or OAuth token · finance:write
  • Canonical address: POST /api/v1/actions/finance.recalculate_commission

Input

FieldTypeRequiredNotes
agreementIduuidAgreement · id of agreement
paymentIduuidCollection · id of payment

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
commission_not_foundThere must be a commission to recalculate.

Events it publishes: finance.commission_recalculated

What it writes: commission (update) — Writes the amount the collections support

curl -X POST https://app.getera.io/api/v1/commissions/actions/recalculate_commission \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"agreementId":"AGREEMENT_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.commission_recalculated"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.commissions.recalculateCommission({
  agreementId: "AGREEMENT_ID"
})
era commissions recalculate_commission \
  --agreement-id "AGREEMENT_ID"
tools/call
{
  "name": "era__finance__recalculate_commission",
  "arguments": {
    "agreementId": "AGREEMENT_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 commission_not_found · 429 slow down

finance.record_commission_payout

Record a commission payout — One transfer to an affiliate that pays a set of pending commissions.

Needs finance:approve · reaches outside (sends, publishes or charges) · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/commission_payouts/actions/record_commission_payout
API key or OAuth token · finance:approve
  • Canonical address: POST /api/v1/actions/finance.record_commission_payout

Input

FieldTypeRequiredNotes
affiliateIduuidyesAffiliate · id of affiliate
commissionIdsobject / arrayyesCommissions
paidOndatePaid on
methodstring (enum)Method · Allowed values: transfer, cash, card, other
referencestringTransfer reference · max 255
notestringNote · max 2000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
no_commissions_selectedA payout pays at least one commission.
affiliate_not_foundThe affiliate must exist.
commission_not_payableEach commission is pending, unpaid, this affiliate’s, one currency.

Events it publishes: finance.commission_payout_recorded, finance.commission_status_changed

What it writes: commission_payout (insert) — Records the transfer; commission (update) — Marks the commissions paid by it

curl -X POST https://app.getera.io/api/v1/commission_payouts/actions/record_commission_payout \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"affiliateId":"AFFILIATE_ID","commissionIds":{}}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.commission_payout_recorded",
    "finance.commission_status_changed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.commissionPayouts.recordCommissionPayout({
  affiliateId: "AFFILIATE_ID",
  commissionIds: {}
})
era commission_payouts record_commission_payout --data '{"affiliateId":"AFFILIATE_ID","commissionIds":{}}'
tools/call
{
  "name": "era__finance__record_commission_payout",
  "arguments": {
    "affiliateId": "AFFILIATE_ID",
    "commissionIds": {}
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:approve · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 no_commissions_selected, affiliate_not_found, commission_not_payable · 429 slow down

finance.record_gift_card_payment

Record a gift card collection — Machine-only (gift_cards.redeemed): the part of a sale a gift card paid, as a collection against its agreement.

Needs finance:write · changes data

Run by Era itself (machine-only). Listed so the reference is complete; an API key or a token is refused (403 machine_only).
POSThttps://app.getera.io/api/v1/payments/actions/record_gift_card_payment
API key or OAuth token · finance:write
  • Canonical address: POST /api/v1/actions/finance.record_gift_card_payment

Input

FieldTypeRequiredNotes
sourceModulestringyesCame from · max 40
sourceRecordIduuidyesSource record
giftCardIduuidyesGift card
giftCardEventIduuidyesRedemption
amountnumber (money)yesAmount
currencystringyesCurrency · max 3
codeTailstringLast characters of the code · max 8

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
no_agreement_for_sourceThe sale must already be in finance.
allocation_currency_mismatchThe card pays only in the agreement currency.
payment_amount_not_positiveA collection is more than zero.
allocation_exceeds_targetA card cannot pay more than is left to collect.

Events it publishes: finance.payment_recorded

What it writes: payment (insert) — The gift card collection; payment_event (insert) — Its charge; payment_allocation (insert) — Allocated to the agreement

Errors: 401 no or expired credential · 403 the credential does not hold finance:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 no_agreement_for_source, allocation_currency_mismatch, payment_amount_not_positive, allocation_exceeds_target · 429 slow down

finance.record_payment

Record a collection — Money arrived: writes the movement, the event and what it paid off.

Needs finance:write · changes data

POSThttps://app.getera.io/api/v1/installments/{id}/actions/record_payment
API key or OAuth token · finance:write
  • Canonical address: POST /api/v1/actions/finance.record_payment

Input

FieldTypeRequiredNotes
installmentIduuidyesFilled from the path {id} · Instalment · id of installment
amountnumber (money)yesAmount
methodstring (enum)Method · Allowed values: transfer, card, cash, other
methodNamestringMethod detail · max 255
referencestringProvider reference · max 512
idempotencyKeystringIdempotency key · Scoped to this workspace and provider — never global. · max 512
occurredAtdate-timeWhen it happened
notestringNote · max 2000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
installment_not_foundThe instalment must exist.
installment_cancelledNothing is collected against a cancelled instalment.
payment_amount_not_positiveA collection is more than zero.
duplicate_payment_idempotency_keyThe same collection is never recorded twice.
overpaymentAllocations may not exceed the instalment.

Events it publishes: finance.payment_recorded

What it writes: payment (insert) — Records the movement; payment_event (insert) — Records the charge event; payment_allocation (insert) — Says what it paid off; installment (update) — Closes the instalment when it is covered

curl -X POST https://app.getera.io/api/v1/installments/INSTALLMENT_ID/actions/record_payment \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"amount":1}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.payment_recorded"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.installments.recordPayment("INSTALLMENT_ID", {
  amount: 1
})
era installments record_payment INSTALLMENT_ID \
  --amount 1
tools/call
{
  "name": "era__finance__record_payment",
  "arguments": {
    "installmentId": "INSTALLMENT_ID",
    "amount": 1
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 installment_not_found, installment_cancelled, payment_amount_not_positive, duplicate_payment_idempotency_key, overpayment · 429 slow down

finance.record_provider_webhook

Record a payment webhook — Admits one verified provider event once and applies it to the canonical payment.

Needs finance:write · reaches outside (sends, publishes or charges)

POSThttps://app.getera.io/api/v1/payments/{id}/actions/record_provider_webhook
API key or OAuth token · finance:write
  • Canonical address: POST /api/v1/actions/finance.record_provider_webhook

Input

FieldTypeRequiredNotes
providerstringyesProvider · max 40
externalIdstringyesProvider event · max 512
paymentIduuidFilled from the path {id} · Payment · id of payment
pspReferencestringProvider reference · max 512
typestringyesEvent type · max 80
amountnumber (money)yesAmount · min 0
currencystringyesCurrency · max 3
messagestringMessage · max 2000
occurredAtdate-timeyesOccurred at
payloadDigeststringyesPayload digest · max 128

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
unknown_payment_providerThe provider must be known.
webhook_payment_not_foundThe event must name a payment in this workspace.
webhook_provider_mismatchThe event and payment providers must match.

Events it publishes: finance.payment_provider_event, finance.payment_recorded

What it writes: finance_provider_event (insert) — Admits the external event once; payment (update) — Moves the amounts and stores what the provider now allows; payment_event (insert) — Records the provider’s answer; payment_allocation (insert) — Puts charged money on the instalment; installment (update) — Closes the instalment once covered

curl -X POST https://app.getera.io/api/v1/payments/PAYMENT_ID/actions/record_provider_webhook \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"provider":"Provider","externalId":"Provider event","type":"Event type","amount":1,"currency":"Currency","occurredAt":"2026-11-02T09:00:00Z"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.payment_provider_event",
    "finance.payment_recorded"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.payments.recordProviderWebhook("PAYMENT_ID", {
  provider: "Provider",
  externalId: "Provider event",
  type: "Event type",
  amount: 1,
  currency: "Currency",
  occurredAt: "2026-11-02T09:00:00Z"
})
era payments record_provider_webhook PAYMENT_ID \
  --provider "Provider" \
  --external-id "Provider event" \
  --type "Event type" \
  --amount 1 \
  --currency "Currency" \
  --occurred-at "2026-11-02T09:00:00Z"
tools/call
{
  "name": "era__finance__record_provider_webhook",
  "arguments": {
    "paymentId": "PAYMENT_ID",
    "provider": "Provider",
    "externalId": "Provider event",
    "type": "Event type",
    "amount": 1,
    "currency": "Currency",
    "occurredAt": "2026-11-02T09:00:00Z"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 unknown_payment_provider, webhook_payment_not_found, webhook_provider_mismatch · 429 slow down

finance.record_source_discounts

Record the shop’s discounts — Machine-only (retail.order_discounts_applied): mirrors the promotions and coupons on an order as discount lines on its agreement.

Needs finance:write · removes or cannot be undone

Run by Era itself (machine-only). Listed so the reference is complete; an API key or a token is refused (403 machine_only).
POSThttps://app.getera.io/api/v1/discount_lines/actions/record_source_discounts
API key or OAuth token · finance:write
  • Canonical address: POST /api/v1/actions/finance.record_source_discounts

Input

FieldTypeRequiredNotes
sourceModulestringyesCame from · max 40
sourceRecordIduuidyesSource record
discountsobject / arrayDiscounts

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
no_agreement_for_sourceThe order must be in finance already.

What it writes: discount_line (delete) — Drops what the shop wrote before; discount_line (insert) — Writes the current set; agreement (update) — Totals the discounts

Errors: 401 no or expired credential · 403 the credential does not hold finance:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 no_agreement_for_source · 429 slow down

finance.refund_payment

Refund a collection — Sends money back. A refund is an event on the collection, not a record of its own.

Needs finance:manage · removes or cannot be undone · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/payments/{id}/actions/refund_payment
API key or OAuth token · finance:manage
  • Canonical address: POST /api/v1/actions/finance.refund_payment

Input

FieldTypeRequiredNotes
paymentIduuidyesFilled from the path {id} · Collection · id of payment
amountnumber (money)yesAmount
reasonstringyesReason · max 2000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
payment_not_foundThe collection must exist.
refund_not_availableThe provider must still allow a refund on this collection.
payment_app_not_connectedThe payment app must still be enabled.
payment_credentials_invalidThe payment app credentials must open from the vault.
refund_exceeds_chargedA refund cannot exceed what was charged.

Events it publishes: finance.payment_refunded

What it writes: payment_event (insert) — Records the refund event; payment (update) — Raises the refunded amount; payment_allocation (insert) — Writes the negative allocation; installment (update) — Reopens an instalment it no longer covers

curl -X POST https://app.getera.io/api/v1/payments/PAYMENT_ID/actions/refund_payment \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"amount":1,"reason":"Reason"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.payment_refunded"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.payments.refundPayment("PAYMENT_ID", {
  amount: 1,
  reason: "Reason"
})
era payments refund_payment PAYMENT_ID \
  --amount 1 \
  --reason "Reason"
tools/call
{
  "name": "era__finance__refund_payment",
  "arguments": {
    "paymentId": "PAYMENT_ID",
    "amount": 1,
    "reason": "Reason"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 payment_not_found, refund_not_available, payment_app_not_connected, payment_credentials_invalid, refund_exceeds_charged · 429 slow down

finance.reject_expense

Reject an expense — Turns an expense down, with the reason the source never required.

Needs finance:approve · changes data

POSThttps://app.getera.io/api/v1/expenses/{id}/actions/reject_expense
API key or OAuth token · finance:approve
  • Canonical address: POST /api/v1/actions/finance.reject_expense

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Expense · id of expense
reasonstringyesReason · max 2000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
expense_not_foundThe expense must exist.
expense_already_decidedThe expense must still be pending.
expense_rejection_reason_requiredA rejection needs a reason.

Events it publishes: finance.expense_rejected

What it writes: expense (update) — Records the rejection

curl -X POST https://app.getera.io/api/v1/expenses/EXPENSE_ID/actions/reject_expense \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"reason":"Reason"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.expense_rejected"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.expenses.rejectExpense("EXPENSE_ID", {
  reason: "Reason"
})
era expenses reject_expense EXPENSE_ID \
  --reason "Reason"
tools/call
{
  "name": "era__finance__reject_expense",
  "arguments": {
    "id": "EXPENSE_ID",
    "reason": "Reason"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:approve · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 expense_not_found, expense_already_decided, expense_rejection_reason_required · 429 slow down

finance.reject_refund

Reject a refund — Turns a request down, with the reason on the collection’s trail.

Needs finance:approve · removes or cannot be undone

POSThttps://app.getera.io/api/v1/payment_events/{id}/actions/reject_refund
API key or OAuth token · finance:approve
  • Canonical address: POST /api/v1/actions/finance.reject_refund

Input

FieldTypeRequiredNotes
requestIduuidyesFilled from the path {id} · Request · id of payment_event
reasonstringyesReason · max 2000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
refund_request_not_foundThe request must exist.
refund_request_resolvedA request is approved or rejected once.

Events it publishes: finance.refund_rejected

What it writes: payment_event (insert) — Records the refund step; payment (update) — Moves the pending and refunded amounts

curl -X POST https://app.getera.io/api/v1/payment_events/PAYMENT_EVENT_ID/actions/reject_refund \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"reason":"Reason"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.refund_rejected"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.paymentEvents.rejectRefund("PAYMENT_EVENT_ID", {
  reason: "Reason"
})
era payment_events reject_refund PAYMENT_EVENT_ID \
  --reason "Reason"
tools/call
{
  "name": "era__finance__reject_refund",
  "arguments": {
    "requestId": "PAYMENT_EVENT_ID",
    "reason": "Reason"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:approve · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 refund_request_not_found, refund_request_resolved · 429 slow down

finance.remove_discount

Remove a discount — Takes a discount off and puts the amount back on the agreement.

Needs finance:manage · removes or cannot be undone · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/discount_lines/{id}/actions/remove_discount
API key or OAuth token · finance:manage
  • Canonical address: POST /api/v1/actions/finance.remove_discount

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Discount · id of discount_line

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
discount_not_foundThe discount must exist.
agreement_not_foundIts agreement must exist.
agreement_closedA closed agreement keeps its discounts.

Events it publishes: finance.discount_removed

What it writes: discount_line (delete) — Removes the discount; agreement (update) — Restores the total

curl -X POST https://app.getera.io/api/v1/discount_lines/DISCOUNT_LINE_ID/actions/remove_discount \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.discount_removed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.discountLines.removeDiscount("DISCOUNT_LINE_ID")
era discount_lines remove_discount DISCOUNT_LINE_ID
tools/call
{
  "name": "era__finance__remove_discount",
  "arguments": {
    "id": "DISCOUNT_LINE_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 discount_not_found, agreement_not_found, agreement_closed · 429 slow down

finance.request_refund

Request a refund — Asks for money to go back. Nothing moves until somebody who can approve says yes.

Needs finance:write · removes or cannot be undone

POSThttps://app.getera.io/api/v1/payments/{id}/actions/request_refund
API key or OAuth token · finance:write
  • Canonical address: POST /api/v1/actions/finance.request_refund

Input

FieldTypeRequiredNotes
paymentIduuidyesFilled from the path {id} · Collection · id of payment
amountnumber (money)yesAmount
reasonstringyesReason · max 2000
idempotencyKeystringIdempotency key · max 512

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
payment_not_foundThe collection must exist.
refund_not_availableThe provider must still allow a refund.
refund_exceeds_chargedA refund cannot exceed what is still refundable.

Events it publishes: finance.refund_requested

What it writes: payment_event (insert) — Records the refund step; payment (update) — Moves the pending and refunded amounts

curl -X POST https://app.getera.io/api/v1/payments/PAYMENT_ID/actions/request_refund \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"amount":1,"reason":"Reason"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.refund_requested"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.payments.requestRefund("PAYMENT_ID", {
  amount: 1,
  reason: "Reason"
})
era payments request_refund PAYMENT_ID \
  --amount 1 \
  --reason "Reason"
tools/call
{
  "name": "era__finance__request_refund",
  "arguments": {
    "paymentId": "PAYMENT_ID",
    "amount": 1,
    "reason": "Reason"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 payment_not_found, refund_not_available, refund_exceeds_charged · 429 slow down

finance.request_refunds_for_source

Answer a cancellation — Machine-only: cancels what was not collected and raises refund requests for what was, when a booking, lead or order is cancelled.

Needs finance:write · changes data

Run by Era itself (machine-only). Listed so the reference is complete; an API key or a token is refused (403 machine_only).
POSThttps://app.getera.io/api/v1/payment_events/actions/request_refunds_for_source
API key or OAuth token · finance:write
  • Canonical address: POST /api/v1/actions/finance.request_refunds_for_source

Input

FieldTypeRequiredNotes
sourceModulestringyesCame from · max 40
sourceRecordIduuidyesSource record
reasonstringReason · max 2000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
no_agreement_for_sourceSomething in finance must hang off the record.

Events it publishes: finance.agreement_cancelled, finance.refund_requested

What it writes: agreement (update) — Cancels an uncollected agreement; installment (update) — Cancels its pending instalments; payment_event (insert) — Records the refund step; payment (update) — Moves the pending and refunded amounts

Errors: 401 no or expired credential · 403 the credential does not hold finance:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 no_agreement_for_source · 429 slow down

finance.return_gift_card_payment

Return a gift card collection — Machine-only (gift_cards.refunded): money that went back onto the card is taken off its collection, so it is owed again.

Needs finance:write · changes data

Run by Era itself (machine-only). Listed so the reference is complete; an API key or a token is refused (403 machine_only).
POSThttps://app.getera.io/api/v1/payment_events/actions/return_gift_card_payment
API key or OAuth token · finance:write
  • Canonical address: POST /api/v1/actions/finance.return_gift_card_payment

Input

FieldTypeRequiredNotes
sourceModulestringyesCame from · max 40
sourceRecordIduuidyesSource record
giftCardIduuidyesGift card
amountnumber (money)yesAmount
idempotencyKeystringIdempotency key · max 512

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
no_agreement_for_sourceThe sale must be in finance.
gift_card_payment_not_foundThat card must have paid against this sale.
refund_exceeds_chargedNo more goes back than the card paid.

Events it publishes: finance.payment_refunded

What it writes: payment_event (insert) — The return, as a refund; payment (update) — Raises the refunded amount; payment_allocation (insert) — The negative allocation that reopens the balance; installment (update) — Reopens the instalment a site-checkout card had closed

Errors: 401 no or expired credential · 403 the credential does not hold finance:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 no_agreement_for_source, gift_card_payment_not_found, refund_exceeds_charged · 429 slow down

finance.run_recurring_expenses

Record recurring expenses — Copies every recurring expense that has come due into a new expense awaiting approval.

Needs finance:write · changes data

POSThttps://app.getera.io/api/v1/expenses/actions/run_recurring_expenses
API key or OAuth token · finance:write
  • Canonical address: POST /api/v1/actions/finance.run_recurring_expenses

Input

FieldTypeRequiredNotes
asOfdateAs of

Events it publishes: finance.expense_created

What it writes: expense (insert) — One pending copy per period that came due; expense (update) — Moves the template’s next occurrence on

curl -X POST https://app.getera.io/api/v1/expenses/actions/run_recurring_expenses \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"asOf":"2026-11-02"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.expense_created"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.expenses.runRecurringExpenses({
  asOf: "2026-11-02"
})
era expenses run_recurring_expenses \
  --as-of "2026-11-02"
tools/call
{
  "name": "era__finance__run_recurring_expenses",
  "arguments": {
    "asOf": "2026-11-02"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 429 slow down

finance.save_expense_category

Save an expense category — Adds or renames an expense category for this workspace.

Needs finance:manage · changes data

POSThttps://app.getera.io/api/v1/expense_categories/{id}/actions/save_expense_category
API key or OAuth token · finance:manage
  • REST alias: PATCH /api/v1/expense_categories/{id}
  • Canonical address: POST /api/v1/actions/finance.save_expense_category

Input

FieldTypeRequiredNotes
iduuidFilled from the path {id} · Category · id of expense_category
namestringyesName · max 255
descriptionstringDescription · max 2000
colourstringColour · max 7

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
expense_category_name_takenCategory names are unique per workspace.
expense_category_not_foundThe category being edited must exist.

Events it publishes: finance.expense_category_changed

What it writes: expense_category (upsert) — Saves the category

curl -X POST https://app.getera.io/api/v1/expense_categories/EXPENSE_CATEGORY_ID/actions/save_expense_category \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"name":"Name"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.expense_category_changed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.expenseCategories.saveExpenseCategory("EXPENSE_CATEGORY_ID", {
  name: "Name"
})
era expense_categories save_expense_category EXPENSE_CATEGORY_ID \
  --name "Name"
tools/call
{
  "name": "era__finance__save_expense_category",
  "arguments": {
    "id": "EXPENSE_CATEGORY_ID",
    "name": "Name"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 expense_category_name_taken, expense_category_not_found · 429 slow down

finance.schedule_installments

Schedule instalments — Writes the whole due-date plan in one transaction.

Needs finance:write · changes data

POSThttps://app.getera.io/api/v1/installments/actions/schedule_installments
API key or OAuth token · finance:write
  • Canonical address: POST /api/v1/actions/finance.schedule_installments

Input

FieldTypeRequiredNotes
agreementIduuidyesAgreement · id of agreement
installmentsobject / arrayyesInstalments

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
agreement_not_foundThe agreement must exist.
agreement_closedA closed agreement is not rescheduled.
no_installmentsA schedule needs at least one instalment.
invalid_installmentsEach instalment needs a due date and an amount.
duplicate_installment_due_dateTwo instalments may not fall on the same day.
installments_do_not_sum_to_totalThe instalments must add up to the agreement.

Events it publishes: finance.installments_scheduled

What it writes: installment (insert) — Writes the schedule

curl -X POST https://app.getera.io/api/v1/installments/actions/schedule_installments \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"agreementId":"AGREEMENT_ID","installments":{}}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.installments_scheduled"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.installments.scheduleInstallments({
  agreementId: "AGREEMENT_ID",
  installments: {}
})
era installments schedule_installments --data '{"agreementId":"AGREEMENT_ID","installments":{}}'
tools/call
{
  "name": "era__finance__schedule_installments",
  "arguments": {
    "agreementId": "AGREEMENT_ID",
    "installments": {}
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 agreement_not_found, agreement_closed, no_installments, invalid_installments, duplicate_installment_due_date, installments_do_not_sum_to_total · 429 slow down

finance.set_commission_status

Move a commission — Follows the source transition table; a cancelled commission is final.

Needs finance:manage · changes data

POSThttps://app.getera.io/api/v1/commissions/{id}/actions/set_commission_status
API key or OAuth token · finance:manage
  • Canonical address: POST /api/v1/actions/finance.set_commission_status

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Commission · id of commission
statusstring (enum)yesStatus · Allowed values: completed, cancelled
paidAtdatePaid on

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
commission_not_foundThe commission must exist.
commission_cancelled_is_finalA cancelled commission is not reopened.
invalid_commission_transitionThe transition must be a permitted one.

Events it publishes: finance.commission_status_changed

What it writes: commission (update) — Moves the commission

curl -X POST https://app.getera.io/api/v1/commissions/COMMISSION_ID/actions/set_commission_status \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"status":"completed"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.commission_status_changed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.commissions.setCommissionStatus("COMMISSION_ID", {
  status: "completed"
})
era commissions set_commission_status COMMISSION_ID \
  --status "completed"
tools/call
{
  "name": "era__finance__set_commission_status",
  "arguments": {
    "id": "COMMISSION_ID",
    "status": "completed"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 commission_not_found, commission_cancelled_is_final, invalid_commission_transition · 429 slow down

finance.stop_recurring_expense

Stop repeating — The expense stays; it is no longer copied each period.

Needs finance:write · changes data

POSThttps://app.getera.io/api/v1/expenses/{id}/actions/stop_recurring_expense
API key or OAuth token · finance:write
  • Canonical address: POST /api/v1/actions/finance.stop_recurring_expense

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Expense · id of expense

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
expense_not_foundThe expense must exist.
expense_not_recurringOnly a recurring expense can stop.

Events it publishes: finance.expense_updated

What it writes: expense (update) — Clears the recurrence

curl -X POST https://app.getera.io/api/v1/expenses/EXPENSE_ID/actions/stop_recurring_expense \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.expense_updated"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.expenses.stopRecurringExpense("EXPENSE_ID")
era expenses stop_recurring_expense EXPENSE_ID
tools/call
{
  "name": "era__finance__stop_recurring_expense",
  "arguments": {
    "id": "EXPENSE_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 expense_not_found, expense_not_recurring · 429 slow down

finance.take_payment

Take a payment — Authorizes or charges an instalment through a payment provider and records its answer.

Needs finance:write · reaches outside (sends, publishes or charges) · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/installments/{id}/actions/take_payment
API key or OAuth token · finance:write
  • Canonical address: POST /api/v1/actions/finance.take_payment

Input

FieldTypeRequiredNotes
installmentIduuidyesFilled from the path {id} · Instalment · id of installment
amountnumber (money)yesAmount
providerstringProvider · One of: manual, dummy, stripe, iyzico, paytr. · max 40
modestring (enum)Mode · Allowed values: charge, authorize
outcomestring (enum)Test outcome · Only the dummy gateway reads this. · Allowed values: success, failure, pending
methodstring (enum)Method · Allowed values: card, transfer, cash, other
idempotencyKeystringIdempotency key · max 255
notestringNote · max 2000
returnUrlstringReturn URL · max 2000
cancelUrlstringCancel URL · max 2000
customerEmailstringCustomer email · max 320

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
unknown_payment_providerThe provider must be installed.
installment_not_foundThe instalment must exist.
installment_cancelledNothing is taken against a cancelled instalment.
payment_amount_not_positiveA payment is more than zero.
overpaymentA payment does not exceed what is still owed.
duplicate_payment_idempotency_keyThe same key is never charged twice.
payment_app_not_connectedAn online provider must be an enabled connected app.
payment_credentials_invalidThe payment app credentials must open from the vault.

Events it publishes: finance.payment_provider_event, finance.payment_recorded

What it writes: payment (insert) — Opens the transaction; payment (update) — Moves the amounts and stores what the provider now allows; payment_event (insert) — Records the provider’s answer; payment_allocation (insert) — Puts charged money on the instalment; installment (update) — Closes the instalment once covered

curl -X POST https://app.getera.io/api/v1/installments/INSTALLMENT_ID/actions/take_payment \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"amount":1}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.payment_provider_event",
    "finance.payment_recorded"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.installments.takePayment("INSTALLMENT_ID", {
  amount: 1
})
era installments take_payment INSTALLMENT_ID \
  --amount 1
tools/call
{
  "name": "era__finance__take_payment",
  "arguments": {
    "installmentId": "INSTALLMENT_ID",
    "amount": 1
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 unknown_payment_provider, installment_not_found, installment_cancelled, payment_amount_not_positive, overpayment, duplicate_payment_idempotency_key, payment_app_not_connected, payment_credentials_invalid · 429 slow down

finance.update_agreement

Edit an agreement — Changes what the agreement says — never its currency.

Needs finance:write · changes data

POSThttps://app.getera.io/api/v1/agreements/{id}/actions/update_agreement
API key or OAuth token · finance:write
  • REST alias: PATCH /api/v1/agreements/{id}
  • Canonical address: POST /api/v1/actions/finance.update_agreement

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Agreement · id of agreement
statusstring (enum)Status · Allowed values: draft, active, fulfilled
descriptionstringWhat it is for · max 4000
notestringInternal note · max 4000
totalAmountnumber (money)Subtotal · min 0
taxTotalAmountnumber (money)Tax · min 0

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
agreement_not_foundThe agreement must exist.
agreement_closedA fulfilled or cancelled agreement is not edited.
agreement_has_scheduleThe total cannot move under instalments that are already scheduled.

Events it publishes: finance.agreement_updated

What it writes: agreement (update) — Updates the agreement

curl -X POST https://app.getera.io/api/v1/agreements/AGREEMENT_ID/actions/update_agreement \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"status":"draft"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.agreement_updated"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.agreements.updateAgreement("AGREEMENT_ID", {
  status: "draft"
})
era agreements update_agreement AGREEMENT_ID \
  --status "draft"
tools/call
{
  "name": "era__finance__update_agreement",
  "arguments": {
    "id": "AGREEMENT_ID",
    "status": "draft"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 agreement_not_found, agreement_closed, agreement_has_schedule · 429 slow down

finance.update_expense

Edit an expense — Corrects an expense that has not been approved yet.

Needs finance:write · changes data

POSThttps://app.getera.io/api/v1/expenses/{id}/actions/update_expense
API key or OAuth token · finance:write
  • REST alias: PATCH /api/v1/expenses/{id}
  • Canonical address: POST /api/v1/actions/finance.update_expense

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Expense · id of expense
titlestringWhat it was · max 255
descriptionstringDescription · max 4000
amountnumber (money)Amount · min 0
vendorNamestringSupplier · max 255
vendorContactstringSupplier contact · max 255
receiptFileIduuidReceipt · id of file
categoryIduuidCategory · id of expense_category
expenseDatedateDate
methodstring (enum)Paid by · Allowed values: transfer, card, cash, other

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
expense_not_foundThe expense must exist.
expense_already_decidedAn approved expense is not edited.
expense_category_not_availableA new category must exist and be in use.

Events it publishes: finance.expense_updated

What it writes: expense (update) — Updates the expense

curl -X POST https://app.getera.io/api/v1/expenses/EXPENSE_ID/actions/update_expense \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"title":"What it was"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.expense_updated"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.expenses.updateExpense("EXPENSE_ID", {
  title: "What it was"
})
era expenses update_expense EXPENSE_ID \
  --title "What it was"
tools/call
{
  "name": "era__finance__update_expense",
  "arguments": {
    "id": "EXPENSE_ID",
    "title": "What it was"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 expense_not_found, expense_already_decided, expense_category_not_available · 429 slow down

finance.update_installment

Edit an instalment — Moves a due date or changes an amount, never below what has been collected.

Needs finance:write · changes data

POSThttps://app.getera.io/api/v1/installments/{id}/actions/update_installment
API key or OAuth token · finance:write
  • REST alias: PATCH /api/v1/installments/{id}
  • Canonical address: POST /api/v1/actions/finance.update_installment

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Instalment · id of installment
dueDatedateDue date
amountnumber (money)Amount · min 0
notestringNote · max 2000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
installment_not_foundThe instalment must exist.
installment_cancelledA cancelled instalment is not edited.
installment_below_collectedAn instalment cannot be worth less than what has been collected against it.

Events it publishes: finance.installment_updated

What it writes: installment (update) — Updates the instalment

curl -X POST https://app.getera.io/api/v1/installments/INSTALLMENT_ID/actions/update_installment \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"dueDate":"2026-11-02"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.installment_updated"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.installments.updateInstallment("INSTALLMENT_ID", {
  dueDate: "2026-11-02"
})
era installments update_installment INSTALLMENT_ID \
  --due-date "2026-11-02"
tools/call
{
  "name": "era__finance__update_installment",
  "arguments": {
    "id": "INSTALLMENT_ID",
    "dueDate": "2026-11-02"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 installment_not_found, installment_cancelled, installment_below_collected · 429 slow down

finance.void_expense

Void an expense — Withdraws an unpaid expense. A paid one is corrected, not removed.

Needs finance:manage · removes or cannot be undone · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/expenses/{id}/actions/void_expense
API key or OAuth token · finance:manage
  • Canonical address: POST /api/v1/actions/finance.void_expense

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Expense · id of expense
reasonstringyesReason · max 2000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
expense_not_foundThe expense must exist.
paid_expense_not_voidableA paid expense is corrected, not voided.

Events it publishes: finance.expense_voided

What it writes: expense (update) — Records the withdrawal

curl -X POST https://app.getera.io/api/v1/expenses/EXPENSE_ID/actions/void_expense \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"reason":"Reason"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "finance.expense_voided"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.expenses.voidExpense("EXPENSE_ID", {
  reason: "Reason"
})
era expenses void_expense EXPENSE_ID \
  --reason "Reason"
tools/call
{
  "name": "era__finance__void_expense",
  "arguments": {
    "id": "EXPENSE_ID",
    "reason": "Reason"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold finance:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 expense_not_found, paid_expense_not_voidable · 429 slow down

Data models

AgreementLine

Table agreement_line · Resource agreement_lines

  • GET /api/v1/agreement_lines
  • GET /api/v1/agreement_lines/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
agreement_idstring (uuid) · uuidagreement
item_modulestring · textyes
item_record_idstring (uuid) · uuidyes
namestring · text
quantitystring (decimal) · numeric
currencystring · bpcharmax 3
unit_amountstring (int64) · int8
total_amountstring (int64) · int8
display_orderinteger · int4
created_atstring (date-time) · timestamptz
tax_snapshotboolean · bool
tax_class_idstring (uuid) · uuidyes
tax_class_namestring · textyes
tax_ratestring (decimal) · numeric
prices_entered_with_taxboolean · bool
discount_amountstring (int64) · int8
net_amountstring (int64) · int8yes
tax_amountstring (int64) · int8yes
gross_amountstring (int64) · int8yes

Primary key: id

Checks:

  • CHECK ((currency ~ '^[A-Z]{3}$'::text))
  • CHECK ((quantity > (0)::numeric))
  • CHECK (((discount_amount >= 0) AND (COALESCE(net_amount, (0)::bigint) >= 0) AND (COALESCE(tax_amount, (0)::bigint) >= 0) AND (COALESCE(gross_amount, (0)::bigint) >= 0) AND ((NOT tax_snapshot) OR ((net_amount IS NOT NULL) AND (tax_amount IS NOT NULL) AND (gross_amount IS NOT NULL)))))
  • CHECK (((tax_rate >= (0)::numeric) AND (tax_rate <= (100)::numeric)))
  • CHECK ((total_amount >= 0))
  • CHECK ((unit_amount >= 0))

Agreement

Table agreement · Resource agreements

ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
kindstring · textcontract, order, booking, subscription_term
statusstring · textdraft, active, fulfilled, cancelled
customer_idstring (uuid) · uuidcustomer
source_modulestring · textyes
source_record_idstring (uuid) · uuidyes
affiliate_idstring (uuid) · uuidyesaffiliate
currencystring · bpcharmax 3
subtotal_amountstring (int64) · int8
discount_total_amountstring (int64) · int8
tax_total_amountstring (int64) · int8
total_amountstring (int64) · int8
notestring · textyes
descriptionstring · textyes
created_by_user_idstring (uuid) · uuidyesapp_user
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz
cancelled_atstring (date-time) · timestamptzyes

Primary key: id

Checks:

  • CHECK ((currency ~ '^[A-Z]{3}$'::text))
  • CHECK ((discount_total_amount >= 0))
  • CHECK ((subtotal_amount >= 0))
  • CHECK ((tax_total_amount >= 0))
  • CHECK ((total_amount >= 0))

CommissionPayout

Table commission_payout · Resource commission_payouts

  • GET /api/v1/commission_payouts
  • GET /api/v1/commission_payouts/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
affiliate_idstring (uuid) · uuidaffiliate
currencystring · bpcharmax 3
amountstring (int64) · int8
commission_countinteger · int4
paid_onstring (date) · date
methodstring · texttransfer, cash, card, other
referencestring · textyes
notestring · textyes
created_by_user_idstring (uuid) · uuidyesapp_user
created_atstring (date-time) · timestamptz

Primary key: id

Checks:

  • CHECK ((amount > 0))
  • CHECK ((commission_count > 0))
  • CHECK ((currency ~ '^[A-Z]{3}$'::text))

Commission

Table commission · Resource commissions

  • GET /api/v1/commissions
  • GET /api/v1/commissions/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
agreement_idstring (uuid) · uuidagreement
affiliate_idstring (uuid) · uuidaffiliate
currencystring · bpcharmax 3
amountstring (int64) · int8
ratestring (decimal) · numeric
statusstring · textpending, completed, cancelled
paid_atstring (date) · dateyes
notesstring · textyes
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz
payout_idstring (uuid) · uuidyescommission_payout
is_manualboolean · bool
recalculated_atstring (date-time) · timestamptzyes

Primary key: id

Checks:

  • CHECK ((amount >= 0))
  • CHECK ((currency ~ '^[A-Z]{3}$'::text))
  • CHECK (((rate >= (0)::numeric) AND (rate <= (100)::numeric)))

DiscountLine

Table discount_line · Resource discount_lines

  • GET /api/v1/discount_lines
  • GET /api/v1/discount_lines/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
target_typestring · textagreement, agreement_line, checkout, checkout_line, invoice
target_idstring (uuid) · uuid
typestring · textmanual, promotion, voucher, shipping, order_promotion
value_typestring · textpercentage, fixed
valuestring (decimal) · numeric
currencystring · bpcharmax 3
amountstring (int64) · int8
namestring · textyes
reasonstring · textyes
applied_bystring (uuid) · uuidyesapp_user
promotion_rule_idstring (uuid) · uuidyes
voucher_idstring (uuid) · uuidyes
voucher_codestring · textyes
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz
fixed_amountstring (int64) · int8yes

Primary key: id

Checks:

  • CHECK (((value_type <> 'fixed'::text) OR (fixed_amount IS NOT NULL)))
  • CHECK ((amount >= 0))
  • CHECK ((currency ~ '^[A-Z]{3}$'::text))
  • CHECK (((fixed_amount IS NULL) OR (fixed_amount >= 0)))
  • CHECK ((value >= (0)::numeric))
  • CHECK (((type <> 'manual'::text) OR (applied_by IS NOT NULL)))
  • CHECK (((value_type <> 'percentage'::text) OR (value <= (100)::numeric)))

ExpenseCategory

Table expense_category · Resource expense_categories

ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
namestring · text
descriptionstring · textyes
colourstring · text
is_activeboolean · bool
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz

Primary key: id

Expense

Table expense · Resource expenses

ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
category_idstring (uuid) · uuidexpense_category
titlestring · text
descriptionstring · textyes
currencystring · bpcharmax 3
amountstring (int64) · int8
expense_datestring (date) · date
payment_datestring (date) · dateyes
methodstring · textcard, transfer, cash, other
vendor_namestring · textyes
vendor_contactstring · textyes
receipt_file_idstring (uuid) · uuidyesfile
is_recurringboolean · bool
recurring_periodstring · textyes
next_occurrencestring (date) · dateyes
approval_statusstring · textpending, approved, rejected
approved_by_user_idstring (uuid) · uuidyesapp_user
approved_atstring (date-time) · timestamptzyes
rejection_reasonstring · textyes
created_by_user_idstring (uuid) · uuidyesapp_user
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz
recurring_untilstring (date) · dateyes
recurring_parent_idstring (uuid) · uuidyesexpense

Primary key: id

Checks:

  • CHECK ((amount >= 0))
  • CHECK ((currency ~ '^[A-Z]{3}$'::text))
  • CHECK (((NOT is_recurring) OR (recurring_period IS NOT NULL)))
  • CHECK (((recurring_period IS NULL) OR (recurring_period = ANY (ARRAY['weekly'::text, 'monthly'::text, 'quarterly'::text, 'yearly'::text]))))
  • CHECK (((approval_status <> 'rejected'::text) OR (rejection_reason IS NOT NULL)))

FinanceProviderEvent

Table finance_provider_event · Resource finance_provider_events

  • GET /api/v1/finance_provider_events
  • GET /api/v1/finance_provider_events/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
providerstring · textstripe, iyzico, paytr
external_idstring · text
payment_idstring (uuid) · uuidyespayment
event_typestring · text
amountstring (int64) · int8
currencystring · bpcharmax 3
psp_referencestring · textyes
payload_digeststring · text
occurred_atstring (date-time) · timestamptz
processed_atstring (date-time) · timestamptz

Primary key: id

Unique: workspace_id, provider, external_id

Checks:

  • CHECK ((amount >= 0))
  • CHECK ((currency ~ '^[A-Z]{3}$'::text))

Installment

Table installment · Resource installments

ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
agreement_idstring (uuid) · uuidagreement
sequenceinteger · int4
due_datestring (date) · date
currencystring · bpcharmax 3
amountstring (int64) · int8
statusstring · textpending, paid, cancelled
notestring · textyes
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz
due_notice_atstring (date-time) · timestamptzyes
overdue_notice_atstring (date-time) · timestamptzyes

Primary key: id

Checks:

  • CHECK ((amount >= 0))
  • CHECK ((currency ~ '^[A-Z]{3}$'::text))
  • CHECK ((sequence > 0))

PaymentAllocation

Table payment_allocation · Resource payment_allocations

  • GET /api/v1/payment_allocations
  • GET /api/v1/payment_allocations/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
payment_idstring (uuid) · uuidpayment
target_typestring · textinstallment, agreement, invoice
target_idstring (uuid) · uuid
currencystring · bpcharmax 3
amountstring (int64) · int8
created_atstring (date-time) · timestamptz

Primary key: id

Checks:

  • CHECK ((amount <> 0))
  • CHECK ((currency ~ '^[A-Z]{3}$'::text))

PaymentEvent

Table payment_event · Resource payment_events

  • GET /api/v1/payment_events
  • GET /api/v1/payment_events/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
payment_idstring (uuid) · uuidpayment
typestring · textauthorization_success, authorization_failure, authorization_adjustment, authorization_request, authorization_action_required, charge_success, charge_failure, charge_back, charge_action_required, charge_request, refund_success, refund_failure, refund_reverse, refund_request, cancel_success, cancel_failure, cancel_request, info
currencystring · bpcharmax 3
amountstring (int64) · int8
include_in_calculationsboolean · bool
psp_referencestring · textyes
idempotency_keystring · textyes
messagestring · textyes
external_urlstring · textyes
app_idstring (uuid) · uuidyes
app_identifierstring · textyes
actor_user_idstring (uuid) · uuidyesapp_user
occurred_atstring (date-time) · timestamptz
created_atstring (date-time) · timestamptz

Primary key: id

Checks:

  • CHECK ((currency ~ '^[A-Z]{3}$'::text))

PaymentProviderConfig

Table payment_provider_config · Resource payment_provider_configs

  • GET /api/v1/payment_provider_configs
  • GET /api/v1/payment_provider_configs/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
providerstring · textstripe, iyzico, paytr
enabledboolean · bool
test_modeboolean · bool
positioninteger · int4
display_namestring · textyes
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz

Primary key: id

Unique: workspace_id, credential_id · workspace_id, provider

Checks:

  • CHECK (("position" >= 0))

Payment

Table payment · Resource payments

  • GET /api/v1/payments
  • GET /api/v1/payments/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
customer_idstring (uuid) · uuidyescustomer
namestring · textyes
messagestring · textyes
psp_referencestring · textyes
idempotency_keystring · textyes
app_idstring (uuid) · uuidyes
app_identifierstring · textyes
external_urlstring · textyes
available_actionsstring[] · text[]
last_refund_successboolean · bool
currencystring · bpcharmax 3
authorized_amountstring (int64) · int8
charged_amountstring (int64) · int8
refunded_amountstring (int64) · int8
canceled_amountstring (int64) · int8
authorize_pending_amountstring (int64) · int8
charge_pending_amountstring (int64) · int8
refund_pending_amountstring (int64) · int8
cancel_pending_amountstring (int64) · int8
methodstring · textcard, transfer, cash, gift_card, other
method_namestring · textyes
cc_first_digitsstring · textyes
cc_last_digitsstring · textyes
cc_brandstring · textyes
cc_exp_monthinteger · int2yes
cc_exp_yearinteger · int2yes
gift_card_last_charsstring · textyes
gift_card_brandstring · textyes
actor_user_idstring (uuid) · uuidyesapp_user
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz
pending_installment_idstring (uuid) · uuidyesinstallment
gift_card_idstring (uuid) · uuidyes
gift_card_event_idstring (uuid) · uuidyes
checkout_urlstring · textyes
provider_statusstring · textyes
provider_expires_atstring (date-time) · timestamptzyes

Primary key: id

Checks:

  • CHECK ((authorize_pending_amount >= 0))
  • CHECK ((authorized_amount >= 0))
  • CHECK ((cancel_pending_amount >= 0))
  • CHECK ((canceled_amount >= 0))
  • CHECK (((cc_exp_month >= 1) AND (cc_exp_month <= 12)))
  • CHECK ((cc_exp_year >= 2000))
  • CHECK ((charge_pending_amount >= 0))
  • CHECK ((charged_amount >= 0))
  • CHECK ((currency ~ '^[A-Z]{3}$'::text))
  • CHECK (((method <> 'gift_card'::text) OR (gift_card_id IS NOT NULL)))
  • CHECK ((refund_pending_amount >= 0))
  • CHECK ((refunded_amount >= 0))

Events

Workflows and webhooks react to these events.

EventWhat happensPayload
finance.agreement_cancelledAn agreement was cancelled.id, customerId
finance.agreement_createdAn agreement was opened.id, customerId, affiliateId, currency, totalAmount, kind, sourceModule, sourceRecordId
finance.agreement_updatedAn agreement changed.id
finance.commission_accruedAn affiliate is owed something.id, agreementId, affiliateId, amount, currency
finance.commission_cancelledA commission was cancelled.id
finance.commission_payout_recordedA transfer to an affiliate paid a set of commissions.id, affiliateId, amount, currency, count
finance.commission_recalculatedA pending commission moved with what has been collected (K-09).id, agreementId, from, to
finance.commission_status_changedA commission moved.id, from, to
finance.discount_appliedAn agreement was discounted.id, agreementId, amount, currency
finance.discount_removedA discount was taken off.id, agreementId
finance.expense_approvedAn expense was signed off.id
finance.expense_category_changedThe expense vocabulary changed.id
finance.expense_createdAn expense was recorded.id, amount, currency
finance.expense_paidAn expense was paid.id
finance.expense_rejectedAn expense was turned down.id
finance.expense_updatedAn expense was corrected.id
finance.expense_voidedAn unpaid expense was withdrawn.id
finance.installment_cancelledAn instalment was taken out of the plan.id, agreementId
finance.installment_dueAn instalment falls due within the reminder window (Finance → reminder days). Raised once per instalment.id, agreementId, customerId, customerName, description, sequence, dueDate, amount, outstandingAmount, currency, outstandingText, daysUntilDue
finance.installment_overdueAn instalment passed its due date with money still owed. Raised once per instalment.id, agreementId, customerId, customerName, description, sequence, dueDate, amount, outstandingAmount, currency, outstandingText, daysOverdue
finance.installment_updatedAn instalment moved or changed.id, agreementId
finance.installments_scheduledA due-date plan was written.agreementId, count, totalAmount
finance.payment_allocatedA collection was spread across targets.id, count
finance.payment_method_changedA connected payment app was enabled, disabled, reordered or switched between test and live mode.provider, enabled
finance.payment_provider_eventA payment provider answered an authorize, capture or release request.id, provider, type, amount
finance.payment_recordedMoney arrived.id, installmentId, agreementId, amount, currency, customerId, customerName, amountText
finance.payment_refundedMoney went back.id, amount, currency
finance.refund_approvedA refund request was approved and executed.id, paymentId, amount, currency
finance.refund_rejectedA refund request was turned down.id, paymentId, amount
finance.refund_requestedA refund was asked for and waits for approval.id, paymentId, amount, currency
Was this page helpful?