ReferenceUpdated: 2026-10-16

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 panel

This 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:

HeaderValue
AuthorizationBearer <SEOASIST_SITE_TOKEN>
X-SEOAsist-TimestampUnix timestamp (seconds)
X-SEOAsist-Signaturesha256=<hex>, where hex = HMAC-SHA256(key = token, message = <timestamp>.<raw body>)
  • Keep the token in the SEOASIST_SITE_TOKEN environment variable; never hard-code it. It's generated in the panel when you press Connect and starts with sa_.
  • 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 equal
  • prefix: the path starts with matchPath
  • regex: JavaScript RegExp test
  • all: every page

When several rules of the same kind match, the most specific wins: exact > prefix > regex > all.

kindpayloadApply 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}
  • externalId given: update that item.
  • updateUrl given (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.
  • html is trusted, already-formatted article HTML (headings, lists, tables, FAQ). Keep the site's design and layout.
  • tags holds 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.txt as text/plain (replacing the previous one).
  • robots-ai.txt: a block of AI-crawler rules. Serve /robots.txt with 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:

TabContents
Next.jslib/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.
PHPseoasist-api.php: an .htaccess rewrite for /api/seoasist/*, a data/seoasist.json store and signature checks with hash_hmac.
TestA 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"
tip

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

MessageMeaning
token rejectedThe 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 responseThe 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.

Was this article helpful?
Didn't find what you need?
Describe the problem and our team will reply by email.
Contact us