We’re building an import tool against the FreeAgent v2 API (sandbox) and ran into three things worth flagging to the docs/API team - one missing documentation, two behaviours that don’t match what’s documented. All requests/responses below are exact, captured directly from a sandbox account and redacted only of account-identifying resource IDs (replaced with placeholder numbers, kept internally consistent) and access tokens.
1. Batch-create for Expenses has a documented request but no documented response
The Expenses page shows a request example for creating several expenses in one call, then moves straight on to “Update an expense” - there’s no response example for the batch call at all.
Determined the actual shape empirically:
Success
POST https://api.sandbox.freeagent.com/v2/expenses
Request body:
{
"expenses": [
{
"user": "https://api.sandbox.freeagent.com/v2/users/100001",
"category": "https://api.sandbox.freeagent.com/v2/categories/100002",
"dated_on": "2026-09-15",
"gross_value": "10.00",
"description": "Test expense A"
},
{
"user": "https://api.sandbox.freeagent.com/v2/users/100001",
"category": "https://api.sandbox.freeagent.com/v2/categories/100002",
"dated_on": "2026-09-15",
"gross_value": "20.00",
"description": "Test expense B"
}
]
}
Response - 201 Created:
{
"expenses": [
{
"url": "https://api.sandbox.freeagent.com/v2/expenses/900001",
"user": "https://api.sandbox.freeagent.com/v2/users/100001",
"category": "https://api.sandbox.freeagent.com/v2/categories/100002",
"dated_on": "2026-09-15",
"currency": "GBP",
"gross_value": "10.0",
"native_gross_value": "10.0",
"sales_tax_rate": "20.0",
"sales_tax_value": "1.67",
"native_sales_tax_value": "1.67",
"sales_tax_status": "TAXABLE",
"description": "Test expense A",
"updated_at": "2026-09-15T14:31:55.000Z",
"created_at": "2026-09-15T14:31:55.000Z"
},
{
"url": "https://api.sandbox.freeagent.com/v2/expenses/900002",
"user": "https://api.sandbox.freeagent.com/v2/users/100001",
"category": "https://api.sandbox.freeagent.com/v2/categories/100002",
"dated_on": "2026-09-15",
"currency": "GBP",
"gross_value": "20.0",
"native_gross_value": "20.0",
"sales_tax_rate": "20.0",
"sales_tax_value": "3.33",
"native_sales_tax_value": "3.33",
"sales_tax_status": "TAXABLE",
"description": "Test expense B",
"updated_at": "2026-09-15T14:31:56.000Z",
"created_at": "2026-09-15T14:31:56.000Z"
}
]
}
One fully-populated record per submitted item, in submission order - safe to zip positionally back onto the request.
Failure - atomic, no partial success
POST https://api.sandbox.freeagent.com/v2/expenses
Request body (first item valid, second has a blank description and no
gross_value):
{
"expenses": [
{
"user": "https://api.sandbox.freeagent.com/v2/users/100001",
"category": "https://api.sandbox.freeagent.com/v2/categories/100002",
"dated_on": "2026-09-15",
"gross_value": "10.00",
"description": "Valid row"
},
{
"user": "https://api.sandbox.freeagent.com/v2/users/100001",
"category": "https://api.sandbox.freeagent.com/v2/categories/100002",
"dated_on": "2026-09-15",
"description": ""
}
]
}
Response - 422 Unprocessable Entity:
{
"errors": [
{ "message": "description can't be blank" },
{ "message": "gross_value cannot be zero" }
]
}
We confirmed by listing expenses immediately afterwards that neither item was created - the batch is rejected atomically, not a partial success. Both error messages came from the second (invalid) item; the first (valid) item contributed nothing to the array, and nothing in the response identifies which submitted item each error belongs to. With more than two items in a batch there’s no way to tell which one(s) caused the rejection without resubmitting individually.
Ask: document both the success and failure response shapes here, and ideally give each error an index or reference back to the submitted item it came from.
2. Estimate status accepts fewer values at create time than documented, and silently rewrites one of them
The Estimate Attributes table lists status as required, with six valid values: Draft, Sent, Open, Approved, Rejected, Invoiced. Nothing on the “Create an estimate” section suggests any restriction at create time vs. update.
Testing each value directly:
POST https://api.sandbox.freeagent.com/v2/estimates
status: "Draft" - created as sent:
{ "estimate": { "contact": "https://api.sandbox.freeagent.com/v2/contacts/800001", "dated_on": "2026-09-15", "reference": "STATUS-TEST-Draft", "currency": "GBP", "estimate_type": "Estimate", "status": "Draft" } }
201 Created, "status": "Draft" in the response - as expected.
status: "Sent" - accepted, but silently changed:
{ "estimate": { "contact": "https://api.sandbox.freeagent.com/v2/contacts/800002", "dated_on": "2026-09-15", "reference": "STATUS-TEST-Sent-2", "currency": "GBP", "estimate_type": "Estimate", "status": "Sent" } }
201 Created:
{
"estimate": {
"url": "https://api.sandbox.freeagent.com/v2/estimates/700002",
"contact": "https://api.sandbox.freeagent.com/v2/contacts/800002",
"reference": "STATUS-TEST-Sent-2",
"estimate_type": "Estimate",
"dated_on": "2026-09-15",
"status": "Open",
"currency": "GBP",
"net_value": "0.0",
"total_value": "0.0",
"contact_name": "API Test Ltd 2",
"involves_sales_tax": false,
"is_interim_uk_vat": false,
"include_sales_tax_on_total_value": true,
"updated_at": "2026-09-15T14:32:42.000Z",
"created_at": "2026-09-15T14:32:42.000Z",
"estimate_items": []
}
}
We sent "status": "Sent" and got back "status": "Open" - 201, no error, nothing in the response flags that the value was changed.
status: "Approved" and status: "Rejected" - both created as sent, no substitution (bodies otherwise identical to the Sent case above, just with "status": "Approved" / "status": "Rejected" in both request and response).
status: "Open":
{ "estimate": { "contact": "...", "dated_on": "2026-09-15", "reference": "STATUS-TEST-Open", "currency": "GBP", "estimate_type": "Estimate", "status": "Open" } }
422 Unprocessable Entity:
{ "errors": [ { "message": "status is not valid" } ] }
status: "Invoiced" - same request shape, same 422:
{ "errors": [ { "message": "status is not valid" } ] }
So of the six documented values, only Draft/Approved/Rejected are created as given; Sent is silently rewritten to Open; and Open/Invoiced are rejected outright, despite being listed as valid status values on the same attributes table.
We suspect this connects to the “Update an estimate” section’s note that “to update the status of an estimate you must use the status transitions to mark an estimate as Draft, Sent, Approved or Rejected” - which reads as being about the update endpoint, but appears to also constrain (and, for Sent, silently rewrite) what’s accepted at create.
One more oddity found while cleaning up test data: an estimate created with a non-Draft status (e.g. Approved) cannot be DELETEd directly - DELETE /v2/estimates/:id returns 409 with no message body beyond "Unknown error". Setting status back to Draft via PUT first, then DELETE, works. This isn’t mentioned anywhere in the Estimates docs either.
Ask: either (a) document which of the six enum values are actually accepted at create time vs. only reachable via a status transition, and that Sent becomes Open if sent directly (or fix that to either reject or accept it as given), or (b) if this is all intentional, document it next to the attribute table and the delete endpoint respectively, rather than leaving it to be discovered by testing every value by hand.
Environment
All requests above ran against api.sandbox.freeagent.com on a sandbox company created and used solely for this testing; every record created during testing (expenses, estimates, the test contacts) has since been deleted. Happy to provide anything else that would help reproduce this.