API v2
Programmatic access to your r2ware account via a versioned, OpenAPI-documented REST API. Use it from scripts, CI, or local tooling to authenticate, manage websites, and validate Decap CMS configuration.
| Resource | URL |
|---|---|
| OpenAPI 3.0 JSON | GET /api/v2/openapi.json |
| Swagger UI | GET /api/v2/docs |
Base URL is your dashboard host, e.g. https://r2ware.dev in production.
Browse the live contract anytime at API v2 documentation.
Prerequisites
- An r2ware account with email + password
curland optionallyjqfor the examples below
Authentication
All protected endpoints expect a bearer token in the Authorization header:
Authorization: Bearer YOUR_TOKEN
Bare tokens (no Bearer prefix) are also accepted.
Get a token — login
curl -sS -X POST "https://r2ware.dev/api/v2/auth/login" \
-H 'Content-Type: application/json' \
-d '{"email":"you@example.com","password":"your-password"}'
Response 201:
{
"token": "…",
"expires_at": "2026-08-18T12:00:00",
"user": {
"id": 1,
"username": "you",
"email": "you@example.com",
"name": null,
"role": "user",
"is_superuser": false
}
}
Login-issued tokens expire after 30 days. Capture the token for later calls:
TOKEN=$(curl -sS -X POST "https://r2ware.dev/api/v2/auth/login" \
-H 'Content-Type: application/json' \
-d '{"email":"you@example.com","password":"your-password"}' \
| jq -r '.token')
Who am I
curl -sS "https://r2ware.dev/api/v2/auth/me" \
-H "Authorization: Bearer $TOKEN"
Response 200: same user object as login (no token wrapper).
Revoke the current token — logout
curl -sS -X POST "https://r2ware.dev/api/v2/auth/logout" \
-H "Authorization: Bearer $TOKEN"
Returns 204 No Content. That token can no longer authenticate. Other tokens
you hold are unaffected.
Create a long-lived token (Settings)
For automation (CI, deploy scripts) that should not depend on your password, create a personal API token in the dashboard:
- Sign in to the dashboard
- Open Settings (account menu)
- Click API Tokens
- Click Create token
- Give it a name (e.g.
ci,laptop) and choose when it expires - Click Create token and copy the secret immediately — it is shown only once
Store the secret in your CI secret store or a local env var. Revoke or delete tokens anytime from the same Settings page.
Login tokens from POST /api/v2/auth/login still expire after 30 days; prefer
Settings-issued tokens for unattended scripts.
Websites
List websites
curl -sS "https://r2ware.dev/api/v2/websites?page=1&per_page=20" \
-H "Authorization: Bearer $TOKEN"
Returns websites you own, newest first.
Response 200:
{
"items": [
{
"id": 12,
"slug": "example",
"default_branch": "main",
"publish_pinned": false,
"is_template": false,
"source_dir": "",
"live_sha": "a1b2c3d…",
"created": "2026-01-15T10:00:00",
"primary_domain_id": 3,
"live_url": "https://example.r2ware.dev"
}
],
"meta": {
"page": 1,
"per_page": 20,
"total": 1
}
}
Query parameters
| Param | Default | Notes |
|---|---|---|
page |
1 |
1-indexed; values below 1 are clamped to 1 |
per_page |
20 |
Clamped to [1, 50] |
include |
— | Repeatable; see expansions below |
Expansions (?include=)
Repeat the parameter to combine expansions, e.g.
?include=metadata&include=domains.
| Value | Included fields | Notes |
|---|---|---|
metadata |
metadata |
Opaque site metadata blob |
git_remote |
git_remote_url |
Origin remote URL from the on-disk repo |
domains |
domains |
Serving domains with verification status |
branches |
branches |
Enabled branch preview URLs |
metadata and git_remote_url are omitted from the list by default because
they cost extra per row. domains and branches are opt-in everywhere.
Create a website
curl -sS -X POST "https://r2ware.dev/api/v2/websites" \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"slug":"my-site"}'
Slugs must be lowercase letters, numbers, hyphens, and underscores only. The call scaffolds the site repository and reloads the web server, the same as creating through the dashboard.
Response 201: the new website object.
Response 409: the slug is already taken (slugs are globally unique).
Get a website
curl -sS "https://r2ware.dev/api/v2/websites/my-site" \
-H "Authorization: Bearer $TOKEN"
Response 200: the website object.
Detail responses include metadata and git_remote_url by default. Add
?include=domains or ?include=branches to include those expansions.
Response 404: the slug does not exist or is not owned by you.
Update a website
curl -sS -X PATCH "https://r2ware.dev/api/v2/websites/my-site" \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"publish_pinned":true}'
Updatable fields:
| Field | Type | Notes |
|---|---|---|
publish_pinned |
bool | Freeze live publish on the current commit |
source_dir |
string | Jekyll source subdir; normalized on save. Changing this rebuilds the default branch |
At least one field is required. Unknown fields return 422.
Response 200: the updated website object.
Response 502: the save succeeded but the rebuild failed. Check the site
dashboard for build details.
Delete a website
curl -sS -X DELETE "https://r2ware.dev/api/v2/websites/my-site" \
-H "Authorization: Bearer $TOKEN"
Deletes the site, moves its files to _trash/, reloads the web server, and
resyncs uptime monitors in the background.
Response 204: no body.
Response 404: the slug does not exist or is not owned by you.
Website fields
| Field | Type | Description |
|---|---|---|
id |
int | Internal site id |
slug |
string | Unique site slug (used in dashboard URLs and preview hosts) |
default_branch |
string | Git branch published live (usually main) |
publish_pinned |
bool | When true, live publish is frozen on the current commit |
is_template |
bool | Offered as a starting point in the template gallery |
source_dir |
string | Jekyll source subdir; "" means repo root |
live_sha |
string|null | Git SHA currently published live |
created |
datetime | When the site was created |
primary_domain_id |
int|null | Primary custom domain id, if any |
live_url |
string | Primary URL the site is served at |
metadata |
object|null | Opaque site metadata; opt-in on list, default on detail |
git_remote_url |
string|null | Origin remote URL; opt-in on list, default on detail |
domains |
array|null | Serving domains; opt-in via ?include=domains |
branches |
array|null | Enabled branch previews; opt-in via ?include=branches |
Domain object
{
"name": "www.example.com",
"url": "https://www.example.com",
"status": "active",
"primary": true
}
When no custom domain is set, the wildcard domain
(https://<slug>.r2ware.dev) is returned as a single synthetic entry with
"status": "active" and "primary": true.
Branch object
{
"name": "staging",
"url": "https://my-site-staging.r2ware.dev",
"public": false
}
Only enabled branches are returned.
Admin config validation
Validate Decap CMS _admin.yml content before committing it.
curl -sS -X POST "https://r2ware.dev/api/v2/admin-config/validate" \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"config":"collections:\n - name: posts\n folder: _posts\n"}'
Response 200 for a valid config:
{
"valid": true,
"message": "Configuration is valid"
}
Response 200 for an invalid config:
{
"valid": false,
"errors": [
{"message": "Collection missing name"}
]
}
A 422 response means the request failed schema validation (for example,
missing the config field), not that the config itself is invalid.
End-to-end example
#!/usr/bin/env bash
set -euo pipefail
EMAIL="${EMAIL:?set EMAIL}"
PASSWORD="${PASSWORD:?set PASSWORD}"
TOKEN=$(curl -sS -X POST "https://r2ware.dev/api/v2/auth/login" \
-H 'Content-Type: application/json' \
-d "{\"email\":\"$EMAIL\",\"password\":\"$PASSWORD\"}" \
| jq -r '.token')
echo "Authenticated. Listing websites…"
curl -sS "https://r2ware.dev/api/v2/websites" \
-H "Authorization: Bearer $TOKEN" \
| jq .
# Optional: revoke when done
curl -sS -X POST "https://r2ware.dev/api/v2/auth/logout" \
-H "Authorization: Bearer $TOKEN" -o /dev/null -w "logout %{http_code}\n"
Or open https://r2ware.dev/api/v2/docs, click Authorize, paste a token, and try the
endpoints interactively.
Errors
API errors return JSON with a stable envelope:
{
"code": 401,
"status": "Unauthorized",
"message": "Invalid credentials"
}
Validation failures (422) may also include an errors object describing
which fields failed.
| Status | When |
|---|---|
200 |
Successful read or validation |
201 |
Login succeeded or website created |
204 |
Logout or delete succeeded |
401 |
Missing, invalid, expired, or revoked token; bad login credentials |
404 |
Unknown website slug or not owned by you |
409 |
Slug already taken |
422 |
Request body or query failed schema validation |
429 |
Rate limit exceeded (create / delete are limited to 10 per minute) |
500 |
Unexpected server error |
502 |
Website update saved but rebuild failed |
Wrong login credentials always return 401 with "Invalid credentials" —
the API does not reveal whether the email exists.
Security practices
- Never commit tokens — store them in env vars or a secret manager
- Prefer login tokens for interactive scripts; they expire in 30 days
- Logout when a script finishes if the token was only needed for that run
- Rotate long-lived tokens periodically; revoke unused ones in Settings → API Tokens
- HTTPS only in production (
https://r2ware.dev)
What's next
v2 covers authentication, website CRUD, and Decap CMS config validation. Check
/api/v2/docs for the live surface as it grows.
Related:
- Getting Started — create and configure websites
- Git Workflow — edit sites via git
- Form Submissions — collect form data from static sites