Skip to main content

Resource filters: controlling what your API integrations can access

How resource filters control what your API integrations can access — concept, visual builder walkthrough, attribute reference, edit-safety, and a worked example.

When you create an API key, you decide what data the integration can touch. Resource filters are how you express that decision. A filter says which entities the integration can see — families with a certain tag, details in a certain project, stashes a specific user created — and pairs with an action (read, write, or download) to form a complete scope grant.

This article walks through what filters are, when you actually need a custom one, and how to author one in the admin UI.

If you only need to grant "all families / all details / all stashes / all tags in a workspace," you can skip this article — Pirros ships system filters for those out of the box, and you'll see them as options when creating an API key. Read on if you need narrower access.


How filters fit into API access

A scope grant on an API key has three parts:

[workspace]  +  [filter]  +  [action]

  • The workspace scopes the grant to one workspace in your firm.

  • The filter says which entities of a specific type (Family / Detail / Stash / Tag) the integration can touch in that workspace.

  • The action says what they can do — read, write, or download.

A single API key can hold many of these grants — different workspaces, different filters, different actions. When the integration makes a request, Pirros checks whether any of the key's grants permits it (OR-semantics across grants).


When you need a custom filter

You want to grant...

Use

Every family in a workspace

The system filter default:families

Every detail in a workspace

The system filter default:details

Every stash in a workspace

The system filter default:stashes

Every tag in a workspace

The system filter default:tags

Only families in one project

A custom filter

Only details with a specific tag

A custom filter

Only "typical" details, not project-specific ones

A custom filter

Only stashes a specific user created

A custom filter

System filters are pre-installed in every workspace and can't be edited or removed. Custom filters are the ones you author below.


Anatomy of a filter

Every filter has:

  • A name — admin-facing label (required, max 255 characters). Pick something descriptive (Concrete details (typical library), not Filter 3). An optional description (max 500 characters) is useful when other admins might wonder why you authored a particular filter.

  • A workspace — filters live inside one workspace and can't span workspaces. Both the workspace and the entity type are locked after creation (the edit form hides them); to change either, create a new filter.

  • An entity type — Family, Detail, Stash, or Tag.

  • One or more conditions — what the entity must satisfy. In the visual builder, conditions can be combined with AND and OR and grouped (nested up to 5 levels deep, max 10 conditions per group).

Note on NOT: the underlying predicate engine and the create/update API support a NOT (negation) node, but the visual builder does not expose a NOT control — the group operator offers only AND and OR, and rows have no negation toggle. If you need negation, you'd build it through the API, not the UI.


Creating a filter

Go to Settings → Resource Filters.

Click New filter. The builder asks for:

