Core Resources
View as Markdown

Type

A Type is an admin-curated Droplet noun (e.g. `recommendation`, `decision`, `incident`) that carries a schema of structured fields the platform renders, filters, and sorts. Each Droplet wears at most one Type.

The Type resource describes how Types live on Drop: Type names appear in each Droplet's Droplet.tags string array, with structured field values stored on App.typeFields keyed by Type name. The Type itself is a registry row with a schema declaring which primitive fields + custom fields its Droplets carry. See the Types concept doc for the why and the model.

The Type object

Every Type carries these attributes.

Attributes

name
string
Lowercase tag name (e.g. recommendation). Unique within (scope, ownerId) — see scope / ownerId below. 2–50 chars, alphanumeric + hyphens, no colons (reserved for namespaced tags). May be created from scratch, or promoted up from an existing tag (USER → SPACE → GLOBAL).
description
string· nullable
One-line prose shown in the type picker, on the admin curation page, and in the chip rail tooltip.
schema
object
Field schema as JSON. primitives is an opt-in subset of the system primitive registry; status (when opted in) declares the per-Type enum values; customFields is the array of Type-bound custom-field declarations. See the concept doc for the primitive registry shape.
promotedAt
timestamp
When the Type was first promoted into the registry.
promotedById
string
User ID of the curator who promoted it. Referenced from the audit log.
deprecatedAt
timestamp· nullable
Set when the Type is demoted back to a plain tag. Existing field values stay on Droplets but stop rendering (no widget). Restorable within 30 days; orphaned blocks are GC-deleted after that.
THE TYPE OBJECT
{
  "id": "stg_recommendation",
  "name": "recommendation",
  "description": "A proposal awaiting decision. Carries status, progress, owner.",
  "schema": {
    "primitives": {
      "status": {
        "values": [
          "draft",
          "in-flight",
          "decided",
          "blocked"
        ]
      },
      "progress": true,
      "priority": true,
      "due": true,
      "trafficLight": true,
      "owner": true
    },
    "customFields": [
      {
        "key": "rice",
        "type": "int",
        "label": "RICE score"
      },
      {
        "key": "quarter",
        "type": "string",
        "label": "Quarter"
      }
    ]
  },
  "promotedById": "usr_kate",
  "promotedAt": "2026-06-10T14:00:00.000Z",
  "deprecatedAt": null
}

Methods

Endpoints to operate on a Type. The example switches between curl, drop CLI, and MCP via the dropdown — your choice persists across the page.

GET

List Types

/api/types

Return the Type registry (admin-curated noun vocabulary). Demoted Types are excluded by default; pass includeDemoted=true to see them. For the free-form tag aggregate (non-Type names in use), see GET /api/tags / drop tags list.

Parameters

includeDemoted
boolean· query
Include demoted Types (default false).
scope
string· query
Filter by tier (USER | SPACE | GLOBAL). Default: viewer-visible (USER + member Spaces + GLOBAL).
spaceSlug
string· query
When scope=SPACE, the Space slug to filter by.
GET/api/types
drop types list
Response200
{
  "types": [
    {
      "id": "tag_recommendation",
      "name": "recommendation",
      "description": "A proposal awaiting decision.",
      "schema": {
        "primitives": {
          "status": {
            "values": [
              "draft",
              "in-flight",
              "decided"
            ]
          },
          "priority": true
        },
        "customFields": []
      },
      "promotedAt": "2026-06-04T18:00:00.000Z",
      "deprecatedAt": null,
      "promotedBy": {
        "email": "jonathan@himarley.com",
        "name": "Jonathan Tushman"
      },
      "usageCount": 6,
      "scope": "GLOBAL",
      "ownerId": null,
      "promotedFromId": null
    }
  ]
}
POST

Create a Type

/api/types

Mint a new Type at the given scope (USER / SPACE / GLOBAL — see scope). USER scope is open to any authenticated user; SPACE requires Space admin on spaceSlug; GLOBAL requires LIBRARY_ADMIN. The schema declares which of the six primitive fields the Type uses (status / progress / priority / due / trafficLight / owner) and any custom key:value fields. status requires per-Type enum values; the other primitives canonicalize to true. Custom fields are capped at 30 per Type; enum values capped at 50 each.

Parameters

