Website automation
Blog automation
Connect a site through a signed integration when it owns its CMS, or use ReputePilot as its managed content store.
Two modes, one rule
| Mode | For | Source of truth | Where you manage content |
|---|---|---|---|
| Push | Sites with their own CMS/database | The site's database | Both: ReputePilot studio and the site's own admin |
| Hosted | Sites with no CMS (static, plain HTML) | ReputePilot | ReputePilot studio only |
Push mode — the signed webhook
The site implements a single endpoint (for example /api/v1/content/public/reputepilot/blogs) and verifies three headers on every request. ReputePilot signs with a per-site shared secret; only holders of the secret can write.
Request signature (v2)
signature = lowercase_hex( HMAC_SHA256( secret,
"v2\n{unix_ts}\n{nonce}\n{METHOD}\n{path_and_query}\n{raw_body}" ) )
headers:
x-rp-timestamp: unix seconds (max clock skew: 5 minutes)
x-rp-nonce: UUID, single-use (replays rejected forever)
x-rp-signature: hex HMAC as above (constant-time compare)- Binding the method and path means a captured signature can never be replayed against another verb or resource.
- Nonces are single-use; retried deliveries of a completed request return the recorded result idempotently.
- Unsigned or invalid requests are rejected 401; an unconfigured receiver fails closed with 503.
Operations
| Verb | Purpose | Notes |
|---|---|---|
POST /blogs | Deliver a blog (publish or draft) | Duplicate slug or near-duplicate content → 409 |
GET /blogs | Inventory of ReputePilot-authored posts | All statuses, drafts included; returns version tokens and coverUrl |
GET /blogs/{id} | Full detail including markdown | |
PUT /blogs/{id} | Update fields, replace the cover, publish, or take down to draft | Requires the current version token; stale writes → 409 so neither side silently overwrites the other |
DELETE /blogs/{id} | Archive (soft delete) | Recoverable from the site admin; hard deletion stays site-only |
Scope: the webhook may only manage content it originated. Site-authored posts return 403 on any remote mutation.
Delivery payload
POST /blogs carries the complete article and its cover image. Every field is camelCase JSON; the body is signed byte-for-byte.
{
"title": "How to Create a Contract Intake Form That Reduces Review Delays",
"subtitle": "A practical guide to collecting the right details before review.",
"slug": "how-to-create-contract-intake-form-reduce-review-delays",
"excerpt": "2-3 plain-language sentences",
"tags": ["contract intake", "legal operations"],
"category": "Operations",
"seo": { "metaTitle": "...", "metaDescription": "...",
"canonicalPath": "/blogs/<slug>", "keywords": ["..."] },
"markdown": "## First section ...",
"mode": "publish", // or "draft"
"cover": {
"url": "https://api.reputepilot.app/api/blog-covers/cov_<id>.jpg",
"contentType": "image/jpeg",
"width": 1200,
"height": 800,
"alt": "<title>",
"data": "<base64 of the same JPEG>"
}
}- The cover is generated per article and branded with your site name: a 1200×800 editorial JPEG under 900 KB, suitable for
og:image,twitter:image, and structured-dataimage. - Store
datadirectly (no outbound request needed) or sideload fromurl, which is permanent and public. Both carry identical bytes. - Report the stored cover back as
coverUrlon inventory and detail rows (an explicitnullwhen a post has none). ReputePilot uses that signal to attach a cover to any post that shipped without one, viaPUTwith acoverobject — so a receiver that adds cover support later is backfilled automatically. - A cover you cannot store should never fail the article: accept the post, leave
coverUrlnull, and the backfill will retry.
The writing engine
- Checks freshness from the site's public blog list — stateless across restarts.
- If the newest post is older than the cadence (default 24 h), it generates an article, then validates: minimum length, SEO metadata bounds, duplicate-title rejection, and no leading H1 (the page renders its own title).
- Renders the cover image, then delivers signed; a 409 from the site is treated as a skip, never a retry loop.
- Each cycle also attaches a cover to at most one earlier post the site reports as cover-less, with a bounded number of attempts per post.
Hosted mode — ReputePilot owns the store
Create a site in the studio and get three consumption surfaces immediately. All content management — editor with live preview, explicit publish/draft per post, AI draft generation, automatic cadence — happens in ReputePilot.
| Surface | URL shape | Use |
|---|---|---|
| Content API | GET /public/blog/{site_key}/posts[/{slug}] | SSR/SSG sites fetch JSON at render or build time |
| Feeds | /public/blog/{site_key}/rss.xml · sitemap.xml | Syndication and search engines; both answer 404 until the site's public blog URL is set |
| Embed | /public/blog/embed.js | Plain HTML sites: one script tag renders the whole blog |
<div id="rp-blog"></div>
<script src="https://api.reputepilot.app/public/blog/embed.js"
data-site="YOUR_SITE_KEY" defer></script>- Only published posts are ever served publicly; the site key is an unguessable capability identifier granting read-only access.
- Markdown is rendered server-side with raw HTML escaped — XSS-safe by construction.
- Generated posts include a
coverobject (url,alt,width,height) branded with the site name; the RSS feed carries it as anenclosureand the embed renders it above each post. - Optional rebuild webhook: static sites get pinged after content changes so they redeploy automatically.
Search visibility for hosted blogs
Hosted posts are indexed under your domain, never under api.reputepilot.app. The API host's robots.txt allows crawlers to fetch only /public/blog/ and /api/blog-covers/; every other API path is disallowed, and public JSON is data for your renderer, not a page. Everything below switches on once the site's public blog URL (an https URL on your website, no query string) is set in the studio under Site settings → Search visibility.
| Signal | What ReputePilot emits | What your site does |
|---|---|---|
| Canonical | canonicalUrl = {publicBaseUrl}/{slug} on every public post; null until configured | Render each post at exactly that address from the Content API and set <link rel="canonical"> to it |
| Sitemap | /public/blog/{site_key}/sitemap.xml listing the blog root and each published canonical with lastmod | Add Sitemap: https://api.reputepilot.app/public/blog/{site_key}/sitemap.xml to your robots.txt, then submit the same URL in Search Console for your verified domain |
| Article data | site.authorName, site.publisherName, site.publisherLogoUrl (https only) plus seo, cover, publishedAt, updatedAt | Emit Article JSON-LD from those fields on the rendered page |
| Slug changes | Old slugs stay in previousSlugs; the Content API answers an old slug with a 301 to the current one | Mirror the redirect (301) on your own routes so rankings follow the post |
- The sitemap lives on the API host but lists URLs on yours. Search engines accept thatcross-host arrangement only when the sitemap URL appears in your
robots.txt— the studio provides the exact line to copy. - The drop-in embed renders in the browser with hash routing (
#/slug), so it does not create indexable pages. Use the Content API for any page you want in search; keep the embed for sites where search traffic is not the goal. - Public responses are cacheable for 120 s (300 s at shared caches); a static site should rebuild on the optional webhook rather than polling.
- Push-mode posts follow the receiving site's own SEO:
seo.canonicalPath(for example/blogs/{slug}) is resolved against that site's host and indexed there.
Plan limits
| Starter | Pro | Authority | Agency | |
|---|---|---|---|---|
| Hosted blog sites | — | 1 | 3 | 10 |
| Blog posts / month | — | 8 | 30 | 120 |
Quotas are enforced centrally before any AI generation runs; exceeding a limit returns plan_limit.