> ## Documentation Index
> Fetch the complete documentation index at: https://docs.causeloop.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Render + Vercel demo

> The live app.causeloop.ai / api.causeloop.ai demo topology as of July 2026: render.yaml, the release_demo branch strategy, and how to ship a demo fix without touching main.

<Note>
  Every fact on this page is labeled **as of July 2026**. This topology is the live YC-demo deployment, distinct from — and, as explained below, currently architecturally behind — the AWS/Azure IaC and local product stack documented elsewhere in [Cloud environments](/deployment/environments). Treat this page as "what's actually running," not "what we're building toward."
</Note>

## Topology

```mermaid theme={null}
flowchart LR
    subgraph DNS["causeloop.ai zone (Vercel nameservers)"]
        A["app.causeloop.ai"]
        B["api.causeloop.ai"]
    end
    A -->|Vercel project frontend<br/>production branch: release_demo| FE[Next.js frontend]
    B -->|CNAME| RB[causeloop-backend.onrender.com]
    RB -->|Render web service<br/>causeloop-api, branch release_demo| API[FastAPI]
    FE -->|"/api/backend/* proxy"| API
    API --> PG[(Render Postgres<br/>causeloop-postgres)]
    API --> RD[(Render Redis<br/>causeloop-redis)]
    WORKER[Render worker<br/>causeloop-pipeline-worker] --> PG
    WORKER --> RD
```

* **`app.causeloop.ai`** is a Vercel project named `frontend`, with its production branch set to `release_demo`. The entire `causeloop.ai` DNS zone runs on Vercel's nameservers.
* **`api.causeloop.ai`** is a CNAME to `causeloop-backend.onrender.com` — a Render web service.

<Warning>
  `backend/render.yaml` (the Render Blueprint committed to this repo) names the web service `causeloop-api`, but the live hostname resolves to `causeloop-backend.onrender.com`. A Blueprint-managed service's default Render hostname follows its `name:` field, so this mismatch is a strong signal that the live `api.causeloop.ai` backend was created manually in the Render dashboard (or renamed after creation) rather than purely from `render.yaml`. Don't assume every setting on the live service — plan, scaling, alerting — matches the Blueprint exactly; verify in the Render dashboard before relying on a Blueprint-only assumption.
</Warning>

## What `render.yaml` actually defines

<Note>
  As of this writing, `render.yaml` is **not present** in this backend repository's current working branch — it was removed in the same commit that introduced the new `product_app.py` allowlisted API, `infra/docker-compose.product.yml`, and the `deploy/aws`/`deploy/azure` IaC (that work has not yet been merged into `main` or `release_demo`). The content below is what's committed on the `main` and `release_demo` branches on GitHub, which is what the live Render deployment actually reads from. If you're reading this from a checkout where `render.yaml` doesn't exist at the repo root, that's expected — go look at `release_demo` for the file this page describes.
</Note>

`render.yaml` is a Render Blueprint defining one database, one Redis instance, and two services:

```yaml theme={null}
databases:
  - name: causeloop-postgres
    plan: starter
    postgresMajorVersion: "16"

services:
  - type: redis
    name: causeloop-redis
    plan: starter
    ipAllowList: []

  - type: web
    name: causeloop-api
    env: python
    plan: starter
    branch: release_demo
    buildCommand: uv sync --frozen
    startCommand: uv run alembic upgrade 20260714_0007 && uv run alembic upgrade 20260722_c002 && uv run uvicorn services.api.main:app --host 0.0.0.0 --port $PORT
    healthCheckPath: /health
    envVars:
      - key: DATABASE_URL
        fromDatabase: { name: causeloop-postgres, property: connectionString }
      - key: MIGRATIONS_DATABASE_URL
        fromDatabase: { name: causeloop-postgres, property: connectionString }
      - key: REDIS_URL
        fromService: { name: causeloop-redis, type: redis, property: connectionString }
      - key: APP_RW_PASSWORD
        sync: false
      - key: SOURCE_CREDS_KEY
        sync: false
      - key: OPENAI_API_KEY
        sync: false

  - type: worker
    name: causeloop-pipeline-worker
    env: python
    plan: starter
    branch: release_demo
    buildCommand: uv sync --frozen
    startCommand: uv run python scripts/run_pipeline_worker.py
    envVars:
      - key: DATABASE_URL
        fromDatabase: { name: causeloop-postgres, property: connectionString }
      - key: REDIS_URL
        fromService: { name: causeloop-redis, type: redis, property: connectionString }
      - key: APP_RW_PASSWORD
        sync: false
      - key: SOURCE_CREDS_KEY
        sync: false
      - key: OPENAI_API_KEY
        sync: false
```

A few things worth reading carefully in the comments this file carries in the repo:

* **`env: python`, not `runtime: python`.** An earlier version of this file used `runtime: python` (not a valid Blueprint key) with a `pip install uv && ...` build command; Render silently ignored both and fell back to its own default `pip install -r requirements.txt` detection, which fails outright since this project has no `requirements.txt` (it's `uv`/`pyproject.toml`-managed). `env: python` is the key that actually makes `buildCommand: uv sync --frozen` run — Render's Python environment already provisions `uv` itself.
* **`alembic upgrade heads` (plural) does not work here.** The `startCommand` explicitly targets two revisions by ID — `20260714_0007` (the unlabeled/default branch head at the time this file was last edited) and `20260722_c002` (the `control` branch) — rather than `alembic upgrade heads`. That's because the `tenant_template` branch is applied per-tenant by the provisioner at provisioning time, with `-x schema=<tenant_schema>` (see [Migrations](/deployment/migrations)), and refuses to run without that argument. Targeting `heads` would fail on a fresh database for exactly that reason.

<Warning>
  That `startCommand` is stale relative to the current `migrations/` directory: the `control` branch has since grown a third revision (`20260727_c003_durable_platform_jobs.py`), which this `startCommand` never applies. If `release_demo` is ever fast-forwarded to include the durable-platform-jobs work without also updating this `startCommand` to `alembic upgrade control@head` (or the new head revision ID), the live API will start against a database missing that migration. See [Migrations](/deployment/migrations) for the full branch layout.
</Warning>

<Warning>
  The `startCommand` boots `services.api.main:app` — the historical, \~258-endpoint research application — not `services.api.product_app:app`, the allowlisted product surface described throughout the rest of this documentation set and used by every other environment's `Dockerfile` `CMD`. This is a direct consequence of `render.yaml` (and the `main`/`release_demo` branches generally) predating the `product_app.py` split: at the commit these branches are currently on, `services/api/main.py` was the only application that existed. Until the newer product-boundary work is merged into `release_demo`, the live demo is running the legacy app, not the minimal allowlisted boundary.
</Warning>

Also note: this Blueprint provisions no object store and no email provider — there is no S3/Blob equivalent or SES/Communication-Services equivalent wired into `causeloop-api` or `causeloop-pipeline-worker` here. Whatever object storage and email delivery the demo currently relies on is whatever the pre-`product_app.py` application generation used; it is not the MinIO→S3/Blob or Mailpit→SES/ACS Email substitution described in [Cloud environments](/deployment/environments), because that split is part of the same unmerged work.

## The `release_demo` branch strategy

Both `github.com/causeloop/backend` and `github.com/causeloop/frontend` keep `main` as their default branch. `release_demo` is a separate, deliberately narrower branch that only ever receives what should currently be live at `app.causeloop.ai` / `api.causeloop.ai`.

```mermaid theme={null}
flowchart LR
    subgraph main["main (default branch)"]
        M1[commit] --> M2[commit] --> M3[commit] --> M4[commit]
    end
    subgraph demo["release_demo (deploy branch)"]
        D1[commit] --> D2["cherry-pick / merge"]
    end
    M2 -.merge or cherry-pick.-> D2
    D2 -->|git push| Render[Render Blueprint sync]
    D2 -->|git push| Vercel[Vercel production deploy]
```

The reason, in the Blueprint's own comment: deploying `release_demo` instead of `main` means day-to-day development on `main` can't break the live demo. To ship a demo fix:

<Steps>
  <Step title="Land the fix on main as usual">
    Open a normal PR against `main`, review, and merge it there first — `main` is still the branch of record for ongoing work.
  </Step>

  <Step title="Merge or cherry-pick the fix into release_demo">
    Either merge `main` into `release_demo` (if you want everything currently on `main`) or `git cherry-pick` just the specific commit(s) that fix the demo-visible issue, if you don't want to pull in unrelated in-flight work.
  </Step>

  <Step title="Push release_demo">
    A push to `release_demo` on `github.com/causeloop/backend` triggers Render's Blueprint sync for `causeloop-api` and `causeloop-pipeline-worker`; a push to `release_demo` on `github.com/causeloop/frontend` triggers a Vercel production deployment for the `frontend` project.
  </Step>

  <Step title="Verify before announcing the fix is live">
    Check the Render service logs for the new build/deploy and confirm `GET /health` (the Blueprint's `healthCheckPath`) returns healthy, then click through the actual demo flow the fix addresses.
  </Step>
</Steps>

This keeps the demo's git history intentionally curated: `release_demo` should only ever contain commits someone deliberately decided belonged in the demo, never an accidental side effect of merging `main`.

## Vercel deploy gotcha: commit author identity

Vercel's GitHub integration only triggers a deploy for commits authored by the GitHub identity the integration is connected to. If a commit on `release_demo` (or on whatever branch feeds it) is authored by a different GitHub identity, Vercel silently skips the deploy — no error, just no new deployment.

The frontend repo works around this with a repo-local git config author:

```text theme={null}
causeloop <290576966+causeloop@users.noreply.github.com>
```

Because this is repo-local (not global), it only applies to commits made inside that specific checkout of the frontend repo — anyone pushing to `release_demo` on the frontend repo from a different clone (or with a different global git identity) should confirm this repo-local author config is in effect, or a merge/cherry-pick that looks successful in git can still fail to produce a new Vercel deployment.

## Related

* [Cloud environments](/deployment/environments)
* [Migrations](/deployment/migrations)
* [AWS deployment](/deployment/aws)
