API Reference

Connect Your Blog to BloGoose

Integrate BloGoose with your website to automatically publish AI-generated content. One endpoint, HMAC authentication, three API calls per post.

Need copy-paste instructions?
Use the Builder Guide for easy markdown prompts you can send to Cursor, a coding assistant, or directly to your developer.
How it works
BloGoose generates SEO-optimized articles and pushes them to your blog via a single API endpoint. Your website needs to implement this endpoint to receive and publish content automatically.

At A Glance

This is the easiest mental model for the BloGoose Blog API:

Minimum viable version
If you only build the basics, start with these 5 things: create_post, update_post, accept_post, upload_image, and list_categories. That is enough for BloGoose to generate previews, upload images, and publish content correctly.

Required Endpoints

If you want BloGoose to connect to your website easily, these are the endpoints and actions your blog API should support.

Simple rule
You can build one single route: POST /hey_api/v1/blog/access. Then use an action field in the JSON body to decide what to do.
ActionNeeded?What it does
create_postRequiredCreates a new post, usually in preview status first.
update_postRequiredUpdates a preview or draft after BloGoose regenerates content.
accept_postRequiredPublishes now or schedules for later. This is how preview becomes live.
get_postRecommendedLets BloGoose or your team fetch one post for inspection.
list_postsRecommendedLets BloGoose look at existing content and avoid duplicates.
delete_postRecommendedArchives a post safely instead of hard deleting it.
upload_imageRequiredUploads featured images and inline content images from base64.
list_categoriesRequiredLets BloGoose know which categories exist.
create_categoryOptionalUseful if you want BloGoose to create missing categories automatically.

Minimum set to go live fast

What You Need To Make BloGoose Work Properly

If you want the full BloGoose workflow to work smoothly on your website, these are the main things you need before going live.

ThingWhy it is neededHow to get it
Blog API endpointYour website must accept posts from BloGoose at POST /hey_api/v1/blog/access.Build it yourself, ask your developer, or use the Builder Guide.
Blog API keyLets BloGoose authenticate to your website.Create this inside your own website/plugin/backend and store it in BloGoose settings.
Blog API secretUsed to generate the HMAC signature for secure requests.Create this inside your own website/plugin/backend and store it in BloGoose settings.
Website URLUsed for niche detection, canonical URL fallback, and overall site context.Your own site homepage URL, like https://yourdomain.com.
Sitemap URLHelps BloGoose understand your existing pages and avoid duplicate topics.Usually something like https://yourdomain.com/sitemap.xml.
AI accessIncluded. BloGoose provides Ogni for writing, research, SERP briefs, and autopilot.Nothing to set up. It is on for every account.
Minimum setup for most users
If you want the simplest setup, you usually only need: your Website URL, your Sitemap URL, and your Blog API key + secret. AI access is included.

Where to enter these in BloGoose

Quick Start

To connect BloGoose to your blog, you need three things:

  1. Base URL, Your blog's root URL (e.g. https://yourblog.com)
  2. API Key, A unique key identifying your integration
  3. API Secret, A shared secret for HMAC request signing

Enter these under Sites → Add Site in your BloGoose dashboard, and your blog will start receiving content.

Your blog must implement the API
Your website needs a server-side handler at /hey_api/v1/blog/access that receives JSON requests and manages posts. If you use WordPress, install the official BloGoose Connector from Sites → Add Site (download ZIP) — no custom coding needed.

Implementation Checklist

Use this checklist if you want to implement the BloGoose Blog API (Hey API) on your own website quickly and correctly.

  1. Create one route at POST /hey_api/v1/blog/access.
  2. Read JSON body and require an action field.
  3. Validate auth using X-API-Key, X-Timestamp, and X-Signature.
  4. Store posts with statuses like draft, preview, scheduled, published, archived.
  5. Generate preview tokens for preview posts and return a preview_url.
  6. Support SEO fields like meta_title, meta_description, meta_keywords, canonical_url, and og_image.
  7. Support image uploads from base64 via upload_image.
  8. Support categories so BloGoose can classify and publish to the correct section.
  9. Support scheduling so accepted posts can go live later.
  10. Return clean JSON in the same response shape for every action.

Suggested Data You Should Store

