zinkee
v0.1.32
Published
CLI for Zinkee API v2
Keywords
Readme
Zinkee CLI
Command-line interface for Zinkee Public API v2.
Status
Implemented command groups:
profilesconfigschemasrecordsfilescommentslogsnavigationteamspacedisplaysautomationsdocument-templates
Requirements
- Node.js 20+
Install Dependencies
npm installDevelopment
npm run build
npm run typecheck
npm testPublishing
Release steps for npm are documented in docs/npm-release.md.
Configuration
The CLI uses a TOML config file:
~/.config/zinkee/config.tomlExample:
default_profile = "prod"
[profiles.prod]
api_key = "token"
base_url = "https://api.zinkee.com"Global Options
| Option | Description |
|---|---|
| --profile <name> | Select a profile from config |
| --base-url <url> | Override the configured base URL |
| --api-key <token> | Override the configured API key |
| --read-only | Run in read-only mode |
| --example | Show examples for a command |
| --json | Emit JSON output |
| --install-completion [shell] | Install shell completion (bash, zsh, fish, powershell) |
| --show-completion [shell] | Print the completion script to stdout |
Shell completion
zinkee --install-completion # auto-detects $SHELL and installs
zinkee --install-completion zsh # force a specific shell
zinkee --show-completion zsh # print the script (for inspection)After installing, restart your shell.
Runtime Resolution
The CLI resolves runtime settings with this precedence:
- CLI flags:
--profile,--base-url,--api-key - Environment variables:
ZINKEE_PROFILE,ZINKEE_API_BASE_URL,ZINKEE_API_KEY ~/.config/zinkee/config.toml
If a runtime API key is provided, a runtime base URL must also be provided. If a runtime base URL is provided, a runtime API key must also be provided.
Example for local development without config:
ZINKEE_API_BASE_URL=http://localhost:8088 \
ZINKEE_API_KEY="<workspace_token>" \
zinkee --json schemas listExample using an env-selected profile:
ZINKEE_PROFILE=local zinkee --json schemas listJSON Contract
Successful commands emit:
{ "data": {}, "meta": {} }Failing commands emit:
{ "error": {}, "meta": {} }Schemas
Create a schema
zinkee schemas create --name "Projects" --slug projects --auditable --commentableList and get schemas
zinkee --json schemas list
zinkee --json schemas get projectsCreate fields
Simple field types use --type, --slug, --label:
zinkee schemas fields create projects --type text --slug name --label "Name"
zinkee schemas fields create projects --type number --slug budget --label "Budget" --required true --default 0
zinkee schemas fields create projects --type date --slug start-date --label "Start Date"
zinkee schemas fields create projects --type file --slug attachment --label "Attachment"
zinkee schemas fields create projects --type autoincremental --slug code --label "Code"
zinkee schemas fields create projects --type member --slug owner --label "Owner"Valid types: text, number, date, file, autoincremental, member, option, reference, formula, schedule.
Complex types require --raw. The JSON type uses the backend ClassName (TextField, OptionField, …) and type-specific fields go inside config:
# Option field
zinkee schemas fields create projects --raw '{
"type":"OptionField","slug":"status","label":"Status",
"config":{
"options":[
{"id":1,"name":"Open","color":"default"},
{"id":2,"name":"In Progress","color":"primary"},
{"id":3,"name":"Done","color":"success"}
]
}
}'
# Reference field (targetSchema and targetDisplayField must be UUIDs)
zinkee schemas fields create tasks --raw '{
"type":"ReferenceField","slug":"project","label":"Project",
"config":{
"targetSchema":"<schema-uuid>",
"targetDisplayField":"<field-uuid>",
"deleteBehavior":"SET_NULL"
}
}'
# Formula field (use formulaText, not expression)
zinkee schemas fields create projects --raw '{
"type":"FormulaField","slug":"code","label":"Code",
"config":{
"formulaText":"concat(\"PRJ-\", <_id-field-uuid>)"
}
}'
# Schedule field (frequency: monthly | daily). Bound to one NumberField (referenceField)
# and two DateField UUIDs (beginning/ending). Cannot be required.
zinkee schemas fields create projects --raw '{
"type":"ScheduleField","slug":"recurrence","label":"Recurrence",
"config":{
"referenceField":"<number-field-uuid>",
"beginningPeriodField":"<date-field-uuid>",
"endingPeriodField":"<date-field-uuid>",
"frequency":"monthly"
}
}'Update field config
Use --default <value> for the common default-value case:
zinkee schemas fields update projects budget --default 0PATCH bodies are flat (no set / unset wrapper). Config properties must be wrapped inside a config key:
# Number formatting
zinkee schemas fields update projects budget --raw '{
"type":"NumberField",
"config":{
"defaultValue":"0",
"formatOptions":{
"format":"number","decimals":2,"thousandsSeparator":true,
"symbolEnabled":true,"symbolValue":" €","symbolPosition":"right"
}
}
}'
# Date formatting
zinkee schemas fields update projects start-date --raw '{
"type":"DateField",
"config":{"formatOptions":{"format":"date","dateFormat":"L","showTime":false}}
}'
# Text type (valid values: NORMAL, LONG)
zinkee schemas fields update projects description --raw '{"type":"TextField","config":{"textType":"LONG"}}'Formula syntax
Formulas reference fields by UUID without brackets:
concat("PRJ-", f1e1d000-0000-0000-0000-000000000005)
if(8e73e31d-c47d-4123-a14c-331991c39c0e > 10, "High", "Low")
date_format(44b72f55-3fc3-40f4-88bb-343bb4a6270c, "AAAA-MM")- Field references: UUID without brackets (no
[uuid]) - Date masks are in Spanish:
AAAA,MM,DD,MMMM,DDDD, etc. - Date masks must be quoted:
"AAAA-MM" - Functions:
concat,if,date_format,datetime_diff, arithmetic (+,-,*,/) - Invalid formulas are created with status
DRAFTand evaluate tonull
Set unique fields
Define a composite unique constraint: the listed fields must be unique together
across the schema's records. --field accepts a field UUID or slug and is repeatable;
the order you pass them is the order of the constraint.
# Single field must be unique
zinkee schemas set-unique-fields contacts --field email
# Composite: the (contact-name, notes) combination must be unique
zinkee schemas set-unique-fields contacts --field contact-name --field notes
# Remove the unique constraint
zinkee schemas set-unique-fields contacts --clearNotes:
- Pass either one or more
--field, or--clear— not both, and not neither. - Every
--fieldmust already exist in the schema, otherwise the command fails. - If existing records already violate the requested constraint, the backend rejects
it with
409 Conflict.
Records
CRUD operations
# Create
zinkee records create projects \
--set name="Website Redesign" \
--set budget=25000 \
--set start-date="2026-01-15T00:00:00.000Z" \
--set status=1
# List and query
zinkee --json records list projects --limit 50
zinkee --json records query projects --limit 10 --sort "_id:desc"
# Query with a free-text search term (matched across the schema's searchable fields)
zinkee --json records query projects --where "status in OPEN" --search "garcia" --limit 25
# Get
zinkee --json records get projects <record-uuid>
# Update
zinkee records update projects <record-uuid> --set status=2
# Delete
zinkee records delete projects <record-uuid>
# Clear all records from a schema (destructive — irreversible)
zinkee records clear projectsOption values are written as numbers (--set status=1) but returned as strings ("1"). Number values are stored and returned as strings.
records clear removes every record from the schema's table. Heritage rules
(CASCADE / SET NULL) propagate to child schemas asynchronously through the
CDC pipeline, so related tables converge eventually. Blocked by --read-only.
Files
# Upload
zinkee --json files upload /path/to/document.pdf
# Download
zinkee files download <file-uuid> --output /path/to/download.pdf
# Attach to a record's FileField
zinkee records update projects <record-uuid> \
--set-json 'attachment=[{"id":"<file-uuid>","name":"doc.pdf","contentType":"application/pdf"}]'
# Detach
zinkee records update projects <record-uuid> --set-json 'attachment=[]'
# Delete (storage only — does NOT clean record references)
zinkee files delete <file-uuid>
# Storage info
zinkee --json files storageImportant: deleting a file does not remove references from records. Manually clear the FileField before or after deleting.
Comments
Requires schemas with commentable: true.
zinkee comments create <schema-slug> <record-uuid> --text "This is a comment"
zinkee comments list <schema-slug> <record-uuid>Logs
Returns the change history for a record. Only populated for schemas with auditable: true.
zinkee logs list <schema-slug> <record-uuid>
zinkee --json logs list <schema-slug> <record-uuid>Navigation
Organize schemas into database folders. The navigation menu holds databases, so only schemas can be moved here.
# List folders with contents
zinkee --json navigation folders list
# Create folder
zinkee navigation folders create --name "CRM" --order 1
# Move a schema into a folder
zinkee navigation resources move <schema-uuid> --to "CRM"
# Move a schema back to the workspace root
zinkee navigation resources move <schema-uuid> --to 00000000-0000-0000-0000-000000000000
# Delete folder (schemas move back to root silently)
zinkee navigation folders delete "CRM"Only schemas can be moved (schedules and sub-schemas follow their parent schema). Root folder cannot be deleted; its UUID is always 00000000-0000-0000-0000-000000000000.
Teamspace
Publish resources (schemas, displays) to the teamspace visible to non-admin users.
# Create teamspace folder
zinkee teamspace folders create --name "Dashboards" --order 1
# Publish a schema (accepts UUID or slug)
zinkee teamspace resources publish projects --to "Dashboards"
# Publish a display
zinkee teamspace resources publish <display-uuid> --to "Dashboards"
# Unpublish
zinkee teamspace resources unpublish <resource-uuid>
# List folders with resources
zinkee --json teamspace folders list --include-resources
# Re-publishing moves the resource to the new folder
zinkee teamspace resources publish projects --to "Other Folder"Displays
Displays are dashboards composed of widgets that visualize data from schemas.
Display folders
Organize displays into folders in the displays menu. Folders are flat (name only) and only displays can be moved into them.
# List display folders (non-root folders plus the root bucket)
zinkee --json displays folders list
# Create a folder
zinkee displays folders create --name "Executive Dashboards"
# Rename a folder
zinkee displays folders update "Executive Dashboards" --name "Executive Dashboards (2026)"
# Move a display into a folder
zinkee displays folders move <display-uuid> --to "Executive Dashboards"
# Delete a folder (its displays move back to the root folder)
zinkee displays folders delete "Executive Dashboards"Creation flow
- Create the display
- Create context variables
- Create widgets with
variableMapbindings - Set freeform layout positions
Templates
freeform— grid withx/y/w/hpositioning (recommended)single_view— single widget viewwidget_row— stacked rowswidget_row_tabs— rows with tabsside_tabs_widgets— tabs with sidebar
Create a freeform display
zinkee --json displays create --name "Project Dashboard" --template freeformAdd a context variable
zinkee --json displays variables create <display-uuid> \
--type reference \
--name "Project" \
--source-schema projects \
--source-field project-nameVariables accept slugs for --source-schema and --source-field.
Add widgets
Every widget MUST include a variableMap that maps each display variable to a field in the widget's schema:
- Same schema as variable: bind to the same display field (e.g.,
project-name) - Different schema: bind to the ReferenceField pointing to the variable's schema (e.g.,
task-project) - No filtering: set to
null
# Table widget (--field accepts slugs)
zinkee --json displays widgets create <display-uuid> \
--type TABLE_OR_SUBTABLE \
--name "Tasks" \
--origin-resource <tasks-schema-uuid> \
--field task-code \
--field task-title \
--field task-status \
--raw '{"bindings":{"variableMap":{"<variable-uuid>":"<task-project-field-uuid>"}}}'
# KPI widget
zinkee --json displays widgets create <display-uuid> \
--type KPI \
--name "Total Hours" \
--origin-resource <hours-schema-uuid> \
--raw '{
"bindings":{"variableMap":{"<variable-uuid>":"<hours-project-field-uuid>"}},
"kpi":{"typeCalc":"SUM","fieldCalc":"hours-field-slug"}
}'
# Chart widget (valid types: lines, bars, stackedBars, combined)
zinkee --json displays widgets create <display-uuid> \
--type CHART \
--name "Hours by Employee" \
--origin-resource <hours-schema-uuid> \
--chart-type bars \
--x-axis-field employee-field-slug \
--raw '{
"bindings":{"variableMap":{"<variable-uuid>":"<hours-project-field-uuid>"}},
"chart":{"yAxisSeries":[{"typeCalc":"SUM","fieldCalc":"hours-field-slug","axisType":"PRIMARY"}]}
}'
# KANBAN widget (field mapping lives in view.viewAdditionalProperties.viewState)
zinkee --json displays widgets create <display-uuid> \
--type KANBAN \
--name "Status board" \
--origin-resource <schema-uuid> \
--field <name-field-uuid> --field <status-field-uuid> --field <amount-field-uuid> \
--column-field <status-field-uuid> \
--swimlane-field <owner-field-uuid> \
--card-title-field <name-field-uuid> \
--card-category1-field <status-field-uuid> \
--aggregation-method sum --aggregation-field <amount-field-uuid> \
--view-state '{"showNoValueColumn":true,"showNoValueSwimlane":true}'
# TIMELINE widget (cardConfig uses category1/category2/category4, no category3)
zinkee --json displays widgets create <display-uuid> \
--type TIMELINE \
--name "Customer schedule" \
--origin-resource <schema-uuid> \
--field <name-field-uuid> --field <start-field-uuid> --field <end-field-uuid> \
--start-date-field <start-field-uuid> \
--end-date-field <end-field-uuid> \
--label-field <name-field-uuid> \
--card-title-field <name-field-uuid> \
--view-state '{"granularity":"month","sidebarWidth":300}'
# HIERARCHY widget (columns + root level + repeatable child levels)
zinkee --json displays widgets create <display-uuid> \
--type HIERARCHY \
--name "Org tree" \
--origin-resource <schema-uuid> \
--field <name-field-uuid> --field <status-field-uuid> --field <amount-field-uuid> \
--hierarchy-label "Name" \
--hierarchy-info "col_1779975207355_zfndknslw:Status" \
--hierarchy-number "col_1779975261490_h8kd3nv9ws:Total amount:SUM" \
--hierarchy-root-name "Customers" \
--hierarchy-root-label-field <name-field-uuid> \
--hierarchy-root-info "col_1779975207355_zfndknslw:<status-field-uuid>" \
--hierarchy-root-number "col_1779975261490_h8kd3nv9ws:<amount-field-uuid>" \
--hierarchy-child '{"name":"Projects","schemaId":"<projects-schema-uuid>","childFieldId":"<project-customer-field-uuid>","labelFieldId":"<project-name-field-uuid>","infoMappings":[],"numberMappings":[{"columnId":"col_1779975261490_h8kd3nv9ws","fieldId":"<project-amount-field-uuid>"}]}'For KANBAN/TIMELINE, cardConfig differs by type: KANBAN exposes category1Field/category2Field/category3Field (--card-category1-field…--card-category3-field), TIMELINE exposes category1Field/category2Field/category4Field (--card-category4-field). UI-state scalars (columnOrder, hiddenColumns, collapsedColumns, hideEmptyColumns, showNoValueColumn, showNoValueSwimlane, granularity, sidebarWidth, visibleFields, collapsedGroups, collapsedSubgroups, showUngroupedEvents, showUnsubgroupedEvents) go through --view-state '<json>' / --view-state-file <path>.
For HIERARCHY, label is required at every level but info/number mappings are optional; each child level relates to its parent through childFieldId (level 2 → root, level 3 → level 2, …). Numeric columns aggregate upward: a parent shows the aggregation of its children's values, falling back to the level's own mapped value when children have none. Column ids (the id in --hierarchy-info/--hierarchy-number, reused as columnId in every mapping) must be unique opaque identifiers you choose — not the column name; the frontend generates them as col_<timestamp>_<random> (e.g. col_1779975207355_zfndknslw). Pass the whole hierarchyConfig at once with --hierarchy-config '<json>' / --hierarchy-config-file <path> if you prefer.
Widget types: TABLE_OR_SUBTABLE, DETAIL, KPI, CHART, TIMELINE, KANBAN, HIERARCHY.
Set freeform layout
After creating widgets, position them on a 12-column grid:
zinkee displays layout update <display-uuid> --template freeform --freeform-layouts '{
"lg": [
{"i":"<kpi-widget-uuid>","x":0,"y":0,"w":3,"h":4},
{"i":"<kpi-widget-uuid>","x":3,"y":0,"w":3,"h":4},
{"i":"<table-widget-uuid>","x":0,"y":4,"w":12,"h":10}
]
}'Widget filters
Pass --filter "<field>:<comparator>:<value>" (repeatable) and the CLI turns it into the structured query.filters[] payload the API expects. Field accepts UUID or slug. Supported comparators: eq, neq, gt, gte, lt, lte, contains, in (comma-separated values), is_empty, is_not_empty.
zinkee --json displays widgets update <display-uuid> <widget-uuid> \
--filter "status:eq:2" \
--filter "amount:gte:500"For comparators the parser does not cover yet (e.g. NOT_CONTAINS, RANGE) keep using --raw:
--raw '{"query":{"filters":[{"origin":{"fieldId":"<field>"},"comparisonOperator":"RANGE","values":["2025-01-01","2025-12-31"]}]}}'Slug support
Slugs are resolved to UUIDs in: --field, --source-schema, --source-field, --x-axis-field, --filter field reference, kpi.fieldCalc, chart.xAxisFieldId, chart.yAxisSeries[].fieldCalc, bindings.variableMap values.
Exception: field references inside the KANBAN/TIMELINE viewState (--column-field, --start-date-field, --card-*-field, --aggregation-field, …) and inside the HIERARCHY hierarchyConfig (--hierarchy-root-label-field, --hierarchy-root-info/--hierarchy-root-number field ids, child level *FieldId/schemaId) are not slug-resolved — the backend only resolves slugs in view.fields, so these must be canonical UUIDs.
Display UUIDs are required for displays widgets create/update and displays variables create.
Automations
Automations execute actions in response to triggers (record events, schedules, webhooks).
Creation flow
- Create automation with trigger
- Add actions
- Wire the flow (connect trigger → actions → actions)
- Activate
Trigger types
# Scheduled (six-field cron — includes seconds)
zinkee --json automations create \
--name "Weekly report" \
--trigger-type scheduled \
--cron "0 15 10 * * 1"
# Record created (requires at least one --condition)
zinkee --json automations create \
--name "High priority task" \
--trigger-type record_created \
--trigger-schema tasks \
--condition priority:eq:3
# Record changed (requires --trigger-field)
zinkee --json automations create \
--name "Status change" \
--trigger-type record_changed \
--trigger-schema tasks \
--trigger-field status
# Webhook
zinkee --json automations create \
--name "External webhook" \
--trigger-type webhookValid trigger types: scheduled, record_created, record_changed, webhook.
Action types
# Create record with literal and trigger-sourced values
zinkee --json automations actions add <automation-uuid> \
--type create_record \
--name "Create time entry" \
--target-schema time-entries \
--map description="Auto-created" \
--map hours=0 \
--map-json project='{"rawValue":null,"source":{"type":"TRIGGER","id":"<project-field-uuid>"}}'
# Update the trigger record
zinkee --json automations actions add <automation-uuid> \
--type update_record \
--name "Mark as processed" \
--target-schema tasks \
--use-trigger-record \
--map processed=true
# Search records
zinkee --json automations actions add <automation-uuid> \
--type search_records \
--name "Find related" \
--target-schema time-entries \
--condition-json '{"field":"project","comparator":"eq","values":[{"rawValue":null,"source":{"type":"TRIGGER","id":"<project-field-uuid>"}}]}'
# HTTP request
zinkee --json automations actions add <automation-uuid> \
--type http_request \
--name "Notify webhook" \
--method POST \
--url https://example.com/hook \
--content-type application/json \
--body '{"event":"task.created"}'
# Execute plugin
zinkee --json automations actions add <automation-uuid> \
--type execute_plugin \
--name "Clone budget" \
--plugin clone-budget \
--arg mode=safeValid action types: create_record, update_record, search_records, send_message, http_request, execute_plugin.
Field mapping format
--map field=value→{"rawValue":"value","source":null}(literal)--map-json field='{"rawValue":null,"source":{"type":"TRIGGER","id":"<field-uuid>"}}'(from trigger)source.typevalues:TRIGGER,PREVIOUS_ACTIONsource.idmust be a field UUID — slugs are not resolved here
Flow wiring
Actions are NOT auto-wired. Connect them explicitly:
zinkee --json automations flow set <automation-uuid> \
--entry <action-1-uuid> \
--transition <action-1-uuid>:<action-2-uuid> \
--transition <action-2-uuid>:<action-3-uuid>Activate / deactivate
zinkee automations activate <automation-uuid>
zinkee automations deactivate <automation-uuid>Webhook configuration
zinkee --json automations webhook set <automation-uuid> \
--active \
--idempotency-key-path "$.id" \
--event-type-path "$.type" \
--allowed-event-type order.created \
--field-mapping '{"variable":"external_id","jsonPath":"$.data.id"}'
zinkee --json automations webhook get <automation-uuid>Stored connections
zinkee --json automations connections create \
--name "ERP API Key" \
--type api_key \
--scope workspace \
--config '{"headerName":"Authorization"}' \
--secret apiKey=super-secret \
--active
zinkee --json automations connections listTroubleshooting
Automation payload is invalid→ trigger is missing or malformed- Use
scheduled(notschedule) as trigger type - Cron must be six fields (includes seconds):
"0 15 10 * * 1" record_createdrequires at least one--conditionrecord_changedrequires at least one--trigger-field- Actions must be wired with
flow setbefore they execute - Use
--json --example automations <subcommand>for canonical examples
Document templates
Document templates group reusable files (e.g. Word/Excel masters) that automations can render against a record via the generate_document action. Names must be unique within the workspace; the <template> argument accepts a UUID or an exact name. The <asset> argument requires a UUID.
Typical lifecycle
- Create an empty template
zinkee --json document-templates create --name "Quarterly report"- Upload one or more asset files
zinkee --json document-templates assets add "Quarterly report" ./requirements.docx
zinkee --json document-templates assets add "Quarterly report" ./contract.docx --name "Customer Contract"- List and inspect
zinkee --json document-templates list
zinkee --json document-templates get "Quarterly report"- Rename / replace assets / remove
zinkee --json document-templates update "Quarterly report" --name "Annual report"
zinkee --json document-templates assets delete <template-uuid> <asset-uuid>
zinkee --json document-templates delete <template-uuid>Download asset bytes
download streams raw bytes — use --output to write to disk (required when combined with --json):
zinkee --json document-templates assets download <template-uuid> <asset-uuid> --output ./contract.docx
zinkee document-templates assets download <template-uuid> <asset-uuid> > contract.docxNotes
getis a client-side helper: the backend has no get-one endpoint, so the CLI filters the list response. Uselistif you want everything in one call.- The
assetsreturned by list/get include a relativelinkready to call directly (it already includes both the templateId and assetId path segments). - Deleting a template removes its asset files from storage; record fields that referenced previously generated documents are not modified.
- Use
--json --example document-templates <subcommand>for canonical examples.
Discovery
zinkee --json schemas list
zinkee --json schemas fields list <schema>
zinkee --json displays list
zinkee --json automations list
zinkee --json automations plugins list
zinkee --json automations connections list
zinkee --json document-templates list
zinkee --example <command path>