# The spatibase command

> Create a project, import places from a file and publish them from the terminal with the spatibase command, with its options and exit codes.

`spatibase` is a small command-line tool that moves the places in a repo into Spatibase and publishes them through the Content Delivery API. It is made for coding agents, so its output is short and plain, it never asks questions, and its errors say what to do next. You can use it yourself too. It needs Node.js 18.3 or newer and has no dependencies.

```bash
npx --yes spatibase@0.1 --help
```

On this page `spatibase` stands for `npx --yes spatibase@0.1`. Pin the version as shown: `0.1` keeps you on compatible releases. The [prompt](https://spatibase.com/docs/build/start-from-your-code/) makes your coding agent run exactly these commands.

## A typical run

```bash
spatibase create "Acme stores"                                   # temporary project, saved in .spatibase/
spatibase model .spatibase/model.json                            # types and fields from a small JSON file
spatibase import public/stores.json --type store --publish       # drafts, marked ready, published as a version
spatibase verify                                                 # counts in the live API
spatibase keep dev@example.com                                   # send the link that keeps the project
```

Then point your app at the Delivery API address that `create` printed.

## Where it keeps its state

`spatibase create` writes `.spatibase/project.json` in the current folder (the project id, its addresses, the secret token and the expiry) and a `.spatibase/.gitignore` containing `*`, so the folder is never committed. Run the other commands from the same folder. The token never appears in the output.

The API address is `--api <url>`, else the `SPATIBASE_API` environment variable, else `https://api.spatibase.com`. Setting it points the tool at another Spatibase server, for example a local one.

## Commands

| Command | What it does |
|---|---|
| `create "<name>" [--ref <label>] [--new]` | Makes a [temporary project](https://spatibase.com/docs/build/start-from-your-code/#temporary-projects) and saves it. Prints the project id, the Delivery address, the expiry and the limits. Refuses when `.spatibase/` already holds a project, unless `--new` |
| `link <code> [--new]` | Connects the folder to a project a person made in the app, with the one-time code from **New project, Coding agent**. Saves the project and a key in `.spatibase/` like `create` does, but the project has no expiry and nothing to keep: `keep` refuses on it and `status` shows what the key can do. Refuses when `.spatibase/` already holds a project, unless `--new`. The code works once, for 15 minutes |
| `describe` | Prints the content model: types, fields with their apiKey and kind, groups |
| `model <spec.json> [--preview]` | Creates fields and types from a JSON file. Safe to run again: existing apiKeys are skipped, and fields new in the file are added to existing types. The file is checked first (field kinds, `locationMode`, `geometryKinds`, parents, one kind per apiKey), so a mistake stops it before anything is created. `--preview` shows what would be created and changes nothing |
| `import <file> --type <apiKey> [options]` | Reads a file and imports it as drafts of a type, optionally marking them ready and publishing the project |
| `publish [--type <apiKey>] [--dry-run]` | Marks all drafts ready (500 at a time), then publishes the project as one new [version](https://spatibase.com/docs/publish/versions/). The publish ships everything that is ready, also what other people marked, plus staged unpublishes, deletes and group changes. Entries that fail validation stay drafts and are listed with the reason. `--dry-run` lists what would be marked and what would ship |
| `verify` | Compares the places in the public Delivery API with the project and lists drafts still waiting, and unpublishes, deletes and group changes staged for the next publish. Types switched off in the app are listed apart and not checked. Exit code 1 when something is not live |
| `status` | Shows the saved project: expiry, whether an email was given, whether it was kept, places and upload size |
| `settings [--name "<name>"] [--description "<text>"] [--add-locale <code>] [--remove-locale <code>] [--locales <list>] [--preview]` | Shows or changes the project's name, description and [languages](https://spatibase.com/docs/content/translations/). `--add-locale de` turns a language on and keeps the default; `--locales en,de` sets the whole list, the first is the default and cannot change later. Renaming keeps the project id and the API addresses |
| `keep <email>` | Sends **Keep your project** to that address (at most 3 different addresses per project) |
| `tools [<tool>]` | Lists the [MCP tools](https://spatibase.com/docs/ai/mcp-server/) the project's key can use, or one tool's description and arguments |
| `call <tool> ['<json>' \| --args-file <file>] [--preview]` | Calls any tool of the project's [MCP server](https://spatibase.com/docs/ai/mcp-server/) and prints the answer as JSON. A mistyped tool name is reported as unknown, with the closest names |

Options for every command: `--json` prints one JSON object instead of text (errors too, on stderr), `--api <url>`, `-h` or `--help` (`spatibase <command> --help` for one command), `--version`.

### The model file

```json
{
  "types": [
    {
      "name": "Store", "apiKey": "store", "locationMode": "required", "geometryKinds": ["Point"],
      "fields": [
        { "label": "Store id", "apiKey": "store_id", "kind": "identifier" },
        { "label": "Phone", "apiKey": "phone", "kind": "phone" },
        { "label": "Category", "apiKey": "category", "kind": "choice", "config": { "options": ["Cafe", "Shop"] } },
        { "label": "Photos", "apiKey": "photos", "kind": "images" }
      ]
    }
  ]
}
```

- Field kinds: `text`, `markdown`, `number`, `boolean`, `date`, `choice`, `link`, `phone`, `contact`, `identifier`, `color`, `images`, `files`, `reference`, `geo`, `embed`, `location`. A kind that does not exist stops the command with the closest match. [Elements](https://spatibase.com/docs/model/elements/) describes them and their `config`. A nested value such as opening hours by day can be a `text` field with `"config": { "editor": "json" }`.
- `locationMode` is `required`, `optional` or `none`. Optional keys: `description`, `parent` (the apiKey of a parent type; list parents first), `required` and `helpText` on a field.
- The title and the coordinates are not fields: every entry has a title, and the type's location holds the coordinates.
- A field listed in several types is created once.

### Import options

`spatibase import <file> --type <apiKey>` reads a `.geojson` or `.json` GeoJSON file, a `.json` array of objects, or a `.csv` or `.tsv` with a header row. It sends 1,000 rows at a time as drafts.

| Option | Meaning |
|---|---|
| `--type <apiKey>` | The type to import into (required; it must exist: run `model` first) |
| `--title <prop>` | The property that holds the title. Default `name`, `title` or `label` |
| `--map field=prop` | Fill the field `field` from the property `prop`. Repeat for several. Properties already named like a field's apiKey need no `--map` |
| `--match-by <field>` | An `identifier` field: rows whose value matches update that entry instead of creating a duplicate. Use it to run an import again |
| `--locale <code>` | The rows are translations into this [project language](https://spatibase.com/docs/content/translations/). Needs `--match-by`; creates nothing; rows without a match are skipped with a warning |
| `--images <prop>[=<dir>]` | The property holds image file paths. Each distinct file is read from `<dir>` (default `./public` if it exists, else `.`; a leading `/` means "inside `<dir>`"), uploaded once (up to 3.5 MB each, larger ones are skipped with a warning) and the value becomes the uploaded images. The type needs an `images` field with that apiKey, or use `--map <field>=<prop>`. `http(s)` URLs are left as they are |
| `--publish` | Mark exactly the entries of this run ready and publish the project in the same run, as one version (which also ships anything already marked ready). Other drafts of the type stay drafts. Not with `--locale`: publish translations afterwards with `publish` |
| `--dry-run` | Check only: counts and warnings, and with `--images` the files are checked on disk (found, size). Nothing is written, uploaded or published |

Before anything is uploaded or written, `import` checks the options against the project: the type must exist, every `--map` field must be a field of the type, `--match-by` must be one of its `identifier` fields, `--images` must be an `images` field and `--locale` a non-default project language. A mistake stops the command with the fields or types that do exist. Properties the import would drop (no field with that apiKey and no `--map`) are listed, so you can map them.

Latitude and longitude are read from the columns `lat`/`latitude` and `lng`/`lon`/`long`/`longitude` (any case) or from a nested `location` or `coordinates` object. Rows without usable coordinates are imported without a location and skipped by types that require one, with a warning. A temporary project holds 1,000 places: further rows are not sent, with a warning. Warnings and value conversion follow the [Import API](https://spatibase.com/docs/reference/import-api/).

## Exit codes

| Code | Meaning |
|---|---|
| 0 | Done |
| 1 | The command failed or, for `verify`, something is not live. The message says what to do |
| 2 | Wrong usage: a missing argument or an unknown option |

## Examples

Import a CSV with different column names, then run it again after fixing the data:

```bash
spatibase import places.csv --type store --title shop_name --map phone=tel --map store_id=id --match-by store_id --dry-run
spatibase import places.csv --type store --title shop_name --map phone=tel --map store_id=id --match-by store_id --publish
```

Images stored in `public/images` and referenced as `"/images/01.webp"`:

```bash
spatibase import public/stores.json --type store --images photos --publish
```

Translations from one file per language:

```bash
spatibase settings --locales en,de          # the first is the default; later languages: --add-locale es
spatibase import i18n/stores.de.json --type store --match-by store_id --locale de
spatibase publish
```

Any other tool, for example the first drafts. `tools` shows which tools the key has and what they take:

```bash
spatibase tools
spatibase tools search_drafts
spatibase call search_drafts '{"limit": 5}'
```

`call` adds `"confirm": true` to the model tools (`create_type`, `create_element`, `assign_elements`, `create_group`, `update_group`, `update_project_settings`) unless you pass `--preview`. The import, `mark_ready`, `publish_project` and `restore_version` tools only dry-run unless you send `"dryRun": false`; prefer `import` and `publish`.

## Without the command

Everything the command does is an HTTP call, for environments without Node.js:

- `POST https://api.spatibase.com/api/agent/projects` with `{"name": "…", "ref": "…"}` (no sign-in) answers `201` with `projectId`, `token`, `mcpUrl`, `deliveryUrl`, `appUrl`, `expiresAt` and `limits`. The `sb_temp_…` token lasts 24 hours and is sent as `Authorization: Bearer <token>`.
- `GET /api/agent/projects/<id>` with the token answers `claimState` (`unclaimed` or `claimed`), `expiresAt`, `emailGiven`, `entries` and `uploadBytes`.
- `POST /api/agent/projects/<id>/claim-email` with `{"email": "…"}` sends **Keep your project** and answers `expiresAt` and `emailSentTo`.
- Model, import, publish and upload are tools of the project's [MCP server](https://spatibase.com/docs/ai/mcp-server/#curl) (`import_features`, `mark_ready`, `publish_project`, `upload_asset`, …), called as one JSON-RPC `POST` each.

The step-by-step version for agents is the [playbook](https://spatibase.com/agents/quickstart.md), with the curl calls in its appendix.

## Related

- [Start from your code](https://spatibase.com/docs/build/start-from-your-code/)
- [MCP server reference](https://spatibase.com/docs/ai/mcp-server/)
- [Import API](https://spatibase.com/docs/reference/import-api/)
- [Limits](https://spatibase.com/docs/reference/limits/)