FieldWhy it matters
title / slugCore blog identity and routing
contentBloGoose sends final HTML here
statusControls preview, scheduling, and publishing
preview_tokenPowers preview URLs safely
meta_title / meta_description / meta_keywordsSEO metadata
featured_image / featured_image_altMain article image + accessibility text
og_image / canonical_urlSocial sharing + canonical SEO support
scheduled_at / published_atPublishing workflow
category_idMaps post into your blog taxonomy
detailsOptional JSON for custom CMS data

Authentication

Every API request is signed with HMAC-SHA256. This ensures requests are authentic and haven't been tampered with.

Required Headers

HeaderValue
Content-Typeapplication/json
X-API-KeyYour API key from BloGoose settings
X-TimestampCurrent Unix timestamp (seconds). Must be within ±5 minutes of server time.
X-SignatureHMAC-SHA256(request_body + timestamp, api_secret), hex encoded

How Signing Works

The signature is computed by concatenating the raw JSON request body with the timestamp, then hashing with your API secret:

import hashlib, hmac, json, time, requests

API_KEY    = "your-api-key"
API_SECRET = "your-api-secret"
BASE_URL   = "https://yourblog.com"

def call_blog_api(body: dict) -> dict:
    raw = json.dumps(body)
    ts  = str(int(time.time()))
    sig = hmac.new(
        API_SECRET.encode(),
        (raw + ts).encode(),
        hashlib.sha256
    ).hexdigest()

    resp = requests.post(
        f"{BASE_URL}/hey_api/v1/blog/access",
        data=raw,
        headers={
            "Content-Type": "application/json",
            "X-API-Key":    API_KEY,
            "X-Timestamp":  ts,
            "X-Signature":  sig,
        }
    )
    return resp.json()
function callBlogApi(array $body): array {
    $raw = json_encode($body);
    $ts  = (string) time();
    $sig = hash_hmac('sha256', $raw . $ts, $apiSecret);

    $ch = curl_init($baseUrl . '/hey_api/v1/blog/access');
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => $raw,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => [
            'Content-Type: application/json',
            'X-API-Key: '    . $apiKey,
            'X-Timestamp: '  . $ts,
            'X-Signature: '  . $sig,
        ],
    ]);
    $resp = curl_exec($ch);
    curl_close($ch);
    return json_decode($resp, true);
}
const crypto = require('crypto');

async function callBlogApi(body) {
  const raw = JSON.stringify(body);
  const ts  = String(Math.floor(Date.now() / 1000));
  const sig = crypto
    .createHmac('sha256', API_SECRET)
    .update(raw + ts)
    .digest('hex');

  const resp = await fetch(
    `${BASE_URL}/hey_api/v1/blog/access`,
    {
      method: 'POST',
      headers: {
        'Content-Type':  'application/json',
        'X-API-Key':     API_KEY,
        'X-Timestamp':   ts,
        'X-Signature':   sig,
      },
      body: raw,
    }
  );
  return resp.json();
}
# Generate signature (bash)
BODY='{"action":"list_categories"}'
TS=$(date +%s)
SIG=$(echo -n "${BODY}${TS}" | \
  openssl dgst -sha256 -hmac "$API_SECRET" | \
  awk '{print $2}')

curl -X POST "https://yourblog.com/hey_api/v1/blog/access" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $API_KEY" \
  -H "X-Timestamp: $TS" \
  -H "X-Signature: $SIG" \
  -d "$BODY"

API Endpoint

All requests go to a single endpoint on your blog:

POST  {BASE_URL}/hey_api/v1/blog/access

The action field in the JSON body determines which operation to perform. This keeps things simple, one URL, one method, many actions.

Publishing Workflow

BloGoose uses a three-step workflow for every article. This gives you full control over what gets published.

Step 1
Create Preview
BloGoose sends the article as a preview. You get a preview URL to review it.
Step 2
Review & Edit
Review the preview. If changes needed, BloGoose updates the content. Same URL, just refresh.
Step 3
Accept & Publish
Accept to publish immediately or schedule for a future date. Preview URL is deactivated.
# The complete lifecycle in 3 API calls:

1. create_post (status: "preview")  →  returns { id, preview_url }
2. update_post (id, content)        →  same preview_url, user refreshes
3. accept_post (id)                 →  published or scheduled

Create Post

Creates a new post on your blog. Use status: "preview" to get a shareable preview URL before publishing.

ParameterTypeDescription
actionstringRequired"create_post"
titlestringRequiredPost title
contentstringRequiredPost body as HTML
category_idintegerRequiredCategory ID (use list_categories to get IDs)
statusstringOptional"preview" for preview URL, "draft" for hidden draft. Default: "draft"
excerptstringOptionalShort summary
featured_imagestringOptionalImage path or URL
meta_titlestringOptionalSEO title (max 70 chars)
meta_descriptionstringOptionalSEO description (max 160 chars)
meta_keywordsstringOptionalComma-separated keywords
og_imagestringOptionalAbsolute OG image URL. If omitted, BloGoose uses the uploaded featured image URL when available.
canonical_urlstringOptionalCanonical URL if different
author_namestringOptionalAuthor display name
scheduled_atstringOptionalISO datetime for scheduling
is_featuredbooleanOptionalFeature this post
is_noindexbooleanOptionalAdd noindex meta tag

Example Request

call_blog_api({
    "action":           "create_post",
    "title":            "10 Tips for Better SEO in 2026",
    "content":          "<h2>Introduction</h2><p>SEO has evolved...</p>",
    "category_id":      3,
    "status":           "preview",
    "meta_title":       "10 SEO Tips for 2026 | Your Blog",
    "meta_description": "Discover 10 actionable SEO strategies...",
    "canonical_url":    "https://yourblog.com/blog/10-tips-for-better-seo-in-2026",
    "og_image":         "https://yourblog.com/uploads/seo/10-tips-og.jpg"
})

Response (201 Created)

{
    "success": true,
    "message": "Post created",
    "data": {
        "id":           42,
        "slug":         "10-tips-for-better-seo-in-2026",
        "preview_url":  "https://yourblog.com/blog/10-tips-for-better-seo-in-2026?preview_id=abc123...",
        "preview_token": "abc123...def456"
    }
}
Preview URLs
The preview_url is a read-only page that looks exactly like the live post but with a "Preview" banner. No authentication needed to view it, share it with your team for review.
Canonical + OG defaults
BloGoose now forwards both canonical_url and og_image. If the article metadata omits them, BloGoose derives the canonical URL from your configured site URL + slug, and uses the uploaded featured image URL as the OG image when available.

Update Post

Update any field on an existing post. Send only the fields you want to change. If the post is still in preview status, the same preview URL works, just refresh.

ParameterTypeDescription
actionstringRequired"update_post"
idintegerRequiredPost ID (or use slug instead)
titlestringOptionalNew title
contentstringOptionalNew HTML content
category_idintegerOptionalNew category
...Any field from Create Post

Example

call_blog_api({
    "action":  "update_post",
    "id":      42,
    "content": "<h2>Revised Introduction</h2><p>Updated content...</p>"
})

Accept / Publish Post

Publish a post immediately or schedule it for a future date. This clears the preview token (preview URL stops working).

ParameterTypeDescription
actionstringRequired"accept_post"
idintegerRequiredPost ID
scheduled_atstringOptionalISO datetime. If future → schedules. If omitted → publishes now.

Publish Immediately

call_blog_api({
    "action": "accept_post",
    "id":     42
})
// → { "success": true, "message": "Post published", "data": { "status": "published", "url": "https://example.com/blog/post/" } }

url is optional. Include it only when status is published. It must be the public https permalink. Scheduled, draft, and preview responses omit it.

Schedule for Later

call_blog_api({
    "action":       "accept_post",
    "id":           42,
    "scheduled_at": "2026-04-20T09:00:00"
})
// → { "success": true, "message": "Post scheduled", "data": { "status": "scheduled" } }

Get Post

Retrieve a single post by ID or slug.

call_blog_api({ "action": "get_post", "id": 42 })
// Returns full post object with all fields
// When status is "published", data may include "url": the public https permalink.
// Omit url for preview, draft, and scheduled posts.

List Posts

Retrieve a paginated list of posts with optional filters.