name
string· body· required
The Type name (slug-style).
schema
object· body· required
The Type schema (primitives + customFields).
description
string· body
Optional summary (≤500 chars).
promoteFrom
string· body
Audit metadata for promote-from-tag.
scope
string· body
Tier (USER | SPACE | GLOBAL). Default GLOBAL.
spaceSlug
string· body
Required when scope=SPACE.
POST/api/types
drop types create --name recommendation --description "A proposal awaiting decision." --schema "[object Object]" --scope GLOBAL
Response201
{
  "id": "tag_recommendation",
  "name": "recommendation",
  "description": "A proposal awaiting decision.",
  "schema": {
    "primitives": {
      "status": {
        "values": [
          "draft",
          "in-flight",
          "decided"
        ]
      },
      "priority": true,
      "due": true
    },
    "customFields": [
      {
        "key": "rationale",
        "type": "markdown"
      }
    ]
  },
  "promotedAt": "2026-06-04T18:00:00.000Z",
  "deprecatedAt": null,
  "promotedBy": {
    "email": "jonathan@himarley.com",
    "name": "Jonathan Tushman"
  },
  "usageCount": 0,
  "scope": "GLOBAL",
  "ownerId": null,
  "promotedFromId": null
}
GET

Read a Droplet's typed field values

/api/apps/{slug}/type-fields

Return the per-Droplet typeFields JSONB blob along with the active Type schemas it references. Used by the detail-page strip and /view overlay to render structured-field widgets. Schemas are filtered to active Types — orphaned blocks for demoted Types appear in typeFields without a matching schema entry.

Parameters

slug
string· path· required
The Droplet slug.
GET/api/apps/{slug}/type-fields
drop types fields show q3-strategy-doc
Response200
{
  "typeFields": {
    "recommendation": {
      "status": "in-flight",
      "priority": "high",
      "progress": 65
    }
  },
  "schemas": {
    "recommendation": {
      "primitives": {
        "status": {
          "values": [
            "draft",
            "in-flight",
            "decided"
          ]
        },
        "priority": true,
        "progress": true
      },
      "customFields": []
    }
  }
}
PATCH

Update a Type

/api/types/{name}

Edit a Type schema or description. At least one of schema / description is required (omit both → 400 NO_CHANGES). Removing a status enum value that's still in use returns 409 STATUS_ENUM_VALUE_IN_USE with the affected Droplet list; retry with forceClearInUseStatus: true to proceed (clears those values).

Parameters

name
string· path· required
The Type name.
schema
object· body
Replacement schema.
description
string· body
Replacement description (empty string clears).
forceClearInUseStatus
boolean· body
Acknowledge removing in-use status values.
scope
string· body
Disambiguate tier (USER | SPACE | GLOBAL) when the same name exists at multiple tiers.
ownerId
string· body
Disambiguate by owner (User.id for USER, Space.id for SPACE, null for GLOBAL).
PATCH/api/types/{name}
drop types edit recommendation --description "A proposal awaiting decision (revised)."
Response200
{
  "id": "tag_recommendation",
  "name": "recommendation",
  "description": "A proposal awaiting decision (revised).",
  "schema": {
    "primitives": {
      "status": {
        "values": [
          "draft",
          "in-flight",
          "decided"
        ]
      }
    },
    "customFields": []
  },
  "promotedAt": "2026-06-04T18:00:00.000Z",
  "deprecatedAt": null,
  "promotedBy": {
    "email": "jonathan@himarley.com",
    "name": "Jonathan Tushman"
  },
  "scope": "GLOBAL",
  "ownerId": null,
  "promotedFromId": null
}
DELETE

Demote a Type

/api/types/{name}

Soft-delete a Type by setting deprecatedAt. Droplets that wore the Type keep its name in their tags array (now a plain tag) and retain their typed-field values in App.typeFields. A daily GC sweep deletes the orphaned field values 30 days after demotion — the retentionUntil field tells callers when. Reversible within the grace window via POST /api/types/{name}/restore.

Parameters

name
string· path· required
The Type name.
scope
string· query
Disambiguate tier (USER | SPACE | GLOBAL) when the same name exists at multiple tiers.
ownerId
string· query
Disambiguate by owner (User.id for USER, Space.id for SPACE, null for GLOBAL).
DELETE/api/types/{name}
drop types demote experiment
Response200
{
  "name": "experiment",
  "deprecatedAt": "2026-06-05T18:00:00.000Z",
  "usageCount": 3,
  "retentionUntil": "2026-07-05T18:00:00.000Z"
}
POST

