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.

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 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.

Today the only trusted source is an LTI 1.3 launch, whose id_token signature is verified against the LMS’s public keys before the launch is accepted. Trust is also opt-in per device, so attaching an LTI launch to an existing device can never silently widen what it retrieves.

Set Audience source on the device (Dashboard → Deployment → Devices):

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 enter comma-separated labels in the Audience column. Leave it empty to keep the library unrestricted.

lti:context:course-4821, lti:context:course-4822

That library is now retrievable only by visitors launching from one of those two courses. To make a library instructor-only regardless of course:

lti:role:instructor

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-host/api/v1/projects/{projectId}/libraries/{libraryId}/audience \
-H "Authorization: Bearer rt_live_..."
{
"libraryId": "",
"audience": ["lti:context:course-4821"],
"restricted": true
}

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-host/api/v1/projects/{projectId}/libraries/{libraryId}/audience \
-H "Authorization: Bearer rt_live_..." \
-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.

audience:read also adds audience and restricted to each entry returned by GET /api/v1/projects/{projectId}/libraries, 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 bypass the filter. Dashboard preview and simulation run as a member of the organization and see everything, which is what makes testing possible. Verify isolation from a real launch, not from Preview.
  • 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 device’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.