StatusThai
สร้าง API key API พร้อมใช้งาน
API Docs
Base URL https://thai.craftcloudnet.com/api.php

เอกสาร StatusThai API

Weather, news and counters as simple JSON, with examples for HTML/JavaScript and PHP.

StatusThai API Documentation

Weather, news and counter API for HTML/JavaScript and PHP developers.

1. Overview

Base URL: https://thai.craftcloudnet.com/api.php

All responses are JSON (UTF-8). Every request needs an API key, which is free.

ResourceMethodPurpose
r=weatherGETCurrent weather from all TMD stations (data refreshed hourly)
r=newsGETLatest news from the Thai Meteorological Department feed (refreshed every 2 minutes)
r=counterGET / POSTRead or increment a named counter

2. Getting an API key

  1. Register or log in at /login.php.
  2. Open /dashboard.php and create a key. By default each member can have 3 keys.
  3. Copy the key immediately. It is shown only once, because only a hash is stored.

Key format: st_ followed by 40 hex characters, for example st_0123456789abcdef0123456789abcdef01234567.

Domain lock (optional): you can lock a key to a domain, for example mysite.com, which also allows www.mysite.com and other subdomains. Browser requests from any other domain are rejected with 403. Server-side requests (PHP, curl) send no Origin header, so they are not filtered by the lock.

3. Authentication

Send the key in the X-API-Key header (recommended) or as a key query parameter.

X-API-Key: st_your_key_here
GET https://thai.craftcloudnet.com/api.php?r=weather&key=st_your_key_here

4. Endpoints

4.1 Weather

GET /api.php?r=weather

ParameterRequiredDescription
provNoCase-insensitive text filter on province and station name, for example prov=เชียงใหม่. Always URL-encode it.

Example response:

{
  "updated": 1794221485,
  "source": "กรมอุตุนิยมวิทยา",
  "stations": [
    {
      "id": "48327",
      "name": "เชียงใหม่",
      "en": "Chiang Mai",
      "prov": "เชียงใหม่",
      "lat": 18.77,
      "lon": 98.97,
      "temp": 30.1,
      "max": 33.0,
      "min": 24.5,
      "rh": 70,
      "wind": 8.5,
      "wdir": 180,
      "rain": 0.0,
      "pres": 1010.2,
      "time": "2026-11-09 10:00:00"
    }
  ]
}
FieldMeaning
updatedUnix timestamp (seconds) of the last refresh from TMD
idWMO station number
name / enStation name in Thai / English
provProvince
lat, lonCoordinates
temp, max, minTemperature now, daily maximum, daily minimum (°C)
rhRelative humidity (%)
wind, wdirWind speed and direction (degrees)
rainRainfall (mm)
presSea-level pressure
timeObservation time as reported by TMD

Any measurement can be null when the station did not report it. Units follow the TMD source data.

4.2 News

GET /api.php?r=news&limit=5

ParameterRequiredDescription
limitNoNumber of items, 1 to 30. Without it you get everything the feed has (up to 30).
{
  "updated": 1794221485,
  "items": [
    {
      "title": "Headline",
      "link": "https://example.com/article",
      "img": "https://example.com/image.jpg",
      "desc": "Short plain-text summary (max about 220 characters)",
      "ts": 1794220000
    }
  ]
}

link and img are empty strings when the feed item has none. ts is the publish time as a Unix timestamp.

4.3 Counter

A counter is a named number belonging to your account. Typical use: page views.

MethodEffect
GET /api.php?r=counter&name=homeRead the value. Returns 404 if the counter does not exist yet.
POST /api.php?r=counter&name=homeAdd 1. Creates the counter on first use.
{ "name": "home", "n": 42 }

5. Errors

Errors return JSON with error (a code for your program) and message (Thai text for people).

{ "error": "invalid_key", "message": "API key ไม่ถูกต้องหรือถูกยกเลิก" }
HTTPerrorCause
304noneData unchanged since your If-None-Match ETag (empty body)
400bad_requestr is not weather, news or counter
400bad_nameInvalid counter name
401invalid_keyKey is wrong, revoked, or the owner is suspended
401key_requiredNo key sent (or counter without a key)
403domainBrowser request from a domain the key is not locked to
403counter_limitCounter quota reached
404not_foundCounter does not exist yet (send a POST to create it)
429limitRate limit exceeded. Read the Retry-After header or the retry field (seconds).
503no_dataNo cached data yet. Retry after 30 seconds.

6. Rate limits and caching

7. HTML / JavaScript examples

7.1 Minimal fetch

<pre id="out">Loading...</pre>
<script>
const API = 'https://thai.craftcloudnet.com/api.php';
const KEY = 'st_your_key_here';

fetch(API + '?r=weather&prov=' + encodeURIComponent('เชียงใหม่'), {
  headers: { 'X-API-Key': KEY }
})
  .then(r => r.json())
  .then(d => { document.getElementById('out').textContent = JSON.stringify(d, null, 2); })
  .catch(e => { document.getElementById('out').textContent = e.message; });
</script>

7.2 Reusable helper with error handling

<script>
const API = 'https://thai.craftcloudnet.com/api.php';
const KEY = 'st_your_key_here';

async function stApi(params, method = 'GET') {
  const url = API + '?' + new URLSearchParams(params).toString();
  const res = await fetch(url, { method, headers: { 'X-API-Key': KEY } });
  const body = await res.json().catch(() => ({}));
  if (!res.ok) {
    const err = new Error(res.status + ' ' + (body.error || '') + ': ' + (body.message || ''));
    err.status = res.status;
    err.retry = body.retry || Number(res.headers.get('Retry-After')) || 0;
    throw err;
  }
  return body;
}

