# dbt Desk > Paste one dbt model — its Jinja SQL and, optionally, its `schema.yml` entry — and run one of four > jobs over that same model: review it against analytics-engineering practice, harden it with an > enforced contract and dbt Mesh access rules, generate the dbt unit tests that pin its logic, or > define the semantic model and MetricFlow metrics that sit on top of it. Live at https://dbt-desk.skillsafe.ai/ · API docs at https://dbt-desk.skillsafe.ai/api.html ## What it is for One work object: a single dbt model. The task sentence the app was built against is *"take one dbt model you already have — its Jinja SQL plus its schema.yml entry — and get it production-ready in one sitting: audit the model against analytics-engineering practice, harden it with an enforced contract and mesh access rules, generate the unit tests that pin its logic, and define the semantic model and metrics that sit on top of it."* Three of the four lanes emit a complete, paste-ready YAML file; the review lane can emit a rewritten `.sql`. Each lane's result carries a button that hands its artifact to the next lane, so the tests lane writes its fixtures against the contract lane's declared columns rather than against a re-paste. ## What runs for free, in your browser, with no account A deterministic scanner (`dbtscan.js`) across **61 rules**. It is not a set of regexes over raw text. It does one character-state pass that produces a masked copy of the file of the *same length*, so every offset and line number still points at what you are looking at: - **Jinja is treated as a preprocessor, because it is one.** dbt renders the whole file through Jinja before any SQL is parsed. So a `{{ ref() }}` inside a SQL comment or inside a SQL string literal is found and counted as a real dependency — and flagged (`DM-JINJA-IN-COMMENT`), because commenting out Jinja does not disable it. Only deleting it, or wrapping it in a `{# ... #}` Jinja comment, does. - **SQL comment bodies and string bodies are blanked**, so `select *` written inside a comment is not reported as code. A double-quoted *identifier* stays readable while quoted *data* is blanked, which is what lets the hardcoded-relation rule see a schema-qualified relation. - Paren depth is computed over real SQL parentheses only, with Jinja regions replaced by a non-identifier run — so a `(` inside a Jinja call cannot open a SQL group and `from {{ ref('x') }}` cannot be mistaken for a bare table name. From that it reads: the model name and layer (from the name prefix *and* from what the model actually reads, reported separately when they disagree), the materialization and every `config()` key, every `ref()`/`source()`/`var()`/`env_var()`, the CTE chain with a use count per CTE, the output columns of the final SELECT *and of every CTE*, relations referenced directly instead of through `ref()`, and — from a small indentation-based YAML parser — the model's description, columns, data types, constraints, data tests, contract, access, group, versions, unit tests, semantic models, metrics and groups. It also draws the upstream lineage as an SVG from the parsed refs and sources, and will hand you a `schema.yml` skeleton for exactly the columns it read, with the grain test pre-filled on the first key-shaped column. All of that is free and needs no sign-in. ## The four lanes | `task` | What it does | Artifact | | --- | --- | --- | | `review` | Reviews the model across nine named checks: `lineage`, `layering`, `grain`, `sql_correctness`, `materialization`, `incremental_safety`, `portability`, `documentation`, `test_coverage`. Names the model's grain explicitly. | optional rewritten `.sql` | | `contract` | Enforced contract with a justified `data_type` per column, `access` modifier, `group` and owner, `versions` and a deprecation plan, plus `dependencies.yml` for any cross-project `ref()`. | `.yml` | | `tests` | dbt `unit_tests:` YAML — fixed rows per mocked upstream, exact expected rows, one behaviour per test. Proves in an `input_coverage` table that every upstream is mocked. | `.yml` | | `metrics` | MetricFlow `semantic_models:` and `metrics:` — entities, dimensions, measures and the metrics built on them, grounded in the columns the model really returns. | `.yml` | ## The accountability contract Every flag the scanner raises for the selected lane is **sent with the run**, and the model is required to return exactly one `coverage_check` entry per flag: `confirmed` (with the finding id that carries it), `set-aside` (real, but another lane's job — the owning lane is named), or `superseded` (the scanner was wrong here, with the reason). A flag the model ignores is rendered to you as *"the model did not account for this flag"* rather than quietly dropped. Flags owned by other lanes travel as context only and are explicitly not the current lane's to answer. ## What it does not do dbt Desk does **not** run `dbt build`, `dbt test`, `dbt parse` or `dbt show`, does not read your `manifest.json` or `catalog.json`, and never connects to a warehouse. It works only from what you paste. The prompt forbids claiming otherwise: it says *"this test asserts"*, never *"this test passes"*, and where a check genuinely needs a run to settle it is marked `not-verified` and the exact dbt command is printed for you to run yourself. It also does not invent facts. Where a `data_type` cannot be derived from a `cast()` in the SQL or a documented column in the YAML, the contract lane says so in `inferred_from` and raises an open question rather than emitting a confident guess. `deprecation_date` is always the placeholder `YYYY-MM-DD (you choose)` — only you know when your consumers can move. ## Driving it programmatically Base URL `https://api.skillsafe.ai/v1/app-api`. The request body **is** the input object — there is no `input` wrapper, and wrapping it returns 200 while hiding `task` from the model. There is no `X-App-Slug` header; the token is app-scoped when it is minted. ```json { "task": "review", "model_sql": "{{ config(materialized='incremental', unique_key='order_id') }}\n\nwith orders as (\n select * from {{ ref('stg_orders') }}\n)\nselect order_id, amount from orders\n", "schema_yml": "version: 2\n\nmodels:\n - name: fct_orders\n columns:\n - name: order_id\n data_tests: [not_null]\n", "model_name": "fct_orders", "layer": "marts", "adapter": "snowflake", "dbt_version": "1.10+", "prescan": {"resources": [], "flags": [], "other_lane_flags": [], "stats": {}} } ``` Every lane returns the same envelope — `lane`, `lane_inferred`, `model_name`, `title`, `layer`, `materialization`, `posture` (`ready`|`hardening-recommended`|`blocked`), `verdict`, `summary`, `assumptions`, `open_questions`, `findings`, `coverage_check`, `artifact`, `next_lane`, `body` — and only `body` differs per lane. `POST /estimate` is free and returns the credit hold without creating a job. Past runs live in a declared `dbtruns` collection; records come back as `{record_id, doc: {...}}`, so read the fields off `doc`. Full walkthrough, in cURL, Python, JavaScript, Go, Java, Ruby, PHP and C#: https://dbt-desk.skillsafe.ai/api.html ## Provenance dbt Desk is a **derived work** built on four agent skills published by dbt Labs in `dbt-labs/dbt-agent-skills`: - `@dbt-labs/using-dbt-for-analytics-engineering` — the review lane - `@dbt-labs/working-with-dbt-mesh` — the contract lane - `@dbt-labs/adding-dbt-unit-test` — the tests lane - `@dbt-labs/building-dbt-semantic-layer` — the metrics lane `@dbt-labs/running-dbt-commands` was read and is not a lane — its value is that the agent runs a CLI, which a browser app cannot do — but it informs the house rules, which is why the review lane names the exact `dbt build --select` / `dbt test --select` command that would settle a `not-verified` check instead of guessing the outcome. Not affiliated with, endorsed by, or maintained by dbt Labs. dbt is a trademark of dbt Labs, Inc. Published on SkillSafe (https://skillsafe.ai/), which supplies the model, the auth and the datastore.