Invoices, balance and payments
GET /invoices
Permissions: billing.invoices:read
| Parameter | Type | Required | Values |
|---|---|---|---|
limit | integer | no, default 20 | 1 to 100 |
cursor | string | no | pagination.nextCursor from the previous page |
status | string | no | UNPAID, PARTIALLY_PAID, PAID, OVERDUE, VOID, PARTIALLY_REFUNDED, REFUNDED |
DRAFT is accepted but never returns anything: drafts do not leave the dashboard.
curl -H "Authorization: Bearer $FRESHPERF_KEY" \ "https://api.freshperf.fr/v1/invoices?status=UNPAID&limit=20"
{ "data": [ { "code": "INV-202608-0082", "kind": "RENEWAL", "status": "PAID", "issuedAt": 1787727600380, "dueAt": 1788300000000, "paidAt": 1787727605569, "currency": "EUR", "totalCents": 360, "amountPaidCents": 360, "balanceDueCents": 0, "serviceCode": "SRV-TISZG5-6000", "orderNumber": "ORD-20260727-0001" } ], "pagination": { "nextCursor": "MTg4", "limit": 20 } }
| Field | Type | Notes |
|---|---|---|
code | string | the identifier every invoice route takes |
kind | string or null | RENEWAL, UPGRADE, ONE_OFF, ADDON_ADDED, ADDON_REMOVED, ADMIN_RESIZE |
status | string | the filter values above |
issuedAt, dueAt | integer | epoch milliseconds |
paidAt | integer or null | epoch milliseconds |
totalCents, amountPaidCents, balanceDueCents | integer | cents, tax included |
serviceCode | string or null | the service billed, when the invoice bills one |
orderNumber | string or null | the originating order |
GET /invoices/{code}
Permissions: billing.invoices:read
Carries the fields above, adds subtotalCents, discountCents, amountRefundedCents, and details:
lines[] field | Type | Notes |
|---|---|---|
name | string | |
description | string or null | |
quantity | integer | |
unitPriceCents, unitTaxCents, totalCents | integer | cents |
periodStart, periodEnd | integer or null | epoch milliseconds |
recurrence | string or null |
payments[] field | Type | Notes |
|---|---|---|
id | integer | |
status | string | PENDING, AUTHORIZED, CAPTURED, SETTLED, FAILED, CANCELLED, REFUNDED, NEEDS_REVIEW |
amountCents | integer | cents |
currency | string or null | |
provider | string or null | the payment provider, or BALANCE for a balance payment |
method | string or null | e.g. CARD |
createdAt | integer | epoch milliseconds |
GET /invoices/{code}/pdf
Permissions: billing.invoices:read
Answers the file, not JSON.
| Header | Value |
|---|---|
Content-Type | application/pdf |
Content-Disposition | attachment; filename="INV-202608-0082.pdf" |
Cache-Control | private, no-store |
curl -H "Authorization: Bearer $FRESHPERF_KEY" \ -o invoice.pdf \ https://api.freshperf.fr/v1/invoices/INV-202608-0082/pdf
| Error | Status | When |
|---|---|---|
INVOICE_NOT_FOUND | 404 | unknown code, or another account's invoice |
PDF_NOT_AVAILABLE | 503 | the document is not rendered yet. Retry-After: 5 |
On an error the body goes back to the usual JSON envelope.
POST /invoices/{code}/pay
Permissions: billing.invoices:pay, plus billing.saved_methods:charge with saved_method.
Required header: Idempotency-Key.
Pays what is left due.
| Field | Type | Required | Values | Where it comes from |
|---|---|---|---|---|
method | string | yes | balance, saved_method | - |
paymentMethodId | integer | yes when saved_method | > 0 | GET /payment-methods → id |
none is not accepted here: 400 VALIDATION_ERROR:method.
curl -X POST -H "Authorization: Bearer $FRESHPERF_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: pay-INV-202608-0082" \ -d '{"method":"balance"}' \ https://api.freshperf.fr/v1/invoices/INV-202608-0082/pay
{ "data": { "status": "CAPTURED", "paymentId": 244, "amountCents": 360, "invoiceCode": "INV-202608-0082", "balanceCents": 4640 } }
| Field | Type | Notes |
|---|---|---|
status | string | NONE, FREE, or a Payment.Status value |
paymentId | integer or null | |
amountCents | integer or null | cents charged |
invoiceCode | string or null | the document settled |
balanceCents | integer or null | the balance afterwards, on a balance payment only |
| Error | Status | When |
|---|---|---|
INVOICE_NOT_FOUND | 404 | unknown code |
INVOICE_ALREADY_PAID | 400 | already settled |
INVOICE_VOID, INVOICE_REFUNDED, INVOICE_NOT_ISSUED, INVOICE_BALANCE_ZERO | 400 | nothing to pay on this document |
CURRENCY_UNSUPPORTED | 400 | currency not supported |
INSUFFICIENT_BALANCE | 402 | balance too short |
PAYMENT_DENIED | 402 | the bank refused |
PAYMENT_METHOD_NOT_FOUND | 404 | method not on the account |
PAYMENT_METHOD_NOT_ACTIVE | 400 | method expired or blocked |
API_KEY_SPENDING_CAP_EXCEEDED | 403 | the key's cap does not cover the amount |
PAYMENT_IN_PROGRESS | 409 | another payment of the same document is running |
PAYMENT_RECORDED_MISMATCH | 409 | reconciliation anomaly. Contact support |
GET /credit-notes
Permissions: billing.credit_notes:read
| Parameter | Type | Required | Values |
|---|---|---|---|
limit | integer | no, default 20 | 1 to 100 |
cursor | string | no | offset cursor |
| Field | Type | Notes |
|---|---|---|
code | string | AV-202608-0001 |
status | string | ISSUED or VOID |
invoiceCode | string or null | the invoice it corrects |
currency | string or null | |
totalCents | integer | cents |
reason | string or null | |
issuedAt | integer | epoch milliseconds |
GET /credit-notes/{code}/pdf
Permissions: billing.credit_notes:read
Same headers and same errors as the invoice PDF, with CREDIT_NOTE_NOT_FOUND on 404.
GET /balance
Permissions: billing.balance:read
{ "data": { "enabled": true, "balanceCents": 4400, "currency": "EUR", "autoRenewFromBalance": false } }
| Field | Type | Notes |
|---|---|---|
enabled | boolean | false when the prepaid balance is not open on the platform |
balanceCents | integer | cents available |
currency | string or null | ISO 4217 |
autoRenewFromBalance | boolean | the account opted to pay its renewals from the balance |
The balance is topped up from the dashboard. The API spends it; it does not fill it.
GET /balance/transactions
Permissions: billing.balance:read
| Parameter | Type | Required | Values |
|---|---|---|---|
limit | integer | no, default 20 | 1 to 100 |
cursor | string | no | offset cursor |
{ "data": [ { "id": 62, "type": "RENEWAL_APPLIED", "amountCents": -300, "balanceAfterCents": 4400, "currency": "EUR", "reason": "Invoice INV-202608-0087", "relatedPaymentId": null, "createdAt": 1787907506000 } ] }
| Field | Type | Notes |
|---|---|---|
id | integer | |
type | string or null | see the table below |
amountCents | integer | signed: positive on a credit, negative on a debit |
balanceAfterCents | integer | the balance just after this movement |
reason | string or null | free-form label |
relatedPaymentId | integer or null | points at an invoice's payments[].id |
createdAt | integer | epoch milliseconds |
type | Meaning |
|---|---|
TOPUP, TOPUP_CARD, TOPUP_PAYPAL, TOPUP_SAVED | a top-up, by the method used |
PAYMENT_APPLIED | the balance paid an order or an invoice |
RENEWAL_APPLIED | the balance paid a renewal |
PAYMENT_REVERSED | a balance payment was undone |
REFUND_TO_BALANCE | a refund came back to the balance |
REFERRAL_CREDIT | referral earnings |
ADMIN_CREDIT, ADMIN_DEBIT | a gesture or a correction |
GET /payment-methods
Permissions: billing.payment_methods:read
The whole list, unpaginated.
{ "data": [ { "id": 14, "type": "CARD", "status": "ACTIVE", "displayName": "MASTERCARD ••••0011", "brand": "MASTERCARD", "last4": "0011", "expMonth": 9, "expYear": 2028, "isDefault": true } ] }
| Field | Type | Notes |
|---|---|---|
id | integer | pass it back as paymentMethodId |
type | string | CARD, SEPA_DIRECT_DEBIT, BANK_TRANSFER, PAYPAL, APPLE_PAY, GOOGLE_PAY, OTHER |
status | string | ACTIVE, INACTIVE, PENDING_VERIFICATION, EXPIRED, BLOCKED |
displayName | string or null | masked label. A PayPal address is partly hidden |
brand, last4 | string or null | cards |
expMonth, expYear | integer or null | cards |
isDefault | boolean | the method account_default uses |
Only an ACTIVE method can be charged. Adding or removing one happens in the dashboard.
What the key has spent
GET /me and GET /me/key return key.spendingCapCents and key.spendingRemainingCents: the key's cap over a rolling 30 days and what is left of it. The individual charges are in the key's log, in the dashboard.