# Quickstart

> From no account to a rendered video in about five minutes, with a free test key that never spends credits.

You will create a project, put a title on its timeline and render it. With a **test** key the render comes back at once as a sample file and nothing is charged, so you can wire up the whole integration before anything costs money.

### Step 1: Create an app and a test key

Sign in at [developer.vidmoat.com](https://developer.vidmoat.com/developer/apps), register an app, and create a **test** key. Pick the **Build & render** preset: it carries `projects.write`, `media.write` and `render.write` on top of the read scopes.

The key is shown once. Copy it and keep it on your server, never in browser code.

### Step 2: Export the key

```bash
export VIDMOAT_KEY="vmk_test_your_key_here"
```

Check it works and see what it can do:

```bash
curl https://api.vidmoat.com/v1/me \
  -H "Authorization: Bearer $VIDMOAT_KEY"
```

`credential.environment` should say `test`, and `credential.scopes` lists what the key carries.

### Step 3: Run the quickstart

Each version creates a project, adds a three-second title, queues a render and polls it until it finishes. They stop on the first HTTP error and never queue the render twice.

```bash quickstart.sh
# Requires Bash, curl and jq. Keep the key on your server.
set -euo pipefail
export VIDMOAT_KEY="YOUR_TEST_OR_LIVE_KEY"
BASE="https://api.vidmoat.com/v1"

# Test keys still create and edit real projects. Renders are sample fixtures.
ID=$(curl --fail-with-body -sS -X POST "$BASE/projects" \
  -H "Authorization: Bearer $VIDMOAT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Hello, Vidmoat API"}' | jq -er '.project.id')

curl --fail-with-body -sS -X POST "$BASE/projects/$ID/commands" \
  -H "Authorization: Bearer $VIDMOAT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"commands":[{"op":"addTextClip","text":"Hello","start":0,"duration":3}]}'

JOB=$(curl --fail-with-body -sS -X POST "$BASE/renders" \
  -H "Authorization: Bearer $VIDMOAT_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"projectId\":\"$ID\"}" | jq -er '.render.id')

# Stop on errors; do not retry a render POST after an uncertain timeout.
for i in $(seq 1 120); do
  RESULT=$(curl --fail-with-body -sS "$BASE/renders/$JOB" \
    -H "Authorization: Bearer $VIDMOAT_KEY")
  STATUS=$(printf '%s' "$RESULT" | jq -er '.render.status')
  case "$STATUS" in
    COMPLETED) printf '%s' "$RESULT" | jq -er '.render.url'; exit 0 ;;
    FAILED|CANCELLED) printf '%s\n' "$RESULT" >&2; exit 1 ;;
  esac
  sleep 5
done
echo "Still processing after 10 minutes. Resume polling $JOB; do not queue it again." >&2
exit 1
```

```js quickstart.mjs
// Node 18 or later. Save as quickstart.mjs, run: node quickstart.mjs
// Keep the key on your server.
const KEY = process.env.VIDMOAT_KEY;
const BASE = 'https://api.vidmoat.com/v1';

async function call(method, path, body) {
  const res = await fetch(BASE + path, {
    method,
    headers: { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json' },
    body: body ? JSON.stringify(body) : undefined,
  });
  const data = await res.json().catch(() => ({}));
  if (!res.ok) throw new Error(data.error?.message ?? `HTTP ${res.status}`);
  return data;
}

const { project } = await call('POST', '/projects', { name: 'Hello, Vidmoat API' });
await call('POST', `/projects/${project.id}/commands`, {
  commands: [{ op: 'addTextClip', text: 'Hello', start: 0, duration: 3 }],
});
const { render } = await call('POST', '/renders', { projectId: project.id });

// Poll the same job. Do not queue another render after a timeout.
for (let i = 0; i < 120; i++) {
  const { render: r } = await call('GET', `/renders/${render.id}`);
  if (r.status === 'COMPLETED') { console.log(r.url); process.exit(0); }
  if (r.status === 'FAILED' || r.status === 'CANCELLED') throw new Error(r.error ?? r.status);
  await new Promise(done => setTimeout(done, 5000));
}
console.error(`Still processing. Resume polling ${render.id}.`);
```

```python quickstart.py
# Python 3.9 or later: pip install requests. Keep the key on your server.
import os, time, requests

BASE = "https://api.vidmoat.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['VIDMOAT_KEY']}"}

def call(method, path, body=None):
    res = requests.request(method, BASE + path, headers=HEADERS, json=body, timeout=60)
    res.raise_for_status()
    return res.json()

project = call("POST", "/projects", {"name": "Hello, Vidmoat API"})["project"]
call("POST", f"/projects/{project['id']}/commands", {
    "commands": [{"op": "addTextClip", "text": "Hello", "start": 0, "duration": 3}],
})
render = call("POST", "/renders", {"projectId": project["id"]})["render"]

# Poll the same job. Do not queue another render after a timeout.
for _ in range(120):
    r = call("GET", f"/renders/{render['id']}")["render"]
    if r["status"] == "COMPLETED":
        print(r["url"])
        break
    if r["status"] in ("FAILED", "CANCELLED"):
        raise SystemExit(r.get("error") or r["status"])
    time.sleep(5)
else:
    raise SystemExit(f"Still processing. Resume polling {render['id']}.")
```

### Step 4: Read the result

With a test key the render is `COMPLETED` immediately, with `test: true` and a sample video `url`. The project and its title are real: open the project in the [Vidmoat editor](https://www.vidmoat.com/dashboard) to see them.

### Step 5: Go live

When the plumbing is right, create a **live** key (it needs a plan with live API access and an app approved in review) and swap it in. The same code now queues a real render against your export allowance. Before you ship, add a [webhook](https://developer.vidmoat.com/developer/docs/webhooks) for `render.completed` instead of polling, and read [errors and retries](https://developer.vidmoat.com/developer/docs/errors).

> **Tip: Look before you render.** Positions are numeric offsets from the canvas centre, and plausible-looking numbers overlap more often than you would expect. `GET /v1/projects/{id}/preview?format=image&at=1` returns the exact frame for free. See [Video preview](https://developer.vidmoat.com/developer/docs/preview).

- [Projects and commands](https://developer.vidmoat.com/developer/docs/concepts/projects-and-commands): What else a command can do: clips, captions, effects, keyframes.
- [Apply commands](https://developer.vidmoat.com/developer/docs/api/projects/apply-commands): The reference for the endpoint you just called.

---

Source: https://developer.vidmoat.com/developer/docs/quickstart
Previous: [Vidmoat developer documentation](https://developer.vidmoat.com/developer/docs.md)
Next: [Authentication](https://developer.vidmoat.com/developer/docs/authentication.md)
All documentation: https://developer.vidmoat.com/llms-full.txt
