Invoices | FieldCamp API
Use the FieldCamp Invoices API to create, list, update, void, and collect on v1 invoices with line items, discounts, markup, and Stripe payment links.
The FieldCamp Invoices API is how your integration turns completed work into money in the bank — create draft invoices, attach line items with document-level or item-level discounts and markup, push status changes, and hand the customer a hosted Stripe payment link. With release_v2, the resource is available under the versioned path /api/v1/invoices, which adds server-side clientId, status, and since filters and a dedicated payment-link endpoint. If you want the in-app workflow instead, the Creating invoices guide walks through the UI step by step.
What changed in release_v2
The /api/v1/invoices namespace is now the recommended path for new integrations. The unversioned /api/invoices endpoints still exist for backward compatibility, but they do not expose the new filters or the Stripe payment-link helper.
- Versioned base path:
/api/v1/invoices(instead of/api/invoices). - Server-side filters on the list endpoint:
clientId,status, andsince. - Status vocabulary aligned with the in-app invoice views and analytics:
unpaid,paid,partial, andoverdue. - Document-level and item-level
discountandmarkupfields on the same payload. - New
GET /api/v1/invoices/{id}/payment-linkendpoint that returns a hosted Stripe checkout URL.
This endpoint requires an fc_live_ API key. See Authentication for header conventions, Errors for the error envelope, and Rate limits for throttling rules.
When to use the Invoices API
Most integrations hit /api/v1/invoices the moment a job is wrapped — the technician marks the job complete, and your backend rolls up the products and services used on the visit into one invoice for the client. You can also use it to:
- Bulk-import historic invoices when migrating from another field-service system.
- Auto-issue invoices on a schedule for recurring jobs.
- Generate a Stripe payment link the second a job is closed out.
- Sync invoice status changes nightly into your data warehouse for custom dashboards and boards.
How an invoice is structured
Every FieldCamp invoice has three layers — header, line items, and totals — and the API mirrors that shape one-for-one.
- Header —
clientId, billing/property address, invoice number, issue date, due date, currency, notes, optional reference to the originating job, and optional document-leveldiscountandmarkup. - Line items — an array of items pulled from your price book and bundles. Each line carries
itemId,description,quantity,unitPrice, an item-levelmarkup(percent or amount), an item-leveldiscount(percent or amount), andtaxIdsresolved from Taxes. - Totals —
subTotalandtotalare stored exactly as you send them. This endpoint performs no arithmetic over the items, and there are nodiscountTotal,markupTotalortaxTotalfields on the record. Send the figures your own system computed; omit them and each is stored as0.
Reuse the same itemId values you use on jobs. That keeps the client detail page consistent and avoids creating duplicate items in your price book.
Filtering and search
GET /api/v1/invoices accepts the following query parameters:
clientId— return invoices for a single client. Useful for portals and statement views.status— exact match againstunpaid,paid,partial, oroverdue.partialcovers any invoice with deposits applied but a balance remaining.since— ISO 8601 timestamp. Returns invoices whoseupdatedAtis greater than or equal to the supplied value. Ideal for nightly delta syncs.limitandcursor— standard pagination parameters used across the API.
Example: invoices for one client
curl "https://api.fieldcamp.ai/api/v1/invoices?clientId=$CLIENT_ID" \
-H "Authorization: Bearer $FIELDCAMP_API_KEY"Example: pull overdue invoices
curl "https://api.fieldcamp.ai/api/v1/invoices?status=overdue&limit=100" \
-H "Authorization: Bearer $FIELDCAMP_API_KEY"Example: incremental sync
curl "https://api.fieldcamp.ai/api/v1/invoices?since=2026-05-01T00:00:00Z" \
-H "Authorization: Bearer $FIELDCAMP_API_KEY"Create an invoice with line items, discounts, and markup
Send a POST /api/v1/invoices request with the header and a lineItems array. Discounts and markup can be applied at the line level on each item, or as a single document-level value that applies to the whole invoice. Markup is computed on unitPrice before tax; discounts are computed after markup.
Look up or create the client
Call GET /api/v1/clients?search=<email> to find an existing record, or POST /api/v1/clients to create one. Reuse the returned id as the clientId on the invoice.
Resolve item and tax IDs
Pull itemId values from Items and taxId values from Taxes. Cache them locally — they almost never change between invoices.
Build the line items
For each line, set quantity, unitPrice, optional markup ({ type: "percent" | "amount", value }), optional discount ({ type: "percent" | "amount", value }), and taxIds. The server multiplies, marks up, discounts, then taxes — in that order.
POST the invoice
Send the payload to POST /api/v1/invoices. The response includes the server-calculated subTotal, discountTotal, markupTotal, taxTotal, and total so you can reconcile before persisting locally.
Store the returned ID
Keep the returned id alongside your internal record. You will need it for status updates, edits, and the Stripe payment-link call below.
Example request
curl -X POST https://api.fieldcamp.ai/api/v1/invoices \
-H "Authorization: Bearer $FIELDCAMP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"clientId": "cli_123",
"jobId": "job_456",
"issueDate": "2026-05-29",
"dueDate": "2026-06-12",
"status": "unpaid",
"currency": "USD",
"discount": { "type": "percent", "value": 5 },
"lineItems": [
{
"itemId": "itm_789",
"description": "Annual HVAC tune-up",
"quantity": 1,
"unitPrice": 199.00,
"markup": { "type": "percent", "value": 10 },
"taxIds": ["tax_001"]
}
],
"notes": "Thanks for your business!"
}'Send monetary values as numbers in major units of the invoice's currency (e.g. 199.50). Mixing strings and numbers across line items is the most common cause of 400 validation errors. See Errors for the full catalog.
Update invoices and move statuses
- Fetch one —
GET /api/v1/invoices/{id}returns the full invoice including computed totals and any signature blocks captured in the field. - Update —
PUT /api/v1/invoices/{id}is a partial update, but its contract differs from create in both directions: it addspaidAmount,paymentStatusandactionStatus, and it removes document-leveltaxes— sendingtaxeson an update returns400. - Status transitions — set
statustounpaid,paid,partial, oroverdue. Status drives where the invoice shows up in the invoice views and analytics and which clients see it in their portal. - Delete —
DELETE /api/v1/invoices/{id}soft-deletes any invoice regardless of state and returns{ id, deleted: true }. There is no draft-only check and no guard on a sent or paid invoice, so treat it as a destructive call and prefer changing the payment status instead if you want your reporting to stay intact. It emitsinvoice.deleted.
overdue is set automatically by FieldCamp when dueDate passes and the invoice is not fully paid. You can also set it explicitly via PUT if your billing rules require it earlier.
Issue a Stripe payment link
If your tenant has connected Stripe, call GET /api/v1/invoices/{id}/payment-link for a payment URL to send to the customer.
It returns four fields:
{
"success": true,
"data": {
"paymentUrl": "https://app.fieldcamp.ai/documentView?token=…&docId=…",
"invoiceId": "6a84…",
"amount": 272.5,
"currency": "usd"
}
}paymentUrl is a FieldCamp-hosted invoice page where the customer pays through your connected Stripe account — it is not a Stripe Checkout URL, so there is no stripePaymentIntentId to reconcile against and no expiresAt.
The link is stable and non-expiring: the view token is minted once per invoice and reused, so calling this endpoint again returns the same URL. That is deliberate — a link already sitting in a customer's inbox keeps working. It is safe to call repeatedly, and safe to store.
curl https://api.fieldcamp.ai/api/v1/invoices/$INVOICE_ID/payment-link \
-H "Authorization: Bearer $FIELDCAMP_API_KEY"You can email the same link to the client via two-way text messaging, or embed it in your own customer portal. Stripe handles the card capture; FieldCamp posts the payment back against the invoice automatically and moves the status from unpaid (or partial) to paid when the balance hits zero. For deposit-style up-front payments, see Deposits and partial payments.
Endpoints
GET /api/v1/invoices— list invoices filtered byclientId,status, and/orsince.POST /api/v1/invoices— create an invoice with line items, discounts, markup, and tax.GET /api/v1/invoices/{id}— fetch a single invoice including computed totals.PUT /api/v1/invoices/{id}— update header fields, line items, or status.DELETE /api/v1/invoices/{id}— delete a draft (prefer a status change for sent invoices).GET /api/v1/invoices/{id}/payment-link— return a Stripe hosted payment URL for the invoice.
Best practices
After a job is marked complete, pull its line items and POST them straight into an invoice. Reusing IDs keeps the audit trail clean.
Pull your taxId list at startup and pass them on every line. The server recomputes taxTotal from those IDs every time.
Use since and clientId to find existing invoices before creating. On a retry you reuse the record instead of double-billing the client.
Bulk back-fills should respect the per-tenant rate limit. Batch in chunks and back off on 429 responses.
FAQs
Which statuses can I send to POST and PUT?
unpaid, paid, partial, and overdue. New invoices default to unpaid if no status is sent.
How do document-level and item-level discounts combine?
Item-level markup and discount are applied first while computing each line. The document-level discount is then applied to the resulting subTotal before tax. The final total is subTotal - documentDiscount + taxTotal.
Can I filter the list endpoint by both clientId and status?
Yes. All three filters (clientId, status, since) are independent and can be combined in any order on GET /api/v1/invoices.
Does the payment link expire?
No. The response carries no expiresAt, and calling the endpoint again returns the same URL rather than a new one — the token is minted once per invoice and reused, so a link you already sent keeps working. The customer always sees the invoice's current balance, because the page reads it live.
What happens if Stripe is not connected?
GET /api/v1/invoices/{id}/payment-link returns 400 with code INTEGRATION_NOT_CONNECTED. Connect Stripe via Connect Stripe for online payments and retry.
Troubleshooting
400 lineItems is required—POST /api/v1/invoicesrequires at least one line item. Add a placeholder labour line if you have nothing else to bill.- Totals don't match my upstream system — remember the calculation order: per-line markup, per-line discount, per-line tax, then document-level discount. Compute totals in the same order before comparing.
statusignored on update — onlyunpaid,paid,partial, andoverdueare accepted. Other values are silently dropped; check the response body for the canonical status.sincenot returning new rows —sincematchesupdatedAt, notissueDate. Use the most recentupdatedAtyou have seen as the next cursor.
Endpoints
/api/v1/invoicesAuthorization
BearerAuth JWT from POST /api/auth/login (response.data.token). Tokens do not expire.
In: header
Query Parameters
Page number, 1-based.
Rows per page. Values above 100 are clamped to 100.
Return only rows whose updatedAt is at or after this ISO-8601 timestamp — the incremental-sync cursor. A value that is not a date returns 400 rather than being ignored.
Order the result as field:direction. Direction is asc or desc (default desc). Sortable fields: createdAt, updatedAt, total, paymentStatus, dueDate, documentNumber. An unsupported field returns 400.
Return only invoices for this client id.
Filter by payment status, matched case-insensitively — paid or unpaid. Matching is on the whole value, so paid never also returns unpaid.
Response Body
application/json
application/json
application/json
curl -X GET "https://example.com/api/v1/invoices"{}/api/v1/invoicesAuthorization
BearerAuth JWT from POST /api/auth/login (response.data.token). Tokens do not expire.
In: header
Response Body
application/json
application/json
curl -X POST "https://example.com/api/v1/invoices"{}/api/v1/invoices/{id}Authorization
BearerAuth JWT from POST /api/auth/login (response.data.token). Tokens do not expire.
In: header
Path Parameters
Response Body
application/json
application/json
curl -X GET "https://example.com/api/v1/invoices/665f1c2ab3e4d5f6a7b8c9d0"{}/api/v1/invoices/{id}Authorization
BearerAuth JWT from POST /api/auth/login (response.data.token). Tokens do not expire.
In: header
Path Parameters
Response Body
application/json
application/json
curl -X PUT "https://example.com/api/v1/invoices/665f1c2ab3e4d5f6a7b8c9d0"{}/api/v1/invoices/{id}Authorization
BearerAuth JWT from POST /api/auth/login (response.data.token). Tokens do not expire.
In: header
Path Parameters
Response Body
application/json
application/json
curl -X DELETE "https://example.com/api/v1/invoices/665f1c2ab3e4d5f6a7b8c9d0"{}/api/v1/invoices/{id}/payment-linkAuthorization
BearerAuth JWT from POST /api/auth/login (response.data.token). Tokens do not expire.
In: header
Path Parameters
Response Body
application/json
application/json
curl -X GET "https://example.com/api/v1/invoices/665f1c2ab3e4d5f6a7b8c9d0/payment-link"{}Related articles
- Authentication
- Errors
- Idempotency
- Rate limits
- Webhooks
- Clients (API)
- Jobs (API)
- Visits (API)
- Items (API)
- Taxes (API)
- Creating invoices
- Invoice views and analytics
- Digital signatures on estimates and invoices
- Connect Stripe for online payments
- Deposits and partial payments
- Managing products and services
- Price book and bundles
- Custom dashboards and boards
- Job and visit statuses
- Client detail page
- Two-way text messaging
- Recurring jobs
Calendar & Accounting Sync API for Google Calendar, Outlook & QuickBooks | FieldCamp
FieldCamp's calendar sync API lets you trigger Google Calendar, Outlook and QuickBooks syncs and pull incremental changes from one place.
Changelog | FieldCamp API
Notable changes to the FieldCamp public API and documentation.