---
title: Type
kind: resource
slug: type
summary: "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."
---

# 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](/docs/resources/droplet) 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](/docs/types) for the why and the model.

## The Type object

### 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](/docs/types#primitive-fields) 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.

### JSON example

```json
{
  "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

### List Types

`GET /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.

**CLI**

```bash
drop types list
```

**MCP**

```json
{
  "tool": "drop_types_list",
  "arguments": {}
}
```

**HTTP**

```bash
curl -X GET 'https://drop.himarley.ai/api/types' \
  -H "Authorization: Bearer $DROP_TOKEN"
```

**Response** (200)

```json
{
  "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
    }
  ]
}
```

### Create a Type

`POST /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.

**CLI**

```bash
drop types create --name recommendation --description "A proposal awaiting decision." --schema "[object Object]" --scope GLOBAL
```

**MCP**

```json
{
  "tool": "drop_types_create",
  "arguments": {
    "name": "recommendation",
    "description": "A proposal awaiting decision.",
    "schema": {
      "primitives": {
        "status": {
          "values": [
            "draft",
            "in-flight",
            "decided"
          ]
        },
        "priority": true,
        "due": true
      },
      "customFields": [
        {
          "key": "rationale",
          "type": "markdown",
          "label": "Rationale"
        }
      ]
    },
    "scope": "GLOBAL"
  }
}
```

**HTTP**

```bash
curl -X POST 'https://drop.himarley.ai/api/types' \
  -H "Authorization: Bearer $DROP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"recommendation","description":"A proposal awaiting decision.","schema":{"primitives":{"status":{"values":["draft","in-flight","decided"]},"priority":true,"due":true},"customFields":[{"key":"rationale","type":"markdown","label":"Rationale"}]},"scope":"GLOBAL"}'
```

**Response** (201)

```json
{
  "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
}
```

### Read a Droplet's typed field values

`GET /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.

**CLI**

```bash
drop types fields show q3-strategy-doc
```

**MCP**

```json
{
  "tool": "drop_get_fields",
  "arguments": {
    "slug": "q3-strategy-doc"
  }
}
```

**HTTP**

```bash
curl -X GET 'https://drop.himarley.ai/api/apps/q3-strategy-doc/type-fields' \
  -H "Authorization: Bearer $DROP_TOKEN"
```

**Response** (200)

```json
{
  "typeFields": {
    "recommendation": {
      "status": "in-flight",
      "priority": "high",
      "progress": 65
    }
  },
  "schemas": {
    "recommendation": {
      "primitives": {
        "status": {
          "values": [
            "draft",
            "in-flight",
            "decided"
          ]
        },
        "priority": true,
        "progress": true
      },
      "customFields": []
    }
  }
}
```

### Update a Type

`PATCH /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).

**CLI**

```bash
drop types edit recommendation --description "A proposal awaiting decision (revised)."
```

**MCP**

```json
{
  "tool": "drop_types_update",
  "arguments": {
    "name": "recommendation",
    "description": "A proposal awaiting decision (revised)."
  }
}
```

**HTTP**

```bash
curl -X PATCH 'https://drop.himarley.ai/api/types/recommendation' \
  -H "Authorization: Bearer $DROP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"description":"A proposal awaiting decision (revised)."}'
```

**Response** (200)

```json
{
  "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
}
```

### Demote a Type

`DELETE /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).

**CLI**

```bash
drop types demote experiment
```

**MCP**

```json
{
  "tool": "drop_types_demote",
  "arguments": {
    "name": "experiment"
  }
}
```

**HTTP**

```bash
curl -X DELETE 'https://drop.himarley.ai/api/types/experiment' \
  -H "Authorization: Bearer $DROP_TOKEN"
```

**Response** (200)

```json
{
  "name": "experiment",
  "deprecatedAt": "2026-06-05T18:00:00.000Z",
  "usageCount": 3,
  "retentionUntil": "2026-07-05T18:00:00.000Z"
}
```

### Restore a demoted Type

`POST /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).

**CLI**

```bash
drop types restore experiment
```

**MCP**

```json
{
  "tool": "drop_types_restore",
  "arguments": {
    "name": "experiment"
  }
}
```

**HTTP**

```bash
curl -X POST 'https://drop.himarley.ai/api/types/experiment/restore' \
  -H "Authorization: Bearer $DROP_TOKEN"
```

**Response** (200)

```json
{
  "name": "experiment",
  "daysSinceDemoted": 3
}
```

### Set typed field values on a Droplet

`POST /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.

**CLI**

```bash
drop types fields set q3-strategy-doc --type recommendation --fields "[object Object]"
```

**MCP**

```json
{
  "tool": "drop_set_field",
  "arguments": {
    "slug": "q3-strategy-doc",
    "type": "recommendation",
    "fields": {
      "status": "in-flight",
      "progress": 65,
      "priority": "high"
    }
  }
}
```

**HTTP**

```bash
curl -X POST 'https://drop.himarley.ai/api/apps/q3-strategy-doc/type-fields' \
  -H "Authorization: Bearer $DROP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"type":"recommendation","fields":{"status":"in-flight","progress":65,"priority":"high"}}'
```

**Response** (200)

```json
{
  "typeFields": {
    "recommendation": {
      "status": "in-flight",
      "progress": 65,
      "priority": "high"
    }
  },
  "type": "recommendation",
  "tag": "recommendation",
  "block": {
    "status": "in-flight",
    "progress": 65,
    "priority": "high"
  }
}
```

### Promote a Type to a higher tier

`POST /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.

**CLI**

```bash
drop types promote-scope experiment --source-scope USER --source-owner-id usr_jonathan --target-scope GLOBAL
```

**MCP**

```json
{
  "tool": "drop_types_promote_scope",
  "arguments": {
    "name": "experiment",
    "sourceScope": "USER",
    "sourceOwnerId": "usr_jonathan",
    "targetScope": "GLOBAL"
  }
}
```

**HTTP**

```bash
curl -X POST 'https://drop.himarley.ai/api/types/experiment/promote' \
  -H "Authorization: Bearer $DROP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"sourceScope":"USER","sourceOwnerId":"usr_jonathan","targetScope":"GLOBAL"}'
```

**Response** (201)

```json
{
  "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
  }
}
```
