| name | genie-space-export-import-api |
| description | Comprehensive patterns for Databricks Genie Space Export/Import API - JSON schema, serialization format, and programmatic deployment. Use when programmatically creating, exporting, or importing Genie Spaces via REST API, troubleshooting API deployment errors, or implementing CI/CD for Genie Spaces. Includes complete GenieSpaceExport schema, API endpoints (List, Get, Create, Update, Delete), JSON format requirements, ID generation, variable substitution, inventory-driven generation patterns, and production deployment checklists. |
| clients | ["ide_cli","genie_code"] |
| bundle_resource | genie |
| deploy_verb | bundle_deploy |
| deploy_note | RULE_8 tiers: T1 native `genie_spaces` bundle entry (preferred; verify CLI support via `bundle validate`) -> T2 provisioning notebook job (active fallback) -> T3 `createAsset`+PATCH dev-authoring loop (Genie-Code only: create a shell with `createAsset(assetType=genie)`, populate the FULL `serialized_space` via `PATCH /api/2.0/genie/spaces/{id}`, then PERSIST that JSON into the bundle and run T1/T2 once in dev for the version-controlled deploy). T3 is sanctioned for fast dev iteration ONLY when the JSON is persisted in the bundle and a job reproduces it; an orphan space with no persisted JSON is the regression. IDE (ide_cli) stays on T1/T2 (bundle) and never uses `createAsset`. Space name + table_identifiers carry the per-user prefix. semantic_warehouse_id is baked at deploy time, never a runtime --var. |
| coverage | full |
| metadata | {"author":"prashanth subrahmanyam","version":"3.8.0","domain":"semantic-layer","role":"worker","pipeline_stage":6,"pipeline_stage_name":"semantic-layer","called_by":["semantic-layer-setup"],"standalone":true,"last_verified":"2026-08-30","volatility":"high","upstream_sources":[{"name":"databricks-agent-skills","repo":"databricks/databricks-agent-skills","paths":"[Truncated]","relationship":"extended","last_synced":"2026-08-30","sync_commit":"ca92a6c"},{"name":"databricks-docs-genie-getspace","url":"https://docs.databricks.com/api/workspace/genie/getspace","relationship":"upstream","last_synced":"2026-08-30"},{"name":"databricks-docs-genie-createspace","url":"https://docs.databricks.com/api/workspace/genie/createspace","relationship":"upstream","last_synced":"2026-08-30"},{"name":"databricks-docs-genie-updatespace","url":"https://docs.databricks.com/api/workspace/genie/updatespace","relationship":"upstream","last_synced":"2026-08-30"},{"name":"databricks-docs-genie-conversation-api","url":"https://docs.databricks.com/aws/en/genie/conversation-api","relationship":"upstream","last_synced":"2026-08-30"}]} |
Genie Space Export/Import API
Overview
This skill provides comprehensive patterns for programmatically creating, exporting, and importing Databricks Genie Spaces via the REST API. It covers the complete GenieSpaceExport JSON schema, API endpoints, common deployment errors, and production-ready workflows including variable substitution and asset inventory-driven generation.
Terminology (2026): Databricks renamed Genie Spaces to Genie Agents. The REST endpoints are unchanged (/api/2.0/genie/spaces), and this skill uses "Genie Space" and "Genie Agent" interchangeably. The API now also exposes Agent-mode endpoints (stream reasoning + SQL + final reports with citations) alongside the Chat-mode and Management APIs covered here. See the Genie Agents API.
When to Use This Skill
Use this skill when you need to:
- Programmatically deploy Genie Spaces via REST API (CI/CD pipelines, environment promotion)
- Export Genie Space configurations for version control, backup, or migration
- Troubleshoot API deployment errors (
BAD_REQUEST, INVALID_PARAMETER_VALUE, INTERNAL_ERROR)
- Implement cross-workspace deployment with template variable substitution
- Generate Genie Spaces from asset inventories to prevent non-existent table errors
- Validate Genie Space JSON structure before deployment
- Understand the complete GenieSpaceExport schema (config, data_sources, instructions, benchmarks)
Start From Templates (Mandatory)
NEVER write the deployment notebook or job YAML from scratch. Writing these from scratch is the #1 source of deployment failures — copy the template, then customize:
assets/templates/deploy_genie_spaces.py → copy to src/{project}_semantic/deploy_genie_spaces.py
assets/templates/genie-deployment-job-template.yml → copy to resources/semantic/genie_deploy_job.yml
The templates encode the correct notebook cell separators, extract_space_config() (wrapped vs raw format handling), validate_genie_json_structure(), array sorting, and base_parameters wiring. Hand-written versions routinely miss one of these and fail in deploy cycles 2–9 (see the retrospective in references/ or the project's retrospectives directory).
End-to-End Deployment? Use the Orchestrator
If you are deploying TVFs + Metric Views + Genie Spaces together (not just a standalone Genie Space), STOP and read semantic-layer/00-semantic-layer-setup/SKILL.md first. That orchestrator:
- Mandates a Gold schema inventory query before artifact creation (prevents phantom table errors)
- Coordinates skill loading across 6 phases with validation gates
- Provides combined job templates with
depends_on chains
This skill handles individual Genie Space API operations. The orchestrator handles the end-to-end semantic layer lifecycle.
Quick Reference
API Operations
| Operation | Method | Endpoint | Use Case |
|---|
| List Spaces | GET | /api/2.0/genie/spaces | Discover existing spaces |
| Get Space | GET | /api/2.0/genie/spaces/{space_id}?include_serialized_space=true | Export config, backup |
| Create Space | POST | /api/2.0/genie/spaces | New deployment, CI/CD |
| Update Space | PATCH | /api/2.0/genie/spaces/{space_id} | Modify config, add benchmarks |
| Delete Space | DELETE | /api/2.0/genie/spaces/{space_id} | Cleanup, teardown |
API Limits
| Resource | Limit | Enforcement |
|---|
instructions.sql_functions | Max 50 | Truncate in generation script |
benchmarks.questions | Max 50 | Truncate in generation script |
data_sources.tables | No hard limit | Keep ~25-30 for performance |
data_sources.metric_views | No hard limit | Keep ~5-10 per space |
Required Root Field
Every Genie Space JSON MUST include an explicit "version" at the root of serialized_space. The API accepts versions 1 and 2; use version 2 — it is the richer schema that supports benchmarks, data_sources.metric_views, example_question_sqls[].parameters, and join_specs:
{"version": 2, "config": {...}, "data_sources": {...}, "instructions": {...}, "benchmarks": {...}}
Omitting version (or sending 0) causes silent failures or API rejection (ExportConverter supports versions 1 and 2, but got 0). The API does NOT default the version — set it explicitly.
Required serialized_space Invariants (MANDATORY — validate BEFORE every POST / PATCH)
Genie Space creation silently succeeds but produces a broken room when these invariants are violated. The API's validation surface is narrow — once the space exists, the only way to diagnose is inspecting the UI. Always run the validator below before calling POST /api/2.0/genie/spaces or PATCH /api/2.0/genie/spaces/{id}.
Field path (inside serialized_space) | Required type | Non-negotiable invariants |
|---|
version | int | Must be 1 or 2; prefer 2. Missing or 0 is rejected. |
config.title | str | Non-empty. Mirrors top-level title in the POST envelope. |
config.description | str | Non-empty. |
config.semantic_warehouse_id | str (16+ hex chars) | Must be the deploy-time warehouse id (see Action S10). Never a template placeholder. |
data_sources.tables | list[object] | Each entry: {identifier: "catalog.schema.table", description?: List[str], column_configs?: [...] }. NO id field (adding one fails with Cannot find field: id). Sorted by identifier; any column_configs sorted by column_name. |
data_sources.metric_views | list[object] | Each entry: {identifier: "catalog.schema.mv_name", description?: List[str], column_configs?: [...] }. NO id field. Sorted by identifier; any column_configs sorted by column_name. |
instructions.general_instructions | list[str] | List of plain strings — no nested objects. |
instructions.sql_functions | list[object] | Each: {id: uuid4.hex, identifier: "catalog.schema.fn_name"}. No other fields. Max 50 entries. |
instructions.example_question_sqls | list[object] | Each: {id: uuid4.hex, question: List[str], sql: List[str]} plus optional parameters: [{name, type_hint, description?: List[str], default_value?}] and usage_guidance?: List[str]. question and sql MUST be List[str], never single strings. |
instructions.join_specs | list[object] | (v2) Each: {id: uuid4.hex, left: {identifier, alias}, right: {identifier, alias}, sql: List[str], comment?: List[str], instruction?: List[str]}. sql MUST contain exactly two elements: the backtick-quoted join condition AND a relationship annotation "--rt=FROM_RELATIONSHIP_TYPE_<CARDINALITY>--". Sorted by id. |
benchmarks.questions | list[object] | Each: {id: uuid4.hex, question: List[str], answer: [{format: "SQL"|"INSTRUCTIONS", content: List[str]}]}. SQL lives inside answer[].content, never as a top-level sql field. Max 50 entries. |
Every id field | str (32 hex chars) | uuid.uuid4().hex — lowercase, no dashes. Regenerate on every new deploy (never copy/paste IDs across environments). Time-ordered 32-hex UUIDs also work and self-satisfy sort order. |
The #1 silent-failure mode observed in production: example_question_sqls[].sql, example_question_sqls[].question, or benchmarks.questions[].answer[].content submitted as a single string instead of List[str]. The API accepts it, but the resulting space has empty/broken example queries because Genie serializes only the first character and then errors internally on render. There is no error surfaced on POST.
Validator — _assert_sql_arrays
Run this against every serialized_space dict BEFORE json.dumps(...) and BEFORE the POST/PATCH. Failing loud here saves ~10–15 minutes of UI-round-trip debugging per iteration.
import re
import uuid as _uuid_mod
from typing import Any, List
_UUID_HEX_RE = re.compile(r"^[0-9a-f]{32}$")
_WAREHOUSE_ID_RE = re.compile(r"^[0-9a-f]{16,}$")
def _is_uuid_hex(value: Any) -> bool:
return isinstance(value, str) and bool(_UUID_HEX_RE.match(value))
def _assert_sql_arrays(space: dict) -> None:
"""
Validate serialized_space invariants before POST / PATCH.
Raises RuntimeError on the FIRST violation — never returns False / warns.
Schema reference: https://docs.databricks.com/aws/en/genie/conversation-api
Enforces:
- version is 1 or 2 (prefer 2); missing / 0 is rejected.
- data_sources.tables / metric_views entries use `identifier` only (NO `id`);
any column_configs are sorted by `column_name`.
- instructions.sql_functions entries: {id, identifier} only.
- instructions.example_question_sqls entries: {id, question: List[str], sql: List[str]}
(optional `parameters` / `usage_guidance` are allowed).
- instructions.join_specs[].sql has exactly two elements (condition + --rt=...-- annotation).
- benchmarks.questions[].answer[].content must be List[str] (SQL lives there,
not as a top-level `sql` field).
"""
errors: List[str] = []
if space.get("version") not in (1, 2):
errors.append(
"serialized_space.version must be 1 or 2 (prefer 2); got %r" % space.get("version")
)
cfg = space.get("config") or {}
if not isinstance(cfg.get("title"), str) or not cfg.get("title"):
errors.append("config.title must be a non-empty string")
if not isinstance(cfg.get("description"), str) or not cfg.get("description"):
errors.append("config.description must be a non-empty string")
wh = cfg.get("semantic_warehouse_id")
if not isinstance(wh, str) or not _WAREHOUSE_ID_RE.match(wh or ""):
errors.append(
"config.semantic_warehouse_id must be a concrete warehouse id baked at deploy time; "
f"got {wh!r}. Template placeholders like '${{warehouse_id}}' are never acceptable."
)
ds = space.get("data_sources") or {}
for key in ("tables", "metric_views"):
items = ds.get(key) or []
if not isinstance(items, list):
errors.append(f"data_sources.{key} must be a list")
continue
idents = [it.get("identifier", "") for it in items]
if idents != sorted(idents):
errors.append(f"data_sources.{key} must be sorted by identifier (got {idents})")
for it in items:
if "id" in it:
errors.append(
f"data_sources.{key} entry MUST NOT include `id` — the API rejects with "
f"`Cannot find field: id`. Use only `identifier` and optional `description`. Got: {it}"
)
ident = it.get("identifier")
if not isinstance(ident, str) or ident.count(".") != 2:
errors.append(
f"data_sources.{key} entry `identifier` must be 'catalog.schema.name': {it}"
)
ccs = it.get("column_configs")
if ccs is not None:
if not isinstance(ccs, list):
errors.append(f"data_sources.{key} entry `column_configs` must be a list: {it}")
else:
names = [c.get("column_name", "") for c in ccs]
if names != sorted(names):
errors.append(
f"data_sources.{key}[{ident}].column_configs must be sorted by column_name (got {names})"
)
instr = space.get("instructions") or {}
sqlfns = instr.get("sql_functions") or []
if not isinstance(sqlfns, list):
errors.append("instructions.sql_functions must be a list")
else:
for idx, it in enumerate(sqlfns):
if not isinstance(it, dict):
errors.append(f"instructions.sql_functions[{idx}] must be an object")
continue
if not _is_uuid_hex(it.get("id")):
errors.append(f"instructions.sql_functions[{idx}].id must be uuid4.hex (32 hex chars)")
ident = it.get("identifier")
if not isinstance(ident, str) or ident.count(".") != 2:
errors.append(