Get a project into Cloud

A Cloud project is a Semantic Rails package: models, relationships, metrics and policies that describe your data. Follow the package authoring guide to build one.

If you use dbt, your package's models read the tables your dbt project builds. Cloud does not import a dbt project.

Use the web editor

Open Projects → Create project, create your project, edit its files, then commit. A commit creates an immutable package version; promotion selects which version an environment serves.

Upload through the REST API

First create a project with POST /v1/tenants/<workspace id>/packages. Then post a version to /v1/tenants/<workspace id>/packages/<project id>/versions. The files field is a JSON map of relative file paths to base64 content.

Authenticate with a Cloud API key that has config:write, sent as Authorization: ApiKey <your key>. Cloud reads Bearer as an OAuth sign-in token, so an API key sent as Bearer is refused.

Upload at most 500 files, 2 MiB per decoded file, and 10 MiB of decoded content in total. Send the complete package, including unchanged files. Upload only package files; keep warehouse credentials out of it.

This example uploads root YAML files named package, defaults, graph, relations, metrics, segments, policies or caveats, YAML under models, relations, metrics, segments, examples and tests, and CSV/SQL under seeds and data, skipping hidden paths and refusing symlinks.

The parent-revision guard is always enforced. Set parent_sha to the current version's 64-character SHA; use null for a project's first version. A stale parent returns a conflict. Read the current version, review the changes and reconcile them before trying again.

create project and upload a version
export WORKSPACE_ID="your-workspace-id"
# Set CLOUD_API_KEY privately to a Cloud API key with config:write.
curl --fail-with-body --request POST \
  "https://cloud.semantic-rails.com/v1/tenants/$WORKSPACE_ID/packages" \
  --header "Authorization: ApiKey $CLOUD_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"name":"my-project","description":"Sales metrics"}'

# Copy the id from that response; this example uploads the first version.
export PROJECT_ID="id-from-create-response"
export PROJECT_DIR="./my-project"
export PARENT_SHA=""
python3 - <<'PYCODE' > version.json
import base64, json, os
from pathlib import Path
root = Path(os.environ["PROJECT_DIR"])
root_files = {f"{name}.{ext}" for name in
              ("package", "defaults", "graph", "relations", "metrics", "segments", "policies", "caveats")
              for ext in ("yml", "yaml")}
sections = dict.fromkeys(("models", "relations", "metrics", "segments", "examples", "tests"), (".yml", ".yaml"))
sections.update(dict.fromkeys(("seeds", "data"), (".csv", ".sql")))
pending, paths = [root], []
while pending:
    directory = pending.pop()
    if directory.is_symlink():
        raise SystemExit(f"Refusing symlink: {directory}")
    for p in sorted(directory.iterdir()):
        relative = p.relative_to(root)
        if any(part.startswith(".") for part in relative.parts):
            continue
        if directory == root and p.name not in root_files and p.name not in sections:
            continue
        if p.is_symlink():
            raise SystemExit(f"Refusing symlink: {p}")
        if p.is_dir():
            if relative.parts[0] in sections:
                pending.append(p)
        elif p.is_file() and (relative.as_posix() in root_files or
                             p.suffix.lower() in sections.get(relative.parts[0], ())):
            paths.append(p)
files = {p.relative_to(root).as_posix(): base64.b64encode(p.read_bytes()).decode()
         for p in sorted(paths)}
print(json.dumps({"files": files, "parent_sha": os.environ["PARENT_SHA"] or None}))
PYCODE
curl --fail-with-body --request POST \
  "https://cloud.semantic-rails.com/v1/tenants/$WORKSPACE_ID/packages/$PROJECT_ID/versions" \
  --header "Authorization: ApiKey $CLOUD_API_KEY" \
  --header "Content-Type: application/json" \
  --data-binary @version.json

For an existing project, get its current SHA from the versions endpoint or the web app and set PARENT_SHA before generating the upload.

Validate and promote

Cloud environmentApp labelPackage environments: namePurpose
devDraftdevelopmentDevelop a version
stagingReviewstagingReview before production
prodLiveproductionServe production queries

Declare the canonical package name under environments: for each environment you use. Unknown or undeclared names are refused with runtime.package_environment_mismatch.

Validation produces a receipt for the exact package version, warehouse binding, environment and engine. Promotion requires a successful matching receipt. If those inputs change, validate again.

Only owners and admins promote. Move a validated version to staging before prod; production requires that version's staging history. Bind the destination to the intended warehouse when you promote.

The expected_current_sha guard refuses a concurrent environment change. Refresh the promotion screen and review the current version before retrying.

Rollback means promoting an earlier version. The same staging history and current validation requirements apply; an older receipt may need to be replaced.

To see which version an environment serves, call GET /v1/tenants/<workspace id>/packages/<project id>/env-pointers/<env> with config:read. It returns current_version_sha and when it was set.

Continue with warehouse connections or assistant connections.