Site API reference (seoasist-site-api/1)
The complete SEOAsist Site API spec: authentication and HMAC signing, the four endpoints, rule kinds and a summary of the ready-made Next.js/PHP samples.
Open this screen in the panelThis page describes the SEOAsist Site API contract (spec seoasist-site-api/1) that your site implements. The Developer guide → Spec tab in the panel and the Setup prompt carry the same contract. For the connection steps, see Connect a custom-built site with the SEOAsist Site API.
General rules
- All endpoints live under one base path, e.g.
https://example.com/api/seoasist. - Always reply with JSON. A non-JSON response counts as an error.
- Never redirect (no 30x). SEOAsist does not follow redirects, so save the final API URL in the panel.
- On failure, reply 4xx/5xx with
{"error":"<short message>"}. That message is shown in the panel. - Validate input, limit the body size (~2 MB) and log calls.
- SEOAsist times out each request after 25 seconds. Requests use the user agent
SEOAsist-SiteAPI/1.0 (+https://seoasist.com). - The URL must be publicly reachable; SEOAsist never calls localhost or private network addresses.
Authentication
Every request carries three headers:
| Header | Value |
|---|---|
Authorization | Bearer <SEOASIST_SITE_TOKEN> |
X-SEOAsist-Timestamp | Unix timestamp (seconds) |
X-SEOAsist-Signature | sha256=<hex>, where hex = HMAC-SHA256(key = token, message = <timestamp>.<raw body>) |
- Keep the token in the
SEOASIST_SITE_TOKENenvironment variable; never hard-code it. It's generated in the panel when you press Connect and starts withsa_. - Compare the token and the signature in constant time.
- For GET requests the raw body is an empty string, so the signed message is
<timestamp>.. - Reply
401 {"error":"unauthorized"}if the signature is wrong or the timestamp is more than 300 seconds old.
const expected = "sha256=" + createHmac("sha256", token).update(`${ts}.${body}`).digest("hex");
GET /manifest
The connection test calls this endpoint. List only the capabilities you implemented.
{"spec":"seoasist-site-api/1","platform":"nextjs","name":"My site","capabilities":["rules","content","files"]}
spec must start with seoasist-site-api/. The recognised capabilities are rules, content and files; anything else is ignored. The panel shows them as SEO rules, Content publishing and llms.txt / robots.
PUT /rules (capability: rules)
Body: {"rules": Rule[]}. This is the complete current rule set; replace everything stored before. An empty array clears all rules. Persist the rules (a DB table or a JSON file) and apply them server-side while rendering, so crawlers see the result in the HTML rather than in client-side JavaScript.
type Rule = { id: string; kind: string; matchType: "exact" | "prefix" | "regex" | "all"; matchPath: string; payload: object };
Matching uses the request pathname without a trailing slash (/ for the home page):
exact: the path is equalprefix: the path starts withmatchPathregex: JavaScript RegExp testall: every page
When several rules of the same kind match, the most specific wins: exact > prefix > regex > all.
| kind | payload | Apply as |
|---|---|---|
title | {"value"} | <title> |
meta_description | {"value"} | <meta name="description"> |
canonical | {"href"} | <link rel="canonical"> (absolute URL) |
robots | {"content"} | <meta name="robots"> (e.g. index,follow, noindex,follow) |
h1 | {"value","selector"?} | Replace the page's main H1 text |
og | {"title"?,"description"?,"image"?,"type"?} | og:* meta tags |
json_ld | {"json"} | <script type="application/ld+json"> (json is an object or array) |
hreflang | {"items":[{"lang","href"}]} | <link rel="alternate" hreflang> |
redirect | {"to","status"?} | HTTP redirect before rendering (default 301) |
img_alt | {"items":[{"match","alt"}]} | Set alt on <img> elements whose src contains match |
lang | {"value"} | <html lang> |
Store unknown kinds and ignore them. Response: {"ok":true,"count":<number stored>}.
SEOAsist sends all enabled rules of the site: those added in the Pixel tab, applied meta suggestions, rules created by workspace tasks and those generated by Auto-fix in Technical Audit.
POST /content (capability: content)
Body:
{"externalId":null,"type":"post","title":"…","slug":"…","html":"<h2>…</h2>","excerpt":"…","metaTitle":"…","metaDescription":"…","status":"draft","featuredImageUrl":"https://…","tags":["…"],"updateUrl":null}
externalIdgiven: update that item.updateUrlgiven (type: "page"): update the existing page at that URL (its main content, title and meta).- Otherwise: create a new blog post using the site's existing content system (DB/CMS/MDX) and its normal URL pattern.
status: "draft"must not be publicly visible.htmlis trusted, already-formatted article HTML (headings, lists, tables, FAQ). Keep the site's design and layout.tagsholds at most 5 tags.
Response: {"id":"<stable id>","url":"<absolute public URL>","status":"draft"|"publish"}. SEOAsist stores the id and sends it back as externalId when it sends the same item again.
PUT /files (capability: files)
Body: {"path":"llms.txt"|"robots-ai.txt","content":string}
llms.txt: serve it at/llms.txtastext/plain(replacing the previous one).robots-ai.txt: a block of AI-crawler rules. Serve/robots.txtwith the site's own rules plus this block appended (replacing the previous block).
Response: {"ok":true,"url":"<public url>"}.
Ready-made samples
The Developer guide dialog has copy-paste code:
| Tab | Contents |
|---|---|
| Next.js | lib/seoasist.ts (JSON-file storage and seoRules(pathname), which picks the most specific rule per kind), app/api/seoasist/[...path]/route.ts (the four endpoints with HMAC verification), and a usage example that applies rules in generateMetadata and serves /llms.txt. |
| PHP | seoasist-api.php: an .htaccess rewrite for /api/seoasist/*, a data/seoasist.json store and signature checks with hash_hmac. |
| Test | A signed curl command for /manifest. |
The /content endpoint in the samples is a stub (TODO); you need to save the post into your own blog/CMS and return its real URL.
Test command:
TOKEN="<SEOASIST_SITE_TOKEN>"
TS=$(date +%s)
SIG=$(printf "%s." "$TS" | openssl dgst -sha256 -hmac "$TOKEN" | sed 's/^.* //')
curl -s "https://example.com/api/seoasist/manifest" \
-H "Authorization: Bearer $TOKEN" \
-H "X-SEOAsist-Timestamp: $TS" \
-H "X-SEOAsist-Signature: sha256=$SIG"
Apache may not pass the Authorization header to PHP. If so, add SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1 to .htaccess.
Error messages shown in the panel
| Message | Meaning |
|---|---|
token rejected | The site replied 401/403 without an error field in the body. |
endpoint not found (is the Site API installed at this URL?) | 404; wrong URL or the API isn't installed. |
redirect to … (use the final API URL) | The site returned a redirect. |
non-JSON response | The response wasn't JSON. |
timeout / unreachable: … | No reply within 25 seconds, or the connection failed. |
unexpected manifest (spec "…") | /manifest didn't return a valid spec. |
If your site returns {"error":"…"} in the body, the panel shows that message as is. For fixes, see Site API connection problems.
Related articles
- Connect a custom-built site with the SEOAsist Site APIFor sites that don't run on WordPress or Shopify: install and connect the Site API so SEO fixes, blog posts and llms.txt are applied to your site automatically, server-side.
- Site API connection test failsFind and fix the cause of “Test failed”: token, signature, wrong URL, redirects, timeouts, or Apache dropping the Authorization header.
- REST API v1: API keys, authentication and endpointsHow to create an API key, send the Authorization header, and call every v1 endpoint, with parameters, the response envelope and error codes.