Skip to content
96Astro API

Blog

Using the 96Astro API from Laravel

· 96Astro API team · Guides

A small Laravel service class with the key on the server, safe retries with idempotency keys, error handling by code, and caching.

The 96Astro API is plain HTTPS and JSON, so Laravel's built-in HTTP client is all you need. This guide sets up a small service class with the key kept on the server, retries that are safe to repeat, and error handling that uses the API's stable error codes.

1. Keep the key in the environment

Create a sandbox key in the dashboard (Keys), and put it in .env. Never put it in JavaScript sent to the browser or in a mobile app.

ASTRO_API_KEY=sk_test_...
ASTRO_API_URL=https://api.96astroapi.com/v1

Expose it through config/services.php:

'astro96' => [
    'key' => env('ASTRO_API_KEY'),
    'url' => env('ASTRO_API_URL', 'https://api.96astroapi.com/v1'),
],

2. A small client

<?php

namespace App\Services;

use Illuminate\Http\Client\PendingRequest;
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;

class Astro96
{
    private function http(): PendingRequest
    {
        return Http::baseUrl(config('services.astro96.url'))
            ->withToken(config('services.astro96.key'))
            ->acceptJson()
            ->timeout(30);
    }

    /** POST /v1/{family}/{capability} and return the response's `data`. */
    public function call(string $family, string $capability, array $body): array
    {
        // One key per logical request: a retry with the same key is never charged twice.
        $idempotencyKey = (string) Str::uuid();

        $response = $this->http()
            ->withHeaders(['Idempotency-Key' => $idempotencyKey])
            ->retry(3, function (int $attempt, \Throwable $e) {
                // Wait as long as the API asks (Retry-After), else back off.
                $after = $e instanceof RequestException ? $e->response->header('Retry-After') : null;
                return $after !== null && $after !== '' ? (int) $after * 1000 : 500 * $attempt;
            }, function (\Throwable $e) {
                // Retry only what the API marks as retryable (rate limits, brief overloads).
                return $e instanceof RequestException
                    && ($e->response->json('error.retryable') === true
                        || $e->response->json('error.code') === 'rate_limited');
            })
            ->post("/{$family}/{$capability}", $body);

        return $response->json('data');
    }
}

The Idempotency-Key header matters: if a timeout makes you send the same call again, the API returns the first result instead of computing and charging it twice. Keep the same key for every retry of one logical request, which is why it is created once, outside the retry.

3. Call a capability

$planets = app(\App\Services\Astro96::class)->call('vedic-charts', 'planet-positions', [
    'datetime'  => '1990-10-18T14:30:00',
    'timezone'  => 'Asia/Kolkata',
    'latitude'  => 28.6139,
    'longitude' => 77.2090,
    'ayanamsha' => 'lahiri',
]);

foreach ($planets['planets'] as $p) {
    echo "{$p['name']}: {$p['sign']} {$p['degree']}°, {$p['nakshatra']} pada {$p['pada']}\n";
}

Send the local birth time with an IANA time zone (Asia/Kolkata, Europe/London), never a numeric offset: the API resolves historical offsets and daylight saving for the date. If a local time happened twice (clocks went back), the API answers 422 ambiguous_local_time and lists the choices, so ask the user rather than guessing.

4. Handle errors by code

Every error has the same shape: error.code (stable, for your code), error.message (for people), error.field (the input at fault) and error.request_id. With retry() configured, Laravel throws a RequestException for any error response that is not retried, or still fails after the last retry; branch on the code:

use Illuminate\Http\Client\RequestException;
use Illuminate\Validation\ValidationException;

try {
    $data = $astro->call('panchang', 'daily-panchang', $body);
} catch (RequestException $e) {
    $error = $e->response->json('error');
    match ($error['code'] ?? null) {
        'invalid_request', 'unsupported_option' => throw ValidationException::withMessages([
            $error['field'] ?? 'request' => $error['message'],
        ]),
        'quota_exhausted' => report('Astro API credits exhausted'),
        default => report("Astro API error {$error['code']} ({$error['request_id']})"),
    };
}

Log the request_id; quote it if you contact support. The errors guide lists every code and what to do about it.

5. Cache what does not change

A birth chart never changes for the same input. Cache it, keyed on the request body:

$key = 'astro96:'.sha1(json_encode([$family, $capability, $body]));
$data = Cache::rememberForever($key, fn () => $astro->call($family, $capability, $body));

Panchang and daily horoscopes change by date and place, so cache them until the end of the day at that place instead of forever. Caching saves credits and makes your pages faster.

6. Queue the heavy work

PDF reports run as background jobs: you create the job, then poll it or receive a signed webhook when it is done. In Laravel, dispatch a queued job that creates the PDF job and stores its ID, and handle the webhook in a controller. The jobs and webhooks guide has the request and the signature check.

Next steps

  • SDKs: the official PHP client has these retries, idempotency keys and typed errors built in, plus a Laravel service provider and facade (composer require 96astro/api, publishing to Packagist soon).

  • Quickstart: your first call with cURL.

  • API reference: every capability's fields.

  • Credits and rate limits: what each call costs and the headers that tell you where you are.