MENU navbar-image

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."
}
 

Request      

POST api/fetch-posts

Headers

Authorization      

Example: Bearer {YOUR_API_TOKEN}

Content-Type      

Example: application/json

Body Parameters

source   string   

The source service to fetch from. Example: "google_news" Enum("google_news", "google_local_search")

topic   string   

The search keywords or query. For google_local_search, include the location within this query (e.g., "coffee shops in Halifax"). Example: "AI advancements"

limit   integer  optional  

optional Maximum number of posts/results to return (1-20). Defaults to 10. Example: 5

after   string  optional  

optional Pagination cursor/token (currently not supported by active sources).

country   string  optional  

optional The 2-letter country code (ISO 3166-1 alpha-2) to search within. Applies only to google_news source. Defaults to 'us' (United States) if omitted. Example: "us"

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."
}
 

Request      

POST api/generate-preview

Headers

Authorization      

Example: Bearer {YOUR_API_TOKEN}

Content-Type      

Example: application/json

Body Parameters

source_ref_service   string  optional  

optional The source service (google_news, google_local_search) of a previously fetched post to use as the primary input. Required if source_ref_id is provided. Cannot be used with manual_prompt. Example: "google_news"

source_ref_id   string  optional  

optional The unique ID of a previously fetched post, found in the /fetch-posts response source_meta.id. Required if source_ref_service is provided. Cannot be used with manual_prompt. Example: "e647eb4609d5313d7e6bd4568311a5f0"

manual_prompt   string  optional  

optional Your primary prompt or instructions for the AI. Required if source_ref_id and source_ref_service are NOT provided. Max 5000 chars. Example: "Write a blog post comparing electric vs. gasoline cars."

attached_urls   string[]  optional  

optional An array of up to 5 external URLs to use as additional source content. Scraping these URLs requires a paid subscription AND use_scraping=true.

*   string  optional  

required_with:attached_urls A valid URL. Example: "https://www.cbc.ca/news/canada/nova-scotia"

focus_prompt   string  optional  

optional Specific angle, tone, target audience, or focus instructions for the AI. Max 1000 chars. Example: "Focus on the environmental impact for a general audience."

hyperlocal_context   string  optional  

optional Additional background context string for the AI to consider. Max 2000 chars. Example: "Assume the reader is familiar with local Halifax landmarks."

format   string   

The desired HTML output format. Example: "listicle" Enum("default", "listicle", "interview")

include_quotes   boolean  optional  

optional Whether the AI should attempt to include quotes from fetched comments (N/A currently) or scraped text (true/false). Defaults to true if omitted. Example: false

openai_model   string   

The OpenAI model to use for generation. Free tier users can only use the configured free model (e.g., 'gpt-4.1-nano'). Example: "gpt-4.1-mini" Enum("gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano")

use_reddit_comments   boolean  optional  

optional Deprecated (Reddit source disabled). Whether to fetch and include comments from a referenced Reddit post. Defaults to true if omitted. Example: true

use_scraping   boolean  optional  

optional Whether to scrape content from attached_urls and/or the external URL linked in a referenced post using Diffbot (true/false). Requires a paid subscription. Defaults to true if omitted. Setting to false can save credits and time if source text isn't needed. Example: false

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/&quot;fd86d066-e784-4c1e-9c10-9d888003620f&quot;" \
    --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/&quot;fd86d066-e784-4c1e-9c10-9d888003620f&quot;"
);

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."
}
 

Request      

GET api/results/{requestId}

Headers

Authorization      

Example: Bearer {YOUR_API_TOKEN}

Content-Type      

Example: application/json

URL Parameters

requestId   string   

The UUID of the generation request (returned by /generate-preview). Example: "fd86d066-e784-4c1e-9c10-9d888003620f"

Body Parameters

request_id   string   

Must be a valid UUID. Example: 66529e01-d113-3473-8d6f-9e11e09332ea