Guides
Testing your integration
Start with a read-only key, check the account's automations before the first write, and keep irreversible operations out of any account that holds real data.
There is no sandbox and there are no test keys: every key starts with fk_live_ and acts on the real data of the account that issued it. Every step below is a way to reduce what a test can touch.
Start with a read-only key
Link to this section: Start with a read-only keyCreate a key with only the :read scopes the integration needs, plus account:read, and build all your reading, paging and parsing with it. A read-only key cannot change anything, so a bug in your code cannot either. The Scopes guide has recipes by integration type. You can also try reads from the OpenAPI reference.
A separate account for writes
Link to this section: A separate account for writesTest writes in a separate account that holds only fictitious data (names such as Acme Rentals, emails at example.), not in the account of a real customer. Use a free trial account: it serves to test the API. An administrator creates API keys and connects OAuth and MCP in it like in any other account, and the trial unlocks every add-on. Know its terms.
- The trial lasts 14 days and needs no card. After it expires it may be extendable once, by 7 days.
- A trial unlocks every add-on while it lasts (
trial). When it ends, the operations behind an add-on answer 402addon_requiredwhile the others keep working, so a test that passed in the trial can fail later. Keys and OAuth connections are not revoked when the trial ends, and authentication does not look at the trial: it checks the key, its owner and the owner's access to the account. - The data of a trial account is purged about 30 days after the trial ends. Do not keep anything there that you need.
- Treat the test account as a real one. Validating an invoice or a credit note is irreversible and uses up numbers of the account's numbering, also in a trial account: validate only what a test needs.
Before writing: check the automations
Link to this section: Before writing: check the automationsA write through the API goes through the same path as a change made in the app, so it fires the account's event workflows. You cannot suppress that per request: there is no header or flag for it. Those workflows can send emails to customers or call third parties, so this check is mandatory before the first write. Events fire after the response and on a best-effort basis; most business writes (orders, invoices, payments, quotes, contacts, tasks, projects) fire one, while some edits, such as tag assignments or item price changes, fire none.
List the active workflows that start on an event or an entity
Call
GET /apiwith/v1 /workflows isActiveset totrueandtriggerTypesset toevent,entity. The key needsworkflows:read, and only administrators can list workflows.Read what each one does
For each workflow,
triggersays which event or entity starts it. Open it in the app to see what it sends.Pause them, or exclude the API
Pause the ones that could reach a customer or a third party while you test. Alternatively, filter the trigger of each workflow by
originto excludeapi(andmcp, if you also test over MCP), so it ignores what your key writes. Resume or restore them afterwards.
If you also test over MCP, an assistant writes with the same effect. Read Safe writes before you let one write.
BASE="${FILMO_BASE_URL:-https://app.filmo.io}"
curl -sS -G "$BASE/api/v1/workflows" \
-H "Authorization: Bearer $FILMO_API_KEY" \
--data-urlencode "isActive=true" \
--data-urlencode "triggerTypes=event,entity" \
--data-urlencode "limit=500"const base = process.env.FILMO_BASE_URL ?? "https://app.filmo.io";
const url = new URL(base + "/api/v1/workflows");
url.searchParams.set("isActive", "true");
url.searchParams.set("triggerTypes", "event,entity");
url.searchParams.set("limit", "500");
const res = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.FILMO_API_KEY}` },
});
if (!res.ok) throw new Error("HTTP " + res.status + " " + (await res.text()));
const { data, pagination } = await res.json();
for (const workflow of data) {
const starts = workflow.trigger.event ?? workflow.trigger.entity;
console.log(workflow.name, "-", workflow.trigger.type, starts);
}
if (pagination.hasMore) console.warn("More workflows: follow pagination.nextCursor");import os
import requests
base = os.environ.get("FILMO_BASE_URL", "https://app.filmo.io")
res = requests.get(
base + "/api/v1/workflows",
headers={"Authorization": f"Bearer {os.environ['FILMO_API_KEY']}"},
params={"isActive": "true", "triggerTypes": "event,entity", "limit": "500"},
timeout=30,
)
res.raise_for_status()
body = res.json()
for workflow in body["data"]:
trigger = workflow["trigger"]
print(workflow["name"], "-", trigger["type"], trigger["event"] or trigger["entity"])
if body["pagination"]["hasMore"]:
print("More workflows: follow pagination.nextCursor")Never in production
Link to this section: Never in productionDo not call these operations from a test against an account that holds real data. They cannot be undone, they produce fiscal documents, or they notify people.
- Validate or void an invoice, a credit note, a contract or a supplier invoice.
- Cancel or sign a contract.
- Receive a purchase order, return order lines or record a payment.
- Run a workflow.
- Cancel an order or remove its reservation.
- Delete a record.
- Create a note that mentions users: it notifies them.
The table is generated from the same lists that drive the badges on the operation pages, so the two cannot disagree.
If you must test in production
Link to this section: If you must test in productionPrefer a separate account. If the only account is the real one, reduce the risk:
- Do the automations check above first.
- Mark every record you create so you can find it, avoid it and clean it up. Create a tag such as
api-testwithPOST /apiand attach it to each test record, and give test records an obvious name prefix. Exclude that tag and that prefix from your reports and from the integration's own queries./v1 /tags - Use a fictitious contact (an email at
example.) for test orders and notes, so that no customer receives anything.com - Never call the operations in the table above.
- Use a key limited to the scopes of the test, and revoke it when you finish.
Simulate the API locally
Link to this section: Simulate the API locallyWithout a sandbox, the only risk-free way to exercise writes, idempotency and error paths is a mock server built from the OpenAPI file:
npm install --global @stoplight/prism-cli
prism mock -d https://docs.filmo.io/openapi.jsonDecimal fields come back as placeholder strings, not real amounts. See OpenAPI file and generated clients for the details and the limits of a mock.
Exercise the failure paths
Link to this section: Exercise the failure paths- 401: call with a wrong key and check that your code alerts rather than retrying.
- 403: call an operation with a key that lacks its scope and read
requiredScopefrom the body. - 429: lower your own limit in a test to force it, and check that you wait for
Retry-After. - A timeout during a write: kill the request after sending it, then retry with the same
Idempotency-Keyand confirm that only one record exists.
Clean up
Link to this section: Clean upNot every record can be deleted, and some changes cannot be undone. Archiving an order makes it invisible to the API: its GET answers 404. Check what is possible for each kind of record in Archiving and deleting before you create test data you will want to remove.
Related pages
Link to this section: Related pagesGuides
Going live checklist
Go through these eight checks before your integration runs against production data. Each one points to the guide that explains it.
Guides
OpenAPI file and generated clients
Filmo publishes its whole API as an OpenAPI 3.1 file. Use it to generate a typed client, to import the API into Postman, Insomnia or Bruno, or to run a mock server on your machine.
Guides
Idempotency and safe retries
Send an
Idempotency-Keyheader 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.
Was this page helpful?