# Quickstart

Ten minutes, no paid model call. The CLI provisions a project and creates a safe nonproduction deployment revision; curl renders the prompt and sends a synthetic log. Every object can also be created in the web app at `https://app.prompton.ai`.

You need `curl` and `jq` for the examples. Readable project, prompt, model and deployment references come from slugs, keys, provider/model strings, version numbers and revisions. Use a new UUIDv7 log ID for a new synthetic call, and keep the same ID only when retrying that call.

## 1. Install the CLI and sign in

```sh
curl -fsSL https://raw.githubusercontent.com/polimo-dev/prompton-cli/v0.8.0/install.sh | PTN_VERSION=v0.8.0 sh
prompton login --host https://app.prompton.ai
```

The CLI prints a browser link and a code:

```text
Open this URL to approve the login:
  https://app.prompton.ai/device?code=H4KP-T7WR

Your code: H4KP-T7WR

Waiting for approval...
```

In the browser, sign in with the email code PromptOn sends you, then approve the CLI device. A new email address becomes an account on its first sign-in.

The `--host` flag keeps the CLI pointed at the same PromptOn app as these docs. The host is remembered in `~/.config/prompton/config.json`. With one organization the CLI selects it; with several, run:

```sh
prompton use --org personal
```

## 2. Create a project

```sh
prompton projects create helpdesk \
  --description 'Customer support app' \
  --idempotent --json > project.json

PTN_SETUP_ENV=$(jq -r '[.environments[] | select(.slug != "production")][-1].slug // empty' project.json)
test -n "$PTN_SETUP_ENV" || { echo "No writable nonproduction environment found"; exit 1; }

prompton use --project helpdesk
```

New projects start with `production` and `staging`. Production is protected. New deployment revisions start in the lowest active nonproduction environment, which is `staging` for a default project. If `--idempotent` reused an existing project with more environments, `PTN_SETUP_ENV` follows that project's returned pipeline order. Promotion to production is a separate web or management API action.

## 3. Create a prompt

One prompt represents one LLM call site in your app. The key is the stable integration name.

```sh
cat > variables.json <<'EOF'
[
  {
    "name": "question",
    "type": "string",
    "required": true,
    "description": "The customer's message"
  },
  {
    "name": "plan",
    "type": "string",
    "required": false,
    "description": "free or pro"
  }
]
EOF

prompton prompt create support_reply \
  --name 'Support reply' \
  --description 'Answers a customer support question' \
  --variables-file variables.json \
  --default-params '{"temperature":0.5}' \
  --idempotent --json
```

The app can also discover variables for you when a prompt version or editor draft uses new Liquid variables. The variables file is still useful because it records descriptions, examples, and required fields.

## 4. Commit prompt content

Create the managed Chat messages. Conversation history stays in your app; PromptOn stores the reusable managed messages, usually system instructions and prompt-specific context.

```sh
cat > messages.json <<'EOF'
[
  {
    "role": "system",
    "content": "You are a friendly support agent for Acme. Answer in two or three sentences; if you are not sure, say so and offer to escalate."
  },
  {
    "role": "user",
    "content": "{{ question }}"
  }
]
EOF

prompton prompt commit support_reply \
  --file messages.json \
  --message "migrated from the app's hardcoded prompt" \
  --json
```

Versions are immutable. Committing a version changes nothing at runtime until a deployment pins it.

## 5. Register the model

Register the provider/model pair you want to use. Deployments use the same readable pair; UUIDs are not accepted as resource selectors.

```sh
prompton models register openai/gpt-4o-mini --idempotent --json > model.json
```

For OpenRouter models, the server fills display name, pricing, context length, and capabilities from the public catalog when it can. This is not a model inference call.

## 6. Deploy to the setup environment

```sh
prompton deploy support_reply \
  --environment "$PTN_SETUP_ENV" \
  --provider openrouter \
  --model openai/gpt-4o-mini \
  --params '{"temperature":0.3}' \
  --version latest \
  --json > deployment.json

DEPLOYMENT_REVISION=$(jq -r '.revision' deployment.json)
PROMPT_VERSION_NUMBER=$(jq -r '.prompt_version_number' deployment.json)
```

The response is a deployment event:

```json
{
  "revision": "v2026.09.03-1",
  "sequence": 1,
  "environment": "staging",
  "model": "openai/gpt-4o-mini",
  "provider": "openrouter",
  "prompt_version_number": 1,
  "params": {"temperature": 0.3},
  "provider_options": {},
  "created_at": "2026-09-03T04:13:40.204518Z"
}
```

A revision is a pin: one prompt version number, one provider/model pair, params, and provider options. It is live for the setup environment as soon as the command succeeds. The example response assumes the default `staging` setup environment; if `PTN_SETUP_ENV` resolves to another environment, your response shows that slug.

## 7. Issue an SDK key

```sh
PTN_KEY=$(prompton api-keys issue --name 'Helpdesk server' --quiet)
```