// Weather for one province, shown as a list
stApi({ r: 'weather', prov: 'เชียงใหม่' })
  .then(d => {
    const ul = document.createElement('ul');
    d.stations.forEach(s => {
      const li = document.createElement('li');
      li.textContent = s.name + ': ' + (s.temp ?? '-') + ' °C';
      ul.appendChild(li);
    });
    document.body.appendChild(ul);
  })
  .catch(e => console.error(e.message));

// Latest 5 headlines
stApi({ r: 'news', limit: 5 }).then(d => console.log(d.items));

// Count a page view
stApi({ r: 'counter', name: 'home' }, 'POST').then(d => console.log('views:', d.n));
</script>

textContent is used on purpose. Use it instead of innerHTML when showing API data, so text from the news feed can never inject HTML.

7.3 Without a custom header (no preflight)

If a proxy or firewall blocks the preflight request, pass the key in the URL instead:

fetch(API + '?r=weather&key=' + KEY).then(r => r.json()).then(console.log);

8. PHP examples

PHP calls run on your server, so the key stays private. You need the curl extension.

8.1 Helper function (st_api.php)

<?php
const ST_API_URL = 'https://thai.craftcloudnet.com/api.php';
const ST_API_KEY = 'st_your_key_here'; // better: environment variable or a config file outside the web root

/**
 * Call the StatusThai API. Returns the decoded JSON array.
 * Throws RuntimeException on network errors or non-200 responses.
 */
function st_api(array $params, string $method = 'GET'): array
{
    $ch = curl_init(ST_API_URL . '?' . http_build_query($params));
    $opts = [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CONNECTTIMEOUT => 6,
        CURLOPT_TIMEOUT        => 15,
        CURLOPT_HTTPHEADER     => ['X-API-Key: ' . ST_API_KEY, 'Accept: application/json'],
    ];
    if ($method === 'POST') {
        $opts[CURLOPT_POST] = true;
        $opts[CURLOPT_POSTFIELDS] = '';
    }
    curl_setopt_array($ch, $opts);

    $body = curl_exec($ch);
    $code = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    $err  = curl_error($ch);
    curl_close($ch);

    if ($body === false) {
        throw new RuntimeException('cURL error: ' . $err);
    }
    $json = json_decode($body, true);
    if ($code !== 200 || !is_array($json)) {
        $msg = is_array($json) ? (($json['error'] ?? '') . ': ' . ($json['message'] ?? '')) : 'invalid response';
        throw new RuntimeException('HTTP ' . $code . ' ' . $msg, $code);
    }
    return $json;
}

8.2 Weather

<?php
require 'st_api.php';

try {
    $data = st_api(['r' => 'weather', 'prov' => 'เชียงใหม่']);
    foreach ($data['stations'] as $s) {
        echo htmlspecialchars($s['name']) . ': ' . ($s['temp'] ?? '-') . " °C\n";
    }
} catch (RuntimeException $e) {
    echo 'Error: ' . htmlspecialchars($e->getMessage());
}

8.3 News

<?php
$news = st_api(['r' => 'news', 'limit' => 5]);
foreach ($news['items'] as $n) {
    echo '<a href="' . htmlspecialchars($n['link']) . '">' . htmlspecialchars($n['title']) . "</a><br>\n";
}

8.4 Counter

<?php
$views = st_api(['r' => 'counter', 'name' => 'home'], 'POST'); // +1
echo 'Page views: ' . (int) $views['n'];

8.5 Safe pattern for a public page: PHP proxy with a cache

Browser JavaScript on your own site calls this file, so the key never reaches the visitor. The file cache keeps you far below the rate limit.

<?php
// weather-proxy.php on YOUR site
require 'st_api.php';
header('Content-Type: application/json; charset=utf-8');

$prov = substr(trim((string) ($_GET['prov'] ?? '')), 0, 60);
$cache = sys_get_temp_dir() . '/weather_' . md5($prov) . '.json';

if (is_file($cache) && time() - filemtime($cache) < 300) { // 5 minutes
    readfile($cache);
    exit;
}
try {
    $params = ['r' => 'weather'] + ($prov !== '' ? ['prov' => $prov] : []);
    $json = json_encode(st_api($params), JSON_UNESCAPED_UNICODE);
    file_put_contents($cache, $json, LOCK_EX);
    echo $json;
} catch (RuntimeException $e) {
    if (is_file($cache)) { readfile($cache); exit; } // serve stale data on failure
    http_response_code(502);
    echo json_encode(['error' => 'upstream']);
}
<script>
fetch('/weather-proxy.php?prov=' + encodeURIComponent('เชียงใหม่'))
  .then(r => r.json())
  .then(console.log);
</script>

9. Security best practices

10. Troubleshooting

SymptomLikely cause and fix
Browser shows NetworkError / CORS error with status 404Wrong host. Use thai.craftcloudnet.com, not craftcloudnet.com.
401 invalid_keyKey typed wrongly, revoked, or the account is suspended. Create a new key in the dashboard.
401 key_requiredThe header was not received. Check the header name X-API-Key, or use ?key=.
403 domainThe key is locked to another domain. A page opened from disk (file://) sends Origin: null, which fails a domain lock. Test from your real domain or clear the lock.
429 limitToo many requests. Wait for Retry-After seconds and cache results on your side.
503 no_dataThe server has no cached data yet. Retry after 30 seconds.
Browser blocks the request before it is sentPage is http:// while the API is https:// (mixed content), or an ad blocker or privacy setting is interfering.
Works in the address bar but not in your scriptOpen developer tools (F12), check the Network tab for the status code and read the JSON message in the response body.