Custom API
Publishing to any endpoint
The Custom API adapter lets you ship posts to any REST endpoint. VectraSEO creates a post by POSTing JSON to {endpoint_url}{publish_path}, and updates it by PUTting the same payload to {endpoint_url}{publish_path}/{id}. The payload follows ingest contract v2. For destinations with product capability rules, expose a public /.well-known/product-capabilities.json contract and save its URL in project settings so unsupported claims can be remediated or held before publish.
{
"contract_version": 2,
"title": "Post Title",
"seo_title": "Search Result Title",
"body_html": "<div data-vectraseo-body><p>Full HTML content...</p></div>",
"slug": "post-title",
"tags": ["seo", "blogging"],
"meta_description": "Useful search snippet for this post.",
"excerpt": "Reader-facing post summary.",
"author": "Author Name",
"author_entity": {
"name": "Author Name",
"url": "https://yoursite.com/about/author",
"image": "https://yoursite.com/img/author.jpg"
},
"canonical_url": "https://yoursite.com/blog/post-title",
"og_image": "https://cdn.vectraseo.com/posts/.../hero.png",
"json_ld": {
"@context": "https://schema.org",
"@type": "Article",
"headline": "Search Result Title",
"description": "Useful search snippet for this post.",
"datePublished": "2026-03-30T00:00:00Z",
"dateModified": "2026-06-12T00:00:00Z",
"author": { "@type": "Person", "name": "Author Name" },
"publisher": { "@type": "Organization", "name": "Your Business", "url": "https://yoursite.com" },
"image": "https://cdn.vectraseo.com/posts/.../hero.png",
"mainEntityOfPage": { "@type": "WebPage", "@id": "https://yoursite.com/blog/post-title" }
},
"images": [
{
"filename": "image.png",
"data": "<base64-encoded>",
"alt": "Alt text"
}
],
"render_hints": {
"show_table_of_contents": false,
"show_in_this_guide": false
},
"published_at": "2026-03-30T00:00:00Z",
"updated_at": "2026-06-12T00:00:00Z",
"status": "published"
}Contract v2 fields
Every v2 field is additive, so an endpoint written for the earlier payload keeps working.
2.canonical_base_url + / + slug. Empty unless canonical_base_url is configured.{name, url?, image?} from author_name (else author, else the business name), author_url and author_image_url. The flat author string carries the same name.images array.Article node, ready to embed in a <script type="application/ld+json"> tag on the post page.<lastmod> so refreshes reach crawlers.published, on a create and on an update alike.Configuration fields
Success response
Answer a create with JSON carrying the post's id and public url. VectraSEO stores that id and addresses every later update and delete as {publish_path}/{id}, so persist it on your side. VectraSEO treats any 2xx as “the post is live”: on a static or CDN-backed site, return it only after the page has been re-rendered, written and its CDN path invalidated. When canonical_base_url is set and the url you return is on a different host, VectraSEO keeps the canonical URL instead.
{
"id": "post-123",
"url": "https://yoursite.com/blog/post-title"
}Updates, republishes and deletes
If your API has no route for PUT {publish_path}/{id} and answers 404 or 405, VectraSEO treats it as a setup gap on your endpoint, not a content problem. The post keeps its published state, the attempt is shown on the post as a hold, and one-click fixes report skipped with reason destination_no_update_route. Your live page never reflects the fix until the route exists. Make PUT an upsert keyed on the {id} in the path, and have it do the same render and cache-invalidation work as your POST.
PUTandDELETEcarry the same auth header asPOST, andPUTsends the full payload.- A create is attempted up to 3 times, with backoff, on a
5xx,408,429or connection error. Any other4xxfails fast and keeps your response body, so return one only for a genuine validation error. - A failed update (outage, rejection or missing route) never changes a live post's
publishedstate; the attempt is recorded on the post. - Behind a CDN, allow
POST,PUTandDELETEon the publish path. A POST-only behaviour silently blocks republishes and deletes.
The body marker
body_html arrives wrapped in one transparent element, <div data-vectraseo-body>…</div>. Render it verbatim and keep its data-* attribute. It has no styling effect, and it is how a one-click fix recovers the exact editable content from your live page. Without it, VectraSEO falls back to your page's single <main> or <article> element and checks it against the last body it sent; if that is not a confident match, the fix is skipped (extract_low_confidence) rather than guessed. A post without the marker gains it the next time it is republished.
Product capability contract
Use this optional public JSON contract to tell VectraSEO which destination-product capabilities are supported, unsupported, planned, beta, deprecated, or unknown.
{
"schema_version": "1.0",
"product": { "name": "Emcognito", "aliases": ["Emcognito aliases"] },
"updated_at": "2026-06-12T00:00:00Z",
"cache_ttl_seconds": 3600,
"capabilities": [
{
"id": "custom_domains",
"name": "Custom domains",
"status": "unsupported",
"blocked_claims": ["custom domain", "custom domains"],
"allowed_qualifiers": ["does not support", "planned", "roadmap", "subdomain"],
"preferred_wording": "Emcognito aliases currently use the shared emcognito.com domain; custom subdomain support is planned, but custom domains are not available today."
}
]
}Rejection response (tell VectraSEO what to avoid)
When you reject a publish because the post claims something your product can’t back, return 422 with a structured violations array. VectraSEO records each rejected claim on the project and excludes it from future generations. A bare 400 with no body is treated as a transient failure and the same claim may be generated again.
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{
"error": "capability_claim_violation",
"message": "Post asserts claims this product cannot back.",
"violations": [
{
"capability_id": "open_source",
"matched_claim": "open source",
"status": "unsupported",
"field": "body_html",
"preferred_wording": "Sendant's source code is not public; do not describe it as open source."
}
]
}For a post that has never been published, a structured rejection like this also starts a free content repair automatically, a limited number of times, and the publish is retried after it. A post that was published before is never repaired this way.
Can't find what
you're looking for?
Reach out to the team. We answer every email and we read every bug report.