Docsary
Markdown Ask Claude Ask ChatGPT
On this page

Redirects

Redirects belong to the site and are stored as one table. There is a read endpoint and a whole-map replace endpoint; there is no way to add or remove a single entry.

For matching rules and precedence, see Redirects.

All endpoints require an organization API key. :site accepts a slug or an id.

Read the table

GET /v1/sites/:site/redirects
curl -s -H "authorization: Bearer $DOCSARY_KEY" \
  "$DOCSARY_API/v1/sites/acme/redirects"
{
  "redirects": [
    { "source_path": "/beta", "target": "/", "code": 302 },
    { "source_path": "/old-guide/setup", "target": "/guide/install", "code": 301 },
    { "source_path": "/welcome", "target": "/", "code": 301 }
  ]
}

Ordered by source path.

Replace the table

PUT /v1/sites/:site/redirects
curl -s -X PUT "$DOCSARY_API/v1/sites/acme/redirects" \
  -H "authorization: Bearer $DOCSARY_KEY" \
  -H "content-type: application/json" \
  -d '{
    "redirects": [
      { "source": "/welcome", "target": "/" },
      { "source": "/old-guide/setup", "target": "/guide/install" },
      { "source": "/beta", "target": "/", "code": 302 },
      { "source": "/status", "target": "https://status.acme.com" }
    ]
  }'
{ "count": 4 }
Field Type Required Notes
redirects array yes The complete table. An empty array clears it.
redirects[].source string yes Request path. Leading slashes are normalized to exactly one.
redirects[].target string yes Site-relative path or absolute http/https URL.
redirects[].code number no 302 for temporary; anything else, including omission, stores as 301.

This is a replace, not a merge. Every entry not present in the request is deleted. Read the table first and send it back complete.

The write field is source; the read field is source_path. A read-modify-write loop must rename the key between the two calls.

The whole table is written in a single transaction — it applies completely or not at all — and takes effect immediately.

A table longer than your redirect ceiling is refused with 403 and quota: "redirects_per_site". Nothing is written, and the table you already had stays in place.

Target validation

Every target is checked before anything is written. One bad target fails the entire request; nothing is partially applied.

Accepted:

Rejected with 400:

{"error":"unsafe redirect target 'javascript:alert(1)' (allowed: http:, https:, or a /site-relative path)"}

That covers javascript:, data:, vbscript:, file:, protocol-relative //host/path, bare #fragment values, schemeless relative paths, and any value containing control characters. mailto: is accepted in site settings but not here — a redirect target becomes a Location header, which is a stricter position than an href.

The same check runs again when a redirect is served. A stored target that fails it is ignored entirely, and the request falls through to the normal page lookup or a 404, rather than emitting an unsafe Location header.

Other errors

Response Cause
400 {"error":"redirects array required"} redirects missing or not an array
400 {"error":"each redirect needs a string source and target"} An entry is null, or source/target is not a string
404 {"error":"site not found"} The site slug or id does not resolve