Skip to content

Metadata filtering

Every artifact, memory, and blueprint carries metadata: free-form key/value pairs you (or your AI) attach when the resource is created or imported. Metadata filtering turns that into a query surface, so a team that stamps env: prod or scope: backend onto its resources can ask for them back.

The same filter works in three places:

  • the REST API, through a single metadata query parameter
  • the web app, through the metadata filter control on the Artifacts, Memory, and Blueprints list pages
  • the MCP server, so a connected AI tool can filter without going through the UI

metadata takes a JSON object mapping each key to an array of accepted values, URL-encoded.

GET /api/v1/{team_id}/artifacts?metadata={"env":["prod"]}

URL-encoded, which is what an HTTP client actually sends:

GET /api/v1/{team_id}/artifacts?metadata=%7B%22env%22%3A%5B%22prod%22%5D%7D

It is accepted on:

EndpointResource
GET /api/v1/{team_id}/artifactsartifacts
GET /api/v1/{team_id}/artifacts/{project_id}artifacts in one project
GET /api/v1/{team_id}/memoriesmemories
GET /api/v1/{team_id}/blueprintsblueprints
GET /api/v1/{team_id}/blueprints/{project_id}blueprints in one project

Within a key, any listed value matches. Across keys, every key must match.

{"env": ["prod", "staging"], "team": ["backend"]}

That reads as: env is prod or staging, and team is backend.

If a resource stores a metadata value as an array, the filter matches any element of it. A resource with {"tags": ["api", "billing"]} is returned by {"tags": ["billing"]}. You do not need to know whether a value was stored as a single string or a list.

Metadata values are compared as they are stored. A filter value that unambiguously reads as a number or a boolean also matches a resource that stored it as one, so {"version": ["3"]} matches both {"version": "3"} and {"version": 3}, and {"active": ["true"]} matches both "true" and true.

An empty array means “this key exists, whatever its value”:

{"env": []}

That returns every resource carrying an env key. It is the right filter when you want to find resources that have been tagged at all, rather than tagged with something specific.

A filter is rejected with 400 if it exceeds any of these:

LimitValue
Keys per filter10
Values per key25
Key length255 characters
Value length512 characters

Malformed JSON is also rejected with 400. The error names the limit that was violated, so a client can correct it.

Filtering is only useful if you know what to filter on. Two catalog endpoints report the metadata a team is actually using.

List the distinct keys on a resource type:

GET /api/v1/{team_id}/metadata/keys?resource_type=artifacts

List the distinct values of one key:

GET /api/v1/{team_id}/metadata/values?resource_type=artifacts&key=env

resource_type is one of artifacts, memories, or blueprints. Both accept an optional project_id to narrow to a single project, an optional q substring filter, and an optional limit (default 100, max 500). Both return a truncated flag reporting that more entries exist than were returned.

Results are scoped to your team. One team never sees another team’s metadata, even when both use the same key.

In the web app you do not call these directly: the metadata filter control on each list page reads them as you type.

An AI tool connected to the MCP server has the same two capabilities.

Filter a listing by passing metadata to vibexp_io_list_resources. Because MCP is JSON based, it takes the object directly rather than a URL-encoded string:

{
"team_id": "your-team",
"resource_type": "artifact",
"metadata": {"env": ["prod", "staging"], "team": ["backend"]}
}

The semantics are identical to the REST parameter, including the empty-array key-exists form and the limits above.

Discover keys and values with vibexp_io_list_resource_metadata. Omit key to list the distinct metadata keys on a resource type, or supply key to list that key’s distinct values:

{
"team_id": "your-team",
"resource_type": "artifact",
"key": "env"
}

This is what lets an agent build a filter from real keys instead of guessing at them.

Three earlier query parameters were removed and no longer filter anything. Requests carrying them still succeed, so an old client or a bookmarked URL degrades to an unfiltered list rather than an error. Nothing is silently mis-filtered, but nothing is filtered either.

RemovedWas used onReplacement
metadata_<key>=valueartifacts, blueprintsmetadata={"<key>":["value"]}
metadata_key + metadata_valuememoriesmetadata={"<key>":["value"]}

The endpoint GET /api/v1/{team_id}/memories/search was also removed. It filtered on a single metadata pair, which GET /api/v1/{team_id}/memories now does through metadata, with more expressive semantics.

If you have scripts or integrations using the old parameters, translate them to metadata before upgrading. The new form is strictly more capable: it filters on several keys at once, accepts several values per key, matches inside arrays, and is served by an index, so it is faster on large teams.