Search by

Official PHP SDK for FreeAstroAPI: Western, Vedic and Chinese astrology, numerology, horoscopes and ephemeris over a typed REST client.

v0.1.0 2026-11-11 16:25 UTC

This package is auto-updated.

Last update: 2026-11-12 11:11:03 UTC


README

CI

Official PHP client for FreeAstroAPI: Western, Vedic and Chinese astrology, numerology, horoscopes, Moon data and ephemeris, with a typed method for every public endpoint.

  • PHP 8.1 or newer.
  • Any PSR-18 HTTP client (Guzzle, Symfony HttpClient, ...), found automatically with php-http/discovery.
  • Request and response shapes generated from the OpenAPI document as PHPStan/Psalm array shapes.

Install

composer require freeastroapi/sdk guzzlehttp/guzzle

Guzzle is the suggested client. Any PSR-18 client with PSR-17 factories works instead, for example composer require freeastroapi/sdk symfony/http-client nyholm/psr7.

Quick start

<?php

require 'vendor/autoload.php';

use FreeAstroApi\Sdk\FreeAstro;

$client = new FreeAstro(apiKey: 'YOUR_API_KEY'); // or set FREEASTRO_API_KEY and call new FreeAstro()

$cities = $client->geo->search(['q' => 'Paris', 'limit' => 3]);
echo $cities['results'][0]['timezone'];

Keep the API key on the server; never send it to browsers.

Examples

Natal chart

// POST /api/v1/natal/calculate?include_minor=true
$chart = $client->natal->calculate(
    ['year' => 1990, 'month' => 6, 'day' => 15, 'hour' => 14, 'minute' => 30, 'city' => 'Paris'],
    ['include_minor' => true],
);

Vedic panchang

// POST /api/v2/vedic/panchang
$panchang = $client->vedic->panchang([
    'year' => 2026,
    'month' => 1,
    'day' => 14,
    'hour' => 6,
    'minute' => 0,
    'city' => 'Delhi',
]);

Geo search

// GET /api/v2/geo/search?q=Lyon&limit=5
$result = $client->geo->search(['q' => 'Lyon', 'limit' => 5]);
foreach ($result['results'] as $city) {
    printf("%s (%s) %s\n", $city['name'], $city['country'], $city['timezone']);
}

Resources

Every operation in the OpenAPI document is a method on a resource property. Resources follow the API paths, exactly like the TypeScript and Python SDKs:

$client->natal->calculate($birth);                               // POST /api/v1/natal/calculate
$client->western->natal->insights($request);                     // POST /api/v1/western/natal/insights
$client->western->progressions->secondaryExactAspects($request); // POST /api/v1/western/progressions/secondary/exact-aspects
$client->chinese->calendar('2026-02-17', ['lang' => 'en']);      // GET /api/v1/chinese/calendar/{date}
$client->horoscope->daily->sign(['sign' => 'aries']);            // GET /api/v3/horoscope/daily/sign

