Community examples

Tide API examples for real client apps.

Start with small functions that perform one request, handle HTTP and API errors, and return the relevant WorldTides array.

Code

API examples by language

Each example keeps the integration surface small: one reusable client, one shared request method, and focused functions for tide extremes and sampled heights.

import Foundation

enum WorldTidesError: Error {
    case badURL, http(Int), api(String)
}

struct TideExtreme: Codable {
    let dt: Int
    let date: String?
    let height: Double
    let type: String
}

struct TideSample: Codable {
    let dt: Int
    let date: String?
    let height: Double
}

struct TideResponse: Codable {
    let status: Int?
    let error: String?
    let extremes: [TideExtreme]?
    let heights: [TideSample]?
}

final class WorldTides {
    let key: String
    let lat: Double
    let lon: Double
    let base = URL(string: "https://www.worldtides.info/api/v3")!

    init(key: String, lat: Double, lon: Double) {
        self.key = key
        self.lat = lat
        self.lon = lon
    }

    func getExtremes(year: Int, month: Int, day: Int, numDays: Int) async throws -> [TideExtreme] {
        try await request(flag: "extremes", year: year, month: month, day: day, numDays: numDays).extremes ?? []
    }

    func getSamples(year: Int, month: Int, day: Int, numDays: Int) async throws -> [TideSample] {
        try await request(flag: "heights", year: year, month: month, day: day, numDays: numDays).heights ?? []
    }

    private func request(flag: String, year: Int, month: Int, day: Int, numDays: Int) async throws -> TideResponse {
        var parts = URLComponents(url: base, resolvingAgainstBaseURL: false)!
        parts.queryItems = [
            URLQueryItem(name: flag, value: nil),
            URLQueryItem(name: "date", value: String(format: "%04d-%02d-%02d", year, month, day)),
            URLQueryItem(name: "days", value: "\(numDays)"),
            URLQueryItem(name: "lat", value: "\(lat)"),
            URLQueryItem(name: "lon", value: "\(lon)"),
            URLQueryItem(name: "key", value: key)
        ]
        guard let url = parts.url else { throw WorldTidesError.badURL }

        let (data, response) = try await URLSession.shared.data(from: url)
        let code = (response as? HTTPURLResponse)?.statusCode ?? 0
        guard (200..<300).contains(code) else { throw WorldTidesError.http(code) }

        let decoded = try JSONDecoder().decode(TideResponse.self, from: data)
        if let status = decoded.status, status != 200 {
            throw WorldTidesError.api(decoded.error ?? "WorldTides error")
        }
        return decoded
    }
}

@main
struct App {
    static func main() async {
        do {
            let tides = WorldTides(key: "<apiKey>", lat: 33.768321, lon: -118.195617)
            let extremes = try await tides.getExtremes(year: 2026, month: 7, day: 1, numDays: 3)
            let samples = try await tides.getSamples(year: 2026, month: 7, day: 1, numDays: 3)

            print("Extremes: \(extremes.count)")
            print("Samples: \(samples.count)")
        } catch {
            print("WorldTides error: \(error)")
        }
    }
}
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import java.net.URI
import java.net.URLEncoder
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.nio.charset.StandardCharsets

@Serializable
data class TideExtreme(
    val dt: Long,
    val height: Double,
    val type: String,
    val date: String? = null,
)

@Serializable
data class TideSample(
    val dt: Long,
    val height: Double,
    val date: String? = null,
)

@Serializable
data class TideResponse(
    val status: Int? = null,
    val error: String? = null,
    val extremes: List<TideExtreme>? = null,
    val heights: List<TideSample>? = null,
)

class WorldTides(private val key: String, private val lat: Double, private val lon: Double) {
    private val base = "https://www.worldtides.info/api/v3"
    private val client = HttpClient.newHttpClient()
    private val json = Json { ignoreUnknownKeys = true }

    fun getExtremes(year: Int, month: Int, day: Int, numDays: Int): List<TideExtreme> =
        request("extremes", year, month, day, numDays).extremes.orEmpty()

    fun getSamples(year: Int, month: Int, day: Int, numDays: Int): List<TideSample> =
        request("heights", year, month, day, numDays).heights.orEmpty()

    private fun request(flag: String, year: Int, month: Int, day: Int, numDays: Int): TideResponse {
        val date = "%04d-%02d-%02d".format(year, month, day)
        val url = "$base?$flag&date=${enc(date)}&days=$numDays&lat=$lat&lon=$lon&key=${enc(key)}"
        val httpRequest = HttpRequest.newBuilder(URI.create(url)).GET().build()
        val httpResponse = client.send(httpRequest, HttpResponse.BodyHandlers.ofString())
        if (httpResponse.statusCode() !in 200..299) {
            error("HTTP ${httpResponse.statusCode()}: ${httpResponse.body()}")
        }
        val decoded = json.decodeFromString<TideResponse>(httpResponse.body())
        if (decoded.status != null && decoded.status != 200) {
            error(decoded.error ?: "WorldTides error")
        }
        return decoded
    }

    private fun enc(value: String) =
        URLEncoder.encode(value, StandardCharsets.UTF_8)
}

