People and Uploaders
Every asset in Playbook records who added it. This guide covers how to look a person up, how to filter assets by uploader, and what happens when a name is ambiguous.
The short version: people are addressed by token, and you get a token by searching for them. A person's name typed into a search query will not find their uploads — it searches the assets' own titles, tags and descriptions.
Prerequisites
- Access token: a Playbook API token, sent as a Bearer token.
- Organization slug (
slug): identifier for your workspace (e.g.coolclient-ltd).
Step 1 — Find the person
GET /v1/{slug}/users
Authorization: Bearer YOUR_TOKEN
| Parameter | Type | Required | Description |
|---|---|---|---|
| query | string | no | Find members by name or email. Fuzzy match — partial and misspelled names work. Ignored below 2 characters. |
| page | integer | no | Page number (default: 1). |
| per_page | integer | no | Results per page (default: 20). |
Omit query to list everyone. The listing only ever contains people you are allowed
to see, and email is present only for those you have permission to view.
curl "https://api.playbook.com/v1/coolclient-ltd/users?query=quackenbsh" \
-H "Authorization: Bearer YOUR_TOKEN" | jq
Note the deliberate misspelling — the match is trigram-based, so it still finds the right person:
{
"data": [
{
"id": 42,
"token": "asg_9f2c8a1b",
"name": "Zephyrine Quackenbush",
"given_name": "Zephyrine",
"family_name": "Quackenbush"
}
],
"pagy": { "current_page": 1, "total_pages": 1, "total_count": 1 }
}
token is the value every uploader filter takes.
Step 2 — Filter assets by uploader
Three endpoints accept an uploader filter. Which one you want depends on the question.
| Endpoint | Parameter | Use it when |
|---|---|---|
GET /v1/{slug}/assets | uploaded_by (one person) | You want a complete list of everything one person added. |
GET /v1/{slug}/search | filters[uploaded_by][] | You are also searching by keyword, tag, or media type. |
GET /v1/{slug}/ai_search | filters[uploaded_by][] | You are also searching by visual content, or by date range. |
/assets walks the database directly with stable cursor pagination, so it returns
everything without a result ceiling. The two search endpoints rank by relevance and
are capped, but let you combine the uploader with the rest of their filters and accept
several people at once.
# Everything one person ever uploaded, complete
curl "https://api.playbook.com/v1/coolclient-ltd/assets?uploaded_by=asg_9f2c8a1b&cursor=true" \
-H "Authorization: Bearer YOUR_TOKEN" | jq
# What two people uploaded in the last week
curl --globoff "https://api.playbook.com/v1/coolclient-ltd/ai_search?query=&filters[uploaded_by][]=asg_9f2c8a1b&filters[uploaded_by][]=asg_1d4e&filters[date_from]=2026-08-13" \
-H "Authorization: Bearer YOUR_TOKEN" | jq
Dates default to the upload date when you filter by uploader
filters[date_field] selects which date a range applies to: original is when the
file itself was created (its EXIF date, where available), uploaded is when it landed
in Playbook. It normally defaults to original.
When filters[uploaded_by] is present it defaults to uploaded instead, because
"what did this person upload last week" is almost never a question about when a photo
was taken. Pass filters[date_field]=original explicitly if you do want the file's own
date.
Passing a name instead of a token
Every uploader filter also accepts a plain name, resolved for you:
- Exactly one member matches — the request succeeds. All three endpoints report
the substitution back under
meta.resolved_uploaded_byso you can see who was picked. - Several members match — the request fails with
422and lists the candidates. Nothing is guessed. - Nobody matches — the request fails with
422.
{
"errors": [
{
"message": "\"Quackenbush\" matches 2 workspace members. Retry with one of the tokens listed in extensions.candidates.",
"extensions": {
"code": "ambiguous_uploader",
"input": "Quackenbush",
"candidates": [
{ "token": "asg_9f2c8a1b", "name": "Zephyrine Quackenbush" },
{ "token": "asg_5b71e3d0", "name": "Bartholomew Quackenbush" }
]
}
}
]
}
Prefer resolving the person yourself first. The fallback exists so that a near miss fails loudly instead of returning an empty list, which would read as "this person uploaded nothing".
At most 20 people can be passed in one request (too_many_uploaders), since every
value that is not already a token costs its own lookup.
Dates are validated, not silently ignored
filters[date_field] must be exactly original or uploaded, and filters[date_from] /
filters[date_to] must be an ISO 8601 date or datetime. A misspelled field name or an
unparseable date returns 422 (invalid_date_field / invalid_date) rather than quietly
filtering on the wrong column or dropping the range.
Reading the uploader off an asset
Every asset payload carries an uploaded_by stamp:
{
"token": "hero-jpg",
"title": "Hero Banner",
"uploaded_by": { "token": "asg_9f2c8a1b", "name": "Zephyrine Quackenbush" }
}
Two cases where it is null or unfilterable:
- The uploader was deleted. Their assets survive, but the link to them does not, so
uploaded_byisnulland those assets cannot be found by uploader. - The uploader was anonymous — someone adding files through a public upload board,
or an integration such as the Slack or Discord app. These are reported on the asset
so you can see where a file came from, but they are not valid filter values; a
request that passes one fails with
422 unknown_uploader.
Using this from an AI agent
Through the Playbook MCP server the same two steps are list_members followed by
ai_search, search_assets, or list_assets with uploaded_by. Call list_members
first whenever a request names a person — passing the name to a search tool's query
will return confident, unrelated results.