---
title: "Demo Credential"
description: "One unauthenticated HTTP call returns a working, scoped, read-only PgBeam credential against our own dataset, so an agent can run the policy engine before it has an account."
canonical: "https://pgbeam.com/docs/demo-credential"
last-updated: "2026-10-22T11:48:14.000Z"
---

# Demo Credential

> One unauthenticated HTTP call returns a working, scoped, read-only PgBeam credential against our own dataset, so an agent can run the policy engine before it has an account.

URL: https://pgbeam.com/docs/demo-credential

Every other way to try PgBeam needs a person: a signup form, an email, a click. This one does not. `POST /v1/demo/credentials` takes no account, no key, and no body, and returns a working Postgres connection string and a hosted MCP token against a dataset PgBeam owns. The credential is read-only, table-scoped, PII-masked, budgeted, and expires in fifteen minutes.

It is the same enforcement path a paying customer's agent runs through. Nothing about the demo is a simulation: the block you see is the wire protocol refusing a statement, and the masked column is the proxy rewriting a result row.

## Get a credential

```bash
curl -sX POST https://api.pgbeam.com/v1/demo/credentials
```

```json
{
  "session_id": "dmo_01m1p96y51eq1bfqf2ftvhbxxf",
  "tier": "free",
  "connection_string": "postgresql://agent_5dfb1981c9a3:****@01m1p961y6e37svwjse91n1gdb.proxy.pgbeam.app:5432/demo",
  "mcp_url": "https://01m1p961y6e37svwjse91n1gdb.proxy.pgbeam.app/mcp",
  "mcp_token": "pba_eyJhbGciOiJIUzI1NiIs...",
  "expires_at": "2026-09-04T13:34:30Z",
  "ttl_seconds": 900,
  "query_budget": 100,
  "dataset": { "schema": "demo", "tables": [] },
  "walkthrough": [],
  "policy_template": {},
  "upgrade": {}
}
```

The connection string and the MCP token are shown once. If you lose them, ask for another credential.

Before you spend a request, `GET /v1/demo` describes the surface: whether it is up, how much of the ceiling is left, what each tier costs, the dataset schema with the treatment applied to every column, and the walkthrough below. It needs no account either.

## The walkthrough

The response carries a `walkthrough` array. Each entry has the SQL to run and the outcome it must produce, so an agent can drive the demo and check its own results without reading this page. Four statements, in order:

**A permitted read returns rows.**

```sql
SELECT id, country, plan FROM demo.customers ORDER BY id LIMIT 5;
```

`demo.customers` is on the credential's table allowlist and none of these columns is masked, so you get rows.

**PII comes back masked.**

```sql
SELECT id, full_name, email, phone FROM demo.customers ORDER BY id LIMIT 3;
```

```
 id |  full_name  |   email    |                              phone
----+-------------+------------+------------------------------------------------------------------
  1 | [redacted]  | [redacted] | f3d9d8b0dd22ed68408427aa27830d05b48b4b4ff3416df29fa288db285acbd4
```

`full_name` and `email` are redacted, `phone` is replaced with a SHA-256 digest. Masking is keyed on the source column, not the result column name, so `SELECT email AS x` and `SELECT upper(email)` are masked too.

**A table off the allowlist is blocked.**

```sql
SELECT employee_name, salary_cents FROM demo.employee_salaries LIMIT 1;
```

```
ERROR:  blocked by PgBeam agent policy: table "demo.employee_salaries" is not in
this credential's allowlist; allowed tables: demo.customers, demo.orders
SQLSTATE 42501
```

The table is really there, with real rows in it. You can see it in `information_schema`. The credential still cannot read a row of it, because the allowlist is enforced in the wire protocol before the statement reaches Postgres.

**A write is refused.**

```sql
INSERT INTO demo.orders (id, customer_id, amount_cents, status, placed_at)
VALUES (999999, 1, 100, 'paid', now());
```

```
ERROR:  blocked by PgBeam agent policy: this credential is read-only; insert is
not permitted; the access mode is a ceiling and the statement allowlist cannot
lift it; use a read-write credential
SQLSTATE 42501
```

