Introduction
API endpoints for fetching content sources and generating AI-powered articles via PostFuel.
Welcome to the PostFuel API! This API allows you to fetch content from various sources and generate AI articles programmatically.
<aside>Use the navigation on the left to explore available endpoints. Code examples are provided in the dark panel on the right.</aside>
### Authentication
Most endpoints require authentication using a Bearer token. Generate your API token from your account settings page and include it in the `Authorization` header:
`Authorization: Bearer {YOUR_API_TOKEN}`
Authenticating requests
To authenticate requests, include a Authorization
header with the value "Bearer {YOUR_API_TOKEN}"
.
All authenticated endpoints are marked with a requires authentication
badge in the documentation below.
Enter your token with the "Bearer " prefix. You can generate API tokens from your dashboard settings under API Tokens.
Content Fetching
Fetch Content Posts
requires authentication
Retrieves a list of recent content items (news articles, local places) based on a topic/query from the specified source. Results are cached temporarily.
This endpoint requires authentication and consumes credits based on the source (for subscribed users) or checks monthly free usage limits (for non-subscribed users).
Example request:
curl --request POST \
"http://localhost:8085/api/fetch-posts" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--data "{
\"source\": \"\\\"google_news\\\" Enum(\\\"google_news\\\", \\\"google_local_search\\\")\",
\"topic\": \"\\\"AI advancements\\\"\",
\"limit\": 5,
\"after\": null,
\"country\": \"\\\"us\\\"\"
}"
const url = new URL(
"http://localhost:8085/api/fetch-posts"
);
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"source": "\"google_news\" Enum(\"google_news\", \"google_local_search\")",
"topic": "\"AI advancements\"",
"limit": 5,
"after": null,
"country": "\"us\""
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'http://localhost:8085/api/fetch-posts';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_TOKEN}',
'Content-Type' => 'application/json',
],
'json' => [
'source' => '"google_news" Enum("google_news", "google_local_search")',
'topic' => '"AI advancements"',
'limit' => 5,
'after' => null,
'country' => '"us"',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
Example response (200):
{
"posts": [
{
"title": "Example News Title",
"summary": "A snippet or description of the content...",
"url": "https://example.com/news-article",
"source": "google_news",
"source_meta": {
"id": "md5_hash_of_url",
"news_source": "Example News Outlet",
"published_date": "1 day ago",
"link_url": "https://example.com/news-article"
},
"comments": []
},
{
"title": "Example Cafe",
"summary": "Cozy spot with great espresso.",
"url": "https://examplecafe.com",
"source": "google_local_search",
"source_meta": {
"id": "md5_hash_of_title_address",
"local_address": "123 Main St, Halifax",
"local_type": "Coffee shop",
"local_rating": "4.5",
"local_reviews": "150",
"link_url": "https://examplecafe.com"
},
"comments": []
}
],
"after": null,
"result_summary_text": "Example News Title, Example Cafe"
}
Example response (401, Unauthenticated):
{
"message": "Unauthenticated."
}
Example response (402, Insufficient Credits (Paid User)):
{
"error": "Insufficient credits.",
"message": "This request requires 14 credits, but you only have 10 available."
}
Example response (403, Forbidden (Free User - Invalid Source/Action)):
{
"message": "Subscription Required.",
"error": "This feature or model requires an active paid subscription."
}
Example response (422, Validation Error):
{
"message": "The given data was invalid.",
"errors": {
"format": [
"The selected format is invalid."
],
"openai_model": [
"Invalid OpenAI model specified. Allowed models are: gpt-4.1, gpt-4.1-mini, gpt-4.1-nano."
]
}
}
Example response (429, Free Tier Limit Reached):
{
"message": "Free Limit Reached.",
"error": "You have reached your monthly limit for this free action. Please upgrade for continued access."
}
Example response (500, Server Error):
{
"message": "An internal server error occurred.",
"error": "Failed to process request due to an unexpected issue. Please try again later or contact support if the problem persists."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Content Generation
Queue Content Generation
requires authentication
Accepts parameters to define content generation requirements. You must provide
EITHER a manual_prompt
OR both source_ref_service
and source_ref_id
.
This endpoint queues background jobs for scraping (via Diffbot, if enabled)
any provided attached_urls
AND/OR the external URL from a referenced post (if applicable and scraping enabled),
and then generates AI content (via OpenAI).
Returns immediately with identifiers (request_id
, batch_id
) to track the request.
Check the /api/results/{request_id}
endpoint for completion status and content.
This endpoint requires authentication and consumes credits based on the model choice and whether scraping is enabled/used (for subscribed users), or checks monthly free usage limits (for non-subscribed users, who are restricted to the free model and cannot use scraping).
Example request:
curl --request POST \
"http://localhost:8085/api/generate-preview" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--data "{
\"source_ref_service\": \"\\\"google_news\\\"\",
\"source_ref_id\": \"\\\"e647eb4609d5313d7e6bd4568311a5f0\\\"\",
\"manual_prompt\": \"\\\"Write a blog post comparing electric vs. gasoline cars.\\\"\",
\"attached_urls\": [
\"https:\\/\\/example.com\\/article1\"
],
\"focus_prompt\": \"\\\"Focus on the environmental impact for a general audience.\\\"\",
\"hyperlocal_context\": \"\\\"Assume the reader is familiar with local Halifax landmarks.\\\"\",
\"format\": \"\\\"listicle\\\" Enum(\\\"default\\\", \\\"listicle\\\", \\\"interview\\\")\",
\"include_quotes\": false,
\"openai_model\": \"\\\"gpt-4.1-mini\\\" Enum(\\\"gpt-4.1\\\", \\\"gpt-4.1-mini\\\", \\\"gpt-4.1-nano\\\")\",
\"use_reddit_comments\": true,
\"use_scraping\": false
}"
const url = new URL(
"http://localhost:8085/api/generate-preview"
);
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"source_ref_service": "\"google_news\"",
"source_ref_id": "\"e647eb4609d5313d7e6bd4568311a5f0\"",
"manual_prompt": "\"Write a blog post comparing electric vs. gasoline cars.\"",
"attached_urls": [
"https:\/\/example.com\/article1"
],
"focus_prompt": "\"Focus on the environmental impact for a general audience.\"",
"hyperlocal_context": "\"Assume the reader is familiar with local Halifax landmarks.\"",
"format": "\"listicle\" Enum(\"default\", \"listicle\", \"interview\")",
"include_quotes": false,
"openai_model": "\"gpt-4.1-mini\" Enum(\"gpt-4.1\", \"gpt-4.1-mini\", \"gpt-4.1-nano\")",
"use_reddit_comments": true,
"use_scraping": false
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'http://localhost:8085/api/generate-preview';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_TOKEN}',
'Content-Type' => 'application/json',
],
'json' => [
'source_ref_service' => '"google_news"',
'source_ref_id' => '"e647eb4609d5313d7e6bd4568311a5f0"',
'manual_prompt' => '"Write a blog post comparing electric vs. gasoline cars."',
'attached_urls' => [
'https://example.com/article1',
],
'focus_prompt' => '"Focus on the environmental impact for a general audience."',
'hyperlocal_context' => '"Assume the reader is familiar with local Halifax landmarks."',
'format' => '"listicle" Enum("default", "listicle", "interview")',
'include_quotes' => false,
'openai_model' => '"gpt-4.1-mini" Enum("gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano")',
'use_reddit_comments' => true,
'use_scraping' => false,
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
Example response (202, Request Accepted):
{
"message": "Content generation request accepted and queued.",
"request_id": "2c212ba1-e466-45dd-91bf-f296d486e641",
"batch_id": "9eb2653b-96b7-40a1-a3d3-682f386867f5"
}
Example response (401, Unauthenticated):
{
"message": "Unauthenticated."
}
Example response (402, Insufficient Credits (Paid User)):
{
"error": "Insufficient credits.",
"message": "This request requires 14 credits, but you only have 10 available."
}
Example response (403, Forbidden (Free User - Paid Feature/Model Attempted)):
{
"message": "Subscription Required.",
"error": "This feature or model requires an active paid subscription."
}
Example response (422, Validation Error):
{
"message": "The given data was invalid.",
"errors": {
"format": [
"The selected format is invalid."
],
"openai_model": [
"Invalid OpenAI model specified. Allowed models are: gpt-4.1, gpt-4.1-mini, gpt-4.1-nano."
]
}
}
Example response (429, Free Tier Limit Reached):
{
"message": "Free Limit Reached.",
"error": "You have reached your monthly limit for this free action. Please upgrade for continued access."
}
Example response (500, Server Error):
{
"message": "An internal server error occurred.",
"error": "Failed to process request due to an unexpected issue. Please try again later or contact support if the problem persists."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Results
Get Content Generation Result
requires authentication
Retrieves the status and results (including generated content, title options,
tags, and context suggestions) for a previously submitted content generation request,
identified by its unique requestId
. Poll this endpoint until the status
is 'completed' or 'failed'.
Example request:
curl --request GET \
--get "http://localhost:8085/api/results/"fd86d066-e784-4c1e-9c10-9d888003620f"" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--data "{
\"request_id\": \"66529e01-d113-3473-8d6f-9e11e09332ea\"
}"
const url = new URL(
"http://localhost:8085/api/results/"fd86d066-e784-4c1e-9c10-9d888003620f""
);
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"request_id": "66529e01-d113-3473-8d6f-9e11e09332ea"
};
fetch(url, {
method: "GET",
headers,
body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'http://localhost:8085/api/results/"fd86d066-e784-4c1e-9c10-9d888003620f"';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_TOKEN}',
'Content-Type' => 'application/json',
],
'json' => [
'request_id' => '66529e01-d113-3473-8d6f-9e11e09332ea',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
Example response (200, Completed):
{
"request_id": "fd86d066-e784-4c1e-9c10-9d888003620f",
"status": "completed",
"created_at": "2025-04-25T15:27:18.000000Z",
"completed_at": "2025-04-25T15:27:45.000000Z",
"results": {
"title_suggestions": [
"Headline Option 1: Generated Title One",
"Headline Option 2: Generated Title Two",
"Headline Option 3: Generated Title Three"
],
"generated_content_html": "<p>This is the generated article content...</p><p>More content...</p>",
"suggested_tags": [
"api",
"laravel",
"content generation"
],
"suggested_contexts": [
"Context suggestion 1",
"Context suggestion 2"
]
},
"error": null
}
Example response (200, Processing):
{
"request_id": "fd86d066-e784-4c1e-9c10-9d888003620f",
"status": "processing",
"created_at": "2025-04-25T15:27:18.000000Z",
"completed_at": null,
"results": null,
"error": null
}
Example response (200, Failed):
{
"request_id": "fd86d066-e784-4c1e-9c10-9d888003620f",
"status": "failed",
"created_at": "2025-04-25T15:27:18.000000Z",
"completed_at": "2025-04-25T15:27:25.000000Z",
"results": null,
"error": "AI service request failed. Status: 500"
}
Example response (400, Invalid Request ID Format):
{
"error": "API key/credentials not found for the specified source.",
"message": "Please add your credentials for 'currents' in your account settings.",
"source": "currents"
}
Example response (401, Unauthenticated):
{
"message": "Unauthenticated."
}
Example response (404, Not Found (Wrong ID or Not Owner)):
{
"message": "Generation result not found."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.