The secret is printed once. It belongs to the project, carries `read` and `logs` by default, and can read any environment in that project.

## 8. Render the deployed prompt

This fills the template and returns the provider request shape. It does not call OpenAI, OpenRouter, or any other model provider.

```sh
curl -sS "https://app.prompton.ai/api/v1/prompts/support_reply/render" \
  -H "Authorization: Bearer $PTN_KEY" \
  -H "content-type: application/json" \
  -d "$(cat <<EOF
{
  "environment": "$PTN_SETUP_ENV",
  "variables": {
    "question": "My invoice shows two charges this month."
  }
}
EOF
)"
```

```json
{
  "key": "support_reply",
  "kind": "chat",
  "deployment": {"revision": "v2026.09.03-1"},
  "template": "default",
  "template_names": ["default"],
  "model": "openai/gpt-4o-mini",
  "provider": "openrouter",
  "api": "chat_completions",
  "request_path": "/api/v1/chat/completions",
  "params": {"temperature": 0.3},
  "provider_options": {},
  "source": "remote",
  "prompt_version": {"number": 1},
  "messages": [
    {"role": "system", "content": "You are a friendly support agent for Acme. Answer in two or three sentences; if you are not sure, say so and offer to escalate."},
    {"role": "user", "content": "My invoice shows two charges this month."}
  ],
  "request": {
    "method": "POST",
    "path": "/api/v1/chat/completions",
    "body": {
      "model": "openai/gpt-4o-mini",
      "messages": [
        {"role": "system", "content": "You are a friendly support agent for Acme. Answer in two or three sentences; if you are not sure, say so and offer to escalate."},
        {"role": "user", "content": "My invoice shows two charges this month."}
      ],
      "temperature": 0.3
    }
  },
  "warnings": [],
  "etag": "sha256-9f2e5c1a7b3d8e4f6a0c2b9d1e7f3a5c8b4d6e2f0a1c3b5d7e9f2a4c6b8d0e1f"
}
```

In production code, use the SDK path described in [SDK config fetch](/sdk-config-fetch): fetch the prompt config on demand, call the provider from your own server with your own provider key, then report a log. `POST /prompts/:key/render` is for smoke tests and debugging.

## 9. Send one synthetic log

Logs are what let PromptOn show production behavior. This example records a fake successful provider call against the setup-environment deployment you just created.

```sh
curl -sS "https://app.prompton.ai/api/v1/logs?environment=$PTN_SETUP_ENV" \
  -H "Authorization: Bearer $PTN_KEY" \
  -H "content-type: application/json" \
  -d "$(cat <<EOF
{
  "logs": [
    {
      "id": "019916f9-c0a1-7d9a-8f2e-3b5d7c1a9e02",
      "prompt_key": "support_reply",
      "deployment_revision": "$DEPLOYMENT_REVISION",
      "template": "default",
      "prompt_version_number": $PROMPT_VERSION_NUMBER,
      "source": "manual",
      "metadata": {"synthetic": true, "purpose": "setup_sample", "provider_called": false},
      "kind": "chat",
      "model": "openai/gpt-4o-mini",
      "provider": "openrouter",
      "params": {"temperature": 0.3},
      "input": {
        "variables": {"question": "My invoice shows two charges this month."},
        "messages": [
          {"role": "system", "content": "You are a friendly support agent for Acme. Answer in two or three sentences; if you are not sure, say so and offer to escalate."},
          {"role": "user", "content": "My invoice shows two charges this month."}
        ],
        "truncated": false
      },
      "output": {
        "content": "It looks like you were billed twice this month. I can check the invoice and refund the duplicate charge if you confirm the duplicate date.",
        "tool_calls": [],
        "truncated": false
      },
      "status": "ok",
      "finish_reason": "stop",
      "usage": {"input_tokens": 60, "output_tokens": 34, "cost_usd": 0.00003, "cost_source": "unknown"},
      "latency_ms": 940,
      "started_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
    }
  ]
}
EOF
)"
```

```json
{"accepted":1,"duplicates":0,"rejected":[],"events":{"accepted":0,"duplicates":0,"rejected":[]}}
```

The log id is the idempotency key. Send the same body again and it is counted as a duplicate instead of being stored twice.

## 10. Open the app

- `https://app.prompton.ai/personal/helpdesk` opens the project.
- `https://app.prompton.ai/personal/helpdesk/prompts/support_reply` opens the prompt hub.
- The Editor shows immutable content versions and the current draft.
- Arena can simulate Chat or Decision prompts. If tools are involved, Arena uses fixed or generated synthetic tool responses; it does not execute your application tools.
- Deployments shows the revision in the setup environment and offers promotion through the environment pipeline.
- Logs shows the synthetic log you just sent.

## What next

- Use [Working with prompts](/workflows) for the human web-app path.
- Use [CLI](/cli) for idempotent provisioning scripts.
- Use [Agent guide](/agent) when handing the integration to a coding agent.
- Use [SDK config fetch](/sdk-config-fetch) for the production request path and cache behavior.