Restore a demoted Type

/api/types/{name}/restore

Clear deprecatedAt on a demoted Type. Restores it as an active member of the registry; orphaned field-value blocks on Droplets become live again. Allowed within the 30-day post-demotion grace window only — beyond that returns 410 STRONG_TAG_DEMOTE_GRACE_EXPIRED with daysSince. Returns 409 STRONG_TAG_NOT_DEMOTED if the Type is already active.

Parameters

name
string· path· required
The Type name.
scope
string· body
Disambiguate tier (USER | SPACE | GLOBAL) when the same name exists at multiple tiers.
ownerId
string· body
Disambiguate by owner (User.id for USER, Space.id for SPACE, null for GLOBAL).
POST/api/types/{name}/restore
drop types restore experiment
Response200
{
  "name": "experiment",
  "daysSinceDemoted": 3
}
POST

Set typed field values on a Droplet

/api/apps/{slug}/type-fields

Write one or more typed field values into the Droplet's App.typeFields blob, scoped to a single Type. The Type must already be in the Droplet's tags array (the type is opted-in by the chip rail, not by this endpoint). Values are coerced + validated against the Type's schema — null / empty string clears a key. Per-field errors return 400 TYPE_FIELD_* codes with { field } extras. Archived Droplets reject writes with 409 APP_ARCHIVED.

Parameters

slug
string· path· required
The Droplet slug.
type
string· body· required
The Type name (legacy alias: tag).
fields
object· body· required
{key: value, ...} — null/"" clears a key.
POST/api/apps/{slug}/type-fields
drop types fields set q3-strategy-doc --type recommendation --fields "[object Object]"
Response200
{
  "typeFields": {
    "recommendation": {
      "status": "in-flight",
      "progress": 65,
      "priority": "high"
    }
  },
  "type": "recommendation",
  "tag": "recommendation",
  "block": {
    "status": "in-flight",
    "progress": 65,
    "priority": "high"
  }
}
POST

Promote a Type to a higher tier

/api/types/{name}/promote

Mint a copy of a USER or SPACE Type at a higher tier (USER → SPACE, or USER/SPACE → GLOBAL). The new Type row carries promotedFromId referencing the source; the source row is left in place. Promoting to GLOBAL requires LIBRARY_ADMIN; promoting to SPACE requires SPACE admin membership. Returns both the new Type and the source Type for client convenience.

Parameters

name
string· path· required
The Type name to promote.
sourceScope
string· body· required
Source tier (USER or SPACE).
sourceOwnerId
string· body
Owner of the source Type (User.id / Space.id / null).
targetScope
string· body· required
Target tier (SPACE | GLOBAL).
targetSpaceSlug
string· body
Required when targetScope=SPACE.
POST/api/types/{name}/promote
drop types promote-scope experiment --source-scope USER --source-owner-id usr_jonathan --target-scope GLOBAL
Response201
{
  "promoted": {
    "id": "tag_experiment_global",
    "name": "experiment",
    "description": "An ongoing test.",
    "schema": {
      "primitives": {
        "status": {
          "values": [
            "draft",
            "running",
            "done"
          ]
        }
      },
      "customFields": []
    },
    "promotedAt": "2026-06-10T18:00:00.000Z",
    "deprecatedAt": null,
    "promotedBy": {
      "email": "jonathan@himarley.com",
      "name": "Jonathan Tushman"
    },
    "usageCount": 0,
    "scope": "GLOBAL",
    "ownerId": null,
    "promotedFromId": "tag_experiment_user"
  },
  "source": {
    "id": "tag_experiment_user",
    "name": "experiment",
    "description": "An ongoing test.",
    "schema": {
      "primitives": {
        "status": {
          "values": [
            "draft",
            "running",
            "done"
          ]
        }
      },
      "customFields": []
    },
    "promotedAt": "2026-06-04T18:00:00.000Z",
    "deprecatedAt": null,
    "promotedBy": {
      "email": "jonathan@himarley.com",
      "name": "Jonathan Tushman"
    },
    "usageCount": 4,
    "scope": "USER",
    "ownerId": "usr_jonathan",
    "promotedFromId": null
  }
}