HTTP API
Use these endpoints from any backend. Authenticate with a Site API key. Prefer calling from your server — never expose the key in the browser.
For Next.js and Node apps, install the official client from npm:
npm install @pengraph/node
See npmjs.com/package/@pengraph/node and the Node.js guide.
Base URL
https://seocondo.com
# self-hosted/local override: http://localhost:3000
Authentication
Create a key in Account settings → Connections → Site API. Choose the site, read Site ID under the dropdown, name the key, click Create key, and copy the full secret from Copy your key now. It is shown once and starts with pg_live_. Send it on every request:
Authorization: Bearer pg_live_xxxxxxxx
The key must belong to the same site as {siteId} in the path. Keys are hashed at rest; the full secret is shown only once when created.
Errors
{ "error": "Human-readable message" }
| Status | Meaning |
|---|---|
401 | Missing or invalid API key |
404 | Post not found / not published |
500 | Server error |
List published posts
GET /api/v1/sites/{siteId}/posts?limit=50&offset=0
Authorization: Bearer pg_live_…
Accept: application/json
| Param | Type | Default | Description |
|---|---|---|---|
limit | integer | 50 | Page size (1–100) |
offset | integer | 0 | Items to skip |
since | date-time | — | Only posts with updated_at ≥ since |
curl -sS \
-H "Authorization: Bearer $PENGRAPH_API_KEY" \
-H "Accept: application/json" \
"https://seocondo.com/api/v1/sites/$PENGRAPH_SITE_ID/posts?limit=20"
200 response
{
"posts": [
{
"id": "…",
"slug": "how-to-seo",
"title": "…",
"content_html": "<p>…</p>"
}
],
"pagination": { "limit": 20, "offset": 0, "total": 1 }
}
Get a post by slug
GET /api/v1/sites/{siteId}/posts/by-slug/{slug}
Authorization: Bearer pg_live_…
Accept: application/json
curl -sS \
-H "Authorization: Bearer $PENGRAPH_API_KEY" \
"https://seocondo.com/api/v1/sites/$PENGRAPH_SITE_ID/posts/by-slug/how-to-seo"
Each successful call counts as one read of that post — the dashboard reports the total, the last read time and a daily trend. Nothing to opt into: sites that already fetch posts this way are counted automatically, and the response is unchanged. The listing endpoint does not count reads.
When an article has moved
When Clara merges duplicate articles or retires one, the old article is no longer published and its address should forward to the article that replaced it. A by-slug request for the old slug then answers 404 with where it moved:
{ "error": "Post moved", "redirect": { "to": "/news/the-article-that-replaced-it", "statusCode": 301 } }
The same path is also in the x-pengraph-redirect response header. Send the visitor there with a
permanent redirect instead of showing "page not found". In Next.js:
const res = await fetch(`${API}/api/v1/sites/${SITE_ID}/posts/by-slug/${slug}`, { headers: { Authorization: `Bearer ${KEY}` } });
if (res.status === 404) {
const body = await res.json();
if (body.redirect?.to) permanentRedirect(body.redirect.to); // from "next/navigation"
notFound();
}
List redirects
GET /api/v1/sites/{siteId}/redirects
Authorization: Bearer pg_live_…
Every forward your site should apply (old path → new path), newest first:
{ "redirects": [{ "from": "/news/old-article", "to": "/news/new-article", "statusCode": 301 }] }
Use it to configure redirects at build time (for example Next.js redirects() in
next.config.js) or in middleware. The list is also shown under Website health → Redirects.
IndexNow key
GET /api/v1/sites/{siteId}/indexnow-key
The site's IndexNow key as plain text, with no API key needed (the key is public by design). Search
engines check that your website serves it at https://your-site/{key}.txt before they accept
Clara's change notices. Point a rewrite from /{key}.txt at this URL; @pengraph/node 0.3.0's
Next.js add-on does it with rewrites(). Answers 404 until SEOcondo has made a key for the site.
The Post object
Use content_html for display. Use id as the stable key when upserting into your DB.
| Field | Type | Notes |
|---|---|---|
id | uuid | Stable PenGraph ID |
site_id | uuid | Owning site |
title | string | |
slug | string | URL slug |
content | string | null | Markdown |
content_html | string | null | Prefer for blog body |
excerpt | string | null | Listing summary |
meta_* / og_* | string | null | SEO / social |
tags / category | array / string | Organisation |
canonical_url | string | null | Original article URL — use as <link rel="canonical"> when syndicating |
published_url | string | null | Publish location on the owning site |
published_at | date-time | ISO 8601 |
updated_at | date-time | ISO 8601 |
created_at | date-time | ISO 8601 |
schema_json | object | null | Ready-made JSON-LD; inject in a <script type="application/ld+json"> |
author | object | null | Byline: name, job_title, bio, credentials, image_url, profile_url, same_as[]. The post's author or the site's default author |
Canonical URLs and syndication
Each post exposes canonical_url: the absolute URL of the original article on the site that owns it. If you republish a post on another domain (for example a /news page that mirrors posts from elsewhere), emit that URL in a <link rel="canonical"> tag and treat your copy as an alternate. Never leave the same article self-canonical on two domains — search engines will pick one and drop the other.
llms.txt
GET /api/v1/sites/{siteId}/llms.txt
Authorization: Bearer <site API key>
Returns text/plain: a ready-made llms.txt for the site (summary, key pages,
published articles) built by SEOcondo. Serve it at /llms.txt on your own domain so AI assistants
can understand the site. Cache it for an hour or so.
Organization JSON-LD
GET /api/v1/sites/{siteId}/organization
Authorization: Bearer <site API key>
Returns application/ld+json: an Organization built from the brand details in SEOcondo, or a
LocalBusiness with address and telephone when the site has a local business profile. Put it in a
<script type="application/ld+json"> tag on the home page.
OpenAPI
Full machine-readable spec: GET /api/v1/openapi.
Using Next.js? See the Node.js package guide.