fun main() {
    val tides = WorldTides("<apiKey>", 33.768321, -118.195617)
    val extremes = tides.getExtremes(2026, 7, 1, 3)
    val samples = tides.getSamples(2026, 7, 1, 3)

    println("Extremes: ${extremes.size}")
    println("Samples: ${samples.size}")
}
curl -fsS "https://www.worldtides.info/api/v3?extremes&date=2026-07-01&days=3&lat=33.768321&lon=-118.195617&key=<apiKey>"

curl -fsS "https://www.worldtides.info/api/v3?heights&date=2026-07-01&days=3&lat=33.768321&lon=-118.195617&key=<apiKey>"
from urllib.parse import urlencode
from urllib.request import urlopen
import json

class WorldTides:
    def __init__(self, key, lat, lon):
        self.key = key
        self.lat = lat
        self.lon = lon
        self.base = "https://www.worldtides.info/api/v3"

    def get_extremes(self, year, month, day, num_days):
        return self._request("extremes", year, month, day, num_days).get("extremes", [])

    def get_samples(self, year, month, day, num_days):
        return self._request("heights", year, month, day, num_days).get("heights", [])

    def _request(self, flag, year, month, day, num_days):
        query = urlencode({
            "date": f"{year:04d}-{month:02d}-{day:02d}",
            "days": num_days,
            "lat": self.lat,
            "lon": self.lon,
            "key": self.key,
        })
        with urlopen(f"{self.base}?{flag}&{query}", timeout=10) as response:
            data = json.loads(response.read().decode("utf-8"))
        if data.get("status", 200) != 200:
            raise RuntimeError(data.get("error", "WorldTides error"))
        return data

tides = WorldTides("<apiKey>", 33.768321, -118.195617)
extremes = tides.get_extremes(2026, 7, 1, 3)
samples = tides.get_samples(2026, 7, 1, 3)
<?php

class WorldTides {
    private string $base = 'https://www.worldtides.info/api/v3';

    public function __construct(
        private string $key,
        private float $lat,
        private float $lon
    ) {}

    public function getExtremes(int $year, int $month, int $day, int $numDays): array {
        return $this->request('extremes', $year, $month, $day, $numDays)['extremes'] ?? [];
    }

    public function getSamples(int $year, int $month, int $day, int $numDays): array {
        return $this->request('heights', $year, $month, $day, $numDays)['heights'] ?? [];
    }

    private function request(string $flag, int $year, int $month, int $day, int $numDays): array {
        $query = http_build_query([
            'date' => sprintf('%04d-%02d-%02d', $year, $month, $day),
            'days' => $numDays,
            'lat' => $this->lat,
            'lon' => $this->lon,
            'key' => $this->key,
        ]);
        $body = file_get_contents($this->base.'?'.$flag.'&'.$query);
        if ($body === false) {
            throw new RuntimeException('WorldTides request failed');
        }
        $json = json_decode($body, true, flags: JSON_THROW_ON_ERROR);
        if (($json['status'] ?? 200) !== 200) {
            throw new RuntimeException($json['error'] ?? 'WorldTides error');
        }
        return $json;
    }
}

$tides = new WorldTides('<apiKey>', 33.768321, -118.195617);
$extremes = $tides->getExtremes(2026, 7, 1, 3);
$samples = $tides->getSamples(2026, 7, 1, 3);
class WorldTides {
  constructor(key, lat, lon) {
    this.key = key;
    this.lat = lat;
    this.lon = lon;
    this.base = "https://www.worldtides.info/api/v3";
  }

  getExtremes(year, month, day, numDays) {
    return this.request("extremes", year, month, day, numDays).then(r => r.extremes ?? []);
  }

  getSamples(year, month, day, numDays) {
    return this.request("heights", year, month, day, numDays).then(r => r.heights ?? []);
  }

