--- name: datajunction-api description: | Activate this skill when authoring DataJunction (DJ) nodes via the REST API directly (curl, HTTP clients) — typically for exploration, ad-hoc prototyping, or namespaces that aren't repo-backed. Keywords: - DJ API, REST API, curl - POST nodes/metric, POST nodes/dimension - create a metric via the API, create a dimension via the API - create a node with curl - prototyping, exploration user-invocable: false --- # DataJunction Direct API Authoring Direct REST API authoring for DJ nodes. Use this skill for quick exploration, prototyping, or working in namespaces that don't use the repo-backed workflow. For the modeling work upstream of any authoring (decomposition, naming, ratio decomposition, etc.), see `datajunction-semantic-model`. For the production-path equivalent of these patterns in YAML, see `datajunction-repo`. ## When to Use the API Approach **✅ MUST use repo workflow instead** (`datajunction-repo`) for: - Namespaces configured as repo-backed and read-only (`git_only: true`) — direct API changes are rejected **✅ Should use repo workflow** for: - Production changes (review required) - Multi-node changes (related metrics/dimensions) - Team environments (multiple contributors) - Audit-trail requirements - Complex refactoring **✅ API workflow is appropriate** for: - Quick exploration and prototyping - Ad-hoc analysis - Single-user, non-production namespaces - Temporary metrics - Non-production experiments - Only when the target namespace is NOT read-only repo-backed --- ## Checking if a Namespace Is Repo-Backed Before authoring via API, verify the target namespace allows it. **Best approach — `get_node_details` MCP tool** (`datajunction-query` skill): ``` get_node_details(name="finance.total_revenue") ``` The response will include git repository information: ``` Git Repository: Repo: owner/dj-finance Branch: main Default Branch: main → This namespace is repo-backed (use git workflow for changes) ``` **Alternative — REST API** (shows read-only status): ```bash curl -b ~/.dj/cookies.txt -X GET $DJ_URL/namespaces/finance/git # Response: { "github_repo_path": "owner/dj-finance", "git_branch": "main", "default_branch": "main", "git_path": "nodes/", "git_only": true ← If true, namespace is read-only (API changes blocked) } ``` **Decision tree:** - **If git info is present AND `git_only: true`**: MUST use repo workflow (API changes will fail) - **If git info is present AND `git_only: false`**: Can use either workflow - **If git info is null**: Use API workflow (direct POST/PATCH) --- ## Creating a Metric ### Metric Structure ```sql SELECT AS FROM ``` Metrics select a **single expression** from a **single source, transform, or dimension node**. They cannot contain WHERE clauses — use CASE WHEN instead. See `datajunction-semantic-model` for the modeling rationale. ### Metric Metadata Fields **Required:** - `name` — Fully qualified metric name (e.g., `finance.total_revenue`) - `query` — SQL aggregation expression **Recommended:** - `description` — Human-readable description - `metric_metadata.direction` — `higher_is_better` / `lower_is_better` / `neutral` - `metric_metadata.unit` — `dollar` / `unitless` (**⚠️ NOT `count`** — server rejects) - `mode` — `draft` / `published` - `required_dimensions` — Dimensions required for this metric to make sense - `owners` — List of email addresses (prefer team emails) ### Examples **COUNT**: ```bash curl -b ~/.dj/cookies.txt -X POST $DJ_URL/nodes/metric/ \ -H 'Content-Type: application/json' \ -d '{ "name": "finance.num_transactions", "description": "Total number of transactions", "query": "SELECT COUNT(transaction_id) AS num_transactions FROM finance.transactions", "owners": ["data-platform-team@company.com"], "mode": "published" }' ``` **SUM**: ```bash curl -b ~/.dj/cookies.txt -X POST $DJ_URL/nodes/metric/ \ -H 'Content-Type: application/json' \ -d '{ "name": "finance.total_revenue", "description": "Total revenue from all transactions", "query": "SELECT SUM(amount_usd) AS total_revenue FROM finance.transactions", "metric_metadata": { "direction": "higher_is_better", "unit": "dollar" }, "owners": ["finance-data-team@company.com"], "mode": "published" }' ``` **Conditional aggregation** (CASE WHEN, not WHERE): ```bash curl -b ~/.dj/cookies.txt -X POST $DJ_URL/nodes/metric/ \ -H 'Content-Type: application/json' \ -d '{ "name": "finance.completed_revenue", "description": "Revenue from completed non-refund transactions", "query": " SELECT SUM( CASE WHEN status = '\''completed'\'' AND refund_flag = false THEN amount_usd ELSE 0 END ) AS completed_revenue FROM finance.transactions ", "metric_metadata": { "direction": "higher_is_better", "unit": "dollar" }, "owners": ["finance-data-team@company.com"], "mode": "published" }' ``` **Ratio over base metrics** (decompose first, then derive — see `datajunction-semantic-model`): ```bash # Step 1: create the base metrics (one curl each) curl -X POST $DJ_URL/nodes/metric/ -d '{ "name": "finance.clicks", "query": "SELECT COUNT_IF(event = '\''click'\'') FROM finance.events", "owners": ["marketing@company.com"], "mode": "published" }' curl -X POST $DJ_URL/nodes/metric/ -d '{ "name": "finance.impressions", "query": "SELECT COUNT_IF(event = '\''impression'\'') FROM finance.events", "owners": ["marketing@company.com"], "mode": "published" }' # Step 2: derived ratio metric referencing the base metrics curl -X POST $DJ_URL/nodes/metric/ -d '{ "name": "finance.conversion_rate", "description": "Click-through rate as percentage", "query": "SELECT finance.clicks * 100.0 / NULLIF(finance.impressions, 0)", "metric_metadata": { "direction": "higher_is_better", "unit": "unitless" }, "owners": ["marketing@company.com"], "mode": "published" }' ``` DJ automatically handles divide-by-zero, but `NULLIF()` is extra safety. --- ## Creating Other Node Types via API Same pattern as metrics — POST JSON to the appropriate endpoint: - `POST /nodes/source/` — source nodes (catalog/schema/table refs) - `POST /nodes/dimension/` — dimension nodes - `POST /nodes/transform/` — transform nodes - `POST /nodes/cube/` — cubes (metric + dimension combinations) For the YAML-equivalent shapes of each, see `datajunction-repo` — the field set is the same, just expressed in JSON instead of YAML. --- ## Updating and Deleting Nodes **Update** (PATCH): ```bash curl -b ~/.dj/cookies.txt -X PATCH $DJ_URL/nodes/finance.total_revenue/ \ -H 'Content-Type: application/json' \ -d '{"description": "Updated description"}' ``` **Deactivate** (soft delete): ```bash curl -b ~/.dj/cookies.txt -X DELETE $DJ_URL/nodes/finance.total_revenue/ ``` Deactivated nodes can be revived. For hard-delete (irreversible), use the `dj` CLI: `dj delete-node finance.total_revenue --hard`.