Multi-tenancy and data isolation
Every Recruso agency is a fully isolated tenant. Your credentials belong to exactly one agency, and no request you can construct will return another agency’s data.
This is the defining behaviour of the API, and it is the reason several things that look like omissions are deliberate: there is no agency parameter, no way to widen scope, and no distinction in the response between “this record does not exist” and “this record is not yours”.
How isolation works
Section titled “How isolation works”Your access token carries an internal agency identifier. Every database connection the API opens is stamped with it, and SQL Server row-level security filters every query against that stamp before the application sees a row.
The important part is where that happens. Isolation is not a WHERE clause the API remembers to
add — it sits underneath the application, so it applies to every query on every code path, including
ones nobody thought about when writing this page. There is no request parameter, header, query
filter or credential type that changes which agency you see.
What this means for your integration
Section titled “What this means for your integration”- You never send an agency identifier. No endpoint takes one. It is inferred from your token.
- A record belonging to another agency returns
404, not403. Its existence is not disclosed, so you cannot probe for it. See Errors. - One credential set serves one agency. An integration spanning several agencies needs one set per agency, and must keep their tokens apart.
- Do not cache across credentials. Two tokens are two tenants. A cache keyed on the path alone will serve one agency’s data to another.
The second narrowing: branch access
Section titled “The second narrowing: branch access”Tenancy is not the only thing filtering your results, and this is the part that surprises people.
Your API app acts as a specific Recruso user, and that user has a list of branches they can access, configured in the Recruso UI. Everything that belongs to a branch is narrowed to that list, on top of the tenancy filter, for reads and writes alike:
- Branches and their cost centres.
- Clients and leads — and everything beneath a client: its sites, their contacts, activities, rate contracts, cost centres and site information. A site belongs to its client’s branch.
- Workers — and everything a worker holds: compliance documents, licences and convictions, previous employments, activities, documents, the profile image and Driver CPC records.
So GET /v1/branches can legitimately return fewer branches than your agency has, a list leaves out
whatever sits under a branch outside that list, and a real id under such a branch returns 404 exactly
as a cross-tenant id would — whether you are reading, changing or deleting it. A request that would
put a record under an inaccessible branch is refused too: with 400 when the branch is a field in the
request body — creating a client, lead, site or worker there, or moving a client or lead there — and
with 404 when it is part of the path, as for cost centres.
If a record you know exists is missing or 404s, check that user’s branch access before assuming a bug in your request.
Common misconceptions
Section titled “Common misconceptions”“There must be an admin token that sees everything.” There is not. Isolation is enforced below the authorization layer, so it constrains every credential the API issues equally. Recruso staff administer agencies through separate internal tooling, not through a wider-scoped API token.
“An empty list means my query was wrong.” It may simply mean your agency has no such records — or that they sit outside your user’s accessible branches. Both return an empty list rather than an error, because both are ordinary states rather than failures.
“A 404 means the id is invalid.” It means the id is not visible to you. A malformed id is rejected as a 400 instead; a 404 is a statement about visibility, and covers soft-deleted records too. Errors has the full catalogue.
Domain model — the entities this scoping applies to.

