Tutorials

Post invoices to your accounting system

You read validated invoices with a windowed pass, then their lines, payments and credit notes, post them to your ledger, and keep your own record of what you posted. The API publishes totals everywhere and the tax split only in an invoice's detail, so plan the calls and the gaps first.

This tutorial builds on the windowed pass of Syncing data and shows only what is specific to invoices. Money you read is always a decimal string with two decimals (the external invoice call of step 8 accepts up to six decimals on input); see Amounts and currencies.

  • Every invoice and credit note carries its totals: subTotal, netPrice, taxesPrice and grossPrice.
  • The split by tax type is in one place: taxBreakdown, only on the detail read of an invoice, and null when it has not been calculated.
  • Invoice lines have no tax type, and the API does not publish the series of an invoice.
  • A credit note has totals only. The spec says: "The signed tax document, the tax breakdown, the lines and the reason for the correction are not published."
  • The customer's tax ID is the current one from the contact record, not the one on the invoice when it was issued.
  • There is no PDF of an invoice. Validating an invoice is irreversible, and neither the response of the validation nor any invoice field carries a tax-authority status. Do not assume from the API whether a validated invoice was submitted to a tax authority; ask your Filmo contact if you need that.
  • A credit note's status is a free string, not a closed list: tolerate any value.

Treat Filmo as the source of amounts and documents, and keep the legally relevant data your accountant needs in your own record at the moment you read it. The field table below says, field by field, where each value comes from or that it is not available. Until Filmo publishes more, post from these totals and from the detail's breakdown; do not infer tax types from line prices.

  • An API key with account:read, invoices:read (invoices, lines and credit notes), treasury:read (payments) and contacts:read (the customer's tax ID). Add invoices:write only if you mirror ERP-issued invoices back into Filmo. Create the key in the API and MCP settings of the app (Ajustes → API y MCP); only administrators can.
  • Node 22 or later, or Python 3 with requests, and the key in the FILMO_API_KEY environment variable.
  • The windowed pass from Syncing data. Do not follow the cursor to find what changed.

Step 1. Set up the client and read the account

Link to this section: Step 1. Set up the client and read the account

Save the client once; every later snippet imports it. It waits out rate_limited for as many seconds as Retry-After says and returns the server's time from the Date header.

# Export FILMO_API_KEY first: a key with the scopes listed above.
FILMO_URL="${FILMO_BASE_URL:-https://app.filmo.io}"

GET /api/v1/account gives you currency and timezone. An invoice carries its own currency, which can differ; post in the invoice's currency.

# Export FILMO_API_KEY first: a key with the scopes listed above.
FILMO_URL="${FILMO_BASE_URL:-https://app.filmo.io}"

curl -sS "$FILMO_URL/api/v1/account" \
  -H "Authorization: Bearer $FILMO_API_KEY"

GET /api/v1/invoices has updatedFrom (on or after) and updatedBefore (before), so a half-open window of updatedAt works exactly as in the guide: one page of up to 500, split the window if pagination.hasMore is true, deduplicate by id. The list has no filter for the document status, so read all of them and split on status: draft has no number yet (post nothing), validated is numbered and ready, voided is handled in step 7. When a draft is validated its record changes, so it comes back in a later window. Validating, voiding and recording a payment all move the invoice's updatedAt. The one-second-earlier stamp that the guide describes belongs to the order that holds the invoice, not to the invoice, but Filmo still gives no guaranteed bound for the delay before a change is readable, so keep the safety margin of the guide (two minutes at least) and deduplicate by id.

FILMO_URL="${FILMO_BASE_URL:-https://app.filmo.io}"

# One window [updatedFrom, updatedBefore), one page. If pagination.hasMore is true, split the window.
curl -sS -G "$FILMO_URL/api/v1/invoices" \
  -H "Authorization: Bearer $FILMO_API_KEY" \
  --data-urlencode "updatedFrom=2026-10-08T00:00:00.000Z" \
  --data-urlencode "updatedBefore=2026-10-09T00:00:00.000Z" \
  --data-urlencode "sort=updatedAt" \
  --data-urlencode "limit=500"
FILMO_URL="${FILMO_BASE_URL:-https://app.filmo.io}"

# One window [updatedFrom, updatedBefore), one page. If pagination.hasMore is true, split the window.
curl -sS -G "$FILMO_URL/api/v1/invoices" \
  -H "Authorization: Bearer $FILMO_API_KEY" \
  --data-urlencode "updatedFrom=2026-10-08T00:00:00.000Z" \
  --data-urlencode "updatedBefore=2026-10-09T00:00:00.000Z" \
  --data-urlencode "sort=updatedAt" \
  --data-urlencode "limit=500"

The list already returns what you need for a posting header: number, issue and due dates, currency, the contact as an id and a name, the totals, the amount collected and the payment status. The watermark moves to B only after every window of the pass was read. Credit notes are read from their own endpoints, in step 6. The invoice list can still return a row with isRectification set, for example an invoice imported from another system: treat it as a document of its own (its amount may be negative) and match it against the credit notes by number, so you do not post it twice.

Step 3. Read the tax breakdown from the detail

Link to this section: Step 3. Read the tax breakdown from the detail

GET /api/v1/invoices/{invoiceId} returns the same invoice plus taxBreakdown: one row per tax type, with taxType, label, rate (a percentage), baseAmount and amount. The detail also has the billing address, the exchange rate and the split of discounts. It costs one call per invoice, so read it once when you first see the invoice validated, and again only when the invoice reappears in a window with a different updatedAt. Send the ETag you stored in If-None-Match to get a 304 when nothing changed; a 304 still counts as a request.

# The detail is the only place with the tax breakdown.
curl -sS "$FILMO_URL/api/v1/invoices/$INVOICE_ID" \
  -H "Authorization: Bearer $FILMO_API_KEY"

When the breakdown is null or does not match

taxBreakdown is null when it has not been calculated. If it is null, or its sums differ from taxesPrice and netPrice, post the invoice by totals and flag it for review; do not build the split yourself from the lines, which carry no tax type.

GET /api/v1/invoices/{invoiceId}/lines returns what was invoiced in document order, with itemName frozen at the moment of issue (it can differ from today's catalog), quantity, unitPrice, linePrice and the invoiced period. Lines of type section and type_group are presentation groupings with no resource and no price. The lines do not include a tax type, as the spec says: "the invoice totals already carry the calculated taxes". A validated invoice does not change, so read its lines once.

# What was invoiced, line by line, with the names frozen when the invoice was issued.
curl -sS -G "$FILMO_URL/api/v1/invoices/$INVOICE_ID/lines" \
  -H "Authorization: Bearer $FILMO_API_KEY" \
  --data-urlencode "limit=500"

GET /api/v1/invoices/{invoiceId}/payments takes the treasury:read scope, not the invoices one, and returns the payments assigned to the invoice. It has no updated* filter, so read it in descending order of sort -updatedAt and stop at the first payment older than what you hold; a payment that changes in the middle of a read moves to the front and gives you a duplicate, which you deduplicate by id, never a gap. A payment has amount, date, reference, treasuryAccountId, a type of normal or deposit, and linkedDocuments, which says how much of it went to each document.

You do not need to read the payments of every invoice every time. The list gives you totalPaid and paymentStatus for each invoice, so read an invoice's payments only when those differ from what you stored. A payment can arrive weeks after the invoice. Recording one moves the invoice's updatedAt, but Filmo gives no guaranteed bound on how late that stamp can land, so once a day, list the invoices that can still be paid with paymentStatuses unpaid,partially_paid,overdue and compare. That list can include drafts: filter them out by status on your side. If the sum of the amounts you hold for an invoice does not match its totalPaid, read its payments again from the beginning.

# Newest change first. Stop reading when updatedAt is older than what you already hold.
curl -sS -G "$FILMO_URL/api/v1/invoices/$INVOICE_ID/payments" \
  -H "Authorization: Bearer $FILMO_API_KEY" \
  --data-urlencode "sort=-updatedAt" \
  --data-urlencode "limit=500"

# Once a day: the invoices that can still receive a payment.
curl -sS -G "$FILMO_URL/api/v1/invoices" \
  -H "Authorization: Bearer $FILMO_API_KEY" \
  --data-urlencode "paymentStatuses=unpaid,partially_paid,overdue" \
  --data-urlencode "limit=500"

Read credit notes from their own endpoints. GET /api/v1/credit-notes accepts the same updatedFrom and updatedBefore window, so the same helper reads them. GET /api/v1/credit-notes/{creditNoteId} returns the same card as the list, so there is no detail call to budget. A credit note has creditNoteNumber, issueDate, status, invoiceId (the invoice it rectifies, or null) and the totals; there is no breakdown, no lines and no reason. Its status is a free string: store it as received and do not branch on a closed list of values. Check the sign of the amounts on a credit note in your own account before you decide the sign of your posting.

# Credit notes have their own endpoints. The same windowed read, on updatedAt.
curl -sS -G "$FILMO_URL/api/v1/credit-notes" \
  -H "Authorization: Bearer $FILMO_API_KEY" \
  --data-urlencode "updatedFrom=2026-10-08T00:00:00.000Z" \
  --data-urlencode "updatedBefore=2026-10-09T00:00:00.000Z" \
  --data-urlencode "sort=updatedAt" \
  --data-urlencode "limit=500"

POST /api/v1/invoices/{invoiceId}/void works only on a validated invoice. The invoice keeps its number, because voiding does not release the numbering, and it moves to voided; it does not issue a credit note. So a voided invoice is a change, not a deletion. The windowed pass returns it with its new status; the weekly sweep of the guide, which compares status and updatedAt, is the safety net if one is missed.

You seeWhat to do
A voided invoice you already postedPost the cancellation in your ledger as your accountant prescribes. Keep the original entry and its number; do not delete it.
A voided invoice you never saw validatedRecord it as voided and post nothing, or post and reverse it if your accountant needs the number in the books.
Money to give backVoiding refunds nothing. If money has to go back, that is another document: a credit note (step 6).
An invoice that disappears from your readsConfirm with GET /api/v1/invoices/{invoiceId} before you change your side; only a 404 means it no longer exists, as in the reconciliation sweep of the guide.

Step 8. Mirror an ERP-issued invoice into Filmo (optional)

Link to this section: Step 8. Mirror an ERP-issued invoice into Filmo (optional)

If your ERP issues some invoices, PUT /api/v1/invoices/external/{originSystem}/{originExternalId} keeps a read-only copy of each in Filmo so the operations team can see it. The copy is never submitted to a tax authority and is not numbered in a Filmo series. The spec is explicit about what that copy is: "Filmo does not issue it: it is not numbered against a Filmo series, not signed and never sent to Verifactu. It is stored validated, locked and marked as externally issued, and in Filmo it cannot be edited, voided, collected, rectified, e-invoiced or emailed: every change arrives through this same call."

This is the only call where you choose the key. The pair originSystem (a lowercase slug of yours, such as acme_erp) and originExternalId (the document's id in your ERP) identifies the mirror: repeating the call updates it, and repeating the same content changes nothing. It answers 201 when it creates the mirror and 200 when it updates it or finds it identical. Send an Idempotency-Key as on any write. The customer is resolved by contactId, then by tax ID, then by exact name; an unknown or ambiguous customer is rejected, because contacts are never created implicitly. The declared totals are checked against the lines with a tolerance of one cent per line, and a mismatch is rejected too. Both answer 400 validation_error, with errors[0].path naming the field (contactId, contactTaxId, contactName, subTotal, taxTotal, total or lines[n].taxRate), and nothing is written. The amounts, quantities and prices of this call accept up to six decimals, and the two-decimal rule applies to what you read back. In the example, 2 × 150.00 + 1 × 80.00 = 380.00 of base, 79.80 of tax at 21% and 459.80 of total.

# originSystem is a lowercase slug you choose; originExternalId is the document id in your ERP.
curl -sS -X PUT "$FILMO_URL/api/v1/invoices/external/acme_erp/INV-2026-0001" \
  -H "Authorization: Bearer $FILMO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "invoiceNumber": "INV-2026-0001",
    "invoiceDate": "2026-10-08",
    "dueDate": "2026-11-07",
    "currency": "EUR",
    "contactTaxId": "B00000000",
    "lines": [
      { "description": "Camera package, 2 days", "quantity": "2", "unitPrice": "150.00", "taxRate": "21" },
      { "description": "Delivery", "quantity": "1", "unitPrice": "80.00", "taxRate": "21" }
    ],
    "subTotal": "380.00",
    "taxTotal": "79.80",
    "total": "459.80",
    "externalStatus": "issued"
  }'

These mirrors can come back in your own read

An invoice you mirror is stored as validated, so it can appear in GET /api/v1/invoices like any other. Keep the Filmo id the call returns next to your ERP id and skip those ids when you post, or you post your own invoice twice. Automations: this call fires the account's event workflows (invoice created, updated or voided) after the response, on a best-effort basis, and you cannot suppress them per request. List the active event workflows first (see Testing) and filter the trigger of any workflow that should not react by origin to exclude api and mcp. A voided invoice in your ERP is sent with externalStatus voided and a reason; Filmo refuses (409, with errors[0].code invalid_state) to turn a voided mirror back into an issued one.

Where each field of your accounting system comes from. "Not available" means the API does not publish it today; the right column gives the workaround.

Accounting fieldFilmo fieldRead withNotes or workaround
Invoice numberinvoiceNumberGET /api/v1/invoicesNull only while the invoice is a draft. A voided invoice keeps its number.
Issue dateinvoiceDateGET /api/v1/invoicesAn instant. Take the date in the business timezone (timezone) for the ledger.
Due datedueDateGET /api/v1/invoicesNull if not set.
Currency and exchange ratecurrency; exchangeRateGET /api/v1/invoices; GET /api/v1/invoices/{invoiceId}The rate is only in the detail and is null when it does not apply.
Customercontact: an id and a nameGET /api/v1/invoicesThe id is your key to the customer; the name is as Filmo shows it.
Customer tax ID at the time of issueNot available-The API returns only the current nif, read with GET /api/v1/contacts/{contactId}. Read the contact when you first see the invoice and store the tax ID with the entry you post.
Billing addressbillingAddressGET /api/v1/invoices/{invoiceId}Detail only; null if not recorded.
Subtotal, discounts, base, tax, totalsubTotal, discountTotal, netPrice, taxesPrice, grossPriceGET /api/v1/invoicesDecimal strings with two decimals. The detail splits the discount into productDiscountTotal and globalDiscountTotal and adds insuranceTotal and depositTotal.
Base and tax by tax typetaxBreakdownGET /api/v1/invoices/{invoiceId}One detail call per invoice; null if not calculated. Not on the list.
LinesitemName, quantity, unitPrice, linePrice, startDate, endDate, rentalDaysGET /api/v1/invoices/{invoiceId}/linesNames are frozen at issue. A line billed at zero has free set.
Tax type of each lineNot available-A line has no tax field. Use the invoice's taxBreakdown for the split by tax type; do not allocate it to lines.
Invoice seriesNot available-The API does not publish the series of an invoice. Take it from the app; do not parse it out of the number unless your accountant confirms the number format.
Paymentsamount, date, reference, treasuryAccountId; on the invoice totalPaid, paymentStatus, paymentMethodGET /api/v1/invoices/{invoiceId}/payments; GET /api/v1/invoicesThe payments need treasury:read. A payment can be split across documents: read linkedDocuments.
Credit notescreditNoteNumber, issueDate, invoiceId, contactId, netPrice, taxesPrice, grossPriceGET /api/v1/credit-notes; GET /api/v1/credit-notes/{creditNoteId}Totals only. Read them from the credit-note endpoints.
Credit note tax breakdown, lines and reasonNot available-Post the credit note by totals and complete the split and the reason from the app.
PDF of the invoiceNot available-No operation returns it. Generate your own document from the data above, or download it from the app.

The table below is generated from the specification. It lists, field by field, what the invoice and credit-note schemas publish, and the accounting data the API does not offer in this version.

FieldTypeAvailable
InvoiceDto.idstringYes
InvoiceDto.invoiceNumberstring | nullYes
InvoiceDto.statusstringYes
InvoiceDto.isRectificationbooleanYes
InvoiceDto.invoiceDatestring | nullYes
InvoiceDto.dueDatestring | nullYes
InvoiceDto.currencystringYes
InvoiceDto.contactobject | nullYes
InvoiceDto.orderIdstring | nullYes
InvoiceDto.orderNumberstring | nullYes
InvoiceDto.projectIdstring | nullYes
InvoiceDto.subTotalstringYes
InvoiceDto.discountTotalstringYes
InvoiceDto.taxesPricestringYes
InvoiceDto.netPricestringYes
InvoiceDto.grossPricestringYes
InvoiceDto.totalPaidstring | nullYes
InvoiceDto.paymentStatusstringYes
InvoiceDto.paymentMethodstring | nullYes
InvoiceDto.tagsarrayYes
InvoiceDto.createdAtstringYes
InvoiceDto.updatedAtstringYes
InvoiceDetailDto.idstringYes
InvoiceDetailDto.invoiceNumberstring | nullYes
InvoiceDetailDto.statusstringYes
InvoiceDetailDto.isRectificationbooleanYes
InvoiceDetailDto.invoiceDatestring | nullYes
InvoiceDetailDto.dueDatestring | nullYes
InvoiceDetailDto.currencystringYes
InvoiceDetailDto.contactobject | nullYes
InvoiceDetailDto.orderIdstring | nullYes
InvoiceDetailDto.orderNumberstring | nullYes
InvoiceDetailDto.projectIdstring | nullYes
InvoiceDetailDto.subTotalstringYes
InvoiceDetailDto.discountTotalstringYes
InvoiceDetailDto.taxesPricestringYes
InvoiceDetailDto.netPricestringYes
InvoiceDetailDto.grossPricestringYes
InvoiceDetailDto.totalPaidstring | nullYes
InvoiceDetailDto.paymentStatusstringYes
InvoiceDetailDto.paymentMethodstring | nullYes
InvoiceDetailDto.tagsarrayYes
InvoiceDetailDto.createdAtstringYes
InvoiceDetailDto.updatedAtstringYes
InvoiceDetailDto.projectNamestring | nullYes
InvoiceDetailDto.taxBreakdownarray | nullYes
InvoiceDetailDto.productDiscountTotalstringYes
InvoiceDetailDto.globalDiscountTotalstringYes
InvoiceDetailDto.insuranceTotalstringYes
InvoiceDetailDto.depositTotalstringYes
InvoiceDetailDto.exchangeRatenumber | nullYes
InvoiceDetailDto.billingAddressobject | nullYes
InvoiceDetailDto.sourceQuoteIdsarray | nullYes
InvoiceDetailDto.sourceProformaIdsarray | nullYes
DocumentLineDto.idstringYes
DocumentLineDto.resourceTypestringYes
DocumentLineDto.transactionTypestringYes
DocumentLineDto.positionintegerYes
DocumentLineDto.itemIdstring | nullYes
DocumentLineDto.itemNamestring | nullYes
DocumentLineDto.variantIdstring | nullYes
DocumentLineDto.variantNamestring | nullYes
DocumentLineDto.quantitynumberYes
DocumentLineDto.orderIdstring | nullYes
DocumentLineDto.orderNumberstring | nullYes
DocumentLineDto.startDatestring | nullYes
DocumentLineDto.endDatestring | nullYes
DocumentLineDto.rentalDaysnumber | nullYes
DocumentLineDto.unitPricestring | nullYes
DocumentLineDto.linePricestring | nullYes
DocumentLineDto.freebooleanYes
DocumentLineDto.notesstring | nullYes
CreditNoteDto.idstringYes
CreditNoteDto.creditNoteNumberstring | nullYes
CreditNoteDto.statusstringYes
CreditNoteDto.issueDatestring | nullYes
CreditNoteDto.currencystringYes
CreditNoteDto.contactIdstring | nullYes
CreditNoteDto.invoiceIdstring | nullYes
CreditNoteDto.subTotalstringYes
CreditNoteDto.taxesPricestringYes
CreditNoteDto.netPricestringYes
CreditNoteDto.grossPricestringYes
CreditNoteDto.createdAtstringYes
CreditNoteDto.updatedAtstringYes
PDF of invoices and other documents—Not available in v1
Composing draft invoices and editing quotes—Not available in v1
Tax breakdown, lines and reason of a credit note—Not available in v1
The customer's tax ID at the time an invoice was issued—Not available in v1
Tax type per line and the series of an invoice—Not available in v1

The list of what the API does not offer in this version is in Capabilities and limits.

StoreWhy
Filmo invoice id next to the entry you postedTo find it again on a void, a payment or a credit note. Identifiers are opaque.
The updatedAt and the ETag of each invoiceTo skip a detail read when nothing changed, and for the weekly sweep.
totalPaid and paymentStatus, and the payments you holdTo decide which invoices need a payments read.
The customer's tax ID at the moment you read the invoiceThe API only returns the current one.
The watermark B of the last complete pass, for invoices and for credit notesThe next pass starts there. It moves only after a full pass.
The Filmo ids of the invoices you mirrored with the external callTo skip them when you read invoices.

Your key allows 120 requests per minute across the whole API, and every authenticated response counts. The expensive part of this tutorial is the tax breakdown: it exists only on the detail read, which costs one call per invoice. For a list read in W windows of which S overflow one page, the cost is W + 2S calls. The initial load is:

Initial load
calls = 1 + (W_inv + 2*S_inv) + (W_cn + 2*S_cn) + N + N + P
  N = validated invoices (one detail call and one lines call each)
  P = invoices with a collection (one payments call each)
minutes = calls / 120

Worked example. 12,000 validated invoices over 24 months, 70% of them with a collection (P = 8,400), and 600 credit notes. Invoices: 24 monthly windows, of which 12 hold more than 500 and overflow: 24 + 2 × 12 = 48 calls. Credit notes: 24 windows, none overflowing: 24 calls. Total: 1 + 48 + 24 + 12,000 + 12,000 + 8,400 = 32,473 calls. At 120 per minute that is 270.6 minutes (4.5 hours); at 96 per minute, leaving a fifth of the quota free, 338.3 minutes (5.6 hours). The detail reads alone are 12,000 of those calls. If you can post by totals and do not need the split by tax type, you skip them and the load is 20,473 calls, 170.6 minutes (2.8 hours).

Daily pace
calls per day = 3 * polls + D + L + Pd + R
  polls = passes per day (each: 1 server time + 1 invoice window + 1 credit-note window)
  D = invoices that appear in a window (one detail call each)
  L = newly validated invoices (one lines call each)
  Pd = invoices whose totalPaid or paymentStatus changed (one payments call each)
  R = open-invoice refresh, once a day: ceil(rows the filter returns / 500), drafts included

Worked example. An hourly pass (24 passes, so 72 calls), 40 new invoices a day and 10 older ones that change, so D = 50 and L = 40. Payments to read: 28 for the new invoices that already carry a collection (70% of 40) and 10 for the changed ones, so Pd = 38. With 300 open invoices the refresh is 1 call. Total: 72 + 50 + 40 + 38 + 1 = 201 calls a day, under two minutes of quota (1.7). The weekly sweep costs the list calls of the load plus one GET per missing id.

Run the initial load at a quiet hour and let the client sleep when it receives rate_limited.

ResponseWhat it means hereWhat to do
401 api_key_owner_inactive or api_key_owner_not_adminThe person who created the key left or stopped being an administrator.Stop posting, alert someone and reissue the key from an administrator account.
403 insufficient_scopeA scope is missing; requiredScope names it. Payments need treasury:read and the tax ID needs contacts:read.Reissue the key with the scopes in the prerequisites.
404 not_foundOn an invoice, a credit note or its payments: it does not exist or is outside your account. Payments of an unknown invoice are a 404, not an empty list.Mark the record as missing and confirm it at the next sweep; do not delete a posted entry.
400 validation_errorA parameter is malformed, or the body of the external call is invalid.Fix the request; errors[] lists each field.
409 conflict on the external callRead errors[0].code. invalid_state: the invoice number is used by another invoice, the key belongs to an invoice that is not a mirror, or a voided mirror was sent back as issued. in_progress: a request with the same key is still running. lock_timeout: another write held the invoice for more than five seconds.For invalid_state, do not retry: fix the data. For in_progress and lock_timeout, wait and retry with the same Idempotency-Key.
422 idempotency_key_reusedYou reused an Idempotency-Key with a different body.Use a new key for a changed document; reuse a key only for a retry of the identical call.
400 validation_error on the external call, for the customer or the totalsThe customer is unknown or ambiguous, or the declared totals do not match the lines. errors[] names the field.Resolve the customer with contactId, fix the totals and send the corrected document. A rejected request leaves no record, so you can reuse the Idempotency-Key or use a new one.
429 rate_limitedYou spent the 120 requests of the minute.Wait for Retry-After seconds (also retryAfter in the problem body), then continue the same window.
500 internal_error, 502 or 504A transient failure. Reads are safe to repeat; for the external call, retry with the same Idempotency-Key.Retry with exponential backoff and do not move the watermark.
503 service_unavailableNo operation of this tutorial documents it. Filmo controls the switch behind it.Do not retry it as a transient failure. Stop that call and contact Filmo support with the requestId.
  • Guides

    Syncing data

    Filmo v1 does not send webhooks, so you keep another system in step by polling: read what changed in half-open time windows, and run a separate sweep to find what was deleted. Do not follow the pagination cursor to decide what changed or what disappeared.

  • Further reading

    Amounts and currencies

    Every monetary amount is a decimal string with two decimals, such as "1234.50", never a floating-point number, with one exception: the prices in the priceConfig and defaultExpenses of a service and the customPrice of an accessory are JSON numbers. Each document carries its own currency, which can differ from the account's.

  • Further reading

    Capabilities and limits of v1

    The API covers most day-to-day work on orders, contacts, items, invoices and the schedule, and leaves a short list of things to the app. Check it here before you design an integration around a feature.

  • Guides

    Idempotency and safe retries

    Send an Idempotency-Key header with every write and reuse the same value when you retry that operation: Filmo returns the stored result of the first attempt instead of writing twice.

  • Tutorials

    Sync your inventory with another system

    You read the catalog, the physical units and the lines of the orders that commit them, load everything once, then poll for changes and reconcile weekly. Filmo does not send webhooks, so polling is how you stay current.