ParameterTypeDescription
actionstringRequired"list_posts"
statusstringOptionalFilter: draft, preview, published, scheduled, archived
category_idintegerOptionalFilter by category
searchstringOptionalFull-text search
pageintegerOptionalPage number (default: 1)
limitintegerOptionalResults per page, 1–100 (default: 20)

Response

{
    "success": true,
    "data": [
        { "id": 42, "title": "10 Tips...", "status": "published", ... },
        { "id": 41, "title": "How to...", "status": "preview", ... }
    ],
    "pagination": {
        "total": 156,
        "page":  1,
        "limit": 20
    }
}

Delete Post

Soft-deletes a post (moves it to archived status). Can be identified by id or slug.

call_blog_api({ "action": "delete_post", "id": 42 })

Upload Image

Upload a featured image or inline image. Send as base64-encoded string or data URI.

ParameterTypeDescription
actionstringRequired"upload_image"
imagestringRequiredBase64-encoded image or data URI
post_idintegerOptionalAssociate with a specific post
filenamestringOptionalSuggested file name, such as clothing-video-generator.webp. Sanitize it, keep it unique, and take the extension from the detected image type, not from this string.
altstringOptionalAlt text from the post. Store it on the media item when the platform supports alt text.
titlestringOptionalMedia title from the post. Ignore any field you do not support.

Response (201 Created)

{
    "success": true,
    "data": {
        "id":        78,
        "filename":  "seo-tips-featured.jpg",
        "file_path": "/uploads/2026/04/seo-tips-featured.jpg",
        "width":     1200,
        "height":    630
    }
}

Categories

List Categories

Returns all active categories with their post counts.

call_blog_api({ "action": "list_categories" })

Create Category

ParameterTypeDescription
actionstringRequired"create_category"
namestringRequiredCategory name
descriptionstringOptionalCategory description
statusstringOptional"active" or "inactive"

Post Statuses

StatusDescriptionVisible?
draftWork in progress, not visible anywhereNo
previewHas a preview URL, not publicly listedPreview URL only
scheduledAccepted, will auto-publish at the scheduled timeNo (until published)
publishedLive and publicly visibleYes
archivedSoft-deleted, no longer visibleNo

HTTP Status Codes

200OK, Request succeeded
201Created, Post or resource created successfully
400Bad Request, Missing or invalid parameters
401Unauthorized, Invalid API key or signature
404Not Found, Post or resource doesn't exist
405Method Not Allowed, Use POST, not GET
413Payload Too Large, Request body exceeds limit
429Rate Limited, Too many requests, slow down
500Server Error, Something went wrong on the server

Rate Limits

The Blog API enforces a rate limit of 60 requests per minute per API key. If you exceed this, you'll receive a 429 response.

Rate limit headers
When rate limited, wait for the Retry-After header value (in seconds) before retrying. BloGoose's autopilot automatically respects rate limits and spaces out requests.

AI Handoff Prompt

If you want an AI builder to recreate this API on another website, paste this exact block into Cursor or any coding assistant:

Build a server-side blog API for my website so BloGoose can publish blog posts into it.

Requirements:
- One endpoint: POST /hey_api/v1/blog/access
- Auth headers: X-API-Key, X-Timestamp, X-Signature
- Signature rule: HMAC-SHA256(raw_json_body + timestamp, API_SECRET)
- Reject invalid API key, invalid signature, and timestamps older/newer than 5 minutes

Actions to support:
- create_post
- update_post
- accept_post
- get_post
- list_posts
- delete_post
- upload_image
- list_categories
- create_category

Important fields to support:
- title
- content (HTML)
- category_id
- status
- meta_title
- meta_description
- meta_keywords
- featured_image
- featured_image_alt
- og_image
- canonical_url
- author_name
- scheduled_at

Preview behavior:
- if create_post uses status=preview, create a preview token and return preview_url
- preview posts must not be publicly listed
- update_post must preserve the same preview_url while still in preview
- accept_post must publish now or schedule later
- accept_post must clear the preview token

Return JSON like:
{
  "success": true,
  "message": "Post created",
  "data": {}
}

Also give me:
1. file structure
2. database schema
3. setup steps
4. curl tests
5. README for the site owner

Error Handling

All error responses follow a consistent format:

{
    "success": false,
    "message": "Validation failed: title is required",
    "errors":  {
        "title": ["The title field is required"]
    }
}

Best practices for error handling:

Complete Integration Example

Here's a complete Python example showing the full lifecycle, from creating a preview to publishing:

import hashlib, hmac, json, time, requests

API_KEY    = "your-api-key"
API_SECRET = "your-api-secret"
BASE_URL   = "https://yourblog.com"
ENDPOINT   = f"{BASE_URL}/hey_api/v1/blog/access"

def call(body):
    raw = json.dumps(body)
    ts  = str(int(time.time()))
    sig = hmac.new(API_SECRET.encode(), (raw + ts).encode(), hashlib.sha256).hexdigest()
    return requests.post(ENDPOINT, data=raw, headers={
        "Content-Type": "application/json",
        "X-API-Key": API_KEY, "X-Timestamp": ts, "X-Signature": sig
    }).json()

# 1. Create a preview post
result = call({
    "action":           "create_post",
    "title":            "10 Tips for Professional Communication",
    "content":          "<h2>Introduction</h2><p>Here are 10 tips...</p>",
    "category_id":      1,
    "status":           "preview",
    "meta_description": "Learn 10 tips for clear professional communication."
})
post_id     = result["data"]["id"]
preview_url = result["data"]["preview_url"]
print(f"Preview: {preview_url}")

# 2. Update content if needed (same preview URL)
call({
    "action":  "update_post",
    "id":      post_id,
    "content": "<h2>Better Intro</h2><p>Revised content...</p>"
})

# 3a. Publish immediately
call({ "action": "accept_post", "id": post_id })

# 3b. Or schedule for later
call({
    "action":       "accept_post",
    "id":           post_id,
    "scheduled_at": "2026-04-20T09:00:00"
})
<?php
$apiKey    = 'your-api-key';
$apiSecret = 'your-api-secret';
$baseUrl   = 'https://yourblog.com';
$endpoint  = $baseUrl . '/hey_api/v1/blog/access';

function call(array $body) {
    global $apiKey, $apiSecret, $endpoint;
    $raw = json_encode($body);
    $ts  = (string) time();
    $sig = hash_hmac('sha256', $raw . $ts, $apiSecret);

    $ch = curl_init($endpoint);
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => $raw,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => [
            'Content-Type: application/json',
            'X-API-Key: ' . $apiKey,
            'X-Timestamp: ' . $ts,
            'X-Signature: ' . $sig,
        ],
    ]);
    $resp = curl_exec($ch);
    curl_close($ch);
    return json_decode($resp, true);
}

// 1. Create preview
$result = call([
    'action'      => 'create_post',
    'title'       => '10 Tips for Professional Communication',
    'content'     => '<h2>Introduction</h2><p>Here are 10 tips...</p>',
    'category_id' => 1,
    'status'      => 'preview',
]);
$postId = $result['data']['id'];

// 2. Update if needed
call(['action' => 'update_post', 'id' => $postId, 'content' => '<h2>Better</h2>...']);

// 3. Publish
call(['action' => 'accept_post', 'id' => $postId]);
const crypto = require('crypto');

const API_KEY    = 'your-api-key';
const API_SECRET = 'your-api-secret';
const ENDPOINT   = 'https://yourblog.com/hey_api/v1/blog/access';

async function call(body) {
  const raw = JSON.stringify(body);
  const ts  = String(Math.floor(Date.now() / 1000));
  const sig = crypto.createHmac('sha256', API_SECRET).update(raw + ts).digest('hex');
  const res = await fetch(ENDPOINT, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': API_KEY, 'X-Timestamp': ts, 'X-Signature': sig,
    },
    body: raw,
  });
  return res.json();
}

// 1. Create preview
const result = await call({
  action: 'create_post',
  title: '10 Tips for Professional Communication',
  content: '<h2>Intro</h2><p>Tips...</p>',
  category_id: 1,
  status: 'preview',
});
const postId = result.data.id;

// 2. Update (optional)
await call({ action: 'update_post', id: postId, content: '<h2>Better</h2>...' });

// 3. Publish
await call({ action: 'accept_post', id: postId });

Need help? Contact us at [email protected] or check the pricing page for plan details.