# NFL (No Foreign Land) API Reference for Location & Tracking This document describes how the No Foreign Land (NFL) API works for submitting boat location and tracking data. Use it to build your own simpler module that does the same thing. --- ## Overview The NFL API accepts **track data** (sequences of GPS points) via a single HTTP POST endpoint. Each submission includes: 1. A **boat API key** (user-specific, from noforeignland.com) 2. A **timestamp** (milliseconds since epoch) 3. A **track** (array of `[timestamp_ms, lat, lon]` points) --- ## API Endpoint | Property | Value | |----------|-------| | **URL** | `https://www.noforeignland.com/home/api/v1/boat/tracking/track` | | **Method** | `POST` | | **Content-Type** | `application/x-www-form-urlencoded` | --- ## Authentication ### 1. Plugin API Key (hardcoded) The Signal K plugin uses a fixed plugin API key sent in the header: ``` X-NFL-API-Key: 0ede6cb6-5213-45f5-8ab4-b4836b236f97 ``` This identifies the plugin/client to the NFL backend. Your own module may need to use the same key or obtain a different one from NFL. ### 2. Boat API Key (user-specific) Each boat/user has a **Boat API Key** from noforeignland.com: - **Where to get it:** Account > Settings > Boat tracking > API Key (on the website only, not in the app) - **How it’s sent:** As a form field `boatApiKey` in the POST body --- ## Request Format ### POST Body (form-urlencoded) | Field | Type | Required | Description | |-------|------|----------|-------------| | `timestamp` | string | Yes | Unix timestamp in **milliseconds** (epoch of the last track point) | | `track` | string | Yes | JSON string of track points (see below) | | `boatApiKey` | string | Yes | User’s boat API key from noforeignland.com | ### Track format `track` is a **JSON string** (so it must be serialized before being sent as form data). Example: ```json [ [1234567890000, 52.1234, 4.5678], [1234567896000, 52.1240, 4.5680], [1234567902000, 52.1245, 4.5685] ] ``` Each point is a **3-element array**: | Index | Type | Description | |-------|------|--------------| | 0 | number | Unix timestamp in **milliseconds** | | 1 | number | Latitude (WGS84, -90 to 90) | | 2 | number | Longitude (WGS84, -180 to 180) | Points should be ordered by time (oldest first). --- ## Response Format ```json { "status": "ok", "message": "optional message" } ``` Or on error: ```json { "status": "error", "message": "Error description" } ``` ### HTTP Status Codes - **200**: Request accepted. Check `status` in the JSON body for success/failure. - **4xx**: Client error (e.g. invalid API key, bad request). Do not retry. - **5xx**: Server error. Retry with backoff. --- ## Minimal Example (cURL) ```bash curl -X POST "https://www.noforeignland.com/home/api/v1/boat/tracking/track" \ -H "X-NFL-API-Key: 0ede6cb6-5213-45f5-8ab4-b4836b236f97" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "timestamp=1234567902000" \ -d "track=[[1234567890000,52.1234,4.5678],[1234567896000,52.1240,4.5680],[1234567902000,52.1245,4.5685]]" \ -d "boatApiKey=YOUR_BOAT_API_KEY" ``` --- ## Minimal Example (JavaScript/Node.js) ```javascript const track = [ [Date.now() - 60000, 52.1234, 4.5678], // 1 min ago [Date.now() - 30000, 52.1240, 4.5680], // 30 sec ago [Date.now(), 52.1245, 4.5685] // now ]; const lastTimestamp = track[track.length - 1][0]; const params = new URLSearchParams(); params.append('timestamp', String(lastTimestamp)); params.append('track', JSON.stringify(track)); params.append('boatApiKey', 'YOUR_BOAT_API_KEY'); const response = await fetch('https://www.noforeignland.com/home/api/v1/boat/tracking/track', { method: 'POST', headers: { 'X-NFL-API-Key': '0ede6cb6-5213-45f5-8ab4-b4836b236f97', }, body: params, }); const result = await response.json(); if (response.ok && result.status === 'ok') { console.log('Track sent successfully'); } else { console.error('Error:', result.message || response.statusText); } ``` --- ## Minimal Example (Python) ```python import requests import json import time track = [ [int(time.time() * 1000) - 60000, 52.1234, 4.5678], # 1 min ago [int(time.time() * 1000) - 30000, 52.1240, 4.5680], # 30 sec ago [int(time.time() * 1000), 52.1245, 4.5685] # now ] last_timestamp = track[-1][0] response = requests.post( 'https://www.noforeignland.com/home/api/v1/boat/tracking/track', headers={'X-NFL-API-Key': '0ede6cb6-5213-45f5-8ab4-b4836b236f97'}, data={ 'timestamp': last_timestamp, 'track': json.dumps(track), 'boatApiKey': 'YOUR_BOAT_API_KEY' } ) result = response.json() if response.ok and result.get('status') == 'ok': print('Track sent successfully') else: print('Error:', result.get('message', response.text)) ``` --- ## Data Validation (from the Signal K plugin) ### Position validation - Latitude: -90 to 90 - Longitude: -180 to 180 - Reject non-numeric, NaN, Infinity - Reject positions near (0, 0) (likely GPS init values) ### Velocity filter (optional) To catch GPS outliers, the plugin rejects points that imply movement faster than a threshold (default 50 m/s ≈ 97 knots): ``` velocity = distance_meters / time_delta_seconds ``` If `velocity > maxVelocity`, the point is dropped. --- ## 24h Keepalive The plugin does not use a separate keepalive endpoint. Instead: - If a boat has not moved for 24 hours, it still saves a point (using the last known position). - That point is included in the next track upload. - The effect is that boats stay “active” on NFL even when stationary. --- ## Retry Logic (from the plugin) - Retry up to 3 times on network errors or 5xx responses. - Do not retry on 4xx client errors. - Increase timeout per attempt (e.g. 30s, 60s, 90s). - Wait 2–4–6 seconds between retries. --- ## Summary: Minimal Module Checklist | Requirement | Details | |-------------|---------| | Endpoint | `POST https://www.noforeignland.com/home/api/v1/boat/tracking/track` | | Header | `X-NFL-API-Key: 0ede6cb6-5213-45f5-8ab4-b4836b236f97` | | Body | `application/x-www-form-urlencoded` with `timestamp`, `track`, `boatApiKey` | | Track format | JSON array of `[timestamp_ms, lat, lon]` | | Success | `response.status === 200` and `body.status === 'ok'` | --- ## Source Files Reference | File | Purpose | |------|---------| | `src/types/api.ts` | API URL, plugin key, response types | | `src/lib/TrackSender.ts` | HTTP request, retry logic, track parsing | | `src/lib/TrackLogger.ts` | Position collection, filtering, JSONL storage | | `src/types/position.ts` | Internal position format | | `src/utils/validation.ts` | Position and velocity checks |