# Create a table: `name`, in `catalog` and `schema` (default: the database's defaults); the definition

`POST /datanotes/operations/create_table/` (operation `create_table`)

Create a table: `name`, in `catalog` and `schema` (default: the database's defaults); the definition
goes in metastore.yaml, the rows in `<root>/<catalog>/<schema>/<name>/`. Names: lowercase letters,
digits, `_` (no dots: pass the catalog and the schema as their own arguments).
- `description` (required): what a row of the table records. Every column needs its own `description`
  too, what it holds: the table is refused otherwise, with every missing one listed. Descriptions are how
  agents and people understand the schema, so write them for a reader who never saw the notes. The
  engine writes those of `id` and of `identity` "uuid" / "created" columns.
- `columns`: specs like `{"name": "kind", "type": "enum", "values": ["a","b"], "required": true,
  "description": "What kind of entry the row is"}`.
  Types: string, integer, number, boolean, date, datetime, enum, link, list (+ `items`), json.
  Attributes: required, default (`"{now}"`/`"{today}"`), min, max, step, values, `ordered` (enum in
  order: filters/avg use the position), ref (link target, full `catalog.schema.table` or "self"),
  unique, label, description, aliases, `generated` (e.g. `"date(occurred_at)"`, `"{name} {surname}"`),
  `identity` ("filename" | "created"), `vocabulary` (allowed values = names of another table's rows),
  `hierarchy: true` (one parent link to its own table), `dense` (written on every row),
  `inverse` on a link column: the column of the `ref` table that links back (`friends` ↔
  `friends`, `children` ↔ `parents`, `partner` ↔ `partner`); writes keep both sides in step.
- `time_column`: rows named by that date/datetime; otherwise `filename` (e.g. `"{name}"`).
- The table's options, as alter_table names them: `aliases` (how agents find it, with the
  description), `partition`, `exclude`, `archive`, `history` (keep its versions; default: the
  database's setting), `autolink_target` (true / false: may automatic links link its rows? default:
  yes, unless its rows are named by a date or a time, as with `time_column`).
- `row_template`: markdown body of new rows. Shared columns come from bases: `set_table_bases`.
- `tags`: key/value labels of the table, strings (`{"domain": "health"}`); list_tables and
  describe_table return them, alter_table changes them.
Enum columns: `values` and, for each, its meaning in `value_descriptions` (`{"stated": "the user said it", …}`): describe_table shows them and a refused value lists them.
Generated link columns: `generated: "link({occurred_at:YYYY-MM-DD})"` with `ref` = the days table links each row to the row of `ref` named by the template (its folder read from the schema; the target may not exist yet).
Filtered link targets (link columns): `ref_where` (a `where` every target must match) and `ref_by` (a column of the row picks the target level: `{"column": "kind", "levels": ["area", "project", "workstream"]}` or `{"column": "kind", "map": {"area": null, "project": ["area"]}}`; null = top level, no link; `target_column` when the target names it differently: an enum target column must have every value the map allows, a string one takes any value). A link to no row of the table, or to the row itself, is refused too. Existing rows that break them refuse the change; `list_link_targets` lists the valid targets of a row.

The schema changes only through operations like this one: never edit metastore.yaml by hand (no validation, no undo).

## Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Datanotes-Actor` | header | `string` | no | JSON: on whose behalf the write is made, recorded in the journal (`harness`, `model`, `session`). |

## Request body

`application/json`, required

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `name` | `string` | yes |  |  |
| `description` | `string \| null` | no | `null` | What a row of the table records (required: the table is refused without it, as without a column's own description). |
| `columns` | `object[]` | yes |  |  |
| `catalog` | `string \| null` | no | `null` |  |
| `schema` | `string \| null` | no | `null` |  |
| `time_column` | `string \| null` | no | `null` |  |
| `filename` | `string \| null` | no | `null` | File name pattern of the rows, e.g. "{name}" (instead of time_column). |
| `partition` | `string \| null` | no | `null` | Sub-folder pattern of the rows, e.g. "{occurred_at:YYYY/MM}"; "" = flat. |
| `exclude` | `string[] \| null` | no | `null` | Folders whose notes are never rows of the table (e.g. templates that create them). |
| `archive` | `string \| null` | no | `null` | Archive folder: a folder name inside the table folder (default: the database's archive_folder). |
| `aliases` | `string[] \| null` | no | `null` | Other words for what the table records, for agents. |
| `history` | `boolean \| null` | no | `null` | Keep the table's versions (time travel, restore); default: the database's setting. |
| `autolink_target` | `boolean \| null` | no | `null` | May automatic links link the table's rows? Default: yes, unless its rows are named by a date or a time. |
| `row_template` | `string \| null` | no | `null` | Markdown body of new rows; {column} placeholders are filled from the row. |
| `fields` | `object \| null` | no | `null` | Untyped fields of the table (name → default), kept on every row; a field cannot share a name with a column. |
| `tags` | `object \| null` | no | `null` | Key/value labels of the table (strings), e.g. {"domain": "health"}. |
| `dry_run` | `boolean` | no | `false` |  |

## Responses

| Status | Description | Body |
| --- | --- | --- |
| 200 | Done. | `OpResult` (application/json) |
| 400 | Bad input: the body is not a JSON object, or an argument is unknown, missing or of the wrong type. | `Error` (application/json) |
| 401 | Missing or wrong bearer token. | `Error` (application/json) |
| 403 | A named token beyond its permissions (the engine's administration, tables outside its `write` patterns, schema changes without `ddl`). | `Error` (application/json) |
| 422 | Refused (validation failures, conflicts, an unmet `expect`): nothing written. Same body as 200, with `ok: false` and the reasons. | `OpResult` (application/json) |
| 500 | Unexpected error (`{error}`), or a write that failed partway and could not be rolled back: `rolled_back: false`, `failed` lists the files left (undo_migration on `migration_id` retries the rollback). | `OpResult \| Error` (application/json) |
| 503 | A write that failed partway (a file another program holds) and was rolled back: `rolled_back: true`, nothing written. Send it again later. Same body as 200, with `ok: false` and `failed`. | `OpResult` (application/json) |

The schemas named here are in [openapi.json](https://datanotes.dev/openapi.json) (`components.schemas`).
