Tag
A free-form label attached to a Droplet. Stored as a string in `App.tags`; anyone with edit permission can add or remove one. The curated counterpart with structured field schemas is the [Type](/docs/resources/type) resource.
Tags are how Droplets find each other. Any string in a Droplet's Droplet.tags array is a tag — there is no per-row Tag model. Most tags are free-form labels (onboarding, q3-2026, customer:acme); a small curated subset are Types, which carry a schema of structured fields. The two share the same column in storage but are surfaced as separate resources in the API: GET /api/tags aggregates free-form usage for discovery, GET /api/types returns the registry. See the Types & tags concept doc for the model.
The Tag object
Every Tag carries these attributes.
Attributes
App.tags. Lowercase, trimmed, deduped on write. Maximum 50 chars per tag, 20 tags per Droplet.{
"name": "onboarding",
"usageCount": 14
}Methods
Endpoints to operate on a Tag. The example switches between curl, drop CLI, and MCP via the dropdown — your choice persists across the page.
List tags in use
Aggregate of free-form tag names currently attached to at least one Droplet, sorted by usage count descending. Excludes Type names (registry entries) — those live at GET /api/types. Restricted to @drop-library-admins — the aggregate is unconditioned by per-Droplet ACL, so it could surface tag names from PRIVATE / SPECIFIC droplets to viewers who can't read them; the curator gate keeps that contained. For per-viewer typeahead, use GET /api/tags/autocomplete (ACL-scoped).
Parameters
drop tags list{
"tags": [
{
"name": "onboarding",
"usageCount": 14
},
{
"name": "q3-2026",
"usageCount": 9
},
{
"name": "customer:acme",
"usageCount": 4
}
]
}Create a Tag
Mint a new Tag in a given scope. USER tags are private to the caller; SPACE tags require spaceSlug and are visible to the Space's members; GLOBAL tags are visible to the whole org and restricted to taxonomers (the route enforces). The same name can exist independently across scopes — customer-acme as a USER tag does not collide with customer-acme at SPACE level.
Parameters
drop tags create --name customer-acme --scope SPACE --space-slug platform --description "Acme account workspace."{
"id": "tg_01HZX9...",
"name": "customer-acme",
"scope": "SPACE",
"ownerId": "space_platform",
"description": "Acme account workspace.",
"createdById": "usr_jonathan",
"createdAt": "2026-06-10T18:00:00.000Z",
"updatedAt": "2026-06-10T18:00:00.000Z",
"promotedFromId": null
}Retrieve a Tag (shadow-aware)
Resolve a Tag name to the viewer's active Tag plus any shadowed entries at other scopes. Without an explicit scope, the route picks USER > SPACE > GLOBAL precedence (the same precedence the chip rail uses when wearing a name). With an explicit scope, the result is constrained to that scope and shadows is empty. Useful for explaining "you typed priority — there's your USER priority AND the GLOBAL priority; which did you mean?" in the editor.
Parameters
drop tags get priority{
"resolved": {
"id": "tg_01HZUSR...",
"name": "priority",
"scope": "USER",
"ownerId": "usr_jonathan",
"description": null,
"createdById": "usr_jonathan",
"createdAt": "2026-06-09T12:00:00.000Z",
"updatedAt": "2026-06-09T12:00:00.000Z",
"promotedFromId": null
},
"shadows": [
{
"id": "tg_01HZGLOB...",
"name": "priority",
"scope": "GLOBAL",
"ownerId": null,
"description": "Org-wide priority signal.",
"createdById": "usr_taxonomer",
"createdAt": "2026-05-01T00:00:00.000Z",
"updatedAt": "2026-05-01T00:00:00.000Z",
"promotedFromId": null
}
]
}Update a Tag
Edit a Tag's name or description. Renaming cascades through AppTagApply rows transactionally — every droplet wearing the old name now wears the new one. At least one of newName / description is required (omit both → 400 NO_CHANGES). Rename collisions within the same scope return 409 TAG_NAME_CONFLICT. GLOBAL renames are taxonomer-only; SPACE renames require Space admin; USER renames are owner-only.
Parameters
drop tags update customer-acme --scope SPACE --description "Acme — net-new ARR account."{
"id": "tg_01HZX9...",
"name": "customer-acme",
"scope": "SPACE",
"ownerId": "space_platform",
"description": "Acme — net-new ARR account.",
"createdById": "usr_jonathan",
"createdAt": "2026-06-10T18:00:00.000Z",
"updatedAt": "2026-06-10T19:30:00.000Z",
"promotedFromId": null
}Delete a Tag
Hard-delete a Tag. Cascades to AppTagApply rows transactionally — the chip disappears from every droplet that wore it. GLOBAL deletes are taxonomer-only; SPACE deletes require Space admin; USER deletes are owner-only. Unlike Types, Tags have no soft-delete / restore window — deletion is final. Promoted-from lineage on derived Tags is preserved as a backref (promotedFromId); deleting the source Tag does NOT remove the derived row.
Parameters
drop tags delete customer-acme --scope SPACE{
"deleted": true,
"name": "customer-acme",
"scope": "SPACE",
"applicationsCascaded": 14
}Promote a Tag to a wider scope
Promote a USER or SPACE Tag to SPACE or GLOBAL. Creates a NEW Tag row at the target scope with promotedFromId pointing at the source — the source row is preserved (the owner can drop it manually if redundant). AppTagApply rows attached to the source are NOT rewritten; the chip rail's shadow-aware lookup will surface the wider-scope match on next render. GLOBAL targets are taxonomer-only; SPACE targets require Space admin on targetSpaceSlug. Promoting onto an existing name at the target scope returns 409 TAG_NAME_CONFLICT.
Parameters
drop tags promote customer-acme --source-scope USER --target-scope SPACE --target-space-slug platform{
"promoted": {
"id": "tg_01HZNEW...",
"name": "customer-acme",
"scope": "SPACE",
"ownerId": "space_platform",
"description": null,
"createdById": "usr_jonathan",
"createdAt": "2026-06-10T20:00:00.000Z",
"updatedAt": "2026-06-10T20:00:00.000Z",
"promotedFromId": "tg_01HZSRC..."
},
"source": {
"id": "tg_01HZSRC...",
"name": "customer-acme",
"scope": "USER",
"ownerId": "usr_jonathan",
"description": null,
"createdById": "usr_jonathan",
"createdAt": "2026-06-09T12:00:00.000Z",
"updatedAt": "2026-06-09T12:00:00.000Z",
"promotedFromId": null
}
}Apply a Tag to a Droplet
Attach a Tag to a Droplet by creating an AppTagApply row. The Tag must exist in some scope visible to the caller; without scope the route uses USER > SPACE > GLOBAL precedence (matches the chip rail). Idempotent — applying the same (Droplet, Tag) pair returns 200 with the existing row. The caller must have write access to the Droplet; archived Droplets reject with 409 APP_ARCHIVED. The response includes shadows (Tags at other scopes with the same name) so the UI can warn "you applied the USER copy; there's also a GLOBAL one."
Parameters
drop tags apply q3-strategy-doc --tag-name customer-acme --scope SPACE{
"applied": {
"id": "tg_01HZX9...",
"name": "customer-acme",
"scope": "SPACE",
"ownerId": "space_platform",
"description": "Acme account workspace.",
"createdById": "usr_jonathan",
"createdAt": "2026-06-10T18:00:00.000Z",
"updatedAt": "2026-06-10T18:00:00.000Z",
"promotedFromId": null
},
"shadows": []
}Remove a Tag from a Droplet
Detach a Tag from a Droplet by deleting its AppTagApply row. The Tag row itself is untouched — only the application is removed. Idempotent — removing a Tag that isn't applied returns 200. The caller must have write access to the Droplet. When multiple AppTagApply rows match the same tagName (because shadows are attached at multiple scopes), all matching rows for that name on the Droplet are removed — symmetric with the chip-rail "remove this chip" affordance.
Parameters
drop tags remove q3-strategy-doc customer-acme{
"removed": true,
"slug": "q3-strategy-doc",
"tagName": "customer-acme"
}