For Website Developers

API v2

Self-documenting REST API for auth and managing websites

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
  • curl and optionally jq for 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:

  1. Sign in to the dashboard
  2. Open Settings (account menu)
  3. Click API Tokens
  4. Click Create token
  5. Give it a name (e.g. ci, laptop) and choose when it expires
  6. 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

  1. Never commit tokens — store them in env vars or a secret manager
  2. Prefer login tokens for interactive scripts; they expire in 30 days
  3. Logout when a script finishes if the token was only needed for that run
  4. Rotate long-lived tokens periodically; revoke unused ones in Settings → API Tokens
  5. 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: