Skip to content
A practical, step-by-step guide

How to use the Beulog Publishing API

Publish Beulog articles as real pages on your own website. Site owners can use this guide to plan the connection; a developer can follow the exact requests and server-side examples.

For custom websites and their developersBy the Beulog teamLast checked: 8 October 2026
Native article pages on your own domain
Jump to a section

Is the API right for your website?

The Publishing API connects a Beulog workspace to your own website or content system. Your server retrieves approved articles, and your website gives each article its own address, design, and search metadata. Ask your developer or website provider to handle the server setup; you can prepare the workspace and review the content yourself.

  • Choose the API for a custom website, a framework such as Next.js, or your own CMS.
  • For WordPress, the ready-made connector handles importing for you. Follow the WordPress guide.
  • An embedded blog is the simpler display option when your site only accepts an HTML block. Its Beulog iframe pages use noindex. For articles published as native, crawlable pages on your domain, use the API or WordPress connector.

Prepare your workspace and key

  1. Verify your account and select the right workspace. Complete email verification, then use the workspace selector in your dashboard. A publishing key belongs to the workspace where you create it; it does not read every workspace in your account.
  2. Set the final website information. In Workspace settings, set Website to your HTTPS site address and Article address pattern to the actual path, such as /blog/{slug}. Fill in Publisher or brand name and, when available, Publisher logo URL. Use Author name and Author profile URL for the real credited author; leaving the name blank credits the brand.
  3. Create a publishing key. Open Integrations → Your own website → Publishing keys → Create key. Give this website its own recognizable key name. Keep Read published articles enabled. Leave Also read drafts and Create new articles unchecked for a normal public website.
  4. Save the key securely on your server. Copy it when it is shown; the full key is not shown again. Store it in your hosting provider’s server environment or secret store. Use a name such as BEULOG_PUBLISHING_KEY, and save the expected workspace ID after testing the connection.
  5. Review and publish a test article. Check its title, description, image description, sources, author, language, and final address before changing its status to Published. A draft stays out of the default public article response.

Test the connection

All endpoints below use the HTTPS base address https://beulog.com/api/v1. Authenticate with the Authorization: Bearer header. Real publishing keys start with ab_live_. These examples contain placeholders, not working keys; run them on your server or in your own terminal after replacing the placeholders.

1. Check your publishing connection
# Run on your server or in your own terminal.
# Replace the placeholder with a read-only publishing key.
export BEULOG_PUBLISHING_KEY='REPLACE_WITH_YOUR_READ_ONLY_KEY'

curl --fail-with-body --silent --show-error --max-time 30 \
  'https://beulog.com/api/v1/connection' \
  --header "Authorization: Bearer $BEULOG_PUBLISHING_KEY" \
  --header 'Accept: application/json'
Example connection response
{
  "api_version": "1.2",
  "workspace": {
    "id": "00000000-0000-4000-8000-000000000001",
    "name": "Example publication",
    "language": "en"
  },
  "scopes": ["articles:read"],
  "analytics": null
}
Illustrative values. Your workspace ID, name, language, permissions, and analytics configuration will differ.

Confirm both workspace.id and workspace.name before importing. Compare the ID against the workspace your website expects, and stop if it differs. api_version identifies the API response contract; article schema_version identifies the exported content format.

If analytics is enabled, analytics contains only the public site ID, consent requirement, and allowed origins. Otherwise it is null. Website-origin settings for embeds and analytics are separate from server API authentication; they do not replace a publishing key.

Endpoints and permissions

Publishing key permissions
ScopeWhat it allows
articles:readRead published articles, test the connection, and read the synchronization feed. Use this alone for a public website.
drafts:readAdditional permission to include drafts with drafts=true. It does not replace articles:read. Keep previews private.
articles:generateRead generation pricing, create jobs, and check job status. This permission can spend credits; keep it in a separate server task/key.
API endpoint reference — paths follow /api/v1
Method and pathRequired scopeResponse and purpose
GET /connectionarticles:readWorkspace identity, scopes, api_version, and optional public analytics configuration.
GET /articlesarticles:readA paginated data array of complete published article exports. drafts=true additionally requires drafts:read.
GET /articles/{id}articles:readOne article by UUID, still inside data: [article]. Draft access uses drafts=true plus drafts:read. Missing or unavailable article: 404.
GET /syncarticles:readA lightweight manifest of published article IDs, titles, and change tokens, with a checkpoint and settings revision.
GET /pricingarticles:generateCurrent maximum credit reservations for article, article_with_image, and research jobs.
POST /generatearticles:generateQueue a generation job. JSON body and Idempotency-Key are required. Returns the job ID, status, and credit accounting.
GET /jobs/{id}articles:generateJob status, stage, error, article_id, cost, reserved_credits, and settled_at.

These publishing endpoints do not provide an article update, delete, or upsert operation. Editing and publishing happen in Beulog; inserting or updating a copy in your own database is your integration’s responsibility. The API retrieves articles by UUID, not by slug. Keep a local slug-to-ID mapping for your website routes.

Download the OpenAPI specification · Download the server-side TypeScript client

Read articles and follow every page

The default article list contains published articles from this key’s workspace, newest first by creation time and ID. A successful empty list means no matching published articles; it does not mean the key failed. Do not silently replace an API error with an empty list.

Article list query parameters
ParameterAccepted value and behavior
limitInteger 1–100; default 20.
cursorCopy pagination.next_cursor exactly and URL-encode it. Maximum 1,000 characters. Keep the same workspace and drafts filter. Stop when next_cursor is null.
offsetLegacy integer offset, 0–100,000; default 0. Prefer cursors. Do not combine a cursor with a nonzero offset.
drafts=trueIncludes drafts as well as published articles; requires both articles:read and drafts:read. Omitting it keeps the default published-only behavior.
2. Read published articles and the next page
curl --fail-with-body --silent --show-error --max-time 30 \
  'https://beulog.com/api/v1/articles?limit=20' \
  --header "Authorization: Bearer $BEULOG_PUBLISHING_KEY" \
  --header 'Accept: application/json'

# For the next page, use the exact pagination.next_cursor value.
BEULOG_NEXT_CURSOR='REPLACE_WITH_NEXT_CURSOR'
curl --fail-with-body --silent --show-error --max-time 30 --get \
  'https://beulog.com/api/v1/articles' \
  --data-urlencode 'limit=20' \
  --data-urlencode "cursor=$BEULOG_NEXT_CURSOR" \
  --header "Authorization: Bearer $BEULOG_PUBLISHING_KEY" \
  --header 'Accept: application/json'

The response has data and pagination. Read each item in data, then follow pagination.next_cursor until it is null. has_more indicates whether another page exists; next_offset is only for the legacy offset flow. Do not decode, edit, or manufacture cursors. Restart a list from the first page if its cursor is rejected.

3. Retrieve a single article
BEULOG_ARTICLE_ID='REPLACE_WITH_ARTICLE_UUID'
curl --fail-with-body --silent --show-error --max-time 30 \
  "https://beulog.com/api/v1/articles/$BEULOG_ARTICLE_ID" \
  --header "Authorization: Bearer $BEULOG_PUBLISHING_KEY" \
  --header 'Accept: application/json'

Replace the ID with an article UUID from data. Read data[0] in the successful detail response; the API does not return a bare article object. A draft or an article from another workspace is unavailable to a published-only key.

What an article response contains

Article export fields used by a website
FieldHow to use it
id, slug, title, statusUse id as the stable source identity, slug for your URL mapping, and status to enforce published-only public pages. Store the connected workspace ID with the article.
revisionOpaque export change token. Compare it with the previous export revision to skip unchanged content. It includes exported content and publishing settings; an article version number alone is insufficient.
htmlSemantic article body fragment with its own article element, H1, sections, links, image alt text, references, and language/direction. It is not a complete HTML document.
head_htmlReady-made title, description, robots, canonical when available, social metadata, and JSON-LD for a server-rendered document head. Use this OR your framework’s metadata mapping.
seoStructured title, description, canonical, robots, language, openGraph, and twitter fields for your framework. canonical may be null if a final address has not been configured.
json_ldStructured data graph generated from the article and workspace settings. Keep author, publisher, language, dates, images, citations, and URL consistent with the visible page.
contentEditable structured content, including imageAlt and imageCaption, sections, takeaways, and FAQs. Use this when building a custom article layout instead of the supplied html.
image_url, image_width, image_heightCover URL and dimensions when available. Preserve accurate dimensions and alt text when replacing the image component; do not invent missing values.
publishing_checks, reading_minutes, schema_versionExport checks, estimated reading time, and content format version. Checks help review the export; they do not certify your site or guarantee search ranking.

Import with the TypeScript client

Download the client and keep it in a server-only module. It uses native fetch and AbortSignal.timeout; use a current server runtime with those APIs. The client rejects browser use. Pass the application origin https://beulog.com to its constructor; it adds /api/v1 itself.

Import every published article on your server
// Server file. Save the downloaded client as ./beulog-client.ts.
import { BeulogClient, type PublishedArticle } from "./beulog-client";

// Implement this adapter using your site's database or CMS.
// Upsert means update the same article, or insert it if it is new.
type ArticleStore = {
  upsert(workspaceId: string, article: PublishedArticle): Promise<void>;
};

export async function importPublishedArticles(store: ArticleStore) {
  const key = process.env.BEULOG_PUBLISHING_KEY;
  const expectedWorkspaceId = process.env.BEULOG_WORKSPACE_ID;
  if (!key || !expectedWorkspaceId) {
    throw new Error("Configure the server key and expected workspace ID.");
  }

  const client = new BeulogClient("https://beulog.com", key);
  const connection = await client.connection();
  if (connection.workspace.id !== expectedWorkspaceId) {
    throw new Error("The key belongs to a different workspace.");
  }

  let imported = 0;
  for await (const article of client.articles()) {
    await store.upsert(connection.workspace.id, article);
    imported += 1;
  }
  return { workspaceId: connection.workspace.id, imported };
}
ArticleStore is an adapter you implement for your own database or CMS. It is not a Beulog SDK method. Set BEULOG_WORKSPACE_ID to the ID verified in the connection response.

client.articles() follows cursor pagination and yields published articles. client.article(id) returns data[0]. The downloadable client also exposes connection(), pricing(), generate(), and job(). For drafts, the sync feed, or conditional ETag requests, use the documented HTTP endpoints directly; the client does not expose those options.

Make your local import idempotent: use a unique key such as (workspace_id, article_id), update the existing row when its export revision changes, and refresh the rendered page and sitemap after a successful update. Define how to preserve any edits made directly on your own site.

Keep your website in sync

For regular updates, /sync is a small change-discovery feed. It returns IDs, titles, and manifest revisions, not article HTML. Fetch a complete article only when its manifest revision is new or changed. Run this in a scheduled server task, not once per visitor.

Synchronization query parameters
ParameterAccepted value and behavior
limitInteger 1–100; default 50.
sincePreviously saved checkpoint, as the exact ISO date-time string returned by the API. Omit for the first full scan.
settings_revisionSave with checkpoint and send with since. If publishing settings changed, or the value is absent/mismatched, the API starts a full scan.
cursorExact pagination.next_cursor from this scan, URL-encoded; maximum 2,000 characters. It preserves this scan’s time boundary. A 400 response means restart this scan.
  1. Start without since/settings_revision, or load the pair from the last fully completed scan. Confirm workspace_id matches your expected workspace.
  2. For each returned item, compare its manifest revision with your stored manifest revision for that article. Fetch changed items from GET /articles/{id}, then upsert the complete export in your local store.
  3. Save the local article and its processed manifest revision together, only after a successful import. Keep export revision and manifest revision in separate fields; they are different tokens and cannot be compared with each other.
  4. Continue using pagination.next_cursor until it is null. Keep the prior saved checkpoint if a page or import fails. Retrying already processed articles must be safe.
  5. After every page and import succeeds, save the returned checkpoint and settings_revision as one pair. For a durable queue, advance only after all pages’ work is safely queued and retryable. Do not use your own machine’s current time as the next checkpoint.
Start and resume an incremental scan
# First scan: no since or settings_revision.
curl --fail-with-body --silent --show-error --max-time 30 \
  'https://beulog.com/api/v1/sync?limit=50' \
  --header "Authorization: Bearer $BEULOG_PUBLISHING_KEY" \
  --header 'Accept: application/json'

# Only after every page was processed successfully, save the returned
# checkpoint and settings_revision. Use both for the next scan.
BEULOG_SYNC_CHECKPOINT='REPLACE_WITH_SAVED_CHECKPOINT'
BEULOG_SETTINGS_REVISION='REPLACE_WITH_SAVED_SETTINGS_REVISION'
curl --fail-with-body --silent --show-error --max-time 30 --get \
  'https://beulog.com/api/v1/sync' \
  --data-urlencode 'limit=50' \
  --data-urlencode "since=$BEULOG_SYNC_CHECKPOINT" \
  --data-urlencode "settings_revision=$BEULOG_SETTINGS_REVISION" \
  --header "Authorization: Bearer $BEULOG_PUBLISHING_KEY" \
  --header 'Accept: application/json'

The feed deliberately overlaps the previous checkpoint by five minutes to reduce missed updates around database commits. Duplicate IDs are expected: deduplicate by source ID and manifest revision. A settings change invalidates an in-progress cursor; restart from the last saved checkpoint/settings pair, allow the resulting full scan, and upsert safely.

Render the article and metadata

Give each article a permanent, public URL on your website. Render the article in the server response or at build time so the text, links, and metadata are available without a browser making an authenticated API call. Google recommends server rendering or pre-rendering because it helps users and crawlers, and some bots do not execute JavaScript. Google’s JavaScript SEO guidance.

Choose one metadata approach

  • Traditional server template: insert head_html into the document’s head and html into the body. Keep the template’s charset and viewport tags. Do not escape the supplied fragments into visible text, or put head_html inside the article body.
  • Framework metadata: map seo into your framework’s metadata API and render json_ld once. Render html in the page body. Do not also insert head_html, because it already contains title, meta tags, canonical, and JSON-LD.
Example: Next.js metadata mapping
// Server-side Next.js App Router metadata helper.
// Save the downloaded client at the import path used below.
import type { Metadata } from "next";
import type { PublishedArticle } from "./beulog-client";

export function articleMetadata(article: PublishedArticle): Metadata {
  const imageAlt = typeof article.content.imageAlt === "string"
    ? article.content.imageAlt
    : article.title;
  const locale = article.seo.openGraph.locale;
  return {
    title: { absolute: article.seo.title },
    description: article.seo.description,
    robots: article.seo.robots,
    alternates: article.seo.canonical
      ? { canonical: article.seo.canonical }
      : undefined,
    openGraph: {
      type: "article",
      title: article.seo.title,
      description: article.seo.description,
      url: article.seo.canonical ?? undefined,
      locale: typeof locale === "string" ? locale : undefined,
      images: article.image_url
        ? [{ url: article.image_url, alt: imageAlt }]
        : [],
    },
    twitter: {
      card: article.image_url ? "summary_large_image" : "summary",
      title: article.seo.title,
      description: article.seo.description,
      images: article.image_url
        ? [{ url: article.image_url, alt: imageAlt }]
        : [],
    },
  };
}
Call articleMetadata(article) from your route’s server-side generateMetadata function. Use the same current article for the page and metadata; add published/modified dates and image dimensions when your stored export provides them. This helper does not create your route or database.
Example: article body and JSON-LD in a Server Component
// A Server Component; article comes from your server-side import.
import type { PublishedArticle } from "./beulog-client";

export function ArticleBody({ article }: { article: PublishedArticle }) {
  const jsonLd = JSON.stringify(article.json_ld).replace(/</g, "\u003c");
  return (
    <>
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: jsonLd }}
      />
      <div dangerouslySetInnerHTML={{ __html: article.html }} />
    </>
  );
}
Use with the framework metadata approach above. The fragment already contains an H1 and article element. Accept these fragments only from your authenticated Beulog integration, apply your site’s content trust policy, and safely serialize JSON-LD as shown.

Set the document language from seo.language and use RTL for Arabic. Preserve the article’s own lang and dir attributes. If you build from content instead, recreate accessible headings, real links, descriptive image alternatives, tables, and references. Keep your site navigation and a useful next step around the article without adding a second H1 for the same article.

Make native pages ready for discovery

  • Match the canonical to the real page. Website plus Article address pattern generates the default canonical; an article’s Published article URL overrides it. Confirm it is the final HTTPS article URL on your domain, not the API URL or an iframe. Fix a missing or incorrect canonical before launch. Avoid conflicting canonical tags from your template or SEO plugin.
  • Use accurate page and social information. Review SEO title and SEO description in the article editor. Keep one title, a useful description, and consistent Open Graph/Twitter image information. Search engines may choose a different title or snippet; metadata is a description, not a ranking promise.
  • Keep images useful and fast. Use the article’s imageAlt to describe the visible image and preserve known width/height. Provide responsive compressed images through your own image pipeline when appropriate. Load the visible article cover promptly and lazy-load images farther down the page. A custom image layout must retain the original meaning and accessible alternatives.
  • Keep structured data truthful. Use the real author or credited organization and an accessible author profile when available. Configure your own publication’s brand and website as publisher; Beulog is the software provider, not automatically the publisher of your website’s articles. Keep language, article URL, dates, and images aligned with what readers see. Preserve useful source citations. Do not add invented ratings, authors, or claims, or expect markup alone to produce rich results.
  • Make articles easy to discover. Link to them from a native blog index and relevant pages using normal links with clear anchor text. Include only current, canonical published URLs in your sitemap. If you publish real translations, use separate language URLs and reciprocal hreflang; do not point to a translation that does not exist.
  • Keep public pages reachable. Published pages intended for search should return useful content with HTTP 200, without a login or unintended noindex. Check robots.txt and your hosting/CDN rules. Expose the article pages to crawlers, not your secret API key. Use private access controls and noindex for draft previews.

Google’s AI Overviews and AI Mode use the same SEO foundations; Google specifies no separate AI schema or special AI file requirement. An eligible page must be indexed and available for a search snippet. Useful original content and accessible native pages help discovery, but neither API use nor metadata guarantees a ranking or an AI citation. Google’s AI features guidance.

Follow the complete publishing, Google, and AI search checklist. It covers crawl permissions for search and AI providers, Search Console checks, page experience, and content review.

Google: canonical URLs · Google: article structured data

Handle unpublishing and missing articles

Unpublishing or deleting an article in Beulog removes it from new published-only API requests. It does not delete a copy already imported into your website. Decide how quickly your website must reflect removals, and implement that policy explicitly.

  • For immediate confirmation of a known article, request GET /articles/{id} with the published-only key. A 404 means it is unavailable through that workspace/key; hide its public copy according to your policy. A 401, 403, 429, timeout, or 5xx is not proof of deletion.
  • For periodic reconciliation, complete a fresh scan of all published articles and compare IDs against local imports from the same verified workspace. Only act on absence after every page succeeds. Do not mass-remove content after a partial scan or a connection error. Where removals are consequential, confirm missing IDs individually before changing their public state.
  • For a genuinely missing public page, return a real 404 or 410 and remove it from your sitemap/internal links. Redirect permanently only when a relevant replacement actually exists; do not redirect every removed article to the home page.
  • Purge or refresh your own page, CDN, and build caches after updates and removals. Keep a clear slug-change policy, including a permanent redirect from an old URL when the same article moves. A network failure should follow your outage policy, not look like an empty successful article.

In Next.js, use notFound() for a confirmed missing resource and verify the final HTTP status in production. Return an appropriate temporary service error for an upstream outage. Avoid sending page content before the resource check if doing so would turn a missing page into a streamed 200 response.

Errors, retries, and request IDs

API errors use a JSON body with error and code. Responses include X-Request-Id for troubleshooting; log the status, code, endpoint, and request ID on your server without logging the key. Keep that request ID when contacting support. v1 responses also identify X-API-Version.

HTTP status and recovery
Status / codeWhat to do
400 invalid_requestCheck UUIDs, query limits, JSON, and Idempotency-Key. Restart a rejected cursor scan; do not repeatedly send the same invalid request.
401 unauthorizedCheck the Bearer key, selected workspace, account verification, and whether the key was revoked. Do not expose the key while debugging.
402 insufficient_creditsGeneration has insufficient credits. Review the balance before trying again; reading published articles does not create a generation job.
403 forbiddenCheck the required scope and account restrictions. Draft reading needs drafts:read in addition to articles:read; generation needs articles:generate.
404 not_foundCheck the endpoint and UUID. For an article, confirm the workspace and whether it is still published. Handle a confirmed missing article on your own site.
409 conflictFor generation, the current reservation may exceed maxCredits. Re-read /pricing and approve a new spending limit deliberately rather than automatically raising it.
429 rate_limitedHonor Retry-After, slow down, and use bounded retries. Limits are 120 API requests per minute per key and 20 generation requests per hour per account.
500 / 502 / 503 / 504Treat service/network failures as temporary. Retry with a delay and a limit, retain a safe prior checkpoint, and alert if failures persist. Do not interpret them as unpublished content.

The supplied client attempts up to three HTTP requests for 429, 502, 503, and 504, using a numeric Retry-After or short exponential delay capped at 60 seconds. It throws BeulogError with status, code, and requestId after an unsuccessful response. Network exceptions and timeouts need your own bounded retry handling. Retrying generation must reuse the original Idempotency-Key.

Caching and conditional requests

Authenticated API responses use Cache-Control: no-store and vary by Authorization. Keep any deliberate local content store private to the integration and namespace it by workspace. GET responses provide ETag; a direct HTTP client can send the same value in If-None-Match. On 304, reuse your stored response and do not parse a JSON body. The downloadable TypeScript client does not implement this conditional flow.

Optional: generate an article safely

Reading and displaying articles does not require generation permission. If you want a server workflow to create articles too, create a separate key with Create new articles enabled. Never give this key to visitors. It can reserve and spend your account’s credits.

POST /generate JSON fields
FieldAccepted value and behavior
topicString, at most 1,500 characters; defaults to an empty string. Set a clear topic for an intentional job.
imageBoolean; default true. Set false to request an article without a generated image.
autoPublishBoolean; default false. Keep false for editorial review before making the article available to public integrations.
kindarticle or research; default article. The downloadable client’s generate() helper creates article jobs; use direct HTTP for research jobs.
maxCreditsOptional integer 1–1,000,000. A reservation above this ceiling returns 409 without reserving credits. Use current /pricing and your own approved limit.
researchIdOptional UUID of an existing research brief in the same workspace. Use direct HTTP; the downloadable client does not expose this field.

Send an Idempotency-Key header of 1–128 characters. Persist one unique value for each intended job before submitting, and reuse it for retries with the same publishing key. The same key/value returns the existing job instead of reserving credits again; it does not apply changed input to that existing job. A new intended job needs a new value.

Queue a draft, then check its job separately
// Separate server task. This key can spend account credits.
import { BeulogClient } from "./beulog-client";

export async function queueReviewedArticle() {
  const key = process.env.BEULOG_GENERATION_KEY;
  // Persist one unique ID per intended job BEFORE submitting it.
  // Reuse that same ID if the request times out or is retried.
  const requestId = process.env.BEULOG_GENERATION_REQUEST_ID;
  if (!key || !requestId) {
    throw new Error("Configure the generation key and stable request ID.");
  }
  const client = new BeulogClient("https://beulog.com", key);
  const pricing = await client.pricing();
  return client.generate({
    topic: "A practical guide to choosing a content calendar",
    image: false,
    autoPublish: false,
    maxCredits: pricing.article,
  }, requestId);
}

export async function checkGenerationJob(jobId: string) {
  const key = process.env.BEULOG_GENERATION_KEY;
  if (!key) throw new Error("Configure the generation key.");
  return new BeulogClient("https://beulog.com", key).job(jobId);
}
Set a persistent request ID between 1 and 128 characters. Save the returned job ID. The example uses the current no-image reservation as maxCredits; your workflow can enforce a lower approved ceiling.
  1. Read /pricing before submission. The article, article_with_image, and research values are maximum reservations, not a promise of the final charge.
  2. Save the job ID from POST /generate. Poll GET /jobs/{id} with the generation key on a scheduled delay, such as every 10–30 seconds, with a time limit and backoff; do not poll in a tight loop.
  3. Continue while status is queued or running. Stop when it is completed or failed. On failure, record the error and request ID; do not automatically submit a new charged job. On completion, article_id identifies the result of an article job.
  4. With autoPublish:false, review the draft in Beulog and publish it when ready. The normal read-only key cannot fetch it until then. A private preview workflow needs articles:read plus drafts:read and the direct ?drafts=true endpoint.
  5. reserved_credits is the reservation; cost is reserved while pending and the final charge after completion; settled_at records settlement. Unused reserved credits are returned after successful completion, and failed jobs are fully refunded.

Check your integration before launch

  • Connection returns the intended verified workspace and only the scopes you need; no publishing key appears in page source, browser requests, or public bundles.
  • A test published article appears once at its final native URL. Its text, one article H1, image alternatives, references, language, and metadata are present in the server-rendered response.
  • The final page has the intended canonical, no unintended noindex, no duplicate metadata/schema, and working social images. Check its structured data with Google’s Rich Results Test and inspect the live URL in Search Console.
  • More than one page of articles imports correctly. Re-running an import creates no duplicates. An edit and a publishing-settings change update the local article and its metadata.
  • A draft stays private. Unpublishing a test article follows your removal policy and cache refresh. A genuinely missing URL returns 404/410; an API outage does not masquerade as an empty successful page.
  • Cursor restarts, rate limits, invalid/revoked keys, timeouts, and failed generation jobs are visible in server monitoring with request IDs and without secrets.

Open Rich Results Test · Open Google Search Console · Continue with the publishing SEO guide

Ready to connect your website?

Open your workspace to create a publishing key, or get help with a step you’re unsure about.