Search by

PHP SDK for Headless.Blog API

dev-main 2026-08-08 17:25 UTC

This package is auto-updated.

Last update: 2026-09-08 17:41:27 UTC


README

Welcome to the official Headless.Blog PHP Developer Guide. This SDK provides a high-performance, strictly typed, multi-lingual, and completely database-agnostic interface for PHP applications, Laravel frameworks, decoupled monoliths, or any server-side integrations to consume blog content.

By utilizing this SDK, your backend or frontend application acts purely as a headless consumer. All complex business logic, multi-tenant security, taxonomy grouping, vector searches, multi-language translation group linkages, CDN optimizations, and data aggregation are handled securely by the Headless.Blog backend.

Installation

Install the package via Composer. The SDK requires PHP 8.2+ and uses Guzzle for HTTP requests.

composer require headlessblog/sdk

Initialization & Core Architecture

1. Strict Token Authentication & Configuration

The Headless API relies on strict API token authentication to isolate multi-tenant data. Initialize the SDK using the Config object:

use HeadlessBlog\Sdk\Config;
use HeadlessBlog\Sdk\Client;

// Initialize Configuration with your active API key from the SaaS Dashboard
$config = new Config(
    apiKey: 'your_api_token_here',
    baseUrl: 'https://api.headless.blog/v1', // Optional, defaults to this URL
    timeout: 30,                             // Optional, timeout in seconds
    defaultLocale: 'en'                      // Optional, default fallback language
);

// Instantiate the SDK Client
$client = new Client($config);

Equal-Peer Multi-Language (i18n) Architecture

The Headless.Blog API implements an Equal-Peer Multi-Language Architecture anchored on shared translation group UUIDs (translation_group_id).

1. Locale Resolution Order

When requesting localized content from any endpoint, the target locale is resolved using the following hierarchy:

  1. Explicit Query Parameter: Passed directly to resource methods (e.g. $client->posts->get($id, locale: 'es') or $client->bootstrap->get(locale: '*') for wildcard).
  2. HTTP Accept-Language Header: Automatically managed when you set a default locale on Config or call $client->setLocale('es').
  3. Tenant Default Language Fallback: Automatically falls back to the tenant's defaultLocale if no locale is matched.

2. Setting Active Locale Dynamically (e.g., in Middleware)

In web applications (Laravel middleware, Symfony request listeners, custom routers), you can set the user's active locale dynamically on the client instance:

// Option A: Set locale globally on the current client instance
$client->setLocale('es');

// Retrieve currently active locale
$currentLocale = $client->getLocale(); // 'es'

// Option B: Immutably clone the client with a new locale
// (Recommended for long-lived application runtimes like Laravel Octane, Swoole, RoadRunner)
$spanishClient = $client->withLocale('es');

3. Response Meta Envelope & Alternate Translations

Response objects from collection/aggregate endpoints include a meta envelope detailing locale resolution. Individual content items contain an availableTranslations array referencing alternate language versions:

$posts = $client->posts->list(['limit' => 10]);

// Access Meta Envelope
$meta = $posts['meta'];
// [
//     'defaultLocale' => 'en',
//     'supportedLocales' => ['en', 'es', 'hu'],
//     'resolvedLocale' => 'es',
//     'requestedLocale' => 'es',
//     'source' => 'query'
// ]

// Access Entity Alternate Translations
$post = $posts['posts'][0];
$translations = $post['availableTranslations'];
// [
//     [
//         'id' => 'post-uuid-en',
//         'locale' => 'en',
//         'title' => 'Understanding Headless Architecture',
//         'slug' => 'understanding-headless-architecture',
//         'url_link' => 'https://myblog.com/blog/understanding-headless-architecture'
//     ]
// ]

4. Building Language Switchers & hreflang Head Tags

Using availableTranslations and meta['supportedLocales'], you can easily render UI language selectors and SEO header tags:

// Render HTML hreflang tags for SEO
foreach ($post['availableTranslations'] as $translation) {
    echo sprintf(
        '<link rel="alternate" hreflang="%s" href="%s" />' . "\n",
        htmlspecialchars($translation['locale']),
        htmlspecialchars($translation['url_link'])
    );
}

Global Error Handling & Exceptions

All API errors throw specific exceptions extending HeadlessBlog\Sdk\Exception\HeadlessBlogException.

BadRequestException (HTTP 400)

Thrown when query parameters or body payloads fail backend validation.

try {
    $client->posts->list(['limit' => 500]); // Exceeds max limit
} catch (\HeadlessBlog\Sdk\Exception\BadRequestException $e) {
    echo $e->getMessage();
    print_r($e->getDetails()); // Specific field validation failures
}

UnauthorizedException (HTTP 401 / 403)

Thrown when the API token is missing, invalid, expired, or restricted by plan limits.

RateLimitException (HTTP 429)

