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.
At A Glance
This is the easiest mental model for the BloGoose Blog API:
- One endpoint: your website exposes
POST /hey_api/v1/blog/access - One auth method: API key + timestamp + HMAC signature
- One workflow: create preview → review/update → accept to publish or schedule
- One response shape: always return
success,message, anddata - One goal: let BloGoose send HTML blog posts safely into your CMS
Required Endpoints
If you want BloGoose to connect to your website easily, these are the endpoints and actions your blog API should support.
POST /hey_api/v1/blog/access.
Then use an action field in the JSON body to decide what to do.
| Action | Needed? | What it does |
|---|---|---|
| create_post | Required | Creates a new post, usually in preview status first. |
| update_post | Required | Updates a preview or draft after BloGoose regenerates content. |
| accept_post | Required | Publishes now or schedules for later. This is how preview becomes live. |
| get_post | Recommended | Lets BloGoose or your team fetch one post for inspection. |
| list_posts | Recommended | Lets BloGoose look at existing content and avoid duplicates. |
| delete_post | Recommended | Archives a post safely instead of hard deleting it. |
| upload_image | Required | Uploads featured images and inline content images from base64. |
| list_categories | Required | Lets BloGoose know which categories exist. |
| create_category | Optional | Useful if you want BloGoose to create missing categories automatically. |
Minimum set to go live fast
- Required:
create_post,update_post,accept_post,upload_image,list_categories - Better: also add
list_postsandget_post - Best: add all actions so your API fully matches the BloGoose flow
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.
| Thing | Why it is needed | How to get it |
|---|---|---|
| Blog API endpoint | Your 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 key | Lets BloGoose authenticate to your website. | Create this inside your own website/plugin/backend and store it in BloGoose settings. |
| Blog API secret | Used to generate the HMAC signature for secure requests. | Create this inside your own website/plugin/backend and store it in BloGoose settings. |
| Website URL | Used for niche detection, canonical URL fallback, and overall site context. | Your own site homepage URL, like https://yourdomain.com. |
| Sitemap URL | Helps BloGoose understand your existing pages and avoid duplicate topics. | Usually something like https://yourdomain.com/sitemap.xml. |
| AI access | Included. BloGoose provides Ogni for writing, research, SERP briefs, and autopilot. | Nothing to set up. It is on for every account. |
Where to enter these in BloGoose
- Website URL + Sitemap URL: Onboarding and Autopilot settings
- Blog API Base URL + API Key + API Secret: Sites → Add Site (Connected Sites)
- AI access: included on every account. Nothing to paste.
Quick Start
To connect BloGoose to your blog, you need three things:
- Base URL, Your blog's root URL (e.g.
https://yourblog.com) - API Key, A unique key identifying your integration
- 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.
/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.
- Create one route at
POST /hey_api/v1/blog/access. - Read JSON body and require an
actionfield. - Validate auth using
X-API-Key,X-Timestamp, andX-Signature. - Store posts with statuses like
draft,preview,scheduled,published,archived. - Generate preview tokens for preview posts and return a
preview_url. - Support SEO fields like
meta_title,meta_description,meta_keywords,canonical_url, andog_image. - Support image uploads from base64 via
upload_image. - Support categories so BloGoose can classify and publish to the correct section.
- Support scheduling so accepted posts can go live later.
- Return clean JSON in the same response shape for every action.
Suggested Data You Should Store
| Field | Why it matters |
|---|---|
| title / slug | Core blog identity and routing |
| content | BloGoose sends final HTML here |
| status | Controls preview, scheduling, and publishing |
| preview_token | Powers preview URLs safely |
| meta_title / meta_description / meta_keywords | SEO metadata |
| featured_image / featured_image_alt | Main article image + accessibility text |
| og_image / canonical_url | Social sharing + canonical SEO support |
| scheduled_at / published_at | Publishing workflow |
| category_id | Maps post into your blog taxonomy |
| details | Optional 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
| Header | Value |
|---|---|
| Content-Type | application/json |
| X-API-Key | Your API key from BloGoose settings |
| X-Timestamp | Current Unix timestamp (seconds). Must be within ±5 minutes of server time. |
| X-Signature | HMAC-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.
# 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.
| Parameter | Type | Description | |
|---|---|---|---|
| action | string | Required | "create_post" |
| title | string | Required | Post title |
| content | string | Required | Post body as HTML |
| category_id | integer | Required | Category ID (use list_categories to get IDs) |
| status | string | Optional | "preview" for preview URL, "draft" for hidden draft. Default: "draft" |
| excerpt | string | Optional | Short summary |
| featured_image | string | Optional | Image path or URL |
| meta_title | string | Optional | SEO title (max 70 chars) |
| meta_description | string | Optional | SEO description (max 160 chars) |
| meta_keywords | string | Optional | Comma-separated keywords |
| og_image | string | Optional | Absolute OG image URL. If omitted, BloGoose uses the uploaded featured image URL when available. |
| canonical_url | string | Optional | Canonical URL if different |
| author_name | string | Optional | Author display name |
| scheduled_at | string | Optional | ISO datetime for scheduling |
| is_featured | boolean | Optional | Feature this post |
| is_noindex | boolean | Optional | Add 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_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_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.
| Parameter | Type | Description | |
|---|---|---|---|
| action | string | Required | "update_post" |
| id | integer | Required | Post ID (or use slug instead) |
| title | string | Optional | New title |
| content | string | Optional | New HTML content |
| category_id | integer | Optional | New 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).
| Parameter | Type | Description | |
|---|---|---|---|
| action | string | Required | "accept_post" |
| id | integer | Required | Post ID |
| scheduled_at | string | Optional | ISO 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.
| Parameter | Type | Description | |
|---|---|---|---|
| action | string | Required | "list_posts" |
| status | string | Optional | Filter: draft, preview, published, scheduled, archived |
| category_id | integer | Optional | Filter by category |
| search | string | Optional | Full-text search |
| page | integer | Optional | Page number (default: 1) |
| limit | integer | Optional | Results 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.
| Parameter | Type | Description | |
|---|---|---|---|
| action | string | Required | "upload_image" |
| image | string | Required | Base64-encoded image or data URI |
| post_id | integer | Optional | Associate with a specific post |
| filename | string | Optional | Suggested 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. |
| alt | string | Optional | Alt text from the post. Store it on the media item when the platform supports alt text. |
| title | string | Optional | Media 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
| Parameter | Type | Description | |
|---|---|---|---|
| action | string | Required | "create_category" |
| name | string | Required | Category name |
| description | string | Optional | Category description |
| status | string | Optional | "active" or "inactive" |
Post Statuses
| Status | Description | Visible? |
|---|---|---|
| draft | Work in progress, not visible anywhere | No |
| preview | Has a preview URL, not publicly listed | Preview URL only |
| scheduled | Accepted, will auto-publish at the scheduled time | No (until published) |
| published | Live and publicly visible | Yes |
| archived | Soft-deleted, no longer visible | No |
HTTP Status Codes
| 200 | OK, Request succeeded |
| 201 | Created, Post or resource created successfully |
| 400 | Bad Request, Missing or invalid parameters |
| 401 | Unauthorized, Invalid API key or signature |
| 404 | Not Found, Post or resource doesn't exist |
| 405 | Method Not Allowed, Use POST, not GET |
| 413 | Payload Too Large, Request body exceeds limit |
| 429 | Rate Limited, Too many requests, slow down |
| 500 | Server 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.
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:
- Always check
success(boolean) in the response - Log the
messagefield for debugging - On
401, verify your API key and that the timestamp is within ±5 minutes - On
429, implement exponential backoff - On
500, retry after a short delay
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.