Arguments are, in order: path parameters (as scalars), the JSON body (an array keyed by the API's field names), the query parameters (an array; null values are left out), and optional RequestOptions.

When an operation exists in several API versions, the plain method name calls the latest one and older versions carry a suffix: $client->vedic->chart() calls /api/v2/vedic/chart and $client->vedic->chartV1() calls /api/v1/vedic/chart; $client->horoscope->daily->signV1() / signV2() reach the older daily horoscopes. Deprecated operations are marked @deprecated.

Resource groups: ai, chinese, ephemeris, geo, horoscope, moon, natal, numerology, skyEvents, space, transits, vedic and western (with astrocartography, directions, electional, natal, progressions, signs, transits and more). FreeAstroApi\Sdk\Operations::ALL maps every operationId to its method.

Types

Bodies and responses are plain PHP arrays at runtime: responses are decoded JSON and are not validated, so new API fields never break your code. Their shapes are documented as type aliases in src/Schemas.php, which PHPStan, Psalm and PhpStorm use for checking and completion:

use FreeAstroApi\Sdk\Schemas;

/**
 * @phpstan-import-type PanchangRequestV2 from Schemas
 */
final class DailyPanchang
{
    /** @param PanchangRequestV2 $birth */
    public function today(array $birth): string { /* ... */ }
}

Every operation has a typed response: JSON operations return their response shape (for example MoonPhaseResponse), image and Markdown operations return string. The shapes describe what the API documents; the SDK never checks them at runtime, so a response with a missing field, a null, an extra field or a new enum value is returned unchanged instead of throwing.

A top-level empty body [] is sent as {}. PHP cannot tell an empty list from an empty object, so to send an empty JSON object inside a body use new \stdClass().

Images, Markdown and PDF

Operations that return another content type return the body as a string: SVG or Markdown text, or raw bytes for PNG and PDF.

$svg = $client->vedic->kpRender($birth + ['format' => 'svg']);
file_put_contents('chart.svg', $svg);

Other endpoints

Call anything else with the generic methods. request() decodes JSON and returns other bodies as strings; requestRaw() returns the PSR-7 response:

$data = $client->request('POST', '/api/v1/example', query: ['lang' => 'fr'], body: ['example' => true]);
$response = $client->requestRaw('GET', '/api/v1/example', accept: 'image/png');

Configuration

use FreeAstroApi\Sdk\FreeAstro;

$client = new FreeAstro(
    apiKey: 'YOUR_API_KEY',                    // default: FREEASTRO_API_KEY
    baseUrl: 'https://api.freeastroapi.com',   // default: FREEASTRO_BASE_URL, then this URL
    timeout: 20.0,                             // seconds
    maxRetries: 2,
    headers: ['X-Request-Source' => 'my-app'],
);

The timeout is applied when the SDK creates the HTTP client itself (Guzzle, or Symfony HttpClient when Guzzle is absent). To pass your own client, configure its timeout there:

$client = new FreeAstro(
    apiKey: 'YOUR_API_KEY',
    httpClient: new \GuzzleHttp\Client(['timeout' => 30, 'proxy' => 'http://proxy:8080']),
);

PSR-17 factories are discovered too; pass requestFactory: and streamFactory: to choose them.

Every request sends User-Agent: freeastroapi-php/<version> and the key in the x-api-key header.

Retries

Failed requests are retried automatically, up to maxRetries times (default 2), with exponential backoff and full jitter: a random wait of up to 0.5 s, then up to 1 s, 2 s, ... capped at 8 s.

  • 429 Too Many Requests is retried for every operation, since the API rejected the request before running it.
  • 408, 500, 502, 503, 504, network errors and timeouts are retried for every operation except the side-effecting ones listed in FreeAstroApi\Sdk\RetryPolicy::SIDE_EFFECTING_OPERATIONS: v1_natal_calculate (can create a paid report job), v1_ai_generate, v2_vedic_chat, v2_western_chat, v2_vedic_qa, v1_western_transits_timeline and v1_western_transits_insights. Those are retried only when you pass an idempotency key (sent as the Idempotency-Key header) or opt in with retry: true.
  • Other 4xx statuses (400, 401, 403, 404, 409, 422, ...) are never retried.

When the API says how long to wait, with a Retry-After header (seconds or an HTTP date) or retry_after_ms in the error body, the SDK waits exactly that long instead of the backoff. If that is more than 30 seconds it does not wait: it throws right away (RateLimitException for a 429) and getRetryAfter() returns the requested delay in seconds, so you can schedule the retry yourself.

Per-request settings override the defaults:

use FreeAstroApi\Sdk\RequestOptions;

$client->western->chat($message, new RequestOptions(idempotencyKey: 'chat-2026-01-14-001')); // retried safely
$client->natal->calculate($birth, options: new RequestOptions(retry: true));  // retry transient errors anyway
$client->geo->search(['q' => 'Paris'], new RequestOptions(retry: false));     // no retries at all, 429 included
$client->vedic->panchang($birth, new RequestOptions(maxRetries: 5));

Generic request() calls follow the same rules, matching side-effecting operations by method and path.

Errors

Every exception implements FreeAstroApi\Sdk\Exception\FreeAstroException. HTTP errors extend ApiException, which carries the status, raw body, decoded body (getDetails(), usually the spec's ErrorResponse: detail, error, retry_after_ms, ...), error code and request id. getErrorCode() returns the stable code from error, detail.error or code:

Status Exception
400, 422 ValidationException (getErrors() lists FastAPI field errors)
401 AuthenticationException
403 PermissionDeniedException
404 NotFoundException
409 ConflictException
429 RateLimitException (getRetryAfter(): Retry-After or retry_after_ms, in seconds)
5xx ServerException
other ApiException

NetworkException means no response arrived (DNS, connection, TLS, timeout); ConfigurationException means no API key or HTTP client was found.

use FreeAstroApi\Sdk\Exception\ApiException;
use FreeAstroApi\Sdk\Exception\RateLimitException;
use FreeAstroApi\Sdk\Exception\ValidationException;

try {
    $chart = $client->natal->calculate($birth);
} catch (ValidationException $e) {
    foreach ($e->getErrors() as $error) {
        echo implode('.', $error['loc']), ': ', $error['msg'], "\n";
    }
} catch (RateLimitException $e) {
    retryLater($e->getRetryAfter());
} catch (ApiException $e) {
    error_log(sprintf('%d %s (request %s)', $e->getStatusCode(), $e->getMessage(), $e->getRequestId() ?? '-'));
}

WordPress

The SDK works in WordPress plugins and themes. Install it with Composer in your plugin together with Guzzle or Symfony HttpClient (composer require freeastroapi/sdk guzzlehttp/guzzle), load vendor/autoload.php from the plugin, and keep the key in an option or wp-config.php constant rather than in front-end code. If other plugins ship different Guzzle versions, prefix your dependencies with a tool such as PHP-Scoper or Strauss, or pass any PSR-18 client you already bundle via httpClient:.

Development

The repository is the library plus an in-repo generator. src/Resources/, src/Schemas.php, src/Operations.php and src/ClientResources.php are generated from spec/freeastro.json by generator/generate.php (plain PHP 8.1, no dependencies); everything else is hand-written.

composer install
composer check          # generated code is current, PSR-12, PHPStan (level max), PHPUnit
composer test:contract  # every operation against a Prism mock (Node.js and Python 3)

Without a local Composer, run the same with Docker:

docker run --rm -v "$PWD":/app -w /app composer:2.10.3 composer install
docker run --rm -v "$PWD":/app -w /app php:8.1-cli vendor/bin/phpunit

To update the spec and regenerate:

python3 scripts/spec_sync.py fetch          # writes the normalised production spec to spec/freeastro.json
composer generate

The Sync spec workflow does this daily and opens a spec-sync/auto pull request when the API changes; Live smoke test calls production daily when the FREEASTRO_TEST_API_KEY secret is set. scripts/spec_sync.py, scripts/with_prism.py and tests/fixtures/synthetic-spec.json are shared with the other FreeAstroAPI SDKs.

Releasing

Bump FreeAstro::VERSION in src/FreeAstro.php, update CHANGELOG.md, then push a matching tag (git tag v0.1.0 && git push origin v0.1.0). The Release workflow runs the checks and creates the GitHub release; Packagist publishes the tag through its GitHub webhook.

Issues and contributions are welcome on GitHub.

Licensed under the Apache License 2.0.