{"id":"0effd653-c9d4-4d0a-8efe-b8b2d3d5f416","entityType":"agent","slug":"clawhub-cargo-ai-cargo-storage","name":"cargo-storage","canonicalUrl":"https://www.xpersona.co/agent/clawhub-cargo-ai-cargo-storage","canonicalPath":"/agent/clawhub-cargo-ai-cargo-storage","generatedAt":"2026-10-10T21:51:01.808Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-10T16:51:36.500Z","emptyReason":null},"description":"Work with the data inside a Cargo workspace — models (Companies, Contacts, Deals…), datasets, columns, relationships, records, and SQL over workspace storage. Triggers: \"what models do I have\", \"show me the schema\", \"add a column for\", \"how many contacts do I have\", \"SELECT … FROM\", \"query my companies table\", \"join contacts to companies\", \"what is the DDL\", \"set up a webhook-fed model\", \"where does this field live\", \"import this into a model\", \"unify these models\", \"merge duplicate accounts\", \"link contacts to companies\", \"set up a relationship between\". Skip when: querying run or batch telemetry rather than business data — use cargo-orchestration; naming a reusable filtered audience — use cargo-segmentation.","descriptionLabel":"Source description","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 1.3K downloads reported by the source. Last updated 10/10/2026.","installCommand":"clawhub skill install s178dcd9wkfn0a2fqrygmt3jzn87j9e1:cargo-storage","sourceUrl":"https://clawhub.ai/cargo-ai/cargo-storage","homepage":"https://clawhub.ai/cargo-ai/skills/cargo-storage","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/cargo-ai/cargo-storage","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/cargo-ai/skills/cargo-storage","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":62,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"cargo-storage technical dossier on Xpersona with agent coverage, OPENCLEW support, and live trust metadata."},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-10T16:51:36.500Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-10T16:51:36.500Z","emptyReason":null},"stars":null,"forks":null,"downloads":1331,"packageName":null,"latestVersion":"1.2.3","tractionLabel":"1.3K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-10T16:51:36.499Z","emptyReason":null},"lastUpdatedAt":"2026-10-10T16:51:36.500Z","lastCrawledAt":"2026-10-10T16:51:36.499Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-11T16:51:36.499Z","lastVerifiedAt":null,"highlights":[{"version":"1.2.3","createdAt":"2026-09-24T19:40:50.493Z","changelog":"cargo-storage 1.2.3 - Updated documentation in SKILL.md with clarified guidance on webhook usage for ingest models. - Added explicit note that webhooks are not for workflows; recommended using native actions for internal Cargo workflows. - Removed the obsolete skill-card.md file.","fileCount":11,"zipByteSize":20742},{"version":"1.2.2","createdAt":"2026-09-01T23:30:57.509Z","changelog":"cargo-storage v1.2.2 - Expanded and clarified documentation in SKILL.md for a broader range of real user prompts and workflows. - Updated dataset/model filtering instructions: direct model listing no longer accepts flags—use client-side filtering. - Added a \"Bootstrap\" section detailing initial login/installation and JSON output/error conventions. - Updated quick reference and example commands to match the latest CLI changes and recommended workflows. - Improved troubleshooting and response shape documentation references. - Removed deprecated file: skill-card.md.","fileCount":11,"zipByteSize":20771},{"version":"1.2.1","createdAt":"2026-08-11T21:43:35.810Z","changelog":"cargo-storage 1.2.1 - Updated login instructions: now documents email-based (`--email`) sign-in, and no longer requires browser for authentication. - Clarified compatibility requirements in the skill metadata. - Removed redundant documentation file (`skill-card.md`).","fileCount":11,"zipByteSize":17519},{"version":"1.2.0","createdAt":"2026-08-08T18:35:27.131Z","changelog":"cargo-storage 1.2.0 - Added documentation and examples for ingest (webhook-fed) models, including instructions for deriving the webhook URL and POSTing records. - Introduced section on previewing models/columns and best practices for validating schema and data after creation. - Added references/examples/ingest-webhook.md and skill-metadata.json files. - Removed deprecated skill-card.md file. - Updated SKILL.md to include new ingest model documentation and preview conventions.","fileCount":11,"zipByteSize":17517},{"version":"1.1.1","createdAt":"2026-05-28T22:12:47.914Z","changelog":"- Updated the Cargo CLI installation instructions to reference the latest version with @cargo-ai/cli@latest. - Prerequisites section now points to a centralized prerequisites document for install, login, and error handling details. - No CLI functionality changes; documentation improvements only.","fileCount":9,"zipByteSize":12562},{"version":"1.1.0","createdAt":"2026-05-28T18:26:20.526Z","changelog":"- Improved documentation with clearer instructions, usage examples, and command references in SKILL.md. - Expanded explanations for models, datasets, columns, relationships, records, and SQL queries. - Added troubleshooting, response shape references, and advanced usage tips. - Prerequisites, installation, and authentication steps are now highlighted. - Quick reference sections enable faster onboarding and easier command discovery.","fileCount":9,"zipByteSize":12816}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s178dcd9wkfn0a2fqrygmt3jzn87j9e1:cargo-storage","setupComplexity":"low","setupSteps":["Install using `clawhub skill install s178dcd9wkfn0a2fqrygmt3jzn87j9e1:cargo-storage` in an isolated environment before connecting it to live workloads.","No published capability contract is available yet, so validate auth and request/response behavior manually.","Review the upstream CLAWHUB listing at https://clawhub.ai/cargo-ai/cargo-storage before using production credentials."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-cargo-ai-cargo-storage/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-cargo-ai-cargo-storage/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-cargo-ai-cargo-storage/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-cargo-ai-cargo-storage/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-cargo-ai-cargo-storage/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-cargo-ai-cargo-storage/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-10T21:51:01.805Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-cargo-ai-cargo-storage/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-cargo-ai-cargo-storage/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-cargo-ai-cargo-storage/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-cargo-ai-cargo-storage/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-10T16:51:36.500Z","emptyReason":null},"readme":"Skill: cargo-storage\n\nOwner: cargo-ai\n\nSummary: Work with the data inside a Cargo workspace — models (Companies, Contacts, Deals…), datasets, columns, relationships, records, and SQL over workspace storage. Triggers: \"what models do I have\", \"show me the schema\", \"add a column for\", \"how many contacts do I have\", \"SELECT … FROM\", \"query my companies table\", \"join contacts to companies\", \"what is the DDL\", \"set up a webhook-fed model\", \"where does this field live\", \"import this into a model\", \"unify these models\", \"merge duplicate accounts\", \"link contacts to companies\", \"set up a relationship between\". Skip when: querying run or batch telemetry rather than business data — use cargo-orchestration; naming a reusable filtered audience — use cargo-segmentation.\n\nTags: latest:1.2.3\n\nVersion history:\n\nv1.2.3 | 2026-09-24T19:40:50.493Z | auto\n\ncargo-storage 1.2.3\n\n- Updated documentation in SKILL.md with clarified guidance on webhook usage for ingest models.\n- Added explicit note that webhooks are not for workflows; recommended using native actions for internal Cargo workflows.\n- Removed the obsolete skill-card.md file.\n\nv1.2.2 | 2026-09-01T23:30:57.509Z | auto\n\ncargo-storage v1.2.2\n\n- Expanded and clarified documentation in SKILL.md for a broader range of real user prompts and workflows.\n- Updated dataset/model filtering instructions: direct model listing no longer accepts flags—use client-side filtering.\n- Added a \"Bootstrap\" section detailing initial login/installation and JSON output/error conventions.\n- Updated quick reference and example commands to match the latest CLI changes and recommended workflows.\n- Improved troubleshooting and response shape documentation references.\n- Removed deprecated file: skill-card.md.\n\nv1.2.1 | 2026-08-11T21:43:35.810Z | auto\n\ncargo-storage 1.2.1\n\n- Updated login instructions: now documents email-based (`--email`) sign-in, and no longer requires browser for authentication.\n- Clarified compatibility requirements in the skill metadata.\n- Removed redundant documentation file (`skill-card.md`).\n\nv1.2.0 | 2026-08-08T18:35:27.131Z | auto\n\ncargo-storage 1.2.0\n\n- Added documentation and examples for ingest (webhook-fed) models, including instructions for deriving the webhook URL and POSTing records.\n- Introduced section on previewing models/columns and best practices for validating schema and data after creation.\n- Added references/examples/ingest-webhook.md and skill-metadata.json files.\n- Removed deprecated skill-card.md file.\n- Updated SKILL.md to include new ingest model documentation and preview conventions.\n\nv1.1.1 | 2026-05-28T22:12:47.914Z | auto\n\n- Updated the Cargo CLI installation instructions to reference the latest version with @cargo-ai/cli@latest.\n- Prerequisites section now points to a centralized prerequisites document for install, login, and error handling details.\n- No CLI functionality changes; documentation improvements only.\n\nv1.1.0 | 2026-05-28T18:26:20.526Z | auto\n\n- Improved documentation with clearer instructions, usage examples, and command references in SKILL.md.\n- Expanded explanations for models, datasets, columns, relationships, records, and SQL queries.\n- Added troubleshooting, response shape references, and advanced usage tips.\n- Prerequisites, installation, and authentication steps are now highlighted.\n- Quick reference sections enable faster onboarding and easier command discovery.\n\nArchive index:\n\nArchive v1.2.3: 11 files, 20742 bytes\n\nFiles: references/examples/columns.md (5387b), references/examples/datasets.md (1234b), references/examples/ingest-webhook.md (6906b), references/examples/models.md (2220b), references/examples/queries.md (5065b), references/response-shapes.md (6454b), references/troubleshooting.md (4921b), skill-card.md (2033b), skill-metadata.json (1308b), SKILL.md (14975b), _meta.json (132b)\n\nFile v1.2.3:SKILL.md\n\n---\nname: cargo-storage\ndescription: \"Work with the data inside a Cargo workspace — models (Companies, Contacts, Deals…), datasets, columns, relationships, records, and SQL over workspace storage. Triggers: \\\"what models do I have\\\", \\\"show me the schema\\\", \\\"add a column for\\\", \\\"how many contacts do I have\\\", \\\"SELECT … FROM\\\", \\\"query my companies table\\\", \\\"join contacts to companies\\\", \\\"what is the DDL\\\", \\\"set up a webhook-fed model\\\", \\\"where does this field live\\\", \\\"import this into a model\\\", \\\"unify these models\\\", \\\"merge duplicate accounts\\\", \\\"link contacts to companies\\\", \\\"set up a relationship between\\\". Skip when: querying run or batch telemetry rather than business data — use cargo-orchestration; naming a reusable filtered audience — use cargo-segmentation.\"\nversion: \"1.2.3\"\ncompatibility: Requires @cargo-ai/cli (npm). Sign in or create an account with `cargo-ai login --email` (emailed code, no browser), `--oauth`, or an API token\nhomepage: https://github.com/getcargohq/cargo-skills\nmetadata:\n  author: getcargo\n  openclaw:\n    requires:\n      bins:\n        - cargo-ai\n    install:\n      - kind: node\n        package: \"@cargo-ai/cli@latest\"\n        bins:\n          - cargo-ai\n    homepage: https://github.com/getcargohq/cargo-skills\n---\n\n# Cargo CLI — Storage\n\nData layer management: inspecting and modifying models, datasets, columns, relationships, unification, and records, and running SQL queries against workspace storage.\n\n> See `references/response-shapes.md` for full JSON response structures.\n> See `references/troubleshooting.md` for common errors and how to fix them.\n> See `references/examples/models.md` for model CRUD, DDL inspection, and schema discovery examples.\n> See `references/examples/datasets.md` for dataset listing and navigation examples.\n> See `references/examples/columns.md` for column creation and management examples.\n> See `references/examples/queries.md` for `storage query execute` / `storage query download` SQL examples (WHERE, aggregations, joins, pagination, exports).\n> See `references/examples/ingest-webhook.md` for ingest (webhook-fed) models — deriving the webhook URL and POSTing records.\n\n## Bootstrap\n\nAlready signed in (`cargo-ai whoami` returns a workspace)? Skip to the next section.\n\n```bash\nnpm install -g @cargo-ai/cli            # no global install? prefix every command with `npx @cargo-ai/cli`\ncargo-ai login --email you@company.com  # emailed code, no browser; creates the account on first use\n                                        # alternatives: --oauth (browser) · --token <api-token> (CI)\ncargo-ai whoami                         # confirm the active workspace before any write\n```\n\nEvery command prints JSON to stdout; failures exit non-zero with `{\"errorMessage\": \"...\"}`. Anything that creates a run or a batch is async — pass `--wait-until-finished` or poll the matching `get`. When the full skill bundle is installed, [`../cargo/references/prerequisites.md`](../cargo/references/prerequisites.md) adds the CLI version pin, token scopes, and the admin-only surface.\n\n## Discover resources first\n\nAlways list before inspecting or modifying.\n\n```bash\ncargo-ai storage dataset list              # all datasets (uuid, slug)\ncargo-ai storage model list                # all models (uuid, name, slug, columns, datasetUuid)\n# `model list` takes no flags — filter its output instead:\ncargo-ai storage model list | jq '[.models[] | select(.datasetUuid == \"<uuid>\")]' \n```\n\n**Retrieve in the UI:** models live at `app.getcargo.io/workspaces/<WORKSPACE_UUID>/models/<MODEL_UUID>`. Get `<WORKSPACE_UUID>` from `cargo-ai whoami` under `workspace.uuid`.\n\n## Quick reference\n\n```bash\ncargo-ai storage model list\ncargo-ai storage model get <model-uuid>\ncargo-ai storage model get-ddl <model-uuid>\ncargo-ai storage dataset list\ncargo-ai storage column list --model-uuid <uuid>\ncargo-ai storage relationship list\ncargo-ai storage record list --model-uuid <uuid>\ncargo-ai storage query execute \"SELECT * FROM default.companies LIMIT 10\"\ncargo-ai storage query download --query \"SELECT * FROM default.companies\"\n```\n\n## Models\n\nModels are structured tables in your workspace (e.g. Companies, Contacts).\n\n```bash\n# List all models\ncargo-ai storage model list\n\n# List models in a dataset — every model carries `datasetUuid`, and\n# `model list` has no flags of its own, so filter client-side\ncargo-ai storage model list | jq '[.models[] | select(.datasetUuid == \"<uuid>\")]' \n\n# Get a single model (includes columns)\ncargo-ai storage model get <model-uuid>\n\n# Get the DDL (full schema, table name and SQL dialect)\ncargo-ai storage model get-ddl <model-uuid>\n# → Useful for column discovery and SQL dialect (BigQuery vs Snowflake) before writing queries\n\n# Create a model\ncargo-ai storage model create \\\n  --slug contacts \\\n  --name \"Contacts\" \\\n  --dataset-uuid <uuid> \\\n  --extractor-slug <extractor-slug> \\\n  --config '{}'\n\n# Update a model\ncargo-ai storage model update --uuid <model-uuid> --name \"New Name\"\n\n# Remove a model\ncargo-ai storage model remove <model-uuid>\n```\n\n**Querying:** Use `cargo-ai storage query execute \"<sql>\"` (or `storage query download --query \"<sql>\"` for full exports) to run SQL against storage. Tables are referenced as `<datasetSlug>.<modelSlug>` (e.g. `default.companies`) and rewritten to the underlying storage table under the hood. See [Query with SQL](#query-with-sql) below.\n\n## Ingest models (webhook-fed)\n\nA model whose extractor has `mode.kind === \"ingest\"` — `http.listenHook` and\nfriends — is filled by **pushing** records to Cargo. The app shows a \"Webhook URL\"\non the model settings screen; **no CLI command or API field returns it**, but it's\nassembled from values the CLI already exposes:\n\n```\n<baseUrl>/v1/models/<model-uuid>/records/ingest?token=<api-token>\n```\n\n```bash\nMODEL_UUID=<model-uuid>\nBASE=$(cargo-ai whoami | jq -r '.baseUrl')\nTOKEN=$(cargo-ai workspaceManagement token list | jq -r '.tokens[0].token')\necho \"$BASE/v1/models/$MODEL_UUID/records/ingest?token=$TOKEN\"\n```\n\n**Not for workflows.** A Cargo workflow writes to a model with the native\n`modelUpsert` / `modelInsert` action (see `cargo-orchestration` →\n`references/nodes.md` → \"Storage\"). An HTTP node calling this URL costs an extra node,\na payload script, and an API token pasted into the node config. The webhook is for\nsystems outside Cargo.\n\nCheck the extractor's mode first — when it reports `\"autoIngest\": true` (calendly,\nsmartlead, instantlyV2, heyReach, cargo signals) Cargo registers the\nhook with the provider itself and the URL must **not** be handed out. Full flow,\npayload shapes, and limits: `references/examples/ingest-webhook.md`.\n\n## Datasets\n\nDatasets are logical groupings of models.\n\n```bash\n# List all datasets\ncargo-ai storage dataset list\n\n# Get a single dataset\ncargo-ai storage dataset get <dataset-uuid>\n```\n\n## Columns\n\nColumns define the schema of a model.\n\n```bash\n# List columns for a model\ncargo-ai storage column list --model-uuid <uuid>\n\n# Create a column\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"my_column\",\"type\":\"string\",\"label\":\"My Column\",\"kind\":\"custom\"}'\n\n# Update a column (pass the full column object — columns are identified by slug, not UUID)\ncargo-ai storage column update \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"my_column\",\"type\":\"string\",\"label\":\"Updated Label\",\"kind\":\"custom\"}'\n\n# Remove a column\ncargo-ai storage column remove --model-uuid <uuid> --column-slug <slug>\n\n# Reorder a column (move to a specific index)\ncargo-ai storage column reorder --model-uuid <uuid> --column-slug <slug> --to-index 2\n```\n\nColumn types: `string`, `number`, `boolean`, `date`, `object`, `array`, `vector`, `any`.\n\nColumn kinds: `custom` (user-defined), `computed` (expression over other columns), `metric` (aggregated from a related model), `lookup` (single field pulled from a related model via a join).\n\n## Preview what you built\n\nA column list doesn't tell the user whether the model is right — rows do. Two checkpoints (the pack-wide convention lives in [`../cargo/references/interaction.md`](../cargo/references/interaction.md) §4):\n\n**1. Right after `model create` / `column create` — show the schema, not rows.** A new model is empty; a `LIMIT 10` here returns nothing and reads as failure. Echo the columns as a compact table instead (column, type, what will fill it).\n\n**2. As soon as data lands — show the rows.** After a batch, play, or import writes into the model, preview it:\n\n```bash\ncargo-ai storage query execute \\\n  \"SELECT * FROM <dataset-slug>.<model-slug> LIMIT 10\"\n```\n\nShow ~10 rows and only the columns that carry meaning. Storage queries are free, so this costs nothing but a few lines of output — and it's the first moment the user can actually see what they built. When a play fills a *new* column, preview that column next to the record's identifying fields (`name`, `domain`) so filled vs. empty is obvious.\n\nIf the preview comes back empty or all-null when it shouldn't, that's a finding — surface it rather than reporting the write as a success. See [`cargo-diagnostics`](../cargo-diagnostics/SKILL.md) to trace why.\n\n## Relationships\n\nRelationships link models together (e.g. Contacts belong to Companies). They are\nauthored from the CLI, not just the UI.\n\n`relationship list` takes **no flags** — it returns every relationship in the\nworkspace. Filter client-side on `fromModelUuid` / `toModelUuid`.\n\n```bash\ncargo-ai storage relationship list\n```\n\n**`relationship set` replaces the dataset's whole relationship set.** It takes a\ndataset and the complete list that should exist within it: entries carrying a\n`uuid` are updated, entries without one are created, and **any existing\nrelationship whose `uuid` is absent from the payload is deleted**. Sending one\nrelationship to a dataset that has five removes the other four. Always `list`\nfirst, then send back the full array with your addition:\n\n```bash\ncargo-ai storage relationship set \\\n  --dataset-uuid <dataset-uuid> \\\n  --relationships '[\n    {\"uuid\":\"<existing-uuid>\",\"fromModelUuid\":\"<contacts-uuid>\",\"fromColumnSlug\":\"account_id\",\"toModelUuid\":\"<companies-uuid>\",\"toColumnSlug\":\"id\",\"relation\":\"manyToOne\"},\n    {\"fromModelUuid\":\"<deals-uuid>\",\"fromColumnSlug\":\"company_id\",\"toModelUuid\":\"<companies-uuid>\",\"toColumnSlug\":\"id\",\"relation\":\"manyToOne\"}\n  ]'\n```\n\n`relation` is `oneToOne`, `manyToOne`, or `oneToMany`. Both models must live in\nthe dataset you pass — relationships never span datasets, so `fromDatasetUuid`\nand `toDatasetUuid` on the response always equal `--dataset-uuid`.\n\nFailure reasons: `datasetNotFound`; `invalidRelationships` (a column slug or\nmodel UUID that doesn't resolve, or a duplicate — including the same pair stated\nin reverse); `modelNotCompatible` (see below).\n\n**Unify models refuse manual relationships.** In the native dataset, a unify\nmodel's relationships are generated during sync, so naming one as `fromModelUuid`\nor `toModelUuid` returns `modelNotCompatible`. Those auto-generated rows are also\nexcluded from the replace above, so a `set` call cannot delete them.\n\n## Unification\n\nUnification is what merges records from several source models into one canonical\naccount/contact — and it is **configurable from the CLI**, via `--unification` on\n`model update`. Pass `null` to clear it.\n\n```bash\n# Connector-driven: the integration decides how records unify\ncargo-ai storage model update --uuid <model-uuid> --unification '{\"source\":\"integration\"}'\n\n# Custom: you name the type, the matching keys, and optionally a parent\ncargo-ai storage model update --uuid <model-uuid> --unification '{\n  \"source\": \"custom\",\n  \"type\": \"account\",\n  \"uniqueColumns\": [{\"slug\":\"domain\",\"reference\":\"domain\"}],\n  \"selectedColumnSlugs\": [\"name\",\"industry\",\"employee_count\"],\n  \"parent\": {\"kind\":\"model\",\"columnSlug\":\"account_id\",\"parentModelUuid\":\"<accounts-uuid>\"}\n}'\n```\n\n| Field | Applies to | Meaning |\n|---|---|---|\n| `source` | both | `integration` (connector-defined) or `custom` |\n| `type` | custom | `account`, `contact`, `accountEvent`, `contactEvent` |\n| `uniqueColumns` | custom | Match keys — `{slug, reference}` per column. This is what decides which rows are the same entity |\n| `selectedColumnSlugs` | custom | Columns carried into the unified model. Omit for all |\n| `timeColumnSlug` | custom | Event timestamp — for the two `*Event` types |\n| `parent` | custom | Links contacts/events to their account: `{\"kind\":\"model\",\"columnSlug\":…,\"parentModelUuid\":…}` or `{\"kind\":\"reference\",\"columnSlug\":…,\"reference\":…}` |\n| `filter` | custom | Segmentation filter restricting which rows unify — same `conjonction` shape as segments |\n\n**Writing the config does not recompute anything.** The unified rows are rebuilt\nby the model's sync run, so follow the update with a run and poll it:\n\n```bash\ncargo-ai storage run create --model-uuid <model-uuid>\ncargo-ai storage run list --model-uuid <model-uuid>\n```\n\nGet the current config from `storage model get <uuid>` → `unification` (`null`\nwhen the model doesn't unify). Once the run finishes, check the row count with\n`storage query execute` before treating the change as done — a too-narrow\n`uniqueColumns` under-merges and a too-broad one collapses distinct entities, and\nboth look like a successful run.\n\n## Records\n\n```bash\n# List records in a model\ncargo-ai storage record list --model-uuid <uuid>\n```\n\nFor advanced record queries (filtering, sorting, pagination), use `segmentation segment fetch` from the `cargo-orchestration` skill.\n\n## Query with SQL\n\nRun SQL against workspace storage with `storage query execute`. Tables are referenced as `<datasetSlug>.<modelSlug>` (e.g. `default.companies`) and rewritten to the underlying storage table under the hood — no DDL lookup is needed for the table name.\n\n```bash\ncargo-ai storage query execute \\\n  \"SELECT name, domain FROM default.companies LIMIT 10\"\n# → { \"rows\": [...] } on success; non-zero exit with { \"errorMessage\": \"...\" } on error\n```\n\nFor full exports, use `storage query download` — it returns a signed URL to a CSV (default) or Parquet file:\n\n```bash\ncargo-ai storage query download \\\n  --query \"SELECT name, domain, revenue FROM default.companies ORDER BY revenue DESC\"\n\ncargo-ai storage query download \\\n  --query \"SELECT * FROM default.companies\" --format parquet\n```\n\nGet column slugs from `storage column list --model-uuid <uuid>` (or run `storage model get-ddl <model-uuid>` for the full schema and SQL dialect). Page through large result sets with `LIMIT` / `OFFSET` directly in the SQL.\n\nSee `references/examples/queries.md` for WHERE clauses, aggregations, joins, date queries, pagination, and the failure shapes returned on error.\n\n## Help\n\nEvery command supports `--help`:\n\n```bash\ncargo-ai storage model list --help\ncargo-ai storage column create --help\ncargo-ai storage relationship set --help\ncargo-ai storage query execute --help\ncargo-ai storage query download --help\n```\n\nFile v1.2.3:_meta.json\n\n{\n  \"ownerId\": \"kn7by8t6yt9yghbxtxz6hv0bts87k6bq\",\n  \"slug\": \"cargo-storage\",\n  \"version\": \"1.2.3\",\n  \"publishedAt\": 1790278850493\n}\n\nFile v1.2.3:references/examples/columns.md\n\n# Column examples\n\n## List columns for a model\n\n```bash\ncargo-ai storage column list --model-uuid <uuid>\n```\n\nResponse includes `uuid`, `slug`, `type`, `label`, and `position` for each column.\n\n## Create a string column\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"website_url\",\"type\":\"string\",\"label\":\"Website URL\",\"kind\":\"custom\"}'\n```\n\n## Create a number column\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"arr\",\"type\":\"number\",\"label\":\"Annual Recurring Revenue\",\"kind\":\"custom\"}'\n```\n\n## Create a date column\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"last_contacted_at\",\"type\":\"date\",\"label\":\"Last Contacted At\",\"kind\":\"custom\"}'\n```\n\n## Create a boolean column\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"is_customer\",\"type\":\"boolean\",\"label\":\"Is Customer\",\"kind\":\"custom\"}'\n```\n\n## Create a computed column\n\nComputed columns derive their value from an expression over other columns.\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"full_name\",\"type\":\"string\",\"label\":\"Full Name\",\"kind\":\"computed\",\"expression\":{\"kind\":\"jsExpression\",\"expression\":\"{{record.first_name}} {{record.last_name}}\",\"instructTo\":\"none\",\"fromRecipe\":false},\"columnsUsed\":[\"first_name\",\"last_name\"]}'\n```\n\n`columnsUsed` is optional but recommended for dependency tracking.\n\n## Create a metric column\n\nMetric columns aggregate data from a related model via a relationship.\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"total_deals\",\"type\":\"number\",\"label\":\"Total Deals\",\"kind\":\"metric\",\"relationshipUuid\":\"<relationship-uuid>\",\"aggregation\":{\"function\":\"count\",\"columnSlug\":\"uuid\"}}'\n```\n\nWith an optional filter:\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"open_deals\",\"type\":\"number\",\"label\":\"Open Deals\",\"kind\":\"metric\",\"relationshipUuid\":\"<relationship-uuid>\",\"aggregation\":{\"function\":\"count\",\"columnSlug\":\"uuid\"},\"filter\":{\"conjonction\":\"and\",\"groups\":[{\"conjonction\":\"and\",\"conditions\":[{\"kind\":\"string\",\"slug\":\"status\",\"operator\":\"is\",\"value\":\"open\"}]}]}}'\n```\n\n## Create a lookup column\n\nLookup columns pull a field value from a related model via a join.\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"company_name\",\"type\":\"string\",\"label\":\"Company Name\",\"kind\":\"lookup\",\"join\":{\"toModelUuid\":\"<company-model-uuid>\",\"fromColumnSlug\":\"company_uuid\",\"toColumnSlug\":\"uuid\"},\"extractColumnSlug\":\"name\"}'\n```\n\nWith an optional filter:\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"primary_contact_email\",\"type\":\"string\",\"label\":\"Primary Contact Email\",\"kind\":\"lookup\",\"join\":{\"toModelUuid\":\"<contacts-model-uuid>\",\"fromColumnSlug\":\"uuid\",\"toColumnSlug\":\"company_uuid\"},\"extractColumnSlug\":\"email\",\"filter\":{\"conjonction\":\"and\",\"groups\":[{\"conjonction\":\"and\",\"conditions\":[{\"kind\":\"boolean\",\"slug\":\"is_primary\",\"operator\":\"isTrue\"}]}]}}'\n```\n\n## Update a column\n\nPass the full column object via `--column`. Columns are identified by `slug` (no UUID).\n\n```bash\ncargo-ai storage column update \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"website_url\",\"type\":\"string\",\"label\":\"Website\",\"kind\":\"custom\"}'\n```\n\n## Remove a column\n\n```bash\ncargo-ai storage column remove --model-uuid <uuid> --column-slug website_url\n```\n\n## Reorder a column\n\nMove a column to a specific position index (0-based).\n\n```bash\ncargo-ai storage column reorder --model-uuid <uuid> --column-slug website_url --to-index 2\n```\n\n## Column types reference\n\n| Type      | Use for                  |\n| --------- | ------------------------ |\n| `string`  | Text, names, URLs, slugs |\n| `number`  | Counts, amounts, scores  |\n| `boolean` | Flags, yes/no values     |\n| `date`    | Timestamps, dates        |\n| `object`  | Nested JSON objects      |\n| `array`   | Lists of values          |\n| `vector`  | Embedding vectors        |\n| `any`     | Untyped / mixed values   |\n\n## Column kinds reference\n\n| Kind       | Use for                                                     | Required extra fields                                                                                    |\n| ---------- | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |\n| `custom`   | User-defined fields                                         | —                                                                                                        |\n| `computed` | Values derived from an expression over other columns        | `expression`; optionally `columnsUsed`                                                                   |\n| `metric`   | Aggregated values from a related model                      | `relationshipUuid`, `aggregation.function`, `aggregation.columnSlug`; optionally `filter`                |\n| `lookup`   | A single field value pulled from a related model via a join | `join.toModelUuid`, `join.fromColumnSlug`, `join.toColumnSlug`, `extractColumnSlug`; optionally `filter` |\n\nColumn `slug` values are used in filter conditions (see `cargo-orchestration` skill's `references/filter-syntax.md`) and in `storage query execute` SQL queries.\n\nFile v1.2.3:references/examples/datasets.md\n\n# Dataset examples\n\n## List all datasets\n\nDatasets group related models together.\n\n```bash\ncargo-ai storage dataset list\n```\n\nResponse includes `uuid`, `name`, and `slug` for each dataset.\n\n## Get a specific dataset\n\n```bash\ncargo-ai storage dataset get <dataset-uuid>\n```\n\n## List models in a dataset\n\n`storage model list` takes **no options** — it always returns every model in the\nworkspace. Each one carries a `datasetUuid`, so narrow it client-side:\n\n```bash\ncargo-ai storage model list | jq '[.models[] | select(.datasetUuid == \"<dataset-uuid>\")]'\n```\n\n## Discover workspace data structure\n\nFull flow to understand how data is organized:\n\n```bash\n# 1. List all datasets\ncargo-ai storage dataset list\n# → Note the dataset UUIDs and slugs\n\n# 2. Group the models by dataset (one call — `model list` has no filter flag)\ncargo-ai storage model list | jq 'group_by(.datasetUuid) | map({datasetUuid: .[0].datasetUuid, models: map(.slug)})'\n# → See which models (tables) belong to each dataset\n\n# 3. Inspect a model's columns\ncargo-ai storage model get <model-uuid>\n# → See column slugs and types for each model\n```\n\nThe dataset `slug` appears in DDL table names (e.g. `datasets_default` for the dataset with slug `default`).\n\nFile v1.2.3:references/examples/ingest-webhook.md\n\n# Ingest models — get the webhook URL and POST records\n\nSome models are fed by **pushing** records to Cargo instead of Cargo pulling them.\nTheir extractor has `mode.kind === \"ingest\"` — the canonical one is the `http`\nintegration's `listenHook` (\"Listen webhook\"), but the same mechanism backs\n`storeleads.listenList`, `rb2b.listenProfiles`, `albacross.listenWebsiteVisits`,\nand others.\n\nThe app shows a **Webhook URL** on the model's settings screen. There is **no CLI\ncommand and no API field that returns it** — the app builds the string client-side.\nYou can build the exact same string from data the CLI already exposes.\n\n## The URL\n\n```\n<baseUrl>/v1/models/<model-uuid>/records/ingest?token=<api-token>\n```\n\n- `<baseUrl>` — `cargo-ai whoami` → `.baseUrl` (e.g. `https://api.getcargo.io`).\n  Note the path is `/v1/models/...`, **not** `/v1/storage/models/...`.\n- `<model-uuid>` — the ingest model's UUID.\n- `<api-token>` — any workspace API token. The token may carry **zero\n  permissions**; this route is explicitly allowed for permission-less tokens so\n  the URL can be handed to a third-party system safely. The app auto-creates one\n  named `Quick access` for exactly this.\n\n`token` can also be sent as an `Authorization: Basic <token>` header instead of a\nquery param — preferable when the receiving system supports custom headers, since\na query param lands in logs.\n\n## Derive it\n\n```bash\n# 1. Find the model and confirm it is an ingest model\ncargo-ai storage model get <model-uuid> | jq '{uuid, slug, extractorSlug, kind, connectorUuid}'\n\n# 2. Confirm the extractor's mode is \"ingest\" (and NOT autoIngest — see below)\ncargo-ai connection integration get http | jq -c '.integration.extractors.listenHook.mode'\n# → {\"kind\":\"ingest\"}\n\n# 3. Pick or create a token (the raw value is on the list response)\ncargo-ai workspaceManagement token list | jq -r '.tokens[0].token'\ncargo-ai workspaceManagement token create --name \"Webhook — <model-slug>\" | jq -r '.token.token'\n```\n\nOne-liner that assembles it:\n\n```bash\nMODEL_UUID=<model-uuid>\nBASE=$(cargo-ai whoami | jq -r '.baseUrl')\nTOKEN=$(cargo-ai workspaceManagement token list | jq -r '.tokens[0].token')\necho \"$BASE/v1/models/$MODEL_UUID/records/ingest?token=$TOKEN\"\n```\n\n> Token values are secrets. Print the URL for the user to copy; don't write it\n> into a file, a commit, or a report.\n\n## Skip models where Cargo owns the hook\n\nSome ingest extractors set `autoIngest: true` — Cargo registers the webhook with\nthe provider itself during setup (calendly, smartlead, instantlyV2, heyReach,\nand cargo's own signal extractors). The app **hides** the URL for\nthose, and handing it out is wrong: the provider is already pointed at it.\n\nCheck before showing anything:\n\n```bash\ncargo-ai connection integration get <integration-slug> \\\n  | jq -c '.integration.extractors[\"<extractor-slug>\"].mode'\n# {\"kind\":\"ingest\"}                    → manual: show the URL\n# {\"kind\":\"ingest\",\"autoIngest\":true}  → Cargo owns it: don't show the URL\n# anything else (fetch/…)              → not an ingest model at all\n```\n\nList every ingest extractor an integration has:\n\n```bash\ncargo-ai connection integration get <integration-slug> \\\n  | jq -c '.integration.extractors | to_entries\n           | map(select(.value.mode.kind==\"ingest\")) | map({(.key): .value.mode})'\n# calendly → [{\"fetchEvents\":{\"kind\":\"ingest\",\"autoIngest\":true}}]\n```\n\n## POST records\n\nThe body is **either one flat object or an array of flat objects** — each object\nbecomes one record, and its keys become columns. Max **100 records per request**.\n\n```bash\n# one record\ncurl -X POST \"$BASE/v1/models/$MODEL_UUID/records/ingest?token=$TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"email\":\"ada@example.com\",\"company\":\"example.com\"}'\n\n# many records\ncurl -X POST \"$BASE/v1/models/$MODEL_UUID/records/ingest?token=$TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '[{\"email\":\"ada@example.com\"},{\"email\":\"grace@example.com\"}]'\n\n# token as a header instead of a query param\ncurl -X POST \"$BASE/v1/models/$MODEL_UUID/records/ingest\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Authorization: Basic $TOKEN\" \\\n  -d '{\"email\":\"ada@example.com\"}'\n# all → 200 {\"message\":\"OK\"}\n```\n\n**The endpoint is insert-only.** Do not send an envelope like\n`{\"kind\":\"insert\",\"records\":[…]}` — there is no unwrapping, so you get one useless\nrow with a `kind` column and a `records` column holding the stringified array.\nThe `{kind: insert|update|remove, records: […]}` shape belongs to the extractor's\ninternal contract, not to this HTTP body; `update` and `remove` are not reachable\nthrough the webhook.\n\nFor `http.listenHook`, the model's id column is `_ingest_id` (a UUID **generated\nserver-side** — never send it; the extractor rejects inserts that carry one) and\nits title column is `_emitted_at`.\n\nThen confirm the rows landed (storage queries are free):\n\n```bash\ncargo-ai storage query execute \"SELECT * FROM <dataset-slug>.<model-slug> LIMIT 10\"\n```\n\n## Create an ingest model from scratch\n\n`model create` has no `--connector-uuid` — the API infers the connector from the\n**dataset**. Every connector automatically owns exactly one `kind: \"connector\"`\ndataset, so the flow is: create the connector, find its dataset, create the model\nin it.\n\n```bash\n# 1. Connector (slug must be snake_case: /^[a-z0-9]+(_[a-z0-9]+)*$/)\ncargo-ai connection connector create \\\n  --name \"Inbound leads\" --slug inbound_leads \\\n  --integration-slug http --config '{}' | jq -r '.connector.uuid'\n\n# 2. Its dataset — dataset list takes no --connector-uuid filter, so filter locally\ncargo-ai storage dataset list \\\n  | jq -c --arg c <connector-uuid> '.datasets[] | select(.connectorUuid==$c) | {uuid, slug}'\n\n# 3. The model\ncargo-ai storage model create \\\n  --slug inbound_leads --name \"Inbound Leads\" \\\n  --dataset-uuid <dataset-uuid> \\\n  --extractor-slug listenHook --config '{}'\n```\n\nThe response comes back with `kind: \"connector\"`, `idColumnSlug: \"_ingest_id\"`,\nand `titleColumnSlug: \"_emitted_at\"`. Columns are then created dynamically from\nthe keys of whatever you POST — you don't declare them up front. Query it as\n`<connector-slug>.<model-slug>`.\n\n## Notes\n\n- The endpoint answers webhook **handshakes** out of the box — Slack\n  `url_verification`, generic `ping`, Microsoft `?validationToken=`, Salesforce\n  SOAP ack, and Meta's `hub.challenge` on `GET` — so most providers validate\n  without extra work.\n- Ingest models can't be refreshed or scheduled; data only arrives when something\n  POSTs. `model refresh` / `--schedule` don't apply.\n- A legacy alias `POST /v1/workflows/<uuid>/hook` still resolves to the same\n  insert (the uuid being the model uuid). Prefer the `/v1/models/...` form.\n- Because the URL is assembled client-side, it is *derived*, not *returned* — if\n  a future CLI release adds a field or a `get-webhook-url` command, prefer that.\n\nFile v1.2.3:references/examples/models.md\n\n# Model examples\n\n## Discover all models\n\n```bash\ncargo-ai storage model list\n```\n\nResponse includes `uuid`, `name`, `slug`, `datasetUuid`, and `columns[]` for each model.\n\n## Find a model by name\n\n```bash\n# List all models and filter by name in the output\ncargo-ai storage model list\n# → Find the entry where \"name\" matches what you're looking for, then extract \"uuid\"\n```\n\n## Get a model's full schema\n\n```bash\ncargo-ai storage model get <model-uuid>\n# → Returns the model with all columns, their types and slugs\n```\n\n## Get the DDL (column types and SQL dialect)\n\n`storage query execute` accepts `<datasetSlug>.<modelSlug>` (e.g. `default.companies`) as the table name, so you don't need the DDL just for the table name. Run `model get-ddl` when you need column types or the SQL dialect.\n\n```bash\ncargo-ai storage model get-ddl <model-uuid>\n```\n\nExample response:\n```json\n{\n  \"ddl\": \"CREATE TABLE `datasets_default.models_companies` (\\n  `uuid` STRING,\\n  `name` STRING,\\n  `domain` STRING,\\n  `employee_count` INT64\\n)\",\n  \"language\": \"bigquery\"\n}\n```\n\nThe `language` field tells you which SQL dialect to use.\n\n## Create a model\n\n```bash\n# First, find the dataset UUID\ncargo-ai storage dataset list\n\n# Create the model\ncargo-ai storage model create \\\n  --slug prospects \\\n  --name \"Prospects\" \\\n  --dataset-uuid <dataset-uuid> \\\n  --extractor-slug <extractor-slug> \\\n  --config '{}'\n```\n\n## Update a model\n\n```bash\ncargo-ai storage model update --uuid <model-uuid> --name \"Qualified Prospects\"\n```\n\n## Remove a model\n\n```bash\ncargo-ai storage model remove <model-uuid>\n```\n\nNote: This will fail if the model is referenced by segments, plays, or tools. Remove or update those resources first.\n\n## Schema discovery workflow\n\nFull flow to understand a model before querying it:\n\n```bash\n# 1. Find the model and its dataset slug\ncargo-ai storage model list\ncargo-ai storage dataset list\n\n# 2. Get the full schema with column types (optional — also returns SQL dialect)\ncargo-ai storage model get <model-uuid>\ncargo-ai storage model get-ddl <model-uuid>\n\n# 3. Query using <datasetSlug>.<modelSlug> as the table name\ncargo-ai storage query execute \\\n  \"SELECT uuid, name, domain FROM default.companies LIMIT 10\"\n```\n\nFile v1.2.3:references/examples/queries.md\n\n# Storage query examples\n\nRun SQL against workspace storage with `cargo-ai storage query execute`. Tables are referenced as `<datasetSlug>.<modelSlug>` and rewritten to the underlying storage table under the hood. No DDL lookup is required for the table name — just use the dataset and model slugs.\n\nFor column slugs, run `cargo-ai storage column list --model-uuid <uuid>` or `cargo-ai storage model get-ddl <model-uuid>` (the DDL also shows column types and the SQL dialect).\n\n## Basic query flow\n\n```bash\n# 1. Discover the dataset slug and the model slug\ncargo-ai storage dataset list   # → datasets[].slug (e.g. \"default\")\ncargo-ai storage model list     # → models[].slug   (e.g. \"companies\")\n\n# 2. Query using <datasetSlug>.<modelSlug> as the table name\ncargo-ai storage query execute \\\n  \"SELECT name, domain, employee_count FROM default.companies LIMIT 10\"\n```\n\nSuccess response:\n\n```json\n{\n  \"rows\": [\n    { \"name\": \"Acme Corp\", \"domain\": \"acme.com\", \"employee_count\": 500 },\n    { \"name\": \"Globex\", \"domain\": \"globex.com\", \"employee_count\": 1200 }\n  ]\n}\n```\n\nFailed commands exit non-zero with `{\"errorMessage\": \"...\"}` (or `{\"reason\": \"clientNotFound\"|\"unknown\"}`). See the error handling section below.\n\n## Query with WHERE clauses\n\n```bash\n# Filter by a column\ncargo-ai storage query execute \\\n  \"SELECT name, domain FROM default.companies WHERE employee_count > 100\"\n\n# Multiple conditions\ncargo-ai storage query execute \\\n  \"SELECT name, domain, revenue FROM default.companies WHERE employee_count > 100 AND country = 'US'\"\n\n# LIKE for partial matches\ncargo-ai storage query execute \\\n  \"SELECT name, domain FROM default.companies WHERE name LIKE '%tech%'\"\n\n# NULL checks\ncargo-ai storage query execute \\\n  \"SELECT name, domain FROM default.companies WHERE email IS NOT NULL\"\n```\n\n## Aggregation queries\n\n```bash\n# Count records\ncargo-ai storage query execute \\\n  \"SELECT COUNT(*) as total FROM default.companies\"\n\n# Group by with counts\ncargo-ai storage query execute \\\n  \"SELECT country, COUNT(*) as count FROM default.companies GROUP BY country ORDER BY count DESC\"\n\n# Sum and average\ncargo-ai storage query execute \\\n  \"SELECT country, SUM(revenue) as total_revenue, AVG(employee_count) as avg_employees FROM default.companies GROUP BY country\"\n```\n\n## Pagination\n\nPage through large result sets with SQL `LIMIT` and `OFFSET` clauses. Always include an `ORDER BY` so pages are stable across calls.\n\n```bash\n# First page\ncargo-ai storage query execute \\\n  \"SELECT * FROM default.companies ORDER BY name LIMIT 100 OFFSET 0\"\n\n# Second page\ncargo-ai storage query execute \\\n  \"SELECT * FROM default.companies ORDER BY name LIMIT 100 OFFSET 100\"\n```\n\n## Download full results\n\nFor exporting full result sets to a file, use `storage query download`. The response is a signed URL.\n\n```bash\ncargo-ai storage query download \\\n  --query \"SELECT name, domain, employee_count, revenue FROM default.companies ORDER BY revenue DESC\"\n\n# Choose the format (csv default, parquet supported)\ncargo-ai storage query download \\\n  --query \"SELECT * FROM default.companies\" --format parquet\n```\n\n## Query across multiple models\n\nJoin on `<datasetSlug>.<modelSlug>` table references:\n\n```bash\ncargo-ai storage query execute \\\n  \"SELECT c.name, c.domain, d.stage, d.amount FROM default.companies c JOIN default.deals d ON c._id = d.company_id WHERE d.amount > 10000\"\n```\n\n## Common table expressions\n\n```bash\ncargo-ai storage query execute \\\n  \"WITH recent AS (SELECT * FROM default.companies WHERE created_at >= CURRENT_DATE - INTERVAL '30' DAY) SELECT count(*) FROM recent\"\n```\n\n## Date queries\n\n```bash\n# Records created in the last 30 days\ncargo-ai storage query execute \\\n  \"SELECT name, created_at FROM default.companies WHERE created_at >= DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY)\"\n\n# Records in a specific range\ncargo-ai storage query execute \\\n  \"SELECT name, created_at FROM default.companies WHERE created_at BETWEEN '2025-01-01' AND '2025-03-31'\"\n```\n\n## Subqueries\n\n```bash\n# Companies with above-average employee count\ncargo-ai storage query execute \\\n  \"SELECT name, employee_count FROM default.companies WHERE employee_count > (SELECT AVG(employee_count) FROM default.companies)\"\n```\n\n## Error handling\n\nIf a query fails, the command exits non-zero. Failure shapes:\n\n```json\n{ \"errorMessage\": \"Table not found: default.nonexistent\" }\n```\n\n```json\n{ \"reason\": \"clientNotFound\" }\n```\n\nCommon causes:\n- Wrong dataset or model slug → re-check with `storage dataset list` and `storage model list`\n- Syntax error → check SQL syntax for your storage SQL dialect (BigQuery vs Snowflake) — `storage model get-ddl` reports `language`\n- `clientNotFound` → no storage client is configured for this workspace\n\n## Discovery commands\n\n```bash\ncargo-ai storage dataset list                  # all datasets (uuid, slug)\ncargo-ai storage model list                    # all models (uuid, name, slug)\ncargo-ai storage model get-ddl <model-uuid>    # column types and SQL dialect\ncargo-ai storage column list --model-uuid <uuid>  # column slugs for a model\n```\n\nFile v1.2.3:references/response-shapes.md\n\n# Response shapes\n\nJSON response structures returned by Cargo CLI commands used in the `cargo-storage` skill.\n\n## cargo-ai storage model list\n\n```json\n{\n  \"models\": [\n    {\n      \"uuid\": \"model-uuid\",\n      \"workspaceUuid\": \"...\",\n      \"slug\": \"companies\",\n      \"name\": \"Companies\",\n      \"datasetUuid\": \"dataset-uuid\",\n      \"extractorSlug\": \"hubspot_companies\",\n      \"idColumnSlug\": \"uuid\",\n      \"titleColumnSlug\": \"name\",\n      \"timeColumnSlug\": null,\n      \"columns\": [\n        { \"slug\": \"name\", \"type\": \"string\", \"label\": \"Name\", \"kind\": \"original\", \"originalSlug\": \"name\" },\n        { \"slug\": \"domain\", \"type\": \"string\", \"label\": \"Domain\", \"kind\": \"original\", \"originalSlug\": \"domain\" }\n      ],\n      \"additionalColumns\": [\n        { \"slug\": \"full_name\", \"type\": \"string\", \"label\": \"Full Name\", \"kind\": \"computed\", \"expression\": { \"kind\": \"jsExpression\", \"expression\": \"...\" }, \"columnsUsed\": [\"first_name\", \"last_name\"] },\n        { \"slug\": \"total_deals\", \"type\": \"number\", \"label\": \"Total Deals\", \"kind\": \"metric\", \"relationshipUuid\": \"...\", \"aggregation\": { \"function\": \"count\", \"columnSlug\": \"uuid\" } }\n      ],\n      \"unification\": null,\n      \"playsCount\": 2,\n      \"segmentsCount\": 1,\n      \"isPaused\": false,\n      \"lastRun\": {\n        \"uuid\": \"run-uuid\",\n        \"status\": \"success\",\n        \"errorMessage\": null,\n        \"createdAt\": \"2025-01-15T00:00:00Z\",\n        \"finishedAt\": \"2025-01-15T00:01:00Z\"\n      },\n      \"createdAt\": \"2025-01-01T00:00:00Z\",\n      \"updatedAt\": \"2025-01-15T00:00:00Z\"\n    }\n  ]\n}\n```\n\n**Key fields:** `uuid`, `slug`, `name`, `datasetUuid`, `idColumnSlug`, `columns` (original columns), `additionalColumns` (custom/computed/metric/lookup columns).\n\n`unification` is `null` unless the model unifies. When set it is either\n`{\"source\":\"integration\"}` or the `custom` shape (`type`, `uniqueColumns`,\noptionally `selectedColumnSlugs` / `timeColumnSlug` / `parent` / `filter`) —\nsee the Unification section of `SKILL.md`.\n\nColumns have no `uuid` — they are identified by `slug` within the model.\n\n## cargo-ai storage model get\n\nSame structure as a single item from `model list`, nested under `model`:\n\n```json\n{\n  \"model\": {\n    \"uuid\": \"model-uuid\",\n    \"slug\": \"companies\",\n    \"name\": \"Companies\",\n    \"datasetUuid\": \"dataset-uuid\",\n    \"columns\": [...],\n    \"additionalColumns\": [...]\n  }\n}\n```\n\n## cargo-ai storage model get-ddl\n\n```json\n{\n  \"ddl\": \"CREATE TABLE `datasets_default.models_companies` (\\n  `uuid` STRING,\\n  `name` STRING,\\n  `domain` STRING,\\n  `employee_count` INT64,\\n  `created_at` TIMESTAMP\\n)\",\n  \"language\": \"bigquery\"\n}\n```\n\n**Key fields:** `ddl` (contains the storage-native table name and column names), `language` (SQL dialect).\n\nFor `cargo-ai storage query execute`, reference tables as `<datasetSlug>.<modelSlug>` (e.g. `default.companies`).\n\n## cargo-ai storage dataset list\n\n```json\n{\n  \"datasets\": [\n    {\n      \"uuid\": \"dataset-uuid\",\n      \"slug\": \"default\",\n      \"workspaceUuid\": \"...\",\n      \"config\": { \"kind\": \"object\" },\n      \"createdAt\": \"2025-01-01T00:00:00Z\"\n    }\n  ]\n}\n```\n\n## cargo-ai storage dataset get\n\n```json\n{\n  \"dataset\": {\n    \"uuid\": \"dataset-uuid\",\n    \"slug\": \"default\",\n    \"workspaceUuid\": \"...\",\n    \"config\": { \"kind\": \"object\" }\n  }\n}\n```\n\n## cargo-ai storage column list\n\nReturns the model's columns (both original and additional). All columns share base fields: `slug`, `type`, `label`, `kind`. Columns have no `uuid` — use `slug` to identify them.\n\n```json\n{\n  \"columns\": [\n    {\n      \"slug\": \"name\",\n      \"type\": \"string\",\n      \"label\": \"Name\",\n      \"kind\": \"original\",\n      \"originalSlug\": \"name\"\n    },\n    {\n      \"slug\": \"full_name\",\n      \"type\": \"string\",\n      \"label\": \"Full Name\",\n      \"kind\": \"computed\",\n      \"expression\": { \"kind\": \"jsExpression\", \"expression\": \"...\" },\n      \"columnsUsed\": [\"first_name\", \"last_name\"]\n    }\n  ]\n}\n```\n\nKind-specific fields are included alongside the base fields:\n\n**`computed`**\n```json\n{\n  \"kind\": \"computed\",\n  \"expression\": { \"kind\": \"jsExpression\", \"value\": \"record.first_name + \\\" \\\" + record.last_name\" },\n  \"columnsUsed\": [\"first_name\", \"last_name\"]\n}\n```\n\n**`metric`**\n```json\n{\n  \"kind\": \"metric\",\n  \"relationshipUuid\": \"relationship-uuid\",\n  \"aggregation\": {\n    \"function\": \"count\",\n    \"columnSlug\": \"uuid\"\n  },\n  \"filter\": null\n}\n```\n\n**`lookup`**\n```json\n{\n  \"kind\": \"lookup\",\n  \"join\": {\n    \"toModelUuid\": \"company-model-uuid\",\n    \"fromColumnSlug\": \"company_uuid\",\n    \"toColumnSlug\": \"uuid\"\n  },\n  \"extractColumnSlug\": \"name\",\n  \"filter\": null\n}\n```\n\n## cargo-ai storage relationship list\n\n```json\n{\n  \"relationships\": [\n    {\n      \"uuid\": \"relationship-uuid\",\n      \"workspaceUuid\": \"workspace-uuid\",\n      \"fromDatasetUuid\": \"dataset-uuid\",\n      \"fromModelUuid\": \"contacts-model-uuid\",\n      \"fromColumnSlug\": \"account_id\",\n      \"fromPropertySlug\": \"hubspot___contacts[0]\",\n      \"toDatasetUuid\": \"dataset-uuid\",\n      \"toModelUuid\": \"companies-model-uuid\",\n      \"toColumnSlug\": \"id\",\n      \"relation\": \"manyToOne\"\n    }\n  ]\n}\n```\n\nWorkspace-wide — the command takes no flags. `fromDatasetUuid` always equals\n`toDatasetUuid`. `fromPropertySlug` / `toPropertySlug` appear only where the\nrelationship keys off a nested property of a connector column.\n\n`relationship set` returns the same shape, holding the dataset's full set after\nthe replace.\n\n## cargo-ai storage record list\n\n```json\n{\n  \"records\": [\n    {\n      \"uuid\": \"record-uuid\",\n      \"name\": \"Acme Corp\",\n      \"domain\": \"acme.com\",\n      \"employee_count\": 500\n    }\n  ]\n}\n```\n\n## cargo-ai storage query execute\n\nTables are referenced as `<datasetSlug>.<modelSlug>` and rewritten to the underlying storage table under the hood.\n\n**Success:**\n\n```json\n{\n  \"rows\": [\n    { \"name\": \"Acme Corp\", \"domain\": \"acme.com\", \"employee_count\": 500 },\n    { \"name\": \"Globex\", \"domain\": \"globex.com\", \"employee_count\": 1200 }\n  ]\n}\n```\n\n**Failure (non-zero exit):**\n\n```json\n{ \"errorMessage\": \"Table not found: default.nonexistent\" }\n```\n\n```json\n{ \"reason\": \"clientNotFound\" }\n```\n\n```json\n{ \"reason\": \"unknown\" }\n```\n\n## cargo-ai storage query download\n\nUsed for full exports. Same table-naming convention as `storage query execute` (`<datasetSlug>.<modelSlug>`). Pass the SQL via `--query`; the response is a signed URL.\n\n**Success:**\n\n```json\n{\n  \"url\": \"https://signed-url-to-csv-or-parquet-file\"\n}\n```\n\n**Failure (non-zero exit):**\n\n```json\n{ \"errorMessage\": \"Table not found: default.nonexistent\" }\n```\n\nFile v1.2.3:references/troubleshooting.md\n\n# Troubleshooting\n\nCommon errors and recovery steps for `cargo-storage` commands.\n\n## General\n\n| Symptom | Cause | Fix |\n|---------|-------|-----|\n| `{\"errorMessage\": \"...\"}` with non-zero exit | Any CLI error | Read the `errorMessage` — it usually says exactly what's wrong |\n| `command not found: cargo-ai` | CLI not installed or not in PATH | Run `npm install -g @cargo-ai/cli` or prefix with `npx @cargo-ai/cli` |\n| `Unauthorized` or `Forbidden` | Bad or expired credentials | Re-run `cargo-ai login --oauth` (browser sign-in) or `cargo-ai login --token <token>`; verify with `cargo-ai whoami` |\n\n## Models\n\n| Symptom | Cause | Fix |\n|---------|-------|-----|\n| `model get` returns not found | Wrong UUID | Re-run `model list` to get the correct UUID |\n| `model get-ddl` returns empty DDL | Model has no sync connection to storage | Confirm the model has an extractor configured and has synced at least once |\n| Table not found in `storage query execute` | Wrong dataset or model slug | Verify with `dataset list` and `model list`; tables are referenced as `<datasetSlug>.<modelSlug>` |\n| `model remove` returns an error | Model is referenced by segments, plays, or tools | Remove or update the dependent resources before deleting the model |\n\n## Columns\n\n| Symptom | Cause | Fix |\n|---------|-------|-----|\n| `column create` fails with slug conflict | A column with that slug already exists | Use `column list --model-uuid <uuid>` to check existing slugs; choose a unique slug |\n| `column update` returns not found | Wrong column slug or model UUID | Re-run `column list --model-uuid <uuid>` to get the correct column slugs |\n| Column type mismatch in queries | Using string operators on a number column | Match the condition type to the column type; see the `cargo-orchestration` skill's `references/filter-syntax.md` |\n\n## Relationships\n\n| Symptom | Cause | Fix |\n|---------|-------|-----|\n| Relationships disappeared after a `set` | `relationship set` replaces the dataset's whole set — anything whose `uuid` is missing from the payload is deleted | `relationship list` first, then send the full array back with your addition, keeping each existing `uuid` |\n| `invalidRelationships` | A model UUID or column slug doesn't resolve, or the payload duplicates a pair (including stated in reverse) | Verify UUIDs with `model list` and slugs with `column list --model-uuid <uuid>` |\n| `modelNotCompatible` | A unify model was named as `fromModelUuid` or `toModelUuid` | Unify-model relationships are generated during sync and can't be authored by hand |\n| `datasetNotFound` | `--dataset-uuid` is wrong, or the two models live in different datasets | Relationships never span datasets; confirm with `model list` → `datasetUuid` |\n| `relationship list` returns everything | It takes no flags and is workspace-wide by design | Filter client-side on `fromModelUuid` / `toModelUuid` |\n\n## Unification\n\n| Symptom | Cause | Fix |\n|---------|-------|-----|\n| `model update --unification` succeeded but nothing merged | Writing the config doesn't recompute; unified rows are rebuilt by the sync run | `storage run create --model-uuid <uuid>`, then poll `storage run list --model-uuid <uuid>` |\n| Duplicates survive unification | `uniqueColumns` is too narrow, or the match key is dirty (mixed case, `www.` prefixes) | Widen or normalize the key, then re-run |\n| Distinct entities merged into one | `uniqueColumns` is too broad (e.g. matching on a shared generic domain) | Add a second key column, or scope with `filter` |\n| Contacts not attached to accounts | `parent` is unset on a `contact` unification | Set `parent` to the account model and the joining column slug |\n\n## Records\n\n| Symptom | Cause | Fix |\n|---------|-------|-----|\n| `record list` returns empty | No records in the model, or wrong model UUID | Verify with `model list`; check that data has been synced |\n| Need filtered record access | `record list` doesn't support filtering | Use `segmentation segment fetch` from the `cargo-orchestration` skill for filtering, sorting, and pagination |\n\n## Queries (`storage query execute` / `storage query download`)\n\n| Symptom | Cause | Fix |\n|---------|-------|-----|\n| `errorMessage` with \"Table not found\" | Wrong dataset or model slug | Verify with `storage dataset list` and `storage model list`. Tables are `<datasetSlug>.<modelSlug>` |\n| `errorMessage` with syntax error | SQL dialect mismatch | Check whether your storage backend is BigQuery, Snowflake, etc. and adjust syntax accordingly. `storage model get-ddl` reports `language` |\n| `reason: \"clientNotFound\"` | No storage client configured | Verify the workspace has an active storage connection |\n| Query returns empty `rows` | Filter too restrictive, or wrong model | Try a broader query first (`SELECT * FROM <dataset>.<model> LIMIT 5`) |\n| Column not found | Wrong column slug | Run `storage column list --model-uuid <uuid>` to get exact slugs |\n\nFile v1.2.3:skill-card.md\n\n## Description:\n\nGuides agents in inspecting and managing Cargo workspace models, datasets, columns, relationships, records, and SQL queries.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[cargo-ai](https://clawhub.ai/user/cargo-ai)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and workspace administrators use this skill to discover Cargo storage schemas, manage models and relationships, query business records, and configure external data ingestion.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Storage changes can alter or delete workspace data, including through relationship replacement.\n\nMitigation: Confirm the workspace and obtain approval before deletes or relationship replacements.\n\nRisk: Exports can disclose workspace records.\n\nMitigation: Require confirmation before exporting data and limit exports to the intended records.\n\nRisk: Webhook URLs containing API tokens can expose credentials through chat, logs, files, or shell history.\n\nMitigation: Confirm token creation and webhook setup; prefer header-based or secret-manager delivery, avoid recording token-bearing URLs, and rotate exposed tokens.\n\n## Reference(s):\n\n- [Cargo Storage skill on ClawHub](https://clawhub.ai/cargo-ai/skills/cargo-storage)\n- [Cargo skills homepage](https://github.com/getcargohq/cargo-skills)\n\n## Skill Output:\n\n**Output Type(s):** [Guidance, Shell commands, SQL queries, Configuration instructions]\n\n**Output Format:** [Markdown with CLI and SQL examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Guidance may include JSON response examples and instructions for CSV or Parquet exports.]\n\n## Skill Version(s):\n\n1.2.3 (source: frontmatter and release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nFile v1.2.3:skill-metadata.json\n\n{\n  \"$comment\": \"Generated by .github/scripts/skills-metadata.mjs — do not hand-edit. Regenerate with: node .github/scripts/skills-metadata.mjs --write .\",\n  \"name\": \"cargo-storage\",\n  \"version\": \"1.2.3\",\n  \"documents\": [\n    {\n      \"path\": \"SKILL.md\",\n      \"kind\": \"entrypoint\",\n      \"title\": \"Cargo CLI — Storage\"\n    },\n    {\n      \"path\": \"references/examples/columns.md\",\n      \"kind\": \"example\",\n      \"title\": \"Column examples\"\n    },\n    {\n      \"path\": \"references/examples/datasets.md\",\n      \"kind\": \"example\",\n      \"title\": \"Dataset examples\"\n    },\n    {\n      \"path\": \"references/examples/ingest-webhook.md\",\n      \"kind\": \"example\",\n      \"title\": \"Ingest models — get the webhook URL and POST records\"\n    },\n    {\n      \"path\": \"references/examples/models.md\",\n      \"kind\": \"example\",\n      \"title\": \"Model examples\"\n    },\n    {\n      \"path\": \"references/examples/queries.md\",\n      \"kind\": \"example\",\n      \"title\": \"Storage query examples\"\n    },\n    {\n      \"path\": \"references/response-shapes.md\",\n      \"kind\": \"reference\",\n      \"title\": \"Response shapes\"\n    },\n    {\n      \"path\": \"references/troubleshooting.md\",\n      \"kind\": \"reference\",\n      \"title\": \"Troubleshooting\"\n    }\n  ],\n  \"contentHash\": \"6acf9eb26bf26cc5ad4fdc1177f9a2918d30492ade49739aab1d8f3d3b6fe233\"\n}\n\nArchive v1.2.2: 11 files, 20771 bytes\n\nFiles: references/examples/columns.md (5387b), references/examples/datasets.md (1234b), references/examples/ingest-webhook.md (6906b), references/examples/models.md (2220b), references/examples/queries.md (5065b), references/response-shapes.md (6454b), references/troubleshooting.md (4921b), skill-card.md (2564b), skill-metadata.json (1308b), SKILL.md (14637b), _meta.json (132b)\n\nFile v1.2.2:SKILL.md\n\n---\nname: cargo-storage\ndescription: \"Work with the data inside a Cargo workspace — models (Companies, Contacts, Deals…), datasets, columns, relationships, records, and SQL over workspace storage. Triggers: \\\"what models do I have\\\", \\\"show me the schema\\\", \\\"add a column for\\\", \\\"how many contacts do I have\\\", \\\"SELECT … FROM\\\", \\\"query my companies table\\\", \\\"join contacts to companies\\\", \\\"what is the DDL\\\", \\\"set up a webhook-fed model\\\", \\\"where does this field live\\\", \\\"import this into a model\\\", \\\"unify these models\\\", \\\"merge duplicate accounts\\\", \\\"link contacts to companies\\\", \\\"set up a relationship between\\\". Skip when: querying run or batch telemetry rather than business data — use cargo-orchestration; naming a reusable filtered audience — use cargo-segmentation.\"\nversion: \"1.2.2\"\ncompatibility: Requires @cargo-ai/cli (npm). Sign in or create an account with `cargo-ai login --email` (emailed code, no browser), `--oauth`, or an API token\nhomepage: https://github.com/getcargohq/cargo-skills\nmetadata:\n  author: getcargo\n  openclaw:\n    requires:\n      bins:\n        - cargo-ai\n    install:\n      - kind: node\n        package: \"@cargo-ai/cli@latest\"\n        bins:\n          - cargo-ai\n    homepage: https://github.com/getcargohq/cargo-skills\n---\n\n# Cargo CLI — Storage\n\nData layer management: inspecting and modifying models, datasets, columns, relationships, unification, and records, and running SQL queries against workspace storage.\n\n> See `references/response-shapes.md` for full JSON response structures.\n> See `references/troubleshooting.md` for common errors and how to fix them.\n> See `references/examples/models.md` for model CRUD, DDL inspection, and schema discovery examples.\n> See `references/examples/datasets.md` for dataset listing and navigation examples.\n> See `references/examples/columns.md` for column creation and management examples.\n> See `references/examples/queries.md` for `storage query execute` / `storage query download` SQL examples (WHERE, aggregations, joins, pagination, exports).\n> See `references/examples/ingest-webhook.md` for ingest (webhook-fed) models — deriving the webhook URL and POSTing records.\n\n## Bootstrap\n\nAlready signed in (`cargo-ai whoami` returns a workspace)? Skip to the next section.\n\n```bash\nnpm install -g @cargo-ai/cli            # no global install? prefix every command with `npx @cargo-ai/cli`\ncargo-ai login --email you@company.com  # emailed code, no browser; creates the account on first use\n                                        # alternatives: --oauth (browser) · --token <api-token> (CI)\ncargo-ai whoami                         # confirm the active workspace before any write\n```\n\nEvery command prints JSON to stdout; failures exit non-zero with `{\"errorMessage\": \"...\"}`. Anything that creates a run or a batch is async — pass `--wait-until-finished` or poll the matching `get`. When the full skill bundle is installed, [`../cargo/references/prerequisites.md`](../cargo/references/prerequisites.md) adds the CLI version pin, token scopes, and the admin-only surface.\n\n## Discover resources first\n\nAlways list before inspecting or modifying.\n\n```bash\ncargo-ai storage dataset list              # all datasets (uuid, slug)\ncargo-ai storage model list                # all models (uuid, name, slug, columns, datasetUuid)\n# `model list` takes no flags — filter its output instead:\ncargo-ai storage model list | jq '[.models[] | select(.datasetUuid == \"<uuid>\")]' \n```\n\n**Retrieve in the UI:** models live at `app.getcargo.io/workspaces/<WORKSPACE_UUID>/models/<MODEL_UUID>`. Get `<WORKSPACE_UUID>` from `cargo-ai whoami` under `workspace.uuid`.\n\n## Quick reference\n\n```bash\ncargo-ai storage model list\ncargo-ai storage model get <model-uuid>\ncargo-ai storage model get-ddl <model-uuid>\ncargo-ai storage dataset list\ncargo-ai storage column list --model-uuid <uuid>\ncargo-ai storage relationship list\ncargo-ai storage record list --model-uuid <uuid>\ncargo-ai storage query execute \"SELECT * FROM default.companies LIMIT 10\"\ncargo-ai storage query download --query \"SELECT * FROM default.companies\"\n```\n\n## Models\n\nModels are structured tables in your workspace (e.g. Companies, Contacts).\n\n```bash\n# List all models\ncargo-ai storage model list\n\n# List models in a dataset — every model carries `datasetUuid`, and\n# `model list` has no flags of its own, so filter client-side\ncargo-ai storage model list | jq '[.models[] | select(.datasetUuid == \"<uuid>\")]' \n\n# Get a single model (includes columns)\ncargo-ai storage model get <model-uuid>\n\n# Get the DDL (full schema, table name and SQL dialect)\ncargo-ai storage model get-ddl <model-uuid>\n# → Useful for column discovery and SQL dialect (BigQuery vs Snowflake) before writing queries\n\n# Create a model\ncargo-ai storage model create \\\n  --slug contacts \\\n  --name \"Contacts\" \\\n  --dataset-uuid <uuid> \\\n  --extractor-slug <extractor-slug> \\\n  --config '{}'\n\n# Update a model\ncargo-ai storage model update --uuid <model-uuid> --name \"New Name\"\n\n# Remove a model\ncargo-ai storage model remove <model-uuid>\n```\n\n**Querying:** Use `cargo-ai storage query execute \"<sql>\"` (or `storage query download --query \"<sql>\"` for full exports) to run SQL against storage. Tables are referenced as `<datasetSlug>.<modelSlug>` (e.g. `default.companies`) and rewritten to the underlying storage table under the hood. See [Query with SQL](#query-with-sql) below.\n\n## Ingest models (webhook-fed)\n\nA model whose extractor has `mode.kind === \"ingest\"` — `http.listenHook` and\nfriends — is filled by **pushing** records to Cargo. The app shows a \"Webhook URL\"\non the model settings screen; **no CLI command or API field returns it**, but it's\nassembled from values the CLI already exposes:\n\n```\n<baseUrl>/v1/models/<model-uuid>/records/ingest?token=<api-token>\n```\n\n```bash\nMODEL_UUID=<model-uuid>\nBASE=$(cargo-ai whoami | jq -r '.baseUrl')\nTOKEN=$(cargo-ai workspaceManagement token list | jq -r '.tokens[0].token')\necho \"$BASE/v1/models/$MODEL_UUID/records/ingest?token=$TOKEN\"\n```\n\nCheck the extractor's mode first — when it reports `\"autoIngest\": true` (calendly,\nsmartlead, instantlyV2, heyReach, cargo signals) Cargo registers the\nhook with the provider itself and the URL must **not** be handed out. Full flow,\npayload shapes, and limits: `references/examples/ingest-webhook.md`.\n\n## Datasets\n\nDatasets are logical groupings of models.\n\n```bash\n# List all datasets\ncargo-ai storage dataset list\n\n# Get a single dataset\ncargo-ai storage dataset get <dataset-uuid>\n```\n\n## Columns\n\nColumns define the schema of a model.\n\n```bash\n# List columns for a model\ncargo-ai storage column list --model-uuid <uuid>\n\n# Create a column\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"my_column\",\"type\":\"string\",\"label\":\"My Column\",\"kind\":\"custom\"}'\n\n# Update a column (pass the full column object — columns are identified by slug, not UUID)\ncargo-ai storage column update \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"my_column\",\"type\":\"string\",\"label\":\"Updated Label\",\"kind\":\"custom\"}'\n\n# Remove a column\ncargo-ai storage column remove --model-uuid <uuid> --column-slug <slug>\n\n# Reorder a column (move to a specific index)\ncargo-ai storage column reorder --model-uuid <uuid> --column-slug <slug> --to-index 2\n```\n\nColumn types: `string`, `number`, `boolean`, `date`, `object`, `array`, `vector`, `any`.\n\nColumn kinds: `custom` (user-defined), `computed` (expression over other columns), `metric` (aggregated from a related model), `lookup` (single field pulled from a related model via a join).\n\n## Preview what you built\n\nA column list doesn't tell the user whether the model is right — rows do. Two checkpoints (the pack-wide convention lives in [`../cargo/references/interaction.md`](../cargo/references/interaction.md) §4):\n\n**1. Right after `model create` / `column create` — show the schema, not rows.** A new model is empty; a `LIMIT 10` here returns nothing and reads as failure. Echo the columns as a compact table instead (column, type, what will fill it).\n\n**2. As soon as data lands — show the rows.** After a batch, play, or import writes into the model, preview it:\n\n```bash\ncargo-ai storage query execute \\\n  \"SELECT * FROM <dataset-slug>.<model-slug> LIMIT 10\"\n```\n\nShow ~10 rows and only the columns that carry meaning. Storage queries are free, so this costs nothing but a few lines of output — and it's the first moment the user can actually see what they built. When a play fills a *new* column, preview that column next to the record's identifying fields (`name`, `domain`) so filled vs. empty is obvious.\n\nIf the preview comes back empty or all-null when it shouldn't, that's a finding — surface it rather than reporting the write as a success. See [`cargo-diagnostics`](../cargo-diagnostics/SKILL.md) to trace why.\n\n## Relationships\n\nRelationships link models together (e.g. Contacts belong to Companies). They are\nauthored from the CLI, not just the UI.\n\n`relationship list` takes **no flags** — it returns every relationship in the\nworkspace. Filter client-side on `fromModelUuid` / `toModelUuid`.\n\n```bash\ncargo-ai storage relationship list\n```\n\n**`relationship set` replaces the dataset's whole relationship set.** It takes a\ndataset and the complete list that should exist within it: entries carrying a\n`uuid` are updated, entries without one are created, and **any existing\nrelationship whose `uuid` is absent from the payload is deleted**. Sending one\nrelationship to a dataset that has five removes the other four. Always `list`\nfirst, then send back the full array with your addition:\n\n```bash\ncargo-ai storage relationship set \\\n  --dataset-uuid <dataset-uuid> \\\n  --relationships '[\n    {\"uuid\":\"<existing-uuid>\",\"fromModelUuid\":\"<contacts-uuid>\",\"fromColumnSlug\":\"account_id\",\"toModelUuid\":\"<companies-uuid>\",\"toColumnSlug\":\"id\",\"relation\":\"manyToOne\"},\n    {\"fromModelUuid\":\"<deals-uuid>\",\"fromColumnSlug\":\"company_id\",\"toModelUuid\":\"<companies-uuid>\",\"toColumnSlug\":\"id\",\"relation\":\"manyToOne\"}\n  ]'\n```\n\n`relation` is `oneToOne`, `manyToOne`, or `oneToMany`. Both models must live in\nthe dataset you pass — relationships never span datasets, so `fromDatasetUuid`\nand `toDatasetUuid` on the response always equal `--dataset-uuid`.\n\nFailure reasons: `datasetNotFound`; `invalidRelationships` (a column slug or\nmodel UUID that doesn't resolve, or a duplicate — including the same pair stated\nin reverse); `modelNotCompatible` (see below).\n\n**Unify models refuse manual relationships.** In the native dataset, a unify\nmodel's relationships are generated during sync, so naming one as `fromModelUuid`\nor `toModelUuid` returns `modelNotCompatible`. Those auto-generated rows are also\nexcluded from the replace above, so a `set` call cannot delete them.\n\n## Unification\n\nUnification is what merges records from several source models into one canonical\naccount/contact — and it is **configurable from the CLI**, via `--unification` on\n`model update`. Pass `null` to clear it.\n\n```bash\n# Connector-driven: the integration decides how records unify\ncargo-ai storage model update --uuid <model-uuid> --unification '{\"source\":\"integration\"}'\n\n# Custom: you name the type, the matching keys, and optionally a parent\ncargo-ai storage model update --uuid <model-uuid> --unification '{\n  \"source\": \"custom\",\n  \"type\": \"account\",\n  \"uniqueColumns\": [{\"slug\":\"domain\",\"reference\":\"domain\"}],\n  \"selectedColumnSlugs\": [\"name\",\"industry\",\"employee_count\"],\n  \"parent\": {\"kind\":\"model\",\"columnSlug\":\"account_id\",\"parentModelUuid\":\"<accounts-uuid>\"}\n}'\n```\n\n| Field | Applies to | Meaning |\n|---|---|---|\n| `source` | both | `integration` (connector-defined) or `custom` |\n| `type` | custom | `account`, `contact`, `accountEvent`, `contactEvent` |\n| `uniqueColumns` | custom | Match keys — `{slug, reference}` per column. This is what decides which rows are the same entity |\n| `selectedColumnSlugs` | custom | Columns carried into the unified model. Omit for all |\n| `timeColumnSlug` | custom | Event timestamp — for the two `*Event` types |\n| `parent` | custom | Links contacts/events to their account: `{\"kind\":\"model\",\"columnSlug\":…,\"parentModelUuid\":…}` or `{\"kind\":\"reference\",\"columnSlug\":…,\"reference\":…}` |\n| `filter` | custom | Segmentation filter restricting which rows unify — same `conjonction` shape as segments |\n\n**Writing the config does not recompute anything.** The unified rows are rebuilt\nby the model's sync run, so follow the update with a run and poll it:\n\n```bash\ncargo-ai storage run create --model-uuid <model-uuid>\ncargo-ai storage run list --model-uuid <model-uuid>\n```\n\nGet the current config from `storage model get <uuid>` → `unification` (`null`\nwhen the model doesn't unify). Once the run finishes, check the row count with\n`storage query execute` before treating the change as done — a too-narrow\n`uniqueColumns` under-merges and a too-broad one collapses distinct entities, and\nboth look like a successful run.\n\n## Records\n\n```bash\n# List records in a model\ncargo-ai storage record list --model-uuid <uuid>\n```\n\nFor advanced record queries (filtering, sorting, pagination), use `segmentation segment fetch` from the `cargo-orchestration` skill.\n\n## Query with SQL\n\nRun SQL against workspace storage with `storage query execute`. Tables are referenced as `<datasetSlug>.<modelSlug>` (e.g. `default.companies`) and rewritten to the underlying storage table under the hood — no DDL lookup is needed for the table name.\n\n```bash\ncargo-ai storage query execute \\\n  \"SELECT name, domain FROM default.companies LIMIT 10\"\n# → { \"rows\": [...] } on success; non-zero exit with { \"errorMessage\": \"...\" } on error\n```\n\nFor full exports, use `storage query download` — it returns a signed URL to a CSV (default) or Parquet file:\n\n```bash\ncargo-ai storage query download \\\n  --query \"SELECT name, domain, revenue FROM default.companies ORDER BY revenue DESC\"\n\ncargo-ai storage query download \\\n  --query \"SELECT * FROM default.companies\" --format parquet\n```\n\nGet column slugs from `storage column list --model-uuid <uuid>` (or run `storage model get-ddl <model-uuid>` for the full schema and SQL dialect). Page through large result sets with `LIMIT` / `OFFSET` directly in the SQL.\n\nSee `references/examples/queries.md` for WHERE clauses, aggregations, joins, date queries, pagination, and the failure shapes returned on error.\n\n## Help\n\nEvery command supports `--help`:\n\n```bash\ncargo-ai storage model list --help\ncargo-ai storage column create --help\ncargo-ai storage relationship set --help\ncargo-ai storage query execute --help\ncargo-ai storage query download --help\n```\n\nFile v1.2.2:_meta.json\n\n{\n  \"ownerId\": \"kn7by8t6yt9yghbxtxz6hv0bts87k6bq\",\n  \"slug\": \"cargo-storage\",\n  \"version\": \"1.2.2\",\n  \"publishedAt\": 1788305457509\n}\n\nFile v1.2.2:references/examples/columns.md\n\n# Column examples\n\n## List columns for a model\n\n```bash\ncargo-ai storage column list --model-uuid <uuid>\n```\n\nResponse includes `uuid`, `slug`, `type`, `label`, and `position` for each column.\n\n## Create a string column\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"website_url\",\"type\":\"string\",\"label\":\"Website URL\",\"kind\":\"custom\"}'\n```\n\n## Create a number column\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"arr\",\"type\":\"number\",\"label\":\"Annual Recurring Revenue\",\"kind\":\"custom\"}'\n```\n\n## Create a date column\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"last_contacted_at\",\"type\":\"date\",\"label\":\"Last Contacted At\",\"kind\":\"custom\"}'\n```\n\n## Create a boolean column\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"is_customer\",\"type\":\"boolean\",\"label\":\"Is Customer\",\"kind\":\"custom\"}'\n```\n\n## Create a computed column\n\nComputed columns derive their value from an expression over other columns.\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"full_name\",\"type\":\"string\",\"label\":\"Full Name\",\"kind\":\"computed\",\"expression\":{\"kind\":\"jsExpression\",\"expression\":\"{{record.first_name}} {{record.last_name}}\",\"instructTo\":\"none\",\"fromRecipe\":false},\"columnsUsed\":[\"first_name\",\"last_name\"]}'\n```\n\n`columnsUsed` is optional but recommended for dependency tracking.\n\n## Create a metric column\n\nMetric columns aggregate data from a related model via a relationship.\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"total_deals\",\"type\":\"number\",\"label\":\"Total Deals\",\"kind\":\"metric\",\"relationshipUuid\":\"<relationship-uuid>\",\"aggregation\":{\"function\":\"count\",\"columnSlug\":\"uuid\"}}'\n```\n\nWith an optional filter:\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"open_deals\",\"type\":\"number\",\"label\":\"Open Deals\",\"kind\":\"metric\",\"relationshipUuid\":\"<relationship-uuid>\",\"aggregation\":{\"function\":\"count\",\"columnSlug\":\"uuid\"},\"filter\":{\"conjonction\":\"and\",\"groups\":[{\"conjonction\":\"and\",\"conditions\":[{\"kind\":\"string\",\"slug\":\"status\",\"operator\":\"is\",\"value\":\"open\"}]}]}}'\n```\n\n## Create a lookup column\n\nLookup columns pull a field value from a related model via a join.\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"company_name\",\"type\":\"string\",\"label\":\"Company Name\",\"kind\":\"lookup\",\"join\":{\"toModelUuid\":\"<company-model-uuid>\",\"fromColumnSlug\":\"company_uuid\",\"toColumnSlug\":\"uuid\"},\"extractColumnSlug\":\"name\"}'\n```\n\nWith an optional filter:\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"primary_contact_email\",\"type\":\"string\",\"label\":\"Primary Contact Email\",\"kind\":\"lookup\",\"join\":{\"toModelUuid\":\"<contacts-model-uuid>\",\"fromColumnSlug\":\"uuid\",\"toColumnSlug\":\"company_uuid\"},\"extractColumnSlug\":\"email\",\"filter\":{\"conjonction\":\"and\",\"groups\":[{\"conjonction\":\"and\",\"conditions\":[{\"kind\":\"boolean\",\"slug\":\"is_primary\",\"operator\":\"isTrue\"}]}]}}'\n```\n\n## Update a column\n\nPass the full column object via `--column`. Columns are identified by `slug` (no UUID).\n\n```bash\ncargo-ai storage column update \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"website_url\",\"type\":\"string\",\"label\":\"Website\",\"kind\":\"custom\"}'\n```\n\n## Remove a column\n\n```bash\ncargo-ai storage column remove --model-uuid <uuid> --column-slug website_url\n```\n\n## Reorder a column\n\nMove a column to a specific position index (0-based).\n\n```bash\ncargo-ai storage column reorder --model-uuid <uuid> --column-slug website_url --to-index 2\n```\n\n## Column types reference\n\n| Type      | Use for                  |\n| --------- | ------------------------ |\n| `string`  | Text, names, URLs, slugs |\n| `number`  | Counts, amounts, scores  |\n| `boolean` | Flags, yes/no values     |\n| `date`    | Timestamps, dates        |\n| `object`  | Nested JSON objects      |\n| `array`   | Lists of values          |\n| `vector`  | Embedding vectors        |\n| `any`     | Untyped / mixed values   |\n\n## Column kinds reference\n\n| Kind       | Use for                                                     | Required extra fields                                                                                    |\n| ---------- | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |\n| `custom`   | User-defined fields                                         | —                                                                                                        |\n| `computed` | Values derived from an expression over other columns        | `expression`; optionally `columnsUsed`                                                                   |\n| `metric`   | Aggregated values from a related model                      | `relationshipUuid`, `aggregation.function`, `aggregation.columnSlug`; optionally `filter`                |\n| `lookup`   | A single field value pulled from a related model via a join | `join.toModelUuid`, `join.fromColumnSlug`, `join.toColumnSlug`, `extractColumnSlug`; optionally `filter` |\n\nColumn `slug` values are used in filter conditions (see `cargo-orchestration` skill's `references/filter-syntax.md`) and in `storage query execute` SQL queries.\n\nFile v1.2.2:references/examples/datasets.md\n\n# Dataset examples\n\n## List all datasets\n\nDatasets group related models together.\n\n```bash\ncargo-ai storage dataset list\n```\n\nResponse includes `uuid`, `name`, and `slug` for each dataset.\n\n## Get a specific dataset\n\n```bash\ncargo-ai storage dataset get <dataset-uuid>\n```\n\n## List models in a dataset\n\n`storage model list` takes **no options** — it always returns every model in the\nworkspace. Each one carries a `datasetUuid`, so narrow it client-side:\n\n```bash\ncargo-ai storage model list | jq '[.models[] | select(.datasetUuid == \"<dataset-uuid>\")]'\n```\n\n## Discover workspace data structure\n\nFull flow to understand how data is organized:\n\n```bash\n# 1. List all datasets\ncargo-ai storage dataset list\n# → Note the dataset UUIDs and slugs\n\n# 2. Group the models by dataset (one call — `model list` has no filter flag)\ncargo-ai storage model list | jq 'group_by(.datasetUuid) | map({datasetUuid: .[0].datasetUuid, models: map(.slug)})'\n# → See which models (tables) belong to each dataset\n\n# 3. Inspect a model's columns\ncargo-ai storage model get <model-uuid>\n# → See column slugs and types for each model\n```\n\nThe dataset `slug` appears in DDL table names (e.g. `datasets_default` for the dataset with slug `default`).\n\nFile v1.2.2:references/examples/ingest-webhook.md\n\n# Ingest models — get the webhook URL and POST records\n\nSome models are fed by **pushing** records to Cargo instead of Cargo pulling them.\nTheir extractor has `mode.kind === \"ingest\"` — the canonical one is the `http`\nintegration's `listenHook` (\"Listen webhook\"), but the same mechanism backs\n`storeleads.listenList`, `rb2b.listenProfiles`, `albacross.listenWebsiteVisits`,\nand others.\n\nThe app shows a **Webhook URL** on the model's settings screen. There is **no CLI\ncommand and no API field that returns it** — the app builds the string client-side.\nYou can build the exact same string from data the CLI already exposes.\n\n## The URL\n\n```\n<baseUrl>/v1/models/<model-uuid>/records/ingest?token=<api-token>\n```\n\n- `<baseUrl>` — `cargo-ai whoami` → `.baseUrl` (e.g. `https://api.getcargo.io`).\n  Note the path is `/v1/models/...`, **not** `/v1/storage/models/...`.\n- `<model-uuid>` — the ingest model's UUID.\n- `<api-token>` — any workspace API token. The token may carry **zero\n  permissions**; this route is explicitly allowed for permission-less tokens so\n  the URL can be handed to a third-party system safely. The app auto-creates one\n  named `Quick access` for exactly this.\n\n`token` can also be sent as an `Authorization: Basic <token>` header instead of a\nquery param — preferable when the receiving system supports custom headers, since\na query param lands in logs.\n\n## Derive it\n\n```bash\n# 1. Find the model and confirm it is an ingest model\ncargo-ai storage model get <model-uuid> | jq '{uuid, slug, extractorSlug, kind, connectorUuid}'\n\n# 2. Confirm the extractor's mode is \"ingest\" (and NOT autoIngest — see below)\ncargo-ai connection integration get http | jq -c '.integration.extractors.listenHook.mode'\n# → {\"kind\":\"ingest\"}\n\n# 3. Pick or create a token (the raw value is on the list response)\ncargo-ai workspaceManagement token list | jq -r '.tokens[0].token'\ncargo-ai workspaceManagement token create --name \"Webhook — <model-slug>\" | jq -r '.token.token'\n```\n\nOne-liner that assembles it:\n\n```bash\nMODEL_UUID=<model-uuid>\nBASE=$(cargo-ai whoami | jq -r '.baseUrl')\nTOKEN=$(cargo-ai workspaceManagement token list | jq -r '.tokens[0].token')\necho \"$BASE/v1/models/$MODEL_UUID/records/ingest?token=$TOKEN\"\n```\n\n> Token values are secrets. Print the URL for the user to copy; don't write it\n> into a file, a commit, or a report.\n\n## Skip models where Cargo owns the hook\n\nSome ingest extractors set `autoIngest: true` — Cargo registers the webhook with\nthe provider itself during setup (calendly, smartlead, instantlyV2, heyReach,\nand cargo's own signal extractors). The app **hides** the URL for\nthose, and handing it out is wrong: the provider is already pointed at it.\n\nCheck before showing anything:\n\n```bash\ncargo-ai connection integration get <integration-slug> \\\n  | jq -c '.integration.extractors[\"<extractor-slug>\"].mode'\n# {\"kind\":\"ingest\"}                    → manual: show the URL\n# {\"kind\":\"ingest\",\"autoIngest\":true}  → Cargo owns it: don't show the URL\n# anything else (fetch/…)              → not an ingest model at all\n```\n\nList every ingest extractor an integration has:\n\n```bash\ncargo-ai connection integration get <integration-slug> \\\n  | jq -c '.integration.extractors | to_entries\n           | map(select(.value.mode.kind==\"ingest\")) | map({(.key): .value.mode})'\n# calendly → [{\"fetchEvents\":{\"kind\":\"ingest\",\"autoIngest\":true}}]\n```\n\n## POST records\n\nThe body is **either one flat object or an array of flat objects** — each object\nbecomes one record, and its keys become columns. Max **100 records per request**.\n\n```bash\n# one record\ncurl -X POST \"$BASE/v1/models/$MODEL_UUID/records/ingest?token=$TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"email\":\"ada@example.com\",\"company\":\"example.com\"}'\n\n# many records\ncurl -X POST \"$BASE/v1/models/$MODEL_UUID/records/ingest?token=$TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '[{\"email\":\"ada@example.com\"},{\"email\":\"grace@example.com\"}]'\n\n# token as a header instead of a query param\ncurl -X POST \"$BASE/v1/models/$MODEL_UUID/records/ingest\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Authorization: Basic $TOKEN\" \\\n  -d '{\"email\":\"ada@example.com\"}'\n# all → 200 {\"message\":\"OK\"}\n```\n\n**The endpoint is insert-only.** Do not send an envelope like\n`{\"kind\":\"insert\",\"records\":[…]}` — there is no unwrapping, so you get one useless\nrow with a `kind` column and a `records` column holding the stringified array.\nThe `{kind: insert|update|remove, records: […]}` shape belongs to the extractor's\ninternal contract, not to this HTTP body; `update` and `remove` are not reachable\nthrough the webhook.\n\nFor `http.listenHook`, the model's id column is `_ingest_id` (a UUID **generated\nserver-side** — never send it; the extractor rejects inserts that carry one) and\nits title column is `_emitted_at`.\n\nThen confirm the rows landed (storage queries are free):\n\n```bash\ncargo-ai storage query execute \"SELECT * FROM <dataset-slug>.<model-slug> LIMIT 10\"\n```\n\n## Create an ingest model from scratch\n\n`model create` has no `--connector-uuid` — the API infers the connector from the\n**dataset**. Every connector automatically owns exactly one `kind: \"connector\"`\ndataset, so the flow is: create the connector, find its dataset, create the model\nin it.\n\n```bash\n# 1. Connector (slug must be snake_case: /^[a-z0-9]+(_[a-z0-9]+)*$/)\ncargo-ai connection connector create \\\n  --name \"Inbound leads\" --slug inbound_leads \\\n  --integration-slug http --config '{}' | jq -r '.connector.uuid'\n\n# 2. Its dataset — dataset list takes no --connector-uuid filter, so filter locally\ncargo-ai storage dataset list \\\n  | jq -c --arg c <connector-uuid> '.datasets[] | select(.connectorUuid==$c) | {uuid, slug}'\n\n# 3. The model\ncargo-ai storage model create \\\n  --slug inbound_leads --name \"Inbound Leads\" \\\n  --dataset-uuid <dataset-uuid> \\\n  --extractor-slug listenHook --config '{}'\n```\n\nThe response comes back with `kind: \"connector\"`, `idColumnSlug: \"_ingest_id\"`,\nand `titleColumnSlug: \"_emitted_at\"`. Columns are then created dynamically from\nthe keys of whatever you POST — you don't declare them up front. Query it as\n`<connector-slug>.<model-slug>`.\n\n## Notes\n\n- The endpoint answers webhook **handshakes** out of the box — Slack\n  `url_verification`, generic `ping`, Microsoft `?validationToken=`, Salesforce\n  SOAP ack, and Meta's `hub.challenge` on `GET` — so most providers validate\n  without extra work.\n- Ingest models can't be refreshed or scheduled; data only arrives when something\n  POSTs. `model refresh` / `--schedule` don't apply.\n- A legacy alias `POST /v1/workflows/<uuid>/hook` still resolves to the same\n  insert (the uuid being the model uuid). Prefer the `/v1/models/...` form.\n- Because the URL is assembled client-side, it is *derived*, not *returned* — if\n  a future CLI release adds a field or a `get-webhook-url` command, prefer that.\n\nFile v1.2.2:references/examples/models.md\n\n# Model examples\n\n## Discover all models\n\n```bash\ncargo-ai storage model list\n```\n\nResponse includes `uuid`, `name`, `slug`, `datasetUuid`, and `columns[]` for each model.\n\n## Find a model by name\n\n```bash\n# List all models and filter by name in the output\ncargo-ai storage model list\n# → Find the entry where \"name\" matches what you're looking for, then extract \"uuid\"\n```\n\n## Get a model's full schema\n\n```bash\ncargo-ai storage model get <model-uuid>\n# → Returns the model with all columns, their types and slugs\n```\n\n## Get the DDL (column types and SQL dialect)\n\n`storage query execute` accepts `<datasetSlug>.<modelSlug>` (e.g. `default.companies`) as the table name, so you don't need the DDL just for the table name. Run `model get-ddl` when you need column types or the SQL dialect.\n\n```bash\ncargo-ai storage model get-ddl <model-uuid>\n```\n\nExample response:\n```json\n{\n  \"ddl\": \"CREATE TABLE `datasets_default.models_companies` (\\n  `uuid` STRING,\\n  `name` STRING,\\n  `domain` STRING,\\n  `employee_count` INT64\\n)\",\n  \"language\": \"bigquery\"\n}\n```\n\nThe `language` field tells you which SQL dialect to use.\n\n## Create a model\n\n```bash\n# First, find the dataset UUID\ncargo-ai storage dataset list\n\n# Create the model\ncargo-ai storage model create \\\n  --slug prospects \\\n  --name \"Prospects\" \\\n  --dataset-uuid <dataset-uuid> \\\n  --extractor-slug <extractor-slug> \\\n  --config '{}'\n```\n\n## Update a model\n\n```bash\ncargo-ai storage model update --uuid <model-uuid> --name \"Qualified Prospects\"\n```\n\n## Remove a model\n\n```bash\ncargo-ai storage model remove <model-uuid>\n```\n\nNote: This will fail if the model is referenced by segments, plays, or tools. Remove or update those resources first.\n\n## Schema discovery workflow\n\nFull flow to understand a model before querying it:\n\n```bash\n# 1. Find the model and its dataset slug\ncargo-ai storage model list\ncargo-ai storage dataset list\n\n# 2. Get the full schema with column types (optional — also returns SQL dialect)\ncargo-ai storage model get <model-uuid>\ncargo-ai storage model get-ddl <model-uuid>\n\n# 3. Query using <datasetSlug>.<modelSlug> as the table name\ncargo-ai storage query execute \\\n  \"SELECT uuid, name, domain FROM default.companies LIMIT 10\"\n```\n\nFile v1.2.2:references/examples/queries.md\n\n# Storage query examples\n\nRun SQL against workspace storage with `cargo-ai storage query execute`. Tables are referenced as `<datasetSlug>.<modelSlug>` and rewritten to the underlying storage table under the hood. No DDL lookup is required for the table name — just use the dataset and model slugs.\n\nFor column slugs, run `cargo-ai storage column list --model-uuid <uuid>` or `cargo-ai storage model get-ddl <model-uuid>` (the DDL also shows column types and the SQL dialect).\n\n## Basic query flow\n\n```bash\n# 1. Discover the dataset slug and the model slug\ncargo-ai storage dataset list   # → datasets[].slug (e.g. \"default\")\ncargo-ai storage model list     # → models[].slug   (e.g. \"companies\")\n\n# 2. Query using <datasetSlug>.<modelSlug> as the table name\ncargo-ai storage query execute \\\n  \"SELECT name, domain, employee_count FROM default.companies LIMIT 10\"\n```\n\nSuccess response:\n\n```json\n{\n  \"rows\": [\n    { \"name\": \"Acme Corp\", \"domain\": \"acme.com\", \"employee_count\": 500 },\n    { \"name\": \"Globex\", \"domain\": \"globex.com\", \"employee_count\": 1200 }\n  ]\n}\n```\n\nFailed commands exit non-zero with `{\"errorMessage\": \"...\"}` (or `{\"reason\": \"clientNotFound\"|\"unknown\"}`). See the error handling section below.\n\n## Query with WHERE clauses\n\n```bash\n# Filter by a column\ncargo-ai storage query execute \\\n  \"SELECT name, domain FROM default.companies WHERE employee_count > 100\"\n\n# Multiple conditions\ncargo-ai storage query execute \\\n  \"SELECT name, domain, revenue FROM default.companies WHERE employee_count > 100 AND country = 'US'\"\n\n# LIKE for partial matches\ncargo-ai storage query execute \\\n  \"SELECT name, domain FROM default.companies WHERE name LIKE '%tech%'\"\n\n# NULL checks\ncargo-ai storage query execute \\\n  \"SELECT name, domain FROM default.companies WHERE email IS NOT NULL\"\n```\n\n## Aggregation queries\n\n```bash\n# Count records\ncargo-ai storage query execute \\\n  \"SELECT COUNT(*) as total FROM default.companies\"\n\n# Group by with counts\ncargo-ai storage query execute \\\n  \"SELECT country, COUNT(*) as count FROM default.companies GROUP BY country ORDER BY count DESC\"\n\n# Sum and average\ncargo-ai storage query execute \\\n  \"SELECT country, SUM(revenue) as total_revenue, AVG(employee_count) as avg_employees FROM default.companies GROUP BY country\"\n```\n\n## Pagination\n\nPage through large result sets with SQL `LIMIT` and `OFFSET` clauses. Always include an `ORDER BY` so pages are stable across calls.\n\n```bash\n# First page\ncargo-ai storage query execute \\\n  \"SELECT * FROM default.companies ORDER BY name LIMIT 100 OFFSET 0\"\n\n# Second page\ncargo-ai storage query execute \\\n  \"SELECT * FROM default.companies ORDER BY name LIMIT 100 OFFSET 100\"\n```\n\n## Download full results\n\nFor exporting full result sets to a file, use `storage query download`. The response is a signed URL.\n\n```bash\ncargo-ai storage query download \\\n  --query \"SELECT name, domain, employee_count, revenue FROM default.companies ORDER BY revenue DESC\"\n\n# Choose the format (csv default, parquet supported)\ncargo-ai storage query download \\\n  --query \"SELECT * FROM default.companies\" --format parquet\n```\n\n## Query across multiple models\n\nJoin on `<datasetSlug>.<modelSlug>` table references:\n\n```bash\ncargo-ai storage query execute \\\n  \"SELECT c.name, c.domain, d.stage, d.amount FROM default.companies c JOIN default.deals d ON c._id = d.company_id WHERE d.amount > 10000\"\n```\n\n## Common table expressions\n\n```bash\ncargo-ai storage query execute \\\n  \"WITH recent AS (SELECT * FROM default.companies WHERE created_at >= CURRENT_DATE - INTERVAL '30' DAY) SELECT count(*) FROM recent\"\n```\n\n## Date queries\n\n```bash\n# Records created in the last 30 days\ncargo-ai storage query execute \\\n  \"SELECT name, created_at FROM default.companies WHERE created_at >= DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY)\"\n\n# Records in a specific range\ncargo-ai storage query execute \\\n  \"SELECT name, created_at FROM default.companies WHERE created_at BETWEEN '2025-01-01' AND '2025-03-31'\"\n```\n\n## Subqueries\n\n```bash\n# Companies with above-average employee count\ncargo-ai storage query execute \\\n  \"SELECT name, employee_count FROM default.companies WHERE employee_count > (SELECT AVG(employee_count) FROM default.companies)\"\n```\n\n## Error handling\n\nIf a query fails, the command exits non-zero. Failure shapes:\n\n```json\n{ \"errorMessage\": \"Table not found: default.nonexistent\" }\n```\n\n```json\n{ \"reason\": \"clientNotFound\" }\n```\n\nCommon causes:\n- Wrong dataset or model slug → re-check with `storage dataset list` and `storage model list`\n- Syntax error → check SQL syntax for your storage SQL dialect (BigQuery vs Snowflake) — `storage model get-ddl` reports `language`\n- `clientNotFound` → no storage client is configured for this workspace\n\n## Discovery commands\n\n```bash\ncargo-ai storage dataset list                  # all datasets (uuid, slug)\ncargo-ai storage model list                    # all models (uuid, name, slug)\ncargo-ai storage model get-ddl <model-uuid>    # column types and SQL dialect\ncargo-ai storage column list --model-uuid <uuid>  # column slugs for a model\n```\n\nFile v1.2.2:references/response-shapes.md\n\n# Response shapes\n\nJSON response structures returned by Cargo CLI commands used in the `cargo-storage` skill.\n\n## cargo-ai storage model list\n\n```json\n{\n  \"models\": [\n    {\n      \"uuid\": \"model-uuid\",\n      \"workspaceUuid\": \"...\",\n      \"slug\": \"companies\",\n      \"name\": \"Companies\",\n      \"datasetUuid\": \"dataset-uuid\",\n      \"extractorSlug\": \"hubspot_companies\",\n      \"idColumnSlug\": \"uuid\",\n      \"titleColumnSlug\": \"name\",\n      \"timeColumnSlug\": null,\n      \"columns\": [\n        { \"slug\": \"name\", \"type\": \"string\", \"label\": \"Name\", \"kind\": \"original\", \"originalSlug\": \"name\" },\n        { \"slug\": \"domain\", \"type\": \"string\", \"label\": \"Domain\", \"kind\": \"original\", \"originalSlug\": \"domain\" }\n      ],\n      \"additionalColumns\": [\n        { \"slug\": \"full_name\", \"type\": \"string\", \"label\": \"Full Name\", \"kind\": \"computed\", \"expression\": { \"kind\": \"jsExpression\", \"expression\": \"...\" }, \"columnsUsed\": [\"first_name\", \"last_name\"] },\n        { \"slug\": \"total_deals\", \"type\": \"number\", \"label\": \"Total Deals\", \"kind\": \"metric\", \"relationshipUuid\": \"...\", \"aggregation\": { \"function\": \"count\", \"columnSlug\": \"uuid\" } }\n      ],\n      \"unification\": null,\n      \"playsCount\": 2,\n      \"segmentsCount\": 1,\n      \"isPaused\": false,\n      \"lastRun\": {\n        \"uuid\": \"run-uuid\",\n        \"status\": \"success\",\n        \"errorMessage\": null,\n        \"createdAt\": \"2025-01-15T00:00:00Z\",\n        \"finishedAt\": \"2025-01-15T00:01:00Z\"\n      },\n      \"createdAt\": \"2025-01-01T00:00:00Z\",\n      \"updatedAt\": \"2025-01-15T00:00:00Z\"\n    }\n  ]\n}\n```\n\n**Key fields:** `uuid`, `slug`, `name`, `datasetUuid`, `idColumnSlug`, `columns` (original columns), `additionalColumns` (custom/computed/metric/lookup columns).\n\n`unification` is `null` unless the model unifies. When set it is either\n`{\"source\":\"integration\"}` or the `custom` shape (`type`, `uniqueColumns`,\noptionally `selectedColumnSlugs` / `timeColumnSlug` / `parent` / `filter`) —\nsee the Unification section of `SKILL.md`.\n\nColumns have no `uuid` — they are identified by `slug` within the model.\n\n## cargo-ai storage model get\n\nSame structure as a single item from `model list`, nested under `model`:\n\n```json\n{\n  \"model\": {\n    \"uuid\": \"model-uuid\",\n    \"slug\": \"companies\",\n    \"name\": \"Companies\",\n    \"datasetUuid\": \"dataset-uuid\",\n    \"columns\": [...],\n    \"additionalColumns\": [...]\n  }\n}\n```\n\n## cargo-ai storage model get-ddl\n\n```json\n{\n  \"ddl\": \"CREATE TABLE `datasets_default.models_companies` (\\n  `uuid` STRING,\\n  `name` STRING,\\n  `domain` STRING,\\n  `employee_count` INT64,\\n  `created_at` TIMESTAMP\\n)\",\n  \"language\": \"bigquery\"\n}\n```\n\n**Key fields:** `ddl` (contains the storage-native table name and column names), `language` (SQL dialect).\n\nFor `cargo-ai storage query execute`, reference tables as `<datasetSlug>.<modelSlug>` (e.g. `default.companies`).\n\n## cargo-ai storage dataset list\n\n```json\n{\n  \"datasets\": [\n    {\n      \"uuid\": \"dataset-uuid\",\n      \"slug\": \"default\",\n      \"workspaceUuid\": \"...\",\n      \"config\": { \"kind\": \"object\" },\n      \"createdAt\": \"2025-01-01T00:00:00Z\"\n    }\n  ]\n}\n```\n\n## cargo-ai storage dataset get\n\n```json\n{\n  \"dataset\": {\n    \"uuid\": \"dataset-uuid\",\n    \"slug\": \"default\",\n    \"workspaceUuid\": \"...\",\n    \"config\": { \"kind\": \"object\" }\n  }\n}\n```\n\n## cargo-ai storage column list\n\nReturns the model's columns (both original and additional). All columns share base fields: `slug`, `type`, `label`, `kind`. Columns have no `uuid` — use `slug` to identify them.\n\n```json\n{\n  \"columns\": [\n    {\n      \"slug\": \"name\",\n      \"type\": \"string\",\n      \"label\": \"Name\",\n      \"kind\": \"original\",\n      \"originalSlug\": \"name\"\n    },\n    {\n      \"slug\": \"full_name\",\n      \"type\": \"string\",\n      \"label\": \"Full Name\",\n      \"kind\": \"computed\",\n      \"expression\": { \"kind\": \"jsExpression\", \"expression\": \"...\" },\n      \"columnsUsed\": [\"first_name\", \"last_name\"]\n    }\n  ]\n}\n```\n\nKind-specific fields are included alongside the base fields:\n\n**`computed`**\n```json\n{\n  \"kind\": \"computed\",\n  \"expression\": { \"kind\": \"jsExpression\", \"value\": \"record.first_name + \\\" \\\" + record.last_name\" },\n  \"columnsUsed\": [\"first_name\", \"last_name\"]\n}\n```\n\n**`metric`**\n```json\n{\n  \"kind\": \"metric\",\n  \"relationshipUuid\": \"relationship-uuid\",\n  \"aggregation\": {\n    \"function\": \"count\",\n    \"columnSlug\": \"uuid\"\n  },\n  \"filter\": null\n}\n```\n\n**`lookup`**\n```json\n{\n  \"kind\": \"lookup\",\n  \"join\": {\n    \"toModelUuid\": \"company-model-uuid\",\n    \"fromColumnSlug\": \"company_uuid\",\n    \"toColumnSlug\": \"uuid\"\n  },\n  \"extractColumnSlug\": \"name\",\n  \"filter\": null\n}\n```\n\n## cargo-ai storage relationship list\n\n```json\n{\n  \"relationships\": [\n    {\n      \"uuid\": \"relationship-uuid\",\n      \"workspaceUuid\": \"workspace-uuid\",\n      \"fromDatasetUuid\": \"dataset-uuid\",\n      \"fromModelUuid\": \"contacts-model-uuid\",\n      \"fromColumnSlug\": \"account_id\",\n      \"fromPropertySlug\": \"hubspot___contacts[0]\",\n      \"toDatasetUuid\": \"dataset-uuid\",\n      \"toModelUuid\": \"companies-model-uuid\",\n      \"toColumnSlug\": \"id\",\n      \"relation\": \"manyToOne\"\n    }\n  ]\n}\n```\n\nWorkspace-wide — the command takes no flags. `fromDatasetUuid` always equals\n`toDatasetUuid`. `fromPropertySlug` / `toPropertySlug` appear only where the\nrelationship keys off a nested property of a connector column.\n\n`relationship set` returns the same shape, holding the dataset's full set after\nthe replace.\n\n## cargo-ai storage record list\n\n```json\n{\n  \"records\": [\n    {\n      \"uuid\": \"record-uuid\",\n      \"name\": \"Acme Corp\",\n      \"domain\": \"acme.com\",\n      \"employee_count\": 500\n    }\n  ]\n}\n```\n\n## cargo-ai storage query execute\n\nTables are referenced as `<datasetSlug>.<modelSlug>` and rewritten to the underlying storage table under the hood.\n\n**Success:**\n\n```json\n{\n  \"rows\": [\n    { \"name\": \"Acme Corp\", \"domain\": \"acme.com\", \"employee_count\": 500 },\n    { \"name\": \"Globex\", \"domain\": \"globex.com\", \"employee_count\": 1200 }\n  ]\n}\n```\n\n**Failure (non-zero exit):**\n\n```json\n{ \"errorMessage\": \"Table not found: default.nonexistent\" }\n```\n\n```json\n{ \"reason\": \"clientNotFound\" }\n```\n\n```json\n{ \"reason\": \"unknown\" }\n```\n\n## cargo-ai storage query download\n\nUsed for full exports. Same table-naming convention as `storage query execute` (`<datasetSlug>.<modelSlug>`). Pass the SQL via `--query`; the response is a signed URL.\n\n**Success:**\n\n```json\n{\n  \"url\": \"https://signed-url-to-csv-or-parquet-file\"\n}\n```\n\n**Failure (non-zero exit):**\n\n```json\n{ \"errorMessage\": \"Table not found: default.nonexistent\" }\n```\n\nFile v1.2.2:references/troubleshooting.md\n\n# Troubleshooting\n\nCommon errors and recovery steps for `cargo-storage` commands.\n\n## General\n\n| Symptom | Cause | Fix |\n|---------|-------|-----|\n| `{\"errorMessage\": \"...\"}` with non-zero exit | Any CLI error | Read the `errorMessage` — it usually says exactly what's wrong |\n| `command not found: cargo-ai` | CLI not installed or not in PATH | Run `npm install -g @cargo-ai/cli` or prefix with `npx @cargo-ai/cli` |\n| `Unauthorized` or `Forbidden` | Bad or expired credentials | Re-run `cargo-ai login --oauth` (browser sign-in) or `cargo-ai login --token <token>`; verify with `cargo-ai whoami` |\n\n## Models\n\n| Symptom | Cause | Fix |\n|---------|-------|-----|\n| `model get` returns not found | Wrong UUID | Re-run `model list` to get the correct UUID |\n| `model get-ddl` returns empty DDL | Model has no sync connection to storage | Confirm the model has an extractor configured and has synced at least once |\n| Table not found in `storage query execute` | Wrong dataset or model slug | Verify with `dataset list` and `model list`; tables are referenced as `<datasetSlug>.<modelSlug>` |\n| `model remove` returns an error | Model is referenced by segments, plays, or tools | Remove or update the dependent resources before deleting the model |\n\n## Columns\n\n| Symptom | Cause | Fix |\n|---------|-------|-----|\n| `column create` fails with slug conflict | A column with that slug already exists | Use `column list --model-uuid <uuid>` to check existing slugs; choose a unique slug |\n| `column update` returns not found | Wrong column slug or model UUID | Re-run `column list --model-uuid <uuid>` to get the correct column slugs |\n| Column type mismatch in queries | Using string operators on a number column | Match the condition type to the column type; see the `cargo-orchestration` skill's `references/filter-syntax.md` |\n\n## Relationships\n\n| Symptom | Cause | Fix |\n|---------|-------|-----|\n| Relationships disappeared after a `set` | `relationship set` replaces the dataset's whole set — anything whose `uuid` is missing from the payload is deleted | `relationship list` first, then send the full array back with your addition, keeping each existing `uuid` |\n| `invalidRelationships` | A model UUID or column slug doesn't resolve, or the payload duplicates a pair (including stated in reverse) | Verify UUIDs with `model list` and slugs with `column list --model-uuid <uuid>` |\n| `modelNotCompatible` | A unify model was named as `fromModelUuid` or `toModelUuid` | Unify-model relationships are generated during sync and can't be authored by hand |\n| `datasetNotFound` | `--dataset-uuid` is wrong, or the two models live in different datasets | Relationships never span datasets; confirm with `model list` → `datasetUuid` |\n| `relationship list` returns everything | It takes no flags and is workspace-wide by design | Filter client-side on `fromModelUuid` / `toModelUuid` |\n\n## Unification\n\n| Symptom | Cause | Fix |\n|---------|-------|-----|\n| `model update --unification` succeeded but nothing merged | Writing the config doesn't recompute; unified rows are rebuilt by the sync run | `storage run create --model-uuid <uuid>`, then poll `storage run list --model-uuid <uuid>` |\n| Duplicates survive unification | `uniqueColumns` is too narrow, or the match key is dirty (mixed case, `www.` prefixes) | Widen or normalize the key, then re-run |\n| Distinct entities merged into one | `uniqueColumns` is too broad (e.g. matching on a shared generic domain) | Add a second key column, or scope with `filter` |\n| Contacts not attached to accounts | `parent` is unset on a `contact` unification | Set `parent` to the account model and the joining column slug |\n\n## Records\n\n| Symptom | Cause | Fix |\n|---------|-------|-----|\n| `record list` returns empty | No records in the model, or wrong model UUID | Verify with `model list`; check that data has been synced |\n| Need filtered record access | `record list` doesn't support filtering | Use `segmentation segment fetch` from the `cargo-orchestration` skill for filtering, sorting, and pagination |\n\n## Queries (`storage query execute` / `storage query download`)\n\n| Symptom | Cause | Fix |\n|---------|-------|-----|\n| `errorMessage` with \"Table not found\" | Wrong dataset or model slug | Verify with `storage dataset list` and `storage model list`. Tables are `<datasetSlug>.<modelSlug>` |\n| `errorMessage` with syntax error | SQL dialect mismatch | Check whether your storage backend is BigQuery, Snowflake, etc. and adjust syntax accordingly. `storage model get-ddl` reports `language` |\n| `reason: \"clientNotFound\"` | No storage client configured | Verify the workspace has an active storage connection |\n| Query returns empty `rows` | Filter too restrictive, or wrong model | Try a broader query first (`SELECT * FROM <dataset>.<model> LIMIT 5`) |\n| Column not found | Wrong column slug | Run `storage column list --model-uuid <uuid>` to get exact slugs |\n\nFile v1.2.2:skill-card.md\n\n## Description:\n\nWork with Cargo workspace storage, including models, datasets, columns, relationships, records, unification, SQL queries, and webhook-fed ingest models.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[cargo-ai](https://clawhub.ai/user/cargo-ai)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and workspace administrators use this skill to inspect and modify Cargo workspace storage, manage schemas and relationships, query records with SQL, and configure webhook-fed ingest models.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Storage administration commands can change or delete Cargo workspace schema and data.\n\nMitigation: List and inspect datasets, models, columns, and relationships before write operations, confirm the active workspace with cargo-ai whoami, and review destructive commands before execution.\n\nRisk: Webhook URL examples can expose workspace API tokens in URLs, logs, or transcripts.\n\nMitigation: Prefer a dedicated least-privilege ingest token passed through an Authorization header or secret manager, avoid storing tokenized URLs, and rotate any token that appears in logs or transcripts.\n\nRisk: The CLI package is installed from a latest-version npm spec.\n\nMitigation: Pin and review the Cargo CLI version in controlled environments before installing or running generated commands.\n\n## Reference(s):\n\n- [Cargo skills homepage](https://github.com/getcargohq/cargo-skills)\n- [Response shapes](references/response-shapes.md)\n- [Troubleshooting](references/troubleshooting.md)\n- [Model examples](references/examples/models.md)\n- [Dataset examples](references/examples/datasets.md)\n- [Column examples](references/examples/columns.md)\n- [Storage query examples](references/examples/queries.md)\n- [Ingest webhook examples](references/examples/ingest-webhook.md)\n\n## Skill Output:\n\n**Output Type(s):** [Guidance, Shell commands, Code, Configuration, Markdown]\n\n**Output Format:** [Markdown with inline bash, SQL, JSON, and curl examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Commands generally return JSON on stdout and non-zero failures with JSON error details.]\n\n## Skill Version(s):\n\n1.2.2 (source: frontmatter, release evidence, skill-metadata.json)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nFile v1.2.2:skill-metadata.json\n\n{\n  \"$comment\": \"Generated by .github/scripts/skills-metadata.mjs — do not hand-edit. Regenerate with: node .github/scripts/skills-metadata.mjs --write .\",\n  \"name\": \"cargo-storage\",\n  \"version\": \"1.2.2\",\n  \"documents\": [\n    {\n      \"path\": \"SKILL.md\",\n      \"kind\": \"entrypoint\",\n      \"title\": \"Cargo CLI — Storage\"\n    },\n    {\n      \"path\": \"references/examples/columns.md\",\n      \"kind\": \"example\",\n      \"title\": \"Column examples\"\n    },\n    {\n      \"path\": \"references/examples/datasets.md\",\n      \"kind\": \"example\",\n      \"title\": \"Dataset examples\"\n    },\n    {\n      \"path\": \"references/examples/ingest-webhook.md\",\n      \"kind\": \"example\",\n      \"title\": \"Ingest models — get the webhook URL and POST records\"\n    },\n    {\n      \"path\": \"references/examples/models.md\",\n      \"kind\": \"example\",\n      \"title\": \"Model examples\"\n    },\n    {\n      \"path\": \"references/examples/queries.md\",\n      \"kind\": \"example\",\n      \"title\": \"Storage query examples\"\n    },\n    {\n      \"path\": \"references/response-shapes.md\",\n      \"kind\": \"reference\",\n      \"title\": \"Response shapes\"\n    },\n    {\n      \"path\": \"references/troubleshooting.md\",\n      \"kind\": \"reference\",\n      \"title\": \"Troubleshooting\"\n    }\n  ],\n  \"contentHash\": \"e71fc21bf17d68ad87d94f3df61e13e0c2b4e0e0272fc24b9a730850681a32a7\"\n}\n\nArchive v1.2.1: 11 files, 17519 bytes\n\nFiles: references/examples/columns.md (5387b), references/examples/datasets.md (947b), references/examples/ingest-webhook.md (6917b), references/examples/models.md (2220b), references/examples/queries.md (5065b), references/response-shapes.md (5675b), references/troubleshooting.md (3403b), skill-card.md (2355b), skill-metadata.json (1308b), SKILL.md (9510b), _meta.json (132b)\n\nFile v1.2.1:SKILL.md\n\n---\nname: cargo-storage\ndescription: Manage models, datasets, columns, and relationships and query workspace storage with SQL using the Cargo CLI. Use when the user wants to inspect or modify data models, create or update columns, list datasets, set model relationships, understand the schema, or run SQL against storage.\nversion: \"1.2.1\"\ncompatibility: Requires @cargo-ai/cli (npm). Sign in or create an account with `cargo-ai login --email` (emailed code, no browser), `--oauth`, or an API token\nhomepage: https://github.com/getcargohq/cargo-skills\nmetadata:\n  author: getcargo\n  openclaw:\n    requires:\n      bins:\n        - cargo-ai\n    install:\n      - kind: node\n        package: \"@cargo-ai/cli@latest\"\n        bins:\n          - cargo-ai\n    homepage: https://github.com/getcargohq/cargo-skills\n---\n\n# Cargo CLI — Storage\n\nData layer management: inspecting and modifying models, datasets, columns, relationships, and records, and running SQL queries against workspace storage.\n\n> See `references/response-shapes.md` for full JSON response structures.\n> See `references/troubleshooting.md` for common errors and how to fix them.\n> See `references/examples/models.md` for model CRUD, DDL inspection, and schema discovery examples.\n> See `references/examples/datasets.md` for dataset listing and navigation examples.\n> See `references/examples/columns.md` for column creation and management examples.\n> See `references/examples/queries.md` for `storage query execute` / `storage query download` SQL examples (WHERE, aggregations, joins, pagination, exports).\n> See `references/examples/ingest-webhook.md` for ingest (webhook-fed) models — deriving the webhook URL and POSTing records.\n\n## Prerequisites\n\nSee [`../cargo/references/prerequisites.md`](../cargo/references/prerequisites.md) for install, login (`--oauth` / `--token`), JSON output conventions, and error shapes. Verify the session with `cargo-ai whoami` before running any of the commands below.\n\n## Discover resources first\n\nAlways list before inspecting or modifying.\n\n```bash\ncargo-ai storage dataset list              # all datasets (uuid, slug)\ncargo-ai storage model list                # all models (uuid, name, slug, columns)\ncargo-ai storage model list --dataset-uuid <uuid>   # models in a specific dataset\n```\n\n**Retrieve in the UI:** models live at `app.getcargo.io/workspaces/<WORKSPACE_UUID>/models/<MODEL_UUID>`. Get `<WORKSPACE_UUID>` from `cargo-ai whoami` under `workspace.uuid`.\n\n## Quick reference\n\n```bash\ncargo-ai storage model list\ncargo-ai storage model get <model-uuid>\ncargo-ai storage model get-ddl <model-uuid>\ncargo-ai storage dataset list\ncargo-ai storage column list --model-uuid <uuid>\ncargo-ai storage relationship list --model-uuid <uuid>\ncargo-ai storage record list --model-uuid <uuid>\ncargo-ai storage query execute \"SELECT * FROM default.companies LIMIT 10\"\ncargo-ai storage query download --query \"SELECT * FROM default.companies\"\n```\n\n## Models\n\nModels are structured tables in your workspace (e.g. Companies, Contacts).\n\n```bash\n# List all models\ncargo-ai storage model list\n\n# List models in a dataset\ncargo-ai storage model list --dataset-uuid <uuid>\n\n# Get a single model (includes columns)\ncargo-ai storage model get <model-uuid>\n\n# Get the DDL (full schema, table name and SQL dialect)\ncargo-ai storage model get-ddl <model-uuid>\n# → Useful for column discovery and SQL dialect (BigQuery vs Snowflake) before writing queries\n\n# Create a model\ncargo-ai storage model create \\\n  --slug contacts \\\n  --name \"Contacts\" \\\n  --dataset-uuid <uuid> \\\n  --extractor-slug <extractor-slug> \\\n  --config '{}'\n\n# Update a model\ncargo-ai storage model update --uuid <model-uuid> --name \"New Name\"\n\n# Remove a model\ncargo-ai storage model remove <model-uuid>\n```\n\n**Querying:** Use `cargo-ai storage query execute \"<sql>\"` (or `storage query download --query \"<sql>\"` for full exports) to run SQL against storage. Tables are referenced as `<datasetSlug>.<modelSlug>` (e.g. `default.companies`) and rewritten to the underlying storage table under the hood. See [Query with SQL](#query-with-sql) below.\n\n## Ingest models (webhook-fed)\n\nA model whose extractor has `mode.kind === \"ingest\"` — `http.listenHook` and\nfriends — is filled by **pushing** records to Cargo. The app shows a \"Webhook URL\"\non the model settings screen; **no CLI command or API field returns it**, but it's\nassembled from values the CLI already exposes:\n\n```\n<baseUrl>/v1/models/<model-uuid>/records/ingest?token=<api-token>\n```\n\n```bash\nMODEL_UUID=<model-uuid>\nBASE=$(cargo-ai whoami | jq -r '.baseUrl')\nTOKEN=$(cargo-ai workspaceManagement token list | jq -r '.tokens[0].token')\necho \"$BASE/v1/models/$MODEL_UUID/records/ingest?token=$TOKEN\"\n```\n\nCheck the extractor's mode first — when it reports `\"autoIngest\": true` (calendly,\nsmartlead, instantlyV2, heyReach, datachimp, cargo signals) Cargo registers the\nhook with the provider itself and the URL must **not** be handed out. Full flow,\npayload shapes, and limits: `references/examples/ingest-webhook.md`.\n\n## Datasets\n\nDatasets are logical groupings of models.\n\n```bash\n# List all datasets\ncargo-ai storage dataset list\n\n# Get a single dataset\ncargo-ai storage dataset get <dataset-uuid>\n```\n\n## Columns\n\nColumns define the schema of a model.\n\n```bash\n# List columns for a model\ncargo-ai storage column list --model-uuid <uuid>\n\n# Create a column\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"my_column\",\"type\":\"string\",\"label\":\"My Column\",\"kind\":\"custom\"}'\n\n# Update a column (pass the full column object — columns are identified by slug, not UUID)\ncargo-ai storage column update \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"my_column\",\"type\":\"string\",\"label\":\"Updated Label\",\"kind\":\"custom\"}'\n\n# Remove a column\ncargo-ai storage column remove --model-uuid <uuid> --column-slug <slug>\n\n# Reorder a column (move to a specific index)\ncargo-ai storage column reorder --model-uuid <uuid> --column-slug <slug> --to-index 2\n```\n\nColumn types: `string`, `number`, `boolean`, `date`, `object`, `array`, `vector`, `any`.\n\nColumn kinds: `custom` (user-defined), `computed` (expression over other columns), `metric` (aggregated from a related model), `lookup` (single field pulled from a related model via a join).\n\n## Preview what you built\n\nA column list doesn't tell the user whether the model is right — rows do. Two checkpoints (the pack-wide convention lives in [`../cargo/references/interaction.md`](../cargo/references/interaction.md) §4):\n\n**1. Right after `model create` / `column create` — show the schema, not rows.** A new model is empty; a `LIMIT 10` here returns nothing and reads as failure. Echo the columns as a compact table instead (column, type, what will fill it).\n\n**2. As soon as data lands — show the rows.** After a batch, play, or import writes into the model, preview it:\n\n```bash\ncargo-ai storage query execute \\\n  \"SELECT * FROM <dataset-slug>.<model-slug> LIMIT 10\"\n```\n\nShow ~10 rows and only the columns that carry meaning. Storage queries are free, so this costs nothing but a few lines of output — and it's the first moment the user can actually see what they built. When a play fills a *new* column, preview that column next to the record's identifying fields (`name`, `domain`) so filled vs. empty is obvious.\n\nIf the preview comes back empty or all-null when it shouldn't, that's a finding — surface it rather than reporting the write as a success. See [`cargo-diagnostics`](../cargo-diagnostics/SKILL.md) to trace why.\n\n## Relationships\n\nRelationships link models together (e.g. Contacts belong to Companies).\n\n```bash\n# List relationships for a model\ncargo-ai storage relationship list --model-uuid <uuid>\n\n# Set a relationship between two models\ncargo-ai storage relationship set \\\n  --from-model-uuid <uuid> \\\n  --to-model-uuid <uuid>\n```\n\n## Records\n\n```bash\n# List records in a model\ncargo-ai storage record list --model-uuid <uuid>\n```\n\nFor advanced record queries (filtering, sorting, pagination), use `segmentation segment fetch` from the `cargo-orchestration` skill.\n\n## Query with SQL\n\nRun SQL against workspace storage with `storage query execute`. Tables are referenced as `<datasetSlug>.<modelSlug>` (e.g. `default.companies`) and rewritten to the underlying storage table under the hood — no DDL lookup is needed for the table name.\n\n```bash\ncargo-ai storage query execute \\\n  \"SELECT name, domain FROM default.companies LIMIT 10\"\n# → { \"rows\": [...] } on success; non-zero exit with { \"errorMessage\": \"...\" } on error\n```\n\nFor full exports, use `storage query download` — it returns a signed URL to a CSV (default) or Parquet file:\n\n```bash\ncargo-ai storage query download \\\n  --query \"SELECT name, domain, revenue FROM default.companies ORDER BY revenue DESC\"\n\ncargo-ai storage query download \\\n  --query \"SELECT * FROM default.companies\" --format parquet\n```\n\nGet column slugs from `storage column list --model-uuid <uuid>` (or run `storage model get-ddl <model-uuid>` for the full schema and SQL dialect). Page through large result sets with `LIMIT` / `OFFSET` directly in the SQL.\n\nSee `references/examples/queries.md` for WHERE clauses, aggregations, joins, date queries, pagination, and the failure shapes returned on error.\n\n## Help\n\nEvery command supports `--help`:\n\n```bash\ncargo-ai storage model list --help\ncargo-ai storage column create --help\ncargo-ai storage relationship set --help\ncargo-ai storage query execute --help\ncargo-ai storage query download --help\n```\n\nFile v1.2.1:_meta.json\n\n{\n  \"ownerId\": \"kn7by8t6yt9yghbxtxz6hv0bts87k6bq\",\n  \"slug\": \"cargo-storage\",\n  \"version\": \"1.2.1\",\n  \"publishedAt\": 1786484615810\n}\n\nFile v1.2.1:references/examples/columns.md\n\n# Column examples\n\n## List columns for a model\n\n```bash\ncargo-ai storage column list --model-uuid <uuid>\n```\n\nResponse includes `uuid`, `slug`, `type`, `label`, and `position` for each column.\n\n## Create a string column\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"website_url\",\"type\":\"string\",\"label\":\"Website URL\",\"kind\":\"custom\"}'\n```\n\n## Create a number column\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"arr\",\"type\":\"number\",\"label\":\"Annual Recurring Revenue\",\"kind\":\"custom\"}'\n```\n\n## Create a date column\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"last_contacted_at\",\"type\":\"date\",\"label\":\"Last Contacted At\",\"kind\":\"custom\"}'\n```\n\n## Create a boolean column\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"is_customer\",\"type\":\"boolean\",\"label\":\"Is Customer\",\"kind\":\"custom\"}'\n```\n\n## Create a computed column\n\nComputed columns derive their value from an expression over other columns.\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"full_name\",\"type\":\"string\",\"label\":\"Full Name\",\"kind\":\"computed\",\"expression\":{\"kind\":\"jsExpression\",\"expression\":\"{{record.first_name}} {{record.last_name}}\",\"instructTo\":\"none\",\"fromRecipe\":false},\"columnsUsed\":[\"first_name\",\"last_name\"]}'\n```\n\n`columnsUsed` is optional but recommended for dependency tracking.\n\n## Create a metric column\n\nMetric columns aggregate data from a related model via a relationship.\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"total_deals\",\"type\":\"number\",\"label\":\"Total Deals\",\"kind\":\"metric\",\"relationshipUuid\":\"<relationship-uuid>\",\"aggregation\":{\"function\":\"count\",\"columnSlug\":\"uuid\"}}'\n```\n\nWith an optional filter:\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"open_deals\",\"type\":\"number\",\"label\":\"Open Deals\",\"kind\":\"metric\",\"relationshipUuid\":\"<relationship-uuid>\",\"aggregation\":{\"function\":\"count\",\"columnSlug\":\"uuid\"},\"filter\":{\"conjonction\":\"and\",\"groups\":[{\"conjonction\":\"and\",\"conditions\":[{\"kind\":\"string\",\"slug\":\"status\",\"operator\":\"is\",\"value\":\"open\"}]}]}}'\n```\n\n## Create a lookup column\n\nLookup columns pull a field value from a related model via a join.\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"company_name\",\"type\":\"string\",\"label\":\"Company Name\",\"kind\":\"lookup\",\"join\":{\"toModelUuid\":\"<company-model-uuid>\",\"fromColumnSlug\":\"company_uuid\",\"toColumnSlug\":\"uuid\"},\"extractColumnSlug\":\"name\"}'\n```\n\nWith an optional filter:\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"primary_contact_email\",\"type\":\"string\",\"label\":\"Primary Contact Email\",\"kind\":\"lookup\",\"join\":{\"toModelUuid\":\"<contacts-model-uuid>\",\"fromColumnSlug\":\"uuid\",\"toColumnSlug\":\"company_uuid\"},\"extractColumnSlug\":\"email\",\"filter\":{\"conjonction\":\"and\",\"groups\":[{\"conjonction\":\"and\",\"conditions\":[{\"kind\":\"boolean\",\"slug\":\"is_primary\",\"operator\":\"isTrue\"}]}]}}'\n```\n\n## Update a column\n\nPass the full column object via `--column`. Columns are identified by `slug` (no UUID).\n\n```bash\ncargo-ai storage column update \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"website_url\",\"type\":\"string\",\"label\":\"Website\",\"kind\":\"custom\"}'\n```\n\n## Remove a column\n\n```bash\ncargo-ai storage column remove --model-uuid <uuid> --column-slug website_url\n```\n\n## Reorder a column\n\nMove a column to a specific position index (0-based).\n\n```bash\ncargo-ai storage column reorder --model-uuid <uuid> --column-slug website_url --to-index 2\n```\n\n## Column types reference\n\n| Type      | Use for                  |\n| --------- | ------------------------ |\n| `string`  | Text, names, URLs, slugs |\n| `number`  | Counts, amounts, scores  |\n| `boolean` | Flags, yes/no values     |\n| `date`    | Timestamps, dates        |\n| `object`  | Nested JSON objects      |\n| `array`   | Lists of values          |\n| `vector`  | Embedding vectors        |\n| `any`     | Untyped / mixed values   |\n\n## Column kinds reference\n\n| Kind       | Use for                                                     | Required extra fields                                                                                    |\n| ---------- | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |\n| `custom`   | User-defined fields                                         | —                                                                                                        |\n| `computed` | Values derived from an expression over other columns        | `expression`; optionally `columnsUsed`                                                                   |\n| `metric`   | Aggregated values from a related model                      | `relationshipUuid`, `aggregation.function`, `aggregation.columnSlug`; optionally `filter`                |\n| `lookup`   | A single field value pulled from a related model via a join | `join.toModelUuid`, `join.fromColumnSlug`, `join.toColumnSlug`, `extractColumnSlug`; optionally `filter` |\n\nColumn `slug` values are used in filter conditions (see `cargo-orchestration` skill's `references/filter-syntax.md`) and in `storage query execute` SQL queries.\n\nFile v1.2.1:references/examples/datasets.md\n\n# Dataset examples\n\n## List all datasets\n\nDatasets group related models together.\n\n```bash\ncargo-ai storage dataset list\n```\n\nResponse includes `uuid`, `name`, and `slug` for each dataset.\n\n## Get a specific dataset\n\n```bash\ncargo-ai storage dataset get <dataset-uuid>\n```\n\n## List models in a dataset\n\n```bash\ncargo-ai storage model list --dataset-uuid <dataset-uuid>\n```\n\n## Discover workspace data structure\n\nFull flow to understand how data is organized:\n\n```bash\n# 1. List all datasets\ncargo-ai storage dataset list\n# → Note the dataset UUIDs and slugs\n\n# 2. For each dataset, list its models\ncargo-ai storage model list --dataset-uuid <dataset-uuid>\n# → See which models (tables) belong to each dataset\n\n# 3. Inspect a model's columns\ncargo-ai storage model get <model-uuid>\n# → See column slugs and types for each model\n```\n\nThe dataset `slug` appears in DDL table names (e.g. `datasets_default` for the dataset with slug `default`).\n\nFile v1.2.1:references/examples/ingest-webhook.md\n\n# Ingest models — get the webhook URL and POST records\n\nSome models are fed by **pushing** records to Cargo instead of Cargo pulling them.\nTheir extractor has `mode.kind === \"ingest\"` — the canonical one is the `http`\nintegration's `listenHook` (\"Listen webhook\"), but the same mechanism backs\n`storeleads.listenList`, `rb2b.listenProfiles`, `albacross.listenWebsiteVisits`,\nand others.\n\nThe app shows a **Webhook URL** on the model's settings screen. There is **no CLI\ncommand and no API field that returns it** — the app builds the string client-side.\nYou can build the exact same string from data the CLI already exposes.\n\n## The URL\n\n```\n<baseUrl>/v1/models/<model-uuid>/records/ingest?token=<api-token>\n```\n\n- `<baseUrl>` — `cargo-ai whoami` → `.baseUrl` (e.g. `https://api.getcargo.io`).\n  Note the path is `/v1/models/...`, **not** `/v1/storage/models/...`.\n- `<model-uuid>` — the ingest model's UUID.\n- `<api-token>` — any workspace API token. The token may carry **zero\n  permissions**; this route is explicitly allowed for permission-less tokens so\n  the URL can be handed to a third-party system safely. The app auto-creates one\n  named `Quick access` for exactly this.\n\n`token` can also be sent as an `Authorization: Basic <token>` header instead of a\nquery param — preferable when the receiving system supports custom headers, since\na query param lands in logs.\n\n## Derive it\n\n```bash\n# 1. Find the model and confirm it is an ingest model\ncargo-ai storage model get <model-uuid> | jq '{uuid, slug, extractorSlug, kind, connectorUuid}'\n\n# 2. Confirm the extractor's mode is \"ingest\" (and NOT autoIngest — see below)\ncargo-ai connection integration get http | jq -c '.integration.extractors.listenHook.mode'\n# → {\"kind\":\"ingest\"}\n\n# 3. Pick or create a token (the raw value is on the list response)\ncargo-ai workspaceManagement token list | jq -r '.tokens[0].token'\ncargo-ai workspaceManagement token create --name \"Webhook — <model-slug>\" | jq -r '.token.token'\n```\n\nOne-liner that assembles it:\n\n```bash\nMODEL_UUID=<model-uuid>\nBASE=$(cargo-ai whoami | jq -r '.baseUrl')\nTOKEN=$(cargo-ai workspaceManagement token list | jq -r '.tokens[0].token')\necho \"$BASE/v1/models/$MODEL_UUID/records/ingest?token=$TOKEN\"\n```\n\n> Token values are secrets. Print the URL for the user to copy; don't write it\n> into a file, a commit, or a report.\n\n## Skip models where Cargo owns the hook\n\nSome ingest extractors set `autoIngest: true` — Cargo registers the webhook with\nthe provider itself during setup (calendly, smartlead, instantlyV2, heyReach,\ndatachimp, and cargo's own signal extractors). The app **hides** the URL for\nthose, and handing it out is wrong: the provider is already pointed at it.\n\nCheck before showing anything:\n\n```bash\ncargo-ai connection integration get <integration-slug> \\\n  | jq -c '.integration.extractors[\"<extractor-slug>\"].mode'\n# {\"kind\":\"ingest\"}                    → manual: show the URL\n# {\"kind\":\"ingest\",\"autoIngest\":true}  → Cargo owns it: don't show the URL\n# anything else (fetch/…)              → not an ingest model at all\n```\n\nList every ingest extractor an integration has:\n\n```bash\ncargo-ai connection integration get <integration-slug> \\\n  | jq -c '.integration.extractors | to_entries\n           | map(select(.value.mode.kind==\"ingest\")) | map({(.key): .value.mode})'\n# calendly → [{\"fetchEvents\":{\"kind\":\"ingest\",\"autoIngest\":true}}]\n```\n\n## POST records\n\nThe body is **either one flat object or an array of flat objects** — each object\nbecomes one record, and its keys become columns. Max **100 records per request**.\n\n```bash\n# one record\ncurl -X POST \"$BASE/v1/models/$MODEL_UUID/records/ingest?token=$TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"email\":\"ada@example.com\",\"company\":\"example.com\"}'\n\n# many records\ncurl -X POST \"$BASE/v1/models/$MODEL_UUID/records/ingest?token=$TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '[{\"email\":\"ada@example.com\"},{\"email\":\"grace@example.com\"}]'\n\n# token as a header instead of a query param\ncurl -X POST \"$BASE/v1/models/$MODEL_UUID/records/ingest\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Authorization: Basic $TOKEN\" \\\n  -d '{\"email\":\"ada@example.com\"}'\n# all → 200 {\"message\":\"OK\"}\n```\n\n**The endpoint is insert-only.** Do not send an envelope like\n`{\"kind\":\"insert\",\"records\":[…]}` — there is no unwrapping, so you get one useless\nrow with a `kind` column and a `records` column holding the stringified array.\nThe `{kind: insert|update|remove, records: […]}` shape belongs to the extractor's\ninternal contract, not to this HTTP body; `update` and `remove` are not reachable\nthrough the webhook.\n\nFor `http.listenHook`, the model's id column is `_ingest_id` (a UUID **generated\nserver-side** — never send it; the extractor rejects inserts that carry one) and\nits title column is `_emitted_at`.\n\nThen confirm the rows landed (storage queries are free):\n\n```bash\ncargo-ai storage query execute \"SELECT * FROM <dataset-slug>.<model-slug> LIMIT 10\"\n```\n\n## Create an ingest model from scratch\n\n`model create` has no `--connector-uuid` — the API infers the connector from the\n**dataset**. Every connector automatically owns exactly one `kind: \"connector\"`\ndataset, so the flow is: create the connector, find its dataset, create the model\nin it.\n\n```bash\n# 1. Connector (slug must be snake_case: /^[a-z0-9]+(_[a-z0-9]+)*$/)\ncargo-ai connection connector create \\\n  --name \"Inbound leads\" --slug inbound_leads \\\n  --integration-slug http --config '{}' | jq -r '.connector.uuid'\n\n# 2. Its dataset — dataset list takes no --connector-uuid filter, so filter locally\ncargo-ai storage dataset list \\\n  | jq -c --arg c <connector-uuid> '.datasets[] | select(.connectorUuid==$c) | {uuid, slug}'\n\n# 3. The model\ncargo-ai storage model create \\\n  --slug inbound_leads --name \"Inbound Leads\" \\\n  --dataset-uuid <dataset-uuid> \\\n  --extractor-slug listenHook --config '{}'\n```\n\nThe response comes back with `kind: \"connector\"`, `idColumnSlug: \"_ingest_id\"`,\nand `titleColumnSlug: \"_emitted_at\"`. Columns are then created dynamically from\nthe keys of whatever you POST — you don't declare them up front. Query it as\n`<connector-slug>.<model-slug>`.\n\n## Notes\n\n- The endpoint answers webhook **handshakes** out of the box — Slack\n  `url_verification`, generic `ping`, Microsoft `?validationToken=`, Salesforce\n  SOAP ack, and Meta's `hub.challenge` on `GET` — so most providers validate\n  without extra work.\n- Ingest models can't be refreshed or scheduled; data only arrives when something\n  POSTs. `model refresh` / `--schedule` don't apply.\n- A legacy alias `POST /v1/workflows/<uuid>/hook` still resolves to the same\n  insert (the uuid being the model uuid). Prefer the `/v1/models/...` form.\n- Because the URL is assembled client-side, it is *derived*, not *returned* — if\n  a future CLI release adds a field or a `get-webhook-url` command, prefer that.\n\nFile v1.2.1:references/examples/models.md\n\n# Model examples\n\n## Discover all models\n\n```bash\ncargo-ai storage model list\n```\n\nResponse includes `uuid`, `name`, `slug`, `datasetUuid`, and `columns[]` for each model.\n\n## Find a model by name\n\n```bash\n# List all models and filter by name in the output\ncargo-ai storage model list\n# → Find the entry where \"name\" matches what you're looking for, then extract \"uuid\"\n```\n\n## Get a model's full schema\n\n```bash\ncargo-ai storage model get <model-uuid>\n# → Returns the model with all columns, their types and slugs\n```\n\n## Get the DDL (column types and SQL dialect)\n\n`storage query execute` accepts `<datasetSlug>.<modelSlug>` (e.g. `default.companies`) as the table name, so you don't need the DDL just for the table name. Run `model get-ddl` when you need column types or the SQL dialect.\n\n```bash\ncargo-ai storage model get-ddl <model-uuid>\n```\n\nExample response:\n```json\n{\n  \"ddl\": \"CREATE TABLE `datasets_default.models_companies` (\\n  `uuid` STRING,\\n  `name` STRING,\\n  `domain` STRING,\\n  `employee_count` INT64\\n)\",\n  \"language\": \"bigquery\"\n}\n```\n\nThe `language` field tells you which SQL dialect to use.\n\n## Create a model\n\n```bash\n# First, find the dataset UUID\ncargo-ai storage dataset list\n\n# Create the model\ncargo-ai storage model create \\\n  --slug prospects \\\n  --name \"Prospects\" \\\n  --dataset-uuid <dataset-uuid> \\\n  --extractor-slug <extractor-slug> \\\n  --config '{}'\n```\n\n## Update a model\n\n```bash\ncargo-ai storage model update --uuid <model-uuid> --name \"Qualified Prospects\"\n```\n\n## Remove a model\n\n```bash\ncargo-ai storage model remove <model-uuid>\n```\n\nNote: This will fail if the model is referenced by segments, plays, or tools. Remove or update those resources first.\n\n## Schema discovery workflow\n\nFull flow to understand a model before querying it:\n\n```bash\n# 1. Find the model and its dataset slug\ncargo-ai storage model list\ncargo-ai storage dataset list\n\n# 2. Get the full schema with column types (optional — also returns SQL dialect)\ncargo-ai storage model get <model-uuid>\ncargo-ai storage model get-ddl <model-uuid>\n\n# 3. Query using <datasetSlug>.<modelSlug> as the table name\ncargo-ai storage query execute \\\n  \"SELECT uuid, name, domain FROM default.companies LIMIT 10\"\n```\n\nFile v1.2.1:references/examples/queries.md\n\n# Storage query examples\n\nRun SQL against workspace storage with `cargo-ai storage query execute`. Tables are referenced as `<datasetSlug>.<modelSlug>` and rewritten to the underlying storage table under the hood. No DDL lookup is required for the table name — just use the dataset and model slugs.\n\nFor column slugs, run `cargo-ai storage column list --model-uuid <uuid>` or `cargo-ai storage model get-ddl <model-uuid>` (the DDL also shows column types and the SQL dialect).\n\n## Basic query flow\n\n```bash\n# 1. Discover the dataset slug and the model slug\ncargo-ai storage dataset list   # → datasets[].slug (e.g. \"default\")\ncargo-ai storage model list     # → models[].slug   (e.g. \"companies\")\n\n# 2. Query using <datasetSlug>.<modelSlug> as the table name\ncargo-ai storage query execute \\\n  \"SELECT name, domain, employee_count FROM default.companies LIMIT 10\"\n```\n\nSuccess response:\n\n```json\n{\n  \"rows\": [\n    { \"name\": \"Acme Corp\", \"domain\": \"acme.com\", \"employee_count\": 500 },\n    { \"name\": \"Globex\", \"domain\": \"globex.com\", \"employee_count\": 1200 }\n  ]\n}\n```\n\nFailed commands exit non-zero with `{\"errorMessage\": \"...\"}` (or `{\"reason\": \"clientNotFound\"|\"unknown\"}`). See the error handling section below.\n\n## Query with WHERE clauses\n\n```bash\n# Filter by a column\ncargo-ai storage query execute \\\n  \"SELECT name, domain FROM default.companies WHERE employee_count > 100\"\n\n# Multiple conditions\ncargo-ai storage query execute \\\n  \"SELECT name, domain, revenue FROM default.companies WHERE employee_count > 100 AND country = 'US'\"\n\n# LIKE for partial matches\ncargo-ai storage query execute \\\n  \"SELECT name, domain FROM default.companies WHERE name LIKE '%tech%'\"\n\n# NULL checks\ncargo-ai storage query execute \\\n  \"SELECT name, domain FROM default.companies WHERE email IS NOT NULL\"\n```\n\n## Aggregation queries\n\n```bash\n# Count records\ncargo-ai storage query execute \\\n  \"SELECT COUNT(*) as total FROM default.companies\"\n\n# Group by with counts\ncargo-ai storage query execute \\\n  \"SELECT country, COUNT(*) as count FROM default.companies GROUP BY country ORDER BY count DESC\"\n\n# Sum and average\ncargo-ai storage query execute \\\n  \"SELECT country, SUM(revenue) as total_revenue, AVG(employee_count) as avg_employees FROM default.companies GROUP BY country\"\n```\n\n## Pagination\n\nPage through large result sets with SQL `LIMIT` and `OFFSET` clauses. Always include an `ORDER BY` so pages are stable across calls.\n\n```bash\n# First page\ncargo-ai storage query execute \\\n  \"SELECT * FROM default.companies ORDER BY name LIMIT 100 OFFSET 0\"\n\n# Second page\ncargo-ai storage query execute \\\n  \"SELECT * FROM default.companies ORDER BY name LIMIT 100 OFFSET 100\"\n```\n\n## Download full results\n\nFor exporting full result sets to a file, use `storage query download`. The response is a signed URL.\n\n```bash\ncargo-ai storage query download \\\n  --query \"SELECT name, domain, employee_count, revenue FROM default.companies ORDER BY revenue DESC\"\n\n# Choose the format (csv default, parquet supported)\ncargo-ai storage query download \\\n  --query \"SELECT * FROM default.companies\" --format parquet\n```\n\n## Query across multiple models\n\nJoin on `<datasetSlug>.<modelSlug>` table references:\n\n```bash\ncargo-ai storage query execute \\\n  \"SELECT c.name, c.domain, d.stage, d.amount FROM default.companies c JOIN default.deals d ON c._id = d.company_id WHERE d.amount > 10000\"\n```\n\n## Common table expressions\n\n```bash\ncargo-ai storage query execute \\\n  \"WITH recent AS (SELECT * FROM default.companies WHERE created_at >= CURRENT_DATE - INTERVAL '30' DAY) SELECT count(*) FROM recent\"\n```\n\n## Date queries\n\n```bash\n# Records created in the last 30 days\ncargo-ai storage query execute \\\n  \"SELECT name, created_at FROM default.companies WHERE created_at >= DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY)\"\n\n# Records in a specific range\ncargo-ai storage query execute \\\n  \"SELECT name, created_at FROM default.companies WHERE created_at BETWEEN '2025-01-01' AND '2025-03-31'\"\n```\n\n## Subqueries\n\n```bash\n# Companies with above-average employee count\ncargo-ai storage query execute \\\n  \"SELECT name, employee_count FROM default.companies WHERE employee_count > (SELECT AVG(employee_count) FROM default.companies)\"\n```\n\n## Error handling\n\nIf a query fails, the command exits non-zero. Failure shapes:\n\n```json\n{ \"errorMessage\": \"Table not found: default.nonexistent\" }\n```\n\n```json\n{ \"reason\": \"clientNotFound\" }\n```\n\nCommon causes:\n- Wrong dataset or model slug → re-check with `storage dataset list` and `storage model list`\n- Syntax error → check SQL syntax for your storage SQL dialect (BigQuery vs Snowflake) — `storage model get-ddl` reports `language`\n- `clientNotFound` → no storage client is configured for this workspace\n\n## Discovery commands\n\n```bash\ncargo-ai storage dataset list                  # all datasets (uuid, slug)\ncargo-ai storage model list                    # all models (uuid, name, slug)\ncargo-ai storage model get-ddl <model-uuid>    # column types and SQL dialect\ncargo-ai storage column list --model-uuid <uuid>  # column slugs for a model\n```\n\nFile v1.2.1:references/response-shapes.md\n\n# Response shapes\n\nJSON response structures returned by Cargo CLI commands used in the `cargo-storage` skill.\n\n## cargo-ai storage model list\n\n```json\n{\n  \"models\": [\n    {\n      \"uuid\": \"model-uuid\",\n      \"workspaceUuid\": \"...\",\n      \"slug\": \"companies\",\n      \"name\": \"Companies\",\n      \"datasetUuid\": \"dataset-uuid\",\n      \"extractorSlug\": \"hubspot_companies\",\n      \"idColumnSlug\": \"uuid\",\n      \"titleColumnSlug\": \"name\",\n      \"timeColumnSlug\": null,\n      \"columns\": [\n        { \"slug\": \"name\", \"type\": \"string\", \"label\": \"Name\", \"kind\": \"original\", \"originalSlug\": \"name\" },\n        { \"slug\": \"domain\", \"type\": \"string\", \"label\": \"Domain\", \"kind\": \"original\", \"originalSlug\": \"domain\" }\n      ],\n      \"additionalColumns\": [\n        { \"slug\": \"full_name\", \"type\": \"string\", \"label\": \"Full Name\", \"kind\": \"computed\", \"expression\": { \"kind\": \"jsExpression\", \"expression\": \"...\" }, \"columnsUsed\": [\"first_name\", \"last_name\"] },\n        { \"slug\": \"total_deals\", \"type\": \"number\", \"label\": \"Total Deals\", \"kind\": \"metric\", \"relationshipUuid\": \"...\", \"aggregation\": { \"function\": \"count\", \"columnSlug\": \"uuid\" } }\n      ],\n      \"playsCount\": 2,\n      \"segmentsCount\": 1,\n      \"isPaused\": false,\n      \"lastRun\": {\n        \"uuid\": \"run-uuid\",\n        \"status\": \"success\",\n        \"errorMessage\": null,\n        \"createdAt\": \"2025-01-15T00:00:00Z\",\n        \"finishedAt\": \"2025-01-15T00:01:00Z\"\n      },\n      \"createdAt\": \"2025-01-01T00:00:00Z\",\n      \"updatedAt\": \"2025-01-15T00:00:00Z\"\n    }\n  ]\n}\n```\n\n**Key fields:** `uuid`, `slug`, `name`, `datasetUuid`, `idColumnSlug`, `columns` (original columns), `additionalColumns` (custom/computed/metric/lookup columns).\n\nColumns have no `uuid` — they are identified by `slug` within the model.\n\n## cargo-ai storage model get\n\nSame structure as a single item from `model list`, nested under `model`:\n\n```json\n{\n  \"model\": {\n    \"uuid\": \"model-uuid\",\n    \"slug\": \"companies\",\n    \"name\": \"Companies\",\n    \"datasetUuid\": \"dataset-uuid\",\n    \"columns\": [...],\n    \"additionalColumns\": [...]\n  }\n}\n```\n\n## cargo-ai storage model get-ddl\n\n```json\n{\n  \"ddl\": \"CREATE TABLE `datasets_default.models_companies` (\\n  `uuid` STRING,\\n  `name` STRING,\\n  `domain` STRING,\\n  `employee_count` INT64,\\n  `created_at` TIMESTAMP\\n)\",\n  \"language\": \"bigquery\"\n}\n```\n\n**Key fields:** `ddl` (contains the storage-native table name and column names), `language` (SQL dialect).\n\nFor `cargo-ai storage query execute`, reference tables as `<datasetSlug>.<modelSlug>` (e.g. `default.companies`).\n\n## cargo-ai storage dataset list\n\n```json\n{\n  \"datasets\": [\n    {\n      \"uuid\": \"dataset-uuid\",\n      \"slug\": \"default\",\n      \"workspaceUuid\": \"...\",\n      \"config\": { \"kind\": \"object\" },\n      \"createdAt\": \"2025-01-01T00:00:00Z\"\n    }\n  ]\n}\n```\n\n## cargo-ai storage dataset get\n\n```json\n{\n  \"dataset\": {\n    \"uuid\": \"dataset-uuid\",\n    \"slug\": \"default\",\n    \"workspaceUuid\": \"...\",\n    \"config\": { \"kind\": \"object\" }\n  }\n}\n```\n\n## cargo-ai storage column list\n\nReturns the model's columns (both original and additional). All columns share base fields: `slug`, `type`, `label`, `kind`. Columns have no `uuid` — use `slug` to identify them.\n\n```json\n{\n  \"columns\": [\n    {\n      \"slug\": \"name\",\n      \"type\": \"string\",\n      \"label\": \"Name\",\n      \"kind\": \"original\",\n      \"originalSlug\": \"name\"\n    },\n    {\n      \"slug\": \"full_name\",\n      \"type\": \"string\",\n      \"label\": \"Full Name\",\n      \"kind\": \"computed\",\n      \"expression\": { \"kind\": \"jsExpression\", \"expression\": \"...\" },\n      \"columnsUsed\": [\"first_name\", \"last_name\"]\n    }\n  ]\n}\n```\n\nKind-specific fields are included alongside the base fields:\n\n**`computed`**\n```json\n{\n  \"kind\": \"computed\",\n  \"expression\": { \"kind\": \"jsExpression\", \"value\": \"record.first_name + \\\" \\\" + record.last_name\" },\n  \"columnsUsed\": [\"first_name\", \"last_name\"]\n}\n```\n\n**`metric`**\n```json\n{\n  \"kind\": \"metric\",\n  \"relationshipUuid\": \"relationship-uuid\",\n  \"aggregation\": {\n    \"function\": \"count\",\n    \"columnSlug\": \"uuid\"\n  },\n  \"filter\": null\n}\n```\n\n**`lookup`**\n```json\n{\n  \"kind\": \"lookup\",\n  \"join\": {\n    \"toModelUuid\": \"company-model-uuid\",\n    \"fromColumnSlug\": \"company_uuid\",\n    \"toColumnSlug\": \"uuid\"\n  },\n  \"extractColumnSlug\": \"name\",\n  \"filter\": null\n}\n```\n\n## cargo-ai storage relationship list\n\n```json\n{\n  \"relationships\": [\n    {\n      \"uuid\": \"relationship-uuid\",\n      \"fromModelUuid\": \"contacts-model-uuid\",\n      \"toModelUuid\": \"companies-model-uuid\",\n      \"fromColumnSlug\": \"company_uuid\",\n      \"toColumnSlug\": \"uuid\",\n      \"relation\": \"manyToOne\"\n    }\n  ]\n}\n```\n\n## cargo-ai storage record list\n\n```json\n{\n  \"records\": [\n    {\n      \"uuid\": \"record-uuid\",\n      \"name\": \"Acme Corp\",\n      \"domain\": \"acme.com\",\n      \"employee_count\": 500\n    }\n  ]\n}\n```\n\n## cargo-ai storage query execute\n\nTables are referenced as `<datasetSlug>.<modelSlug>` and rewritten to the underlying storage table under the hood.\n\n**Success:**\n\n```json\n{\n  \"rows\": [\n    { \"name\": \"Acme Corp\", \"domain\": \"acme.com\", \"employee_count\": 500 },\n    { \"name\": \"Globex\", \"domain\": \"globex.com\", \"employee_count\": 1200 }\n  ]\n}\n```\n\n**Failure (non-zero exit):**\n\n```json\n{ \"errorMessage\": \"Table not found: default.nonexistent\" }\n```\n\n```json\n{ \"reason\": \"clientNotFound\" }\n```\n\n```json\n{ \"reason\": \"unknown\" }\n```\n\n## cargo-ai storage query download\n\nUsed for full exports. Same table-naming convention as `storage query execute` (`<datasetSlug>.<modelSlug>`). Pass the SQL via `--query`; the response is a signed URL.\n\n**Success:**\n\n```json\n{\n  \"url\": \"https://signed-url-to-csv-or-parquet-file\"\n}\n```\n\n**Failure (non-zero exit):**\n\n```json\n{ \"errorMessage\": \"Table not found: default.nonexistent\" }\n```\n\nFile v1.2.1:references/troubleshooting.md\n\n# Troubleshooting\n\nCommon errors and recovery steps for `cargo-storage` commands.\n\n## General\n\n| Symptom | Cause | Fix |\n|---------|-------|-----|\n| `{\"errorMessage\": \"...\"}` with non-zero exit | Any CLI error | Read the `errorMessage` — it usually says exactly what's wrong |\n| `command not found: cargo-ai` | CLI not installed or not in PATH | Run `npm install -g @cargo-ai/cli` or prefix with `npx @cargo-ai/cli` |\n| `Unauthorized` or `Forbidden` | Bad or expired credentials | Re-run `cargo-ai login --oauth` (browser sign-in) or `cargo-ai login --token <token>`; verify with `cargo-ai whoami` |\n\n## Models\n\n| Symptom | Cause | Fix |\n|---------|-------|-----|\n| `model get` returns not found | Wrong UUID | Re-run `model list` to get the correct UUID |\n| `model get-ddl` returns empty DDL | Model has no sync connection to storage | Confirm the model has an extractor configured and has synced at least once |\n| Table not found in `storage query execute` | Wrong dataset or model slug | Verify with `dataset list` and `model list`; tables are referenced as `<datasetSlug>.<modelSlug>` |\n| `model remove` returns an error | Model is referenced by segments, plays, or tools | Remove or update the dependent resources before deleting the model |\n\n## Columns\n\n| Symptom | Cause | Fix |\n|---------|-------|-----|\n| `column create` fails with slug conflict | A column with that slug already exists | Use `column list --model-uuid <uuid>` to check existing slugs; choose a unique slug |\n| `column update` returns not found | Wrong column slug or model UUID | Re-run `column list --model-uuid <uuid>` to get the correct column slugs |\n| Column type mismatch in queries | Using string operators on a number column | Match the condition type to the column type; see the `cargo-orchestration` skill's `references/filter-syntax.md` |\n\n## Relationships\n\n| Symptom | Cause | Fix |\n|---------|-------|-----|\n| `relationship set` fails | One or both model UUIDs are wrong | Verify both model UUIDs with `model list` |\n| `relationship list` returns empty | No relationships defined for that model | This is expected if relationships haven't been configured yet |\n\n## Records\n\n| Symptom | Cause | Fix |\n|---------|-------|-----|\n| `record list` returns empty | No records in the model, or wrong model UUID | Verify with `model list`; check that data has been synced |\n| Need filtered record access | `record list` doesn't support filtering | Use `segmentation segment fetch` from the `cargo-orchestration` skill for filtering, sorting, and pagination |\n\n## Queries (`storage query execute` / `storage query download`)\n\n| Symptom | Cause | Fix |\n|---------|-------|-----|\n| `errorMessage` with \"Table not found\" | Wrong dataset or model slug | Verify with `storage dataset list` and `storage model list`. Tables are `<datasetSlug>.<modelSlug>` |\n| `errorMessage` with syntax error | SQL dialect mismatch | Check whether your storage backend is BigQuery, Snowflake, etc. and adjust syntax accordingly. `storage model get-ddl` reports `language` |\n| `reason: \"clientNotFound\"` | No storage client configured | Verify the workspace has an active storage connection |\n| Query returns empty `rows` | Filter too restrictive, \n\nArchive v1.2.0: 11 files, 17517 bytes\n\nFiles: references/examples/columns.md (5387b), references/examples/datasets.md (947b), references/examples/ingest-webhook.md (6917b), references/examples/models.md (2220b), references/examples/queries.md (5065b), references/response-shapes.md (5675b), references/troubleshooting.md (3403b), skill-card.md (2431b), skill-metadata.json (1308b), SKILL.md (9462b), _meta.json (132b)\n\nArchive v1.1.1: 9 files, 12562 bytes\n\nFiles: references/examples/columns.md (5387b), references/examples/datasets.md (947b), references/examples/models.md (2220b), references/examples/queries.md (5065b), references/response-shapes.md (5675b), references/troubleshooting.md (3403b), skill-card.md (2286b), SKILL.md (7143b), _meta.json (132b)\n\nArchive v1.1.0: 9 files, 12816 bytes\n\nFiles: references/examples/columns.md (5387b), references/examples/datasets.md (947b), references/examples/models.md (2220b), references/examples/queries.md (5065b), references/response-shapes.md (5675b), references/troubleshooting.md (3403b), skill-card.md (2572b), SKILL.md (7443b), _meta.json (132b)","readmeExcerpt":"Skill: cargo-storage Owner: cargo-ai Summary: Work with the data inside a Cargo workspace — models (Companies, Contacts, Deals…), datasets, columns, relationships, records, and SQL over workspace storage. Triggers: \"what models do I have\", \"show me the schema\", \"add a column for\", \"how many contacts do I have\", \"SELECT … FROM\", \"query my companies table\", \"join contacts to companies\", \"what is the DDL\", \"set up a web","codeSnippets":[],"executableExamples":[{"language":"bash","snippet":"npm install -g @cargo-ai/cli            # no global install? prefix every command with `npx @cargo-ai/cli`\ncargo-ai login --email you@company.com  # emailed code, no browser; creates the account on first use\n                                        # alternatives: --oauth (browser) · --token <api-token> (CI)\ncargo-ai whoami                         # confirm the active workspace before any write"},{"language":"bash","snippet":"cargo-ai storage dataset list              # all datasets (uuid, slug)\ncargo-ai storage model list                # all models (uuid, name, slug, columns, datasetUuid)\n# `model list` takes no flags — filter its output instead:\ncargo-ai storage model list | jq '[.models[] | select(.datasetUuid == \"<uuid>\")]'"},{"language":"bash","snippet":"cargo-ai storage model list\ncargo-ai storage model get <model-uuid>\ncargo-ai storage model get-ddl <model-uuid>\ncargo-ai storage dataset list\ncargo-ai storage column list --model-uuid <uuid>\ncargo-ai storage relationship list\ncargo-ai storage record list --model-uuid <uuid>\ncargo-ai storage query execute \"SELECT * FROM default.companies LIMIT 10\"\ncargo-ai storage query download --query \"SELECT * FROM default.companies\""},{"language":"bash","snippet":"# List all models\ncargo-ai storage model list\n\n# List models in a dataset — every model carries `datasetUuid`, and\n# `model list` has no flags of its own, so filter client-side\ncargo-ai storage model list | jq '[.models[] | select(.datasetUuid == \"<uuid>\")]' \n\n# Get a single model (includes columns)\ncargo-ai storage model get <model-uuid>\n\n# Get the DDL (full schema, table name and SQL dialect)\ncargo-ai storage model get-ddl <model-uuid>\n# → Useful for column discovery and SQL dialect (BigQuery vs Snowflake) before writing queries\n\n# Create a model\ncargo-ai storage model create \\\n  --slug contacts \\\n  --name \"Contacts\" \\\n  --dataset-uuid <uuid> \\\n  --extractor-slug <extractor-slug> \\\n  --config '{}'\n\n# Update a model\ncargo-ai storage model update --uuid <model-uuid> --name \"New Name\"\n\n# Remove a model\ncargo-ai storage model remove <model-uuid>"},{"language":"text","snippet":"<baseUrl>/v1/models/<model-uuid>/records/ingest?token=<api-token>"},{"language":"bash","snippet":"MODEL_UUID=<model-uuid>\nBASE=$(cargo-ai whoami | jq -r '.baseUrl')\nTOKEN=$(cargo-ai workspaceManagement token list | jq -r '.tokens[0].token')\necho \"$BASE/v1/models/$MODEL_UUID/records/ingest?token=$TOKEN\""}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\nname: cargo-storage\ndescription: \"Work with the data inside a Cargo workspace — models (Companies, Contacts, Deals…), datasets, columns, relationships, records, and SQL over workspace storage. Triggers: \\\"what models do I have\\\", \\\"show me the schema\\\", \\\"add a column for\\\", \\\"how many contacts do I have\\\", \\\"SELECT … FROM\\\", \\\"query my companies table\\\", \\\"join contacts to companies\\\", \\\"what is the DDL\\\", \\\"set up a webhook-fed model\\\", \\\"where does this field live\\\", \\\"import this into a model\\\", \\\"unify these models\\\", \\\"merge duplicate accounts\\\", \\\"link contacts to companies\\\", \\\"set up a relationship between\\\". Skip when: querying run or batch telemetry rather than business data — use cargo-orchestration; naming a reusable filtered audience — use cargo-segmentation.\"\nversion: \"1.2.3\"\ncompatibility: Requires @cargo-ai/cli (npm). Sign in or create an account with `cargo-ai login --email` (emailed code, no browser), `--oauth`, or an API token\nhomepage: https://github.com/getcargohq/cargo-skills\nmetadata:\n  author: getcargo\n  openclaw:\n    requires:\n      bins:\n        - cargo-ai\n    install:\n      - kind: node\n        package: \"@cargo-ai/cli@latest\"\n        bins:\n          - cargo-ai\n    homepage: https://github.com/getcargohq/cargo-skills\n---\n\n# Cargo CLI — Storage\n\nData layer management: inspecting and modifying models, datasets, columns, relationships, unification, and records, and running SQL queries against workspace storage.\n\n> See `references/response-shapes.md` for full JSON response structures.\n> See `references/troubleshooting.md` for common errors and how to fix them.\n> See `references/examples/models.md` for model CRUD, DDL inspection, and schema discovery examples.\n> See `references/examples/datasets.md` for dataset listing and navigation examples.\n> See `references/examples/columns.md` for column creation and management examples.\n> See `references/examples/queries.md` for `storage query execute` / `storage query download` SQL examples (WHERE, aggregations, joins, pagination, exports).\n> See `references/examples/ingest-webhook.md` for ingest (webhook-fed) models — deriving the webhook URL and POSTing records.\n\n## Bootstrap\n\nAlready signed in (`cargo-ai whoami` returns a workspace)? Skip to the next section.\n\n```bash\nnpm install -g @cargo-ai/cli            # no global install? prefix every command with `npx @cargo-ai/cli`\ncargo-ai login --email you@company.com  # emailed code, no browser; creates the account on first use\n                                        # alternatives: --oauth (browser) · --token <api-token> (CI)\ncargo-ai whoami                         # confirm the active workspace before any write\n```\n\nEvery command prints JSON to stdout; failures exit non-zero with `{\"errorMessage\": \"...\"}`. Anything that creates a run or a batch is async — pass `--wait-until-finished` or poll the matching `get`. When the full skill bundle is installed, [`../cargo/references/prerequisites.md`](../cargo/references/prerequisites.md) ad"},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn7by8t6yt9yghbxtxz6hv0bts87k6bq\",\n  \"slug\": \"cargo-storage\",\n  \"version\": \"1.2.3\",\n  \"publishedAt\": 1790278850493\n}"},{"path":"references/examples/columns.md","content":"# Column examples\n\n## List columns for a model\n\n```bash\ncargo-ai storage column list --model-uuid <uuid>\n```\n\nResponse includes `uuid`, `slug`, `type`, `label`, and `position` for each column.\n\n## Create a string column\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"website_url\",\"type\":\"string\",\"label\":\"Website URL\",\"kind\":\"custom\"}'\n```\n\n## Create a number column\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"arr\",\"type\":\"number\",\"label\":\"Annual Recurring Revenue\",\"kind\":\"custom\"}'\n```\n\n## Create a date column\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"last_contacted_at\",\"type\":\"date\",\"label\":\"Last Contacted At\",\"kind\":\"custom\"}'\n```\n\n## Create a boolean column\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"is_customer\",\"type\":\"boolean\",\"label\":\"Is Customer\",\"kind\":\"custom\"}'\n```\n\n## Create a computed column\n\nComputed columns derive their value from an expression over other columns.\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"full_name\",\"type\":\"string\",\"label\":\"Full Name\",\"kind\":\"computed\",\"expression\":{\"kind\":\"jsExpression\",\"expression\":\"{{record.first_name}} {{record.last_name}}\",\"instructTo\":\"none\",\"fromRecipe\":false},\"columnsUsed\":[\"first_name\",\"last_name\"]}'\n```\n\n`columnsUsed` is optional but recommended for dependency tracking.\n\n## Create a metric column\n\nMetric columns aggregate data from a related model via a relationship.\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"total_deals\",\"type\":\"number\",\"label\":\"Total Deals\",\"kind\":\"metric\",\"relationshipUuid\":\"<relationship-uuid>\",\"aggregation\":{\"function\":\"count\",\"columnSlug\":\"uuid\"}}'\n```\n\nWith an optional filter:\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"open_deals\",\"type\":\"number\",\"label\":\"Open Deals\",\"kind\":\"metric\",\"relationshipUuid\":\"<relationship-uuid>\",\"aggregation\":{\"function\":\"count\",\"columnSlug\":\"uuid\"},\"filter\":{\"conjonction\":\"and\",\"groups\":[{\"conjonction\":\"and\",\"conditions\":[{\"kind\":\"string\",\"slug\":\"status\",\"operator\":\"is\",\"value\":\"open\"}]}]}}'\n```\n\n## Create a lookup column\n\nLookup columns pull a field value from a related model via a join.\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"company_name\",\"type\":\"string\",\"label\":\"Company Name\",\"kind\":\"lookup\",\"join\":{\"toModelUuid\":\"<company-model-uuid>\",\"fromColumnSlug\":\"company_uuid\",\"toColumnSlug\":\"uuid\"},\"extractColumnSlug\":\"name\"}'\n```\n\nWith an optional filter:\n\n```bash\ncargo-ai storage column create \\\n  --model-uuid <uuid> \\\n  --column '{\"slug\":\"primary_contact_email\",\"type\":\"string\",\"label\":\"Primary Contact Email\",\"kind\":\"lookup\",\"join\":{\"toModelUuid\":\"<contacts-model-uuid>\",\"fromColumnSlug\":\"uuid\",\"toColumnSlug\":\"company_uuid\"},\"extractColumnSlug\":\"email\",\"filter\":{\"conjonction\":\"and\",\"groups\":[{\"conj"},{"path":"references/examples/datasets.md","content":"# Dataset examples\n\n## List all datasets\n\nDatasets group related models together.\n\n```bash\ncargo-ai storage dataset list\n```\n\nResponse includes `uuid`, `name`, and `slug` for each dataset.\n\n## Get a specific dataset\n\n```bash\ncargo-ai storage dataset get <dataset-uuid>\n```\n\n## List models in a dataset\n\n`storage model list` takes **no options** — it always returns every model in the\nworkspace. Each one carries a `datasetUuid`, so narrow it client-side:\n\n```bash\ncargo-ai storage model list | jq '[.models[] | select(.datasetUuid == \"<dataset-uuid>\")]'\n```\n\n## Discover workspace data structure\n\nFull flow to understand how data is organized:\n\n```bash\n# 1. List all datasets\ncargo-ai storage dataset list\n# → Note the dataset UUIDs and slugs\n\n# 2. Group the models by dataset (one call — `model list` has no filter flag)\ncargo-ai storage model list | jq 'group_by(.datasetUuid) | map({datasetUuid: .[0].datasetUuid, models: map(.slug)})'\n# → See which models (tables) belong to each dataset\n\n# 3. Inspect a model's columns\ncargo-ai storage model get <model-uuid>\n# → See column slugs and types for each model\n```\n\nThe dataset `slug` appears in DDL table names (e.g. `datasets_default` for the dataset with slug `default`)."},{"path":"references/examples/ingest-webhook.md","content":"# Ingest models — get the webhook URL and POST records\n\nSome models are fed by **pushing** records to Cargo instead of Cargo pulling them.\nTheir extractor has `mode.kind === \"ingest\"` — the canonical one is the `http`\nintegration's `listenHook` (\"Listen webhook\"), but the same mechanism backs\n`storeleads.listenList`, `rb2b.listenProfiles`, `albacross.listenWebsiteVisits`,\nand others.\n\nThe app shows a **Webhook URL** on the model's settings screen. There is **no CLI\ncommand and no API field that returns it** — the app builds the string client-side.\nYou can build the exact same string from data the CLI already exposes.\n\n## The URL\n\n```\n<baseUrl>/v1/models/<model-uuid>/records/ingest?token=<api-token>\n```\n\n- `<baseUrl>` — `cargo-ai whoami` → `.baseUrl` (e.g. `https://api.getcargo.io`).\n  Note the path is `/v1/models/...`, **not** `/v1/storage/models/...`.\n- `<model-uuid>` — the ingest model's UUID.\n- `<api-token>` — any workspace API token. The token may carry **zero\n  permissions**; this route is explicitly allowed for permission-less tokens so\n  the URL can be handed to a third-party system safely. The app auto-creates one\n  named `Quick access` for exactly this.\n\n`token` can also be sent as an `Authorization: Basic <token>` header instead of a\nquery param — preferable when the receiving system supports custom headers, since\na query param lands in logs.\n\n## Derive it\n\n```bash\n# 1. Find the model and confirm it is an ingest model\ncargo-ai storage model get <model-uuid> | jq '{uuid, slug, extractorSlug, kind, connectorUuid}'\n\n# 2. Confirm the extractor's mode is \"ingest\" (and NOT autoIngest — see below)\ncargo-ai connection integration get http | jq -c '.integration.extractors.listenHook.mode'\n# → {\"kind\":\"ingest\"}\n\n# 3. Pick or create a token (the raw value is on the list response)\ncargo-ai workspaceManagement token list | jq -r '.tokens[0].token'\ncargo-ai workspaceManagement token create --name \"Webhook — <model-slug>\" | jq -r '.token.token'\n```\n\nOne-liner that assembles it:\n\n```bash\nMODEL_UUID=<model-uuid>\nBASE=$(cargo-ai whoami | jq -r '.baseUrl')\nTOKEN=$(cargo-ai workspaceManagement token list | jq -r '.tokens[0].token')\necho \"$BASE/v1/models/$MODEL_UUID/records/ingest?token=$TOKEN\"\n```\n\n> Token values are secrets. Print the URL for the user to copy; don't write it\n> into a file, a commit, or a report.\n\n## Skip models where Cargo owns the hook\n\nSome ingest extractors set `autoIngest: true` — Cargo registers the webhook with\nthe provider itself during setup (calendly, smartlead, instantlyV2, heyReach,\nand cargo's own signal extractors). The app **hides** the URL for\nthose, and handing it out is wrong: the provider is already pointed at it.\n\nCheck before showing anything:\n\n```bash\ncargo-ai connection integration get <integration-slug> \\\n  | jq -c '.integration.extractors[\"<extractor-slug>\"].mode'\n# {\"kind\":\"ingest\"}                    → manual: show the URL\n# {\"kind\":\"ingest\",\"autoIngest\":true}  → Cargo owns it: don't show the URL\n# anything else "}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":null,"editorialQuality":{"score":100,"threshold":65,"status":"thin","wordCount":1453,"uniquenessScore":40,"reasons":["uniqueness-below-45"]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-10T16:51:36.500Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-10T16:51:36.500Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-10T21:51:01.808Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}