# CQGMA Spot API Documentation

## Overview

The CQGMA Spot API provides recent GMA and WWFF spots as JSON data.

It is intended for lightweight clients, dashboards, tools, spot displays and integrations.

Please use the API fairly. The service is provided by CQGMA.org and should not be polled more often than once per minute.

## Available Endpoints

| Purpose | URL |
|---|---|
| Last 10 spots | `https://www.cqgma.org/api/spots/10/` |
| Last 25 spots | `https://www.cqgma.org/api/spots/25/` |
| Last WWFF spots | `https://www.cqgma.org/api/spots/wwff/?key=GMA-1234-XXXXXXXXXX` |
| Last GMA-only spots | `https://www.cqgma.org/api/spots/gma/` |

## WWFF Endpoint

The main WWFF endpoint is:

```text
https://www.cqgma.org/api/spots/wwff/?key=GMA-1234-XXXXXXXXXX
```

This endpoint returns the current WWFF spot list as JSON.

The data is generated server-side from a cache file. The cache file is normally refreshed once per minute.

## API Key for WWFF Endpoint

The WWFF spot endpoint requires a valid API key.

API keys can be requested via cqGMA support:

```text
https://www.cqgma.org/support/contact.html
```

The API key can be supplied as URL parameter:

```text
https://www.cqgma.org/api/spots/wwff/?key=GMA-1234-XXXXXXXXXX
```

or as HTTP header:

```text
X-API-Key: GMA-1234-XXXXXXXXXX
```

Example API key format:

```text
GMA-1234-XXXXXXXXXX
```

## Fair Use Policy

Please do not poll the API more often than once per minute.

Limits:

```text
Maximum rate: 1 request per 60 seconds
Maximum daily requests: 1440 requests per client per UTC day
```

The daily limit includes all requests, including requests that receive a `429 Too Many Requests` response.

This means that aggressive polling will use up the daily request limit quickly, even if many of the requests are blocked.

## HTTP Caching

API responses are cacheable for 60 seconds.

Clients should respect these HTTP headers:

```text
Cache-Control: public, max-age=60
Expires: ...
Last-Modified: ...
ETag: ...
```

Recommended client behavior:

1. Poll no more than once per minute.
2. Respect `Retry-After` after receiving `429 Too Many Requests`.
3. Use HTTP caching with `ETag` and `Last-Modified` where possible.
4. Avoid parallel requests from the same IP address or same API key.
5. Do not request the same endpoint repeatedly in short intervals.

## Rate Limit Headers

Responses may include headers such as:

```text
X-RateLimit-Limit: 1440
X-RateLimit-Remaining: 1439
Retry-After: 60
```

If the request rate is too high, the server returns:

```text
HTTP/1.1 429 Too Many Requests
```

## Example 401 Response: Missing API Key

```json
{
  "ok": false,
  "error": "missing_api_key",
  "message": "An API key is required for this API.",
  "source": "CQGMA WWFF API",
  "website": "https://www.cqgma.org"
}
```

## Example 403 Response: Invalid API Key

```json
{
  "ok": false,
  "error": "invalid_api_key",
  "message": "The supplied API key is invalid or inactive.",
  "source": "CQGMA WWFF API",
  "website": "https://www.cqgma.org"
}
```

## Example 429 Response: Too Frequent Requests

```json
{
  "ok": false,
  "error": "rate limit exceeded",
  "message": "Please request CQGMA spots not more than once per minute.",
  "retry_after_seconds": 42,
  "limit_per_day": 1440,
  "remaining_today": 1438,
  "source": "CQGMA Spot API",
  "website": "https://www.cqgma.org",
  "info": "Free GMA and WWFF spot service provided by CQGMA.org. Please avoid excessive polling."
}
```

## Example 429 Response: Daily Limit Exceeded

```json
{
  "ok": false,
  "error": "daily limit exceeded",
  "message": "Maximum 1440 spot requests per day allowed. This limit includes successful and blocked requests.",
  "limit_per_day": 1440,
  "reset": "00:00 UTC",
  "source": "CQGMA Spot API",
  "website": "https://www.cqgma.org",
  "info": "Free GMA and WWFF spot service provided by CQGMA.org. Please use one request per minute maximum."
}
```

## JSON Structure

The exact JSON fields can change slightly depending on endpoint and spot type.

Common top-level fields:

| Field | Description |
|---|---|
| `SOURCE` | Source name of the feed |
| `RECORDS` | Number of returned spot records |
| `TIMESTAMP` | Unix timestamp of the feed generation |
| `RCD` | Array containing the spot records |

Common spot fields:

| Field | Description |
|---|---|
| `DATE` | Spot date |
| `TIME` | Spot time |
| `SPOTTER` | Callsign of the spotter |
| `ACTIVATOR` | Callsign of the activator |
| `REF` | Reference, for example GMA or WWFF reference |
| `NAME` | Name of the reference, summit, park or location |
| `LAT` | Latitude |
| `LON` | Longitude |
| `MODE` | Operating mode |
| `QRG` | Frequency |
| `TEXT` | Additional spot text or comment |

## PHP Example: WWFF Endpoint with API Key

This example reads the WWFF spot feed using an API key.

```php
<?php
// ------------------------------------------------------------
// PHP example: read WWFF JSON feed from CQGMA Spot API
// ------------------------------------------------------------

function GetWWFFjson($key)
{
    $url = "https://www.cqgma.org/api/spots/wwff/?key=" . urlencode($key);

    $stream = stream_context_create(array(
        'http' => array(
            'timeout' => 30
        )
    ));

    $json = file_get_contents($url, false, $stream);

    if ($json === false) {
        echo "Could not read CQGMA WWFF spot feed.";
        return;
    }

    $data = json_decode($json, true);

    if (!is_array($data)) {
        echo "Invalid JSON received.";
        return;
    }

    echo htmlspecialchars($data["SOURCE"]) . "<br>" . PHP_EOL;
    echo htmlspecialchars($data["RECORDS"]) . "<br>" . PHP_EOL;
    echo date('d/m/Y H:i:s', $data["TIMESTAMP"]) . "<br>" . PHP_EOL;
}

$key = "GMA-1234-XXXXXXXXXX";

GetWWFFjson($key);
?>
```

## PHP Example

This example reads the last 10 GMA spots and displays them in a simple HTML table.

```php
<?php
// ------------------------------------------------------------
// PHP example: read JSON feed from CQGMA Spot API
// ------------------------------------------------------------

function GetGMAjson()
{
    $url = "https://www.cqgma.org/api/spots/10/";

    $stream = stream_context_create(array(
        'http' => array(
            'timeout' => 30
        )
    ));

    $json = file_get_contents($url, false, $stream);

    if ($json === false) {
        echo "Could not read CQGMA spot feed.";
        return;
    }

    $data = json_decode($json, true);

    if (!is_array($data)) {
        echo "Invalid JSON received.";
        return;
    }

    echo htmlspecialchars($data["SOURCE"]) . "<br>" . PHP_EOL;
    echo htmlspecialchars($data["RECORDS"]) . "<br>" . PHP_EOL;
    echo date('d/m/Y H:i:s', $data["TIMESTAMP"]) . "<br>" . PHP_EOL;

    echo "<table border='1'>" . PHP_EOL;

    foreach ($data["RCD"] as $spot) {
        echo "<tr>";
        echo "<td>" . htmlspecialchars($spot["DATE"]) . "</td>" . PHP_EOL;
        echo "<td>" . htmlspecialchars($spot["TIME"]) . "</td>" . PHP_EOL;
        echo "<td>" . htmlspecialchars($spot["SPOTTER"]) . "</td>" . PHP_EOL;
        echo "<td>" . htmlspecialchars($spot["ACTIVATOR"]) . "</td>" . PHP_EOL;
        echo "<td>" . htmlspecialchars($spot["REF"]) . "</td>" . PHP_EOL;
        echo "<td>" . htmlspecialchars($spot["NAME"]) . "</td>" . PHP_EOL;
        echo "<td>" . htmlspecialchars($spot["LAT"]) . "</td>" . PHP_EOL;
        echo "<td>" . htmlspecialchars($spot["LON"]) . "</td>" . PHP_EOL;
        echo "<td>" . htmlspecialchars($spot["MODE"]) . "</td>" . PHP_EOL;
        echo "<td>" . htmlspecialchars($spot["QRG"]) . "</td>" . PHP_EOL;
        echo "<td>" . htmlspecialchars($spot["TEXT"]) . "</td>" . PHP_EOL;
        echo "</tr>" . PHP_EOL;
    }

    echo "</table>" . PHP_EOL;
}

GetGMAjson();
?>
```

## Notes for Client Developers

Please do not disable SSL certificate verification in production clients.

Older example code sometimes used disabled SSL verification for quick testing. For production tools, normal certificate verification should remain enabled.

If your client supports conditional requests, use `ETag` and `Last-Modified` to avoid unnecessary downloads.

For the WWFF endpoint, always send your API key either as `key` URL parameter or as `X-API-Key` HTTP header.

## Contact

CQGMA website:

```text
https://www.cqgma.org/support/contact.html
```