1. Workspace. Pick the workspace this filter lives in. (Can't be changed later.)

2. Entity type. Family, Detail, Stash, or Tag. The available attributes change based on what you pick — see the Attribute reference below. (Can't be changed later.)

3. Name and description. The name shows up wherever this filter is referenced — the API Keys creation flow, the filters list, audit logs.

4. Conditions. Build the predicate using the visual rows. Each row is [attribute] [operator] [value], where the builder shows the raw attribute and operator codes (e.g. project_type, eq, intersects) rather than prose labels. Add a row with the + button. Combine rows into groups using the AND / OR group control.

5. Preview the match count. Click Preview match count to run a count of how many entities in the workspace currently satisfy the filter. (The count is not live — it runs when you click, then refreshes as you edit.) If the filter matches more than 10,000, the count shows as 10,000+ (truncated) rather than an exact number. Preview is available for Family, Detail, and Stash filters; Tag filters can't be previewed.

Always check the match count before saving. Two failure modes to catch:

  • Zero matches — for a list/search grant, the integration's requests will return 403 insufficient_scope. Usually a sign that conditions are too strict or have a typo.

  • Way more than expected — usually a sign of a missing AND clause or an OR where you meant AND.

6. Save. The filter is now available in the API Keys creation flow as a scope option.


Attribute reference

What you can filter on, per entity type. The builder uses the raw field codes shown below.

Family (evaluated via the database — all attributes work)

Attribute code

Type

Example

name

text

name eq "Beam Column Joint"

category

text

category eq "Structural"

classification

text

classification eq "Concrete"

tags

list

tags intersects ["concrete","beam"]

project_id

UUID

project_id in ["<project-uuid-1>","<project-uuid-2>"]

created_at

date

created_at gt "2025-01-01"

Detail

Attribute code

Type

Example

type

text

type eq "concrete"

project_type

text

project_type eq "typical" (vs. project-specific)

tags

list

tags contains ["concrete","reviewed"]

ℹ️ v1 limitation for details. Detail search / list / preview runs against the search index, which only supports type, project_type, and tags. Filtering details by status or created_at is not supported in v1 (planned for a later release). Filtering by a specific project_id works when reading a single detail by ID, but is not available in detail search/preview, so for a search-backed integration treat project_id as not-yet-available too.

Stash (evaluated via the database — all attributes work)

Attribute code

Type

Example

project_id

UUID

project_id eq "<project-uuid>"

is_permanent

true/false

is_permanent eq true

created_at

date

created_at gt "2026-01-01"

created_by

UUID

created_by eq "<user-uuid>"

Values for project_id, created_by, and other UUID-typed attributes must be UUIDs, not names or emails. Date values must be ISO-8601.

Tag

Attribute code

Type

Example

name

text

name eq "concrete"


Operators

Operators available per value type (builder shows the raw codes):

Value type

Operators (code → meaning)

Text

eq equals, neq not equals, in is one of, nin is none of

Number

eq, neq, gt greater than, gte greater or equal, lt less than, lte less or equal, in is one of, nin is none of

Date

eq, neq, gt, gte, lt, lte (no in/nin)

List (tags)

intersects has any of, contains has all of, eq_set equals exactly

True / False

eq

UUID (project_id, created_by, …)

eq, neq, in, nin

Notes: Date does not support in/nin ("is one of"). For Detail filters, the list operator eq_set ("equals exactly") is not supported by the search index and is rejected at evaluation — use intersects/contains for detail tag filters.

The builder only shows operators valid for the attribute you've picked.


Combining conditions

Rows in the builder can be grouped with AND and OR (the visual builder does not offer NOT — see the note under "Anatomy of a filter"):

  • AND (default) — an entity must satisfy all conditions in the group.

  • OR — an entity must satisfy at least one condition in the group.

You can nest groups up to 5 levels deep (max 10 conditions per group). For example, "concrete details OR steel details":

[ type eq "concrete" ]  OR  [ type eq "steel" ]


Attaching a filter to an API key

Once you save a filter, it shows up in the API Keys creation flow alongside the system filters. Go to Settings → API Keys, create or edit a key, find your filter in the scope grants section, and check the action(s) you want to grant (read / write / download — note tag filters offer only read / write, no download).

An API key can hold multiple filters of the same type — Pirros uses OR-semantics across grants. For example, a key with both default:families and "Concrete details (typical library)" can read every family and read concrete details from the typical library, but not other details.


⚠️ Editing a filter changes access immediately

Editing a filter's conditions immediately changes effective access for every API key that holds that filter — including in-flight tokens. There is no version, no grace period, no token-rotation gate: the API loads the current filter row on every request. Treat filter edits as a permissions change, not a refactor.

When you save an edit, the confirmation dialog tells you how many API keys reference this filter (e.g. "This will immediately affect 3 credentials. Continue?"). It shows the count only — not the individual key names. Read this number before clicking Save changes.

If you need to be conservative — for example, you're tightening a filter and want to verify integrations still work — do this instead:

  1. Create a new filter with the new conditions.

  2. Add it to the API keys that should use the new behavior (alongside the old filter).

  3. Verify the integrations still work with the broader access (old + new).

  4. Remove the old filter from those keys.

  5. Archive the old filter when nothing references it.

This pattern lets you roll back instantly if something breaks.


Archiving a filter

To remove a filter, Archive it from the filters list. Archiving is a soft-delete:

  • The filter no longer evaluates — any API key still holding it loses that grant on its next request (list/write → 403 insufficient_scope; single-resource GET → 404 not_found).

  • The filter row is preserved so audit history still resolves to a real name.

  • Archived filters can't be un-archived from the UI. If you need it back, create a new one with the same conditions.

You can't archive or edit a system filter (both actions are disabled for them).


System filters

Pirros ships system filters per firm: four are created in each workspace, plus one firm-global filter:

Filter (name shown in UI)

Scope

Allows

default:families

per workspace

Every family in the workspace

default:details

per workspace

Every detail in the workspace

default:stashes

per workspace

Every stash in the workspace

default:tags

per workspace

Every tag in the workspace

default:any

firm-global (one per firm)

Every entity type across the firm — appears once, labeled (firm-global)

System filters appear with their literal default:* names, can't be edited or archived, and are pre-installed when the workspace (or firm) is created. Use them as the default for broad-access integrations.


Worked example: data warehouse with narrow access

Scenario. Acme Engineering uses a third-party data-warehouse vendor to pull detail metadata into a reporting dashboard. The vendor should only see concrete-tagged details in Acme's typical-details library — not project-specific details, and not steel or wood details.

Step 1. Author the filter.

Go to Settings → Resource Filters → New filter.

  • Workspace: Acme Main

  • Entity type: Detail

  • Name: Concrete details (typical library)

  • Description: Used by the data-warehouse vendor. Concrete details only, typical project type.

  • Conditions:

  • project_type eq "typical"

  • AND tags intersects ["concrete"]

Click Preview match count — it shows 1,247 details match. That tracks with what Acme expects. Save.

Step 2. Attach it to the vendor's API key.

Go to Settings → API Keys, either edit the vendor's existing key or create a new one named Acme Data Warehouse. In the scope grants section, find Concrete details (typical library), check Read, and save.

Step 3. Confirm.

When the vendor's integration calls GET /v1/workspaces/{acme-uuid}/details/{some-detail-id}:

  • If that detail is concrete + typical → 200 OK

  • If that detail is concrete + project-specific → 404 not_found

  • If that detail is steel + typical → 404 not_found

For a single-detail GET, a detail outside the granted filter returns 404 not_found (no existence disclosure) — not a 403. (403 insufficient_scope is what you'd see on the list/search endpoint, e.g. GET /details, when no eligible filter is granted.) Either way, the vendor sees exactly the slice Acme intended, and nothing else.


What's next

If you run into trouble authoring a filter or aren't sure how to express what you need, reach out to [email protected].

Did this answer your question?