Thrown when client IP exceeds sliding-window rate limits.

ServerException (HTTP 500+)

Thrown on catastrophic server or database errors.

Endpoint Reference Guide

All resource methods return parsed associative arrays.

1. Application Bootstrap & Navigation

Delivers foundational categories, tags, multi-dimensional taxonomies, and post types filtered by locale.

Method:

// @param string|null $locale Optional ISO language code or '*' for all
$bootstrap = $client->bootstrap->get(locale: 'es');

Response Schema:

[
    'meta' => [
        'defaultLocale' => 'en',
        'supportedLocales' => ['en', 'es', 'hu'],
        'resolvedLocale' => 'es',
        'requestedLocale' => 'es',
        'source' => 'query'
    ],
    'categories' => [
        [
            'id' => 12,
            'translationGroupId' => 'tg-cat-01',
            'locale' => 'es',
            'parentId' => null,
            'name' => 'Recetas',
            'slug' => 'recetas',
            'url_link' => 'https://myblog.com/blog/category/recetas',
            'description' => 'Recetas deliciosas',
            'imagePath' => 'https://cdn.headless.blog/...',
            'position' => 0,
            'availableTranslations' => [
                ['locale' => 'en', 'slug' => 'recipes', 'name' => 'Recipes']
            ]
        ]
    ],
    'tags' => [ /* Localized Tags */ ],
    'taxonomies' => [ /* Localized Taxonomies */ ],
    'postTypes' => [ /* Localized Post Types */ ]
]

2. Website Settings

Delivers general website settings and multi-language configuration.

Method:

$settings = $client->settings->get();

Response Schema:

[
    'id' => 'uuid',
    'websiteName' => 'My Blog',
    'websiteBaseUrl' => 'https://myblog.com',
    'blogLandingPageUrl' => 'blog',
    'blogLandingPageTitle' => 'Read Our Blog',
    'metaSeoKeywords' => 'blog, kitchen, travel',
    'highlightedCategoryId' => 5,
    'semanticSearchHeadlessApi' => true,
    'semanticSearchWebsite' => true,
    'defaultLocale' => 'en',
    'supportedLocales' => ['en', 'es', 'hu']
]

3. Consolidated Homepage Data

Aggregates localized hero, recent, featured, and favorite post sections in a single response to eliminate UI waterfall loading.

Method:

// @param string|null $locale Optional ISO language code
$homeData = $client->home->get(locale: 'es');

Response Schema:

[
    'meta' => [ /* Resolution Meta */ ],
    'section-hero' => [ /* Post object */ ],
    'section-recent' => [ /* Array of 5 MobilePostSnippets */ ],
    'section-featured' => [ /* Array of 6 MobilePostSnippets */ ],
    'section-favorite' => [ /* Array of Curated Highlights */ ]
]

4. Testimonials API

Serves a paginated list of enabled customer testimonials filtered by locale.

Method:

// @param int $page Page number (default: 1)
// @param int $limit Items per page (default: 10, max: 50)
// @param string|null $locale Optional ISO language code
$testimonials = $client->testimonials->list(page: 1, limit: 10, locale: 'es');

Response Schema:

[
    'meta' => [ /* Resolution Meta */ ],
    'pagination' => ['totalItems' => 12, 'totalPages' => 2, 'currentPage' => 1, 'limit' => 10],
    'testimonials' => [
        [
            'id' => 'testim-uuid-es',
            'translationGroupId' => 'tg-testim-01',
            'locale' => 'es',
            'authorName' => 'Carlos Mendoza',
            'authorTitle' => 'Chef Ejecutivo',
            'authorCompany' => 'Restaurante Madrid',
            'avatarUrl' => 'https://cdn.headless.blog/...',
            'quote' => 'Excelente plataforma headless.',
            'rating' => 5,
            'createdAt' => '2026-08-01T10:00:00Z',
            'availableTranslations' => [
                ['id' => 'testim-uuid-en', 'locale' => 'en', 'authorName' => 'Carlos Mendoza']
            ]
        ]
    ]
]

5. Hybrid Search API

Executes high-speed semantic vector search or classic SQL keyword search strictly filtered by locale.

Method:

// @param string $query Required search string
// @param string|null $locale Optional ISO language code
$results = $client->search->query(query: 'Cómo Hornear Pan', locale: 'es');

Response Schema:

[
    [
        'id' => 'post-uuid-es',
        'title' => 'Cómo Hornear Pan',
        'slug' => 'como-hornear-pan',
        'url_link' => 'https://myblog.com/blog/como-hornear-pan',
        'excerpt' => 'Guía para principiantes.',
        'featuredImagePathThumbnail' => 'https://cdn.headless.blog/...',
        'contentType' => 'markdown',
        'categories' => [['id' => 12, 'name' => 'Recetas', 'slug' => 'recetas', 'url_link' => 'https://...']],
        'tags' => [['name' => 'Panadería', 'slug' => 'panaderia', 'url_link' => 'https://...']]
    ]
]

