Skip to main content

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

  1. Access token: a Playbook API token, sent as a Bearer token.
  2. Organization slug (slug): identifier for your workspace (e.g. coolclient-ltd).

Step 1 — Find the person

GET /v1/{slug}/users
Authorization: Bearer YOUR_TOKEN
ParameterTypeRequiredDescription
querystringnoFind members by name or email. Fuzzy match — partial and misspelled names work. Ignored below 2 characters.
pageintegernoPage number (default: 1).
per_pageintegernoResults 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",
"email": "[email protected]",
"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.

EndpointParameterUse it when
GET /v1/{slug}/assetsuploaded_by (one person)You want a complete list of everything one person added.
GET /v1/{slug}/searchfilters[uploaded_by][]You are also searching by keyword, tag, or media type.
GET /v1/{slug}/ai_searchfilters[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_by so you can see who was picked.
  • Several members match — the request fails with 422 and 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_by is null and 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.