Error text is written for a language model to read: what was refused, why, and what would have to change.

## The dataset

Three tables in the `demo` schema, holding a fictional company's records. Nothing in it is real and nothing in it is a customer's.

| Table                    | Reachable | What it demonstrates                                                     |
| ------------------------ | --------- | ------------------------------------------------------------------------ |
| `demo.customers`         | yes       | Masking: `full_name` and `email` redacted, `phone` hashed                |
| `demo.orders`            | yes       | A normal read, and the target of the refused write                       |
| `demo.employee_salaries` | no        | The allowlist: the table exists and every statement naming it is blocked |

Relations are schema-qualified on purpose. A demo credential cannot write `search_path` (PgBeam blocks that for agent credentials as an allowlist-evasion guard), so an unqualified name will not resolve.

The dataset is shared by every concurrent demo and every tier is read-only, so nothing you do changes what the next visitor sees.

## Limits

| Tier     | TTL    | Query budget | Max rows | Price |
| -------- | ------ | ------------ | -------- | ----- |
| Free     | 15 min | 100          | 200      | free  |
| Extended | 4 h    | 5,000        | 10,000   | $1    |

The extended tier is bought over HTTP 402. Ask for it with `{"tier": "extended"}`; if no payment has settled you get a 402 carrying the challenge, and you replay the request with `payment_proof` set. `GET /v1/demo` reports `purchasable` per tier, so a client can tell a tier that needs payment from one that cannot be bought here at all.

Beyond the per-credential budget there are two limits on the endpoint itself:

* **Per client, three credentials an hour.** Exceeding it returns 429. Releasing a credential does not refund the quota.
* **A global ceiling on concurrent live credentials.** When it is full you get 503 with a `Retry-After` naming the seconds until the oldest session expires. It is not a 429, because backing off individually does not create capacity, and it is not a 402, because paying would not either.

If you finish early, hand the slot back so the next agent is not turned away:

```bash
curl -sX POST https://api.pgbeam.com/v1/demo/credentials/{session_id}/release \
  -H 'Content-Type: application/json' \
  -d '{"mcp_token": "pba_..."}'
```

## Using the MCP endpoint instead

The same credential comes with a hosted MCP URL and a bearer token, so an MCP client can run the walkthrough as tool calls rather than SQL:

```json
{
  "mcpServers": {
    "pgbeam-demo": {
      "url": "https://<subdomain>.proxy.pgbeam.app/mcp",
      "headers": { "Authorization": "Bearer pba_..." }
    }
  }
}
```

MCP tools run over a loopback Postgres session as the same credential, so they hit the same policy. A `query` tool call against `demo.employee_salaries` is blocked with the same reason the wire path gives. See [MCP](/docs/mcp).

## Taking it to your own database

The response's `policy_template` is the exact policy the demo enforced, in the shape the policy endpoint accepts. That is the upgrade path: nothing about the demo credential carries over (it expires, and it only ever pointed at our dataset), but the policy document does.

```bash
# 1. Sign up and create a project pointed at your own database.
# 2. Post the policy the demo handed you, unchanged apart from the table names.
curl -X POST https://api.pgbeam.com/v1/projects/{project_id}/policies \
  -H "Authorization: Bearer pbo_..." \
  -H 'Content-Type: application/json' \
  -d @policy_template.json

# 3. Issue a credential bound to it.
pgbeam agents create --name my-agent --policy pol_...
```

See [Policies](/docs/policies), [Allowlists](/docs/allowlists), [Masking](/docs/masking), and [Budgets](/docs/budgets) for what each field does.

## What the demo is not

* It is not a sandbox you can write to. For write isolation against your own data, see [Sandbox writes](/docs/sandbox-writes), which routes writes to an ephemeral branch.
* It is not a free tier of the product. It is a fixed dataset PgBeam owns, sized to demonstrate enforcement, not to build against.
* It is not a place to put anything of yours. There is nothing to upload, and the credential cannot create a table.