  async request(flag, year, month, day, numDays) {
    const date = `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
    const params = new URLSearchParams({
      date, days: String(numDays), lat: String(this.lat), lon: String(this.lon), key: this.key
    });
    const response = await fetch(`${this.base}?${flag}&${params}`);
    const json = await response.json();
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    if ((json.status ?? 200) !== 200) throw new Error(json.error ?? "WorldTides error");
    return json;
  }
}

const tides = new WorldTides("<apiKey>", 33.768321, -118.195617);
const extremes = await tides.getExtremes(2026, 7, 1, 3);
const samples = await tides.getSamples(2026, 7, 1, 3);
<?php

function worldtides_request($flag, $year, $month, $day, $num_days) {
    $url = add_query_arg([
        $flag => null,
        'date' => sprintf('%04d-%02d-%02d', $year, $month, $day),
        'days' => $num_days,
        'lat' => 33.768321,
        'lon' => -118.195617,
        'key' => '<apiKey>',
    ], 'https://www.worldtides.info/api/v3');

    $response = wp_remote_get($url, ['timeout' => 10]);
    if (is_wp_error($response)) {
        return $response;
    }
    $json = json_decode(wp_remote_retrieve_body($response), true);
    if (($json['status'] ?? 200) !== 200) {
        return new WP_Error('worldtides_error', $json['error'] ?? 'WorldTides error');
    }
    return $json;
}

function getExtremes($year, $month, $day, $num_days) {
    $json = worldtides_request('extremes', $year, $month, $day, $num_days);
    return is_wp_error($json) ? $json : ($json['extremes'] ?? []);
}

function getSamples($year, $month, $day, $num_days) {
    $json = worldtides_request('heights', $year, $month, $day, $num_days);
    return is_wp_error($json) ? $json : ($json['heights'] ?? []);
}
using System;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;

public sealed class WorldTides {
    readonly HttpClient http = new();
    readonly string key;
    readonly double lat;
    readonly double lon;

    public WorldTides(string key, double lat, double lon) {
        this.key = key;
        this.lat = lat;
        this.lon = lon;
    }

    public async Task<object[]> GetExtremes(int year, int month, int day, int numDays) =>
        (await Request("extremes", year, month, day, numDays)).extremes ?? [];

    public async Task<object[]> GetSamples(int year, int month, int day, int numDays) =>
        (await Request("heights", year, month, day, numDays)).heights ?? [];

    async Task<Response> Request(string flag, int year, int month, int day, int numDays) {
        var date = $"{year:D4}-{month:D2}-{day:D2}";
        var url = $"https://www.worldtides.info/api/v3?{flag}&date={date}&days={numDays}&lat={lat}&lon={lon}&key={Uri.EscapeDataString(key)}";
        var response = await http.GetAsync(url);
        var json = await response.Content.ReadFromJsonAsync<Response>();
        if (!response.IsSuccessStatusCode) throw new HttpRequestException($"HTTP {(int)response.StatusCode}");
        if ((json?.status ?? 200) != 200) throw new Exception(json?.error ?? "WorldTides error");
        return json!;
    }

    record Response(int status, string? error, object[]? extremes, object[]? heights);
}

public static class App {
    public static async Task Main() {
        var tides = new WorldTides("<apiKey>", 33.768321, -118.195617);
        var extremes = await tides.GetExtremes(2026, 7, 1, 3);
        var samples = await tides.GetSamples(2026, 7, 1, 3);

        Console.WriteLine($"Extremes: {extremes.Length}");
        Console.WriteLine($"Samples: {samples.Length}");
    }
}
library(httr2)
library(jsonlite)

WorldTides <- function(key, lat, lon) {
  lat_string <- format(lat, scientific = FALSE, trim = TRUE, digits = 15)
  lon_string <- format(lon, scientific = FALSE, trim = TRUE, digits = 15)

  request <- function(flag, year, month, day, num_days) {
    date <- sprintf("%04d-%02d-%02d", year, month, day)
    response <- httr2::request("https://www.worldtides.info/api/v3") |>
      req_url_query(!!flag := "", date = date, days = num_days, lat = lat_string, lon = lon_string, key = key) |>
      req_error(is_error = function(resp) FALSE) |>
      req_perform()
    if (resp_status(response) >= 300) stop(resp_body_string(response))
    json <- jsonlite::fromJSON(resp_body_string(response), simplifyVector = FALSE)
    if (!is.null(json$status) && json$status != 200) stop(ifelse(is.null(json$error), "WorldTides error", json$error))
    json
  }

  list(
    getExtremes = function(year, month, day, num_days) request("extremes", year, month, day, num_days)$extremes,
    getSamples = function(year, month, day, num_days) request("heights", year, month, day, num_days)$heights
  )
}

tides <- WorldTides("<apiKey>", 33.768321, -118.195617)
extremes <- tides$getExtremes(2026, 7, 1, 3)
samples <- tides$getSamples(2026, 7, 1, 3)
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
)

type WorldTides struct {
	Key string
	Lat float64
	Lon float64
}

func (w WorldTides) GetExtremes(year, month, day, numDays int) ([]any, error) {
	json, err := w.request("extremes", year, month, day, numDays)
	return json.Extremes, err
}

func (w WorldTides) GetSamples(year, month, day, numDays int) ([]any, error) {
	json, err := w.request("heights", year, month, day, numDays)
	return json.Heights, err
}

func (w WorldTides) request(flag string, year, month, day, numDays int) (*Response, error) {
	values := url.Values{
		"date": {fmt.Sprintf("%04d-%02d-%02d", year, month, day)},
		"days": {fmt.Sprint(numDays)},
		"lat":  {fmt.Sprint(w.Lat)},
		"lon":  {fmt.Sprint(w.Lon)},
		"key":  {w.Key},
	}
	resp, err := http.Get("https://www.worldtides.info/api/v3?" + flag + "&" + values.Encode())
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode >= 300 {
		return nil, fmt.Errorf("HTTP %d", resp.StatusCode)
	}
	var out Response
	if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
		return nil, err
	}
	if out.Status != 0 && out.Status != 200 {
		return nil, fmt.Errorf("%s", out.Error)
	}
	return &out, nil
}

type Response struct {
	Status   int    `json:"status"`
	Error    string `json:"error"`
	Extremes []any  `json:"extremes"`
	Heights  []any  `json:"heights"`
}