Skip to content

Audience Labels

Audience labels let a single assistant serve several groups from the same project while keeping each group’s knowledge private to that group.

In plain English: one assistant can have a single knowledge base, but different people should only see the shelves that are relevant to them. If a learner is in course A, they should not retrieve course B’s materials. If an instructor is in a different role, they may see a different subset of the same project.

The classic case: you ingest documents through the API on behalf of a customer whose source system (an LMS, an intranet, a document store) had per-user permissions. Once the content is in Ragtime, those permissions are gone. Audience labels restore a coarse version of them.

A good mental model is a library as a locked cabinet. The cabinet has labels on it such as lti:context:course-4821 or lti:role:instructor. A visitor carries a verified set of labels from a trusted launch. Ragtime only opens the cabinet when the labels overlap.

  • A library is the permission boundary. There are no per-document or per-chunk ACLs — if a visitor can retrieve from a library, they can retrieve anything in it.
  • Each link between a project and a library carries a list of audience labels. An empty list means “unrestricted”.
  • A visitor carries a set of verified labels, derived server-side.
  • Retrieval returns a library only if it is unrestricted, or if the visitor’s labels overlap the library’s labels.

Because the filter is applied inside the retrieval query rather than after it, restricted content never enters the model’s context, never appears in citations, and never leaks through diagnostics.

Visitor labels are only ever derived from a source Ragtime can verify. They are never read from a request body, query string, or header — a caller-supplied label would be a trivial authorization bypass.

Ragtime derives labels from verified organization membership, organization roles, project membership, a specific signed-in user, and LTI 1.3 launches. LTI launch tokens are verified against the learning platform’s public keys before their context and role values become available as audience rules.

Set Audience source on the channel (Dashboard → Deployment → Channels):

Setting Effect
None Visitors carry no labels. Only unrestricted libraries are searchable.
LTI launch Labels are derived from the verified launch.

An LTI launch produces two kinds of label:

lti:context:<contextId> # the course / context the learner launched from
lti:role:<role> # instructor, learner, contentdeveloper, …

Roles are normalised: the full IMS role URI http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor and the short form Instructor both collapse to lti:role:instructor.

Go to Dashboard → Settings → Access, find the linked library, and use the tag picker in the Audience column. Leave it empty to keep the library unrestricted.

For your own groups, enter ordinary labels such as staff or customers. For identity-based access, select a friendly verified rule:

  • all members of an organization
  • an organization role
  • members of a project
  • a specific organization member
  • a context or role observed in a successful verified learning-platform launch

Ragtime stores the canonical org:…, org-role:…, project:…, user:…, or lti:… value behind the friendly choice. Do not type those internal identifiers yourself. Manually invented reserved values are rejected.

Custom labels are lowercased and de-duplicated on save. A link may hold up to 50 labels.

If the groups come from a system that already knows them, you probably do not want to maintain labels by hand. Two scopes cover this, and they are deliberately separate from ingest: a compromised ingestion connector must not be able to clear a library’s labels and expose it to everyone.

Scope Grants
audience:read Read a library’s labels
audience:write Replace a library’s labels

Read the current labels:

Terminal window
curl https://your-ragtime-instance.com/api/v1/projects/{projectId}/libraries/{libraryId}/audience \
-H "Authorization: Bearer rt_live_YOUR_KEY_HERE"
{
"libraryId": "…",
"audience": ["lti:context:course-4821"],
"restricted": true,
"availableDerivedAudiences": [
{
"value": "lti:context:course-4821",
"label": "Academy: context course-4821",
"description": "Anyone arriving through this verified course or learning context.",
"group": "Learning platform"
}
]
}

When the calling key also has audience:write, availableDerivedAudiences is the authoritative list of reserved values it may submit. Read-only audience keys do not receive this derived-rule catalog. Use the friendly metadata in configuration interfaces instead of asking an operator to type internal IDs. Ordinary custom labels do not need to appear in this list. The project-scoped API catalog excludes specific-user rules and sibling projects to avoid exposing organization-wide member or project metadata; configure specific-user rules in the dashboard.

Replace them. PUT overwrites the whole list rather than merging, so send the complete set every time:

Terminal window
curl -X PUT https://your-ragtime-instance.com/api/v1/projects/{projectId}/libraries/{libraryId}/audience \
-H "Authorization: Bearer rt_live_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{"audience": ["lti:context:course-4821", "lti:context:course-4822"]}'

Sending {"audience": []} removes the restriction and makes the library visible to every visitor of the project. This is a widening operation and is recorded in the audit log along with the previous value.

Labels over the limits are rejected, not silently dropped — a caller that believes it restricted a library but did not is the exact failure this feature exists to prevent.

Reserved values are also rejected unless they are currently present in availableDerivedAudiences or already stored on that library. An unavailable legacy value may remain while other labels are edited, and can always be removed, but it cannot be newly added. A PUT with such a value returns 400 and lists the offending values in details.invalidLabels:

{
"code": "invalid_request",
"error": "Some derived audience labels are not available to this project.",
"retryable": false,
"hint": "Use only values listed in `availableDerivedAudiences` from GET on this endpoint (requires the `audience:write` scope).",
"details": { "invalidLabels": ["lti:context:course-9999"] }
}

A library that is not linked to the project returns 404. Authentication, scope, project and rate-limit refusals work the same on every endpoint; see Errors and Rate limits.

GET /api/v1/projects/{projectId}/libraries requires the ingest scope. When the key also holds audience:read, each entry it returns gains audience and restricted, which is the easiest way for a sync job to reconcile state in one call. Keys without that scope see the list without them, since label names can themselves reveal cohort names.

  • Existing projects are unaffected. Every pre-existing link has an empty audience, which means unrestricted.
  • No labels means unrestricted-only, not deny-all. A visitor with no verified labels still reaches every library you have not restricted.
  • Organization members are also filtered. A labelled library is available only when the member presents a matching derived or granted label.
  • A restricted library is invisible, not empty. It does not appear in retrieval diagnostics either, so its name cannot be inferred.
  • Assistant topics follow the same rule. Topics from restricted libraries are excluded from the compiled system prompt, because that prompt is shared across every visitor to the assistant.

If you label a library but leave the channel’s Audience source on None, that library becomes unreachable by everyone — no visitor can produce a matching label. The dashboard warns about this, but it is the most likely mistake when first setting things up.

Audience labels are a coarse partition, deliberately. They do not reproduce per-user document permissions from a source system, and they do not attempt to track individual users. If two people belong to the same course, they see the same knowledge. Model your libraries around the groups that genuinely need different answers, not around individual users.

If you need a fully separate persona, prompt, or model as well as separate knowledge, use a separate project instead.