6. Posts Index & Filtering

Serves paginated lists of blog posts with availableTranslations metadata.

Method:

// @param array $filters Key-value parameters (page, limit, category, tag, taxonomyGroup, taxonomyTerm, postType, locale)
$postsData = $client->posts->list([
    'page' => 1,
    'limit' => 12,
    'category' => 'recetas',
    'locale' => 'es'
]);

Response Schema:

[
    'meta' => [ /* Resolution Meta */ ],
    'pagination' => ['totalItems' => 42, 'totalPages' => 4, 'currentPage' => 1, 'limit' => 12],
    'posts' => [
        [
            'id' => 'post-uuid-es',
            'translationGroupId' => 'tg-post-100',
            'locale' => 'es',
            'title' => 'Arquitectura Headless Explicada',
            'slug' => 'arquitectura-headless-explicada',
            'url_link' => 'https://myblog.com/blog/arquitectura-headless-explicada',
            'excerpt' => 'Un resumen ejecutivo.',
            'featuredImagePath' => 'https://cdn.headless.blog/...',
            'publishedAt' => '2026-08-08T12:00:00Z',
            'categories' => [ /* MobileCategorySnippets */ ],
            'tags' => [ /* MobileTagSnippets */ ],
            'availableTranslations' => [
                [
                    'id' => 'post-uuid-en',
                    'locale' => 'en',
                    'title' => 'Understanding Headless Architecture',
                    'slug' => 'understanding-headless-architecture',
                    'url_link' => 'https://myblog.com/blog/understanding-headless-architecture'
                ]
            ]
        ]
    ]
]

7. Post Details

Fetches comprehensive article details, SEO metadata, FAQs, and alternate translations array.

Methods:

// @param string $id Lookup by post UUID
// @param string|null $locale Optional ISO language code
$post = $client->posts->get(id: 'post-uuid-es', locale: 'es');

// @param string $slug Lookup by URL slug
// @param string|null $locale Optional ISO language code
$post = $client->posts->getBySlug(slug: 'arquitectura-headless-explicada', locale: 'es');

8. Comments Engine

Retrieves or submits comments for a specific post.

Retrieve Comments:

$comments = $client->posts->getComments('post-uuid-es');

Submit a Comment:

$response = $client->posts->addComment('post-uuid-es', [
    'content'     => '¡Excelente artículo!',
    'authorName'  => 'Maria Garcia',
    'authorEmail' => 'maria@example.com',
    'parentId'    => null
]);

9. Taxonomies & Metadata

Categories & Tags:

$categories = $client->categories->list(locale: 'es');
$category   = $client->categories->getBySlug('recetas', locale: 'es');

$tags = $client->tags->list(locale: 'es');
$tag  = $client->tags->getBySlug('sin-azucar', locale: 'es');

Taxonomies:

$taxonomies = $client->taxonomies->list(locale: 'es');
$group      = $client->taxonomies->getByGroupSlug('dificultad', locale: 'es');
$term       = $client->taxonomies->getByTermSlug('dificultad', 'facil', locale: 'es');

Post Types & Content Types:

$postTypes = $client->postTypes->list(locale: 'es');
$contentTypes = $client->contentTypes->list();

10. Newsletter Subscription

Registers a subscriber to the tenant's mailing list.

Method:

$response = $client->newsletter->subscribe(email: 'user@example.com', name: 'Maria Garcia');

11. Multi-Lingual Sitemap Generation

Generates standard sitemap items with alternates array grouping multi-lingual versions sharing the same translation_group_id.

Method:

$sitemapData = $client->sitemap->list();

Response Schema:

[
    [
        'url' => 'https://myblog.com/blog/arquitectura-headless-explicada',
        'locale' => 'es',
        'translationGroupId' => 'tg-post-100',
        'lastModified' => '2026-08-08T12:00:00Z',
        'changeFrequency' => 'weekly',
        'priority' => 0.7,
        'alternates' => [
            ['locale' => 'en', 'url' => 'https://myblog.com/blog/understanding-headless-architecture']
        ]
    ]
]

12. New Posts Since

Returns count of newly published posts since a given ISO timestamp.

Method:

$result = $client->posts->getNewPostsSince('2026-08-01T00:00:00Z');

Contributing & Development

Local Setup & Testing

  1. Clone the repository:
    git clone https://github.com/headlessblog/php-sdk.git
    cd php-sdk
  2. Install dependencies:
    composer install
  3. Run the unit test suite:
    php test/sdk_unit_test.php
  4. Run integration tests with a live API key:
    HEADLESS_BLOG_API_KEY="your_api_key" php test/api_test.php

License

This SDK is open-sourced software licensed under the MIT license.