Skip to content

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”.

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.

  • You never send an agency identifier. No endpoint takes one. It is inferred from your token.
  • A record belonging to another agency returns 404, not 403. 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.

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.

“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.