Complete, runnable scripts for registration, computing OTL corrections,
and managing your account. Requires the requests library
(pip install requests).
Registration creates your account and sends a verification email. After clicking the link in that email your account is active and your API key is shown. No password required — your API key is your sole credential.
import requests
BASE_URL = "https://otl.teromovigo.com"
r = requests.post(
f"{BASE_URL}/v1/auth/register",
json={"email": "johndoe@anywhere.com"},
)
print(r.json())
# Check your inbox, click the verification link.
# Your API key is displayed on that page — copy and save it.
{'message': 'Account created. A verification email has been sent to johndoe@anywhere.com. Click the link in the email to activate your account and receive your API key.'}
If you have lost your API key — or never saved it — just send your registered
email address to POST /v1/auth/recover. Your API key will be
emailed to you immediately. No password needed.
import requests
BASE_URL = "https://otl.teromovigo.com"
r = requests.post(
f"{BASE_URL}/v1/auth/recover",
json={"email": "johndoe@anywhere.com"},
)
print(r.json())
{'message': 'If that email address is registered, your API key has been sent to it.'}
Submit one or more stations and receive a structured JSON response with
amplitudes in millimetres and phases in degrees for all 11 tidal harmonics
and three displacement components.
Component order: amp[0] = radial (positive up),
amp[1] = NS tangential (positive south), amp[2] = EW tangential (positive west).
lat / lon (degrees) or as ECEF Cartesian
X / Y / Z (metres).
The two forms are mutually exclusive; providing both is an error.
When X/Y/Z is used the server converts to geodetic on the WGS84 ellipsoid
and returns both the computed lat/lon and the derived h in the response.
An optional ellipsoidal height h (metres) may also be supplied
alongside lat / lon; it is written into the station
coordinate line of the BLQ output and echoed back in JSON responses.
earth_model — the Green's function / Earth structure model used for the
elastic loading computation.
Options: "PREM", "PREM_disp", "STW105",
"STW105_disp".
Default: "STW105_disp" (anelastic STW105 — recommended for
high-accuracy geodetic work).
cmc — Centre of Mass Correction (boolean, default true).
Adds a rigid-body geocentre displacement derived from the degree-1 harmonic of
the ocean tide field. This depends only on the tide model (not the Earth model)
and is required by most geodetic software. Set to false to obtain
the raw loading values without CMC.
See IERS Conventions, Chapter 7 for the authoritative
definition.
import requests
BASE_URL = "https://otl.teromovigo.com"
API_KEY = "otl_your_api_key_here"
r = requests.post(
f"{BASE_URL}/v1/compute/",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"stations": [
{"name": "ONSA", "lat": 57.3958, "lon": 11.9253, "h": 10.8}, # Onsala, Sweden; h optional
# or ECEF: {"name": "ONSA", "X": 3370658.4, "Y": 711917.2, "Z": 5349786.0} (h derived)
{"name": "COIM", "lat": 40.2033, "lon": -8.4103}, # Coimbra, Portugal
],
"tide_model": "FES2014b", # "FES2014b" | "FES2022b" | "GOT5.5" | "TPXO10_atlas_v2"
"earth_model": "STW105_disp", # default — omit to use this
"cmc": True, # available for all four tide models
"format": "json",
},
)
r.raise_for_status()
data = r.json()
info = data["info"]
print(f"Created: {info['created']}")
print(f"Tide model: {info['tide_model']}")
print(f"Earth model: {info['earth_model']}")
print(f"CMC: {info['cmc']}")
print(f"Message: {info['message']}")
print(f"Stations used: {data['stations_used']}")
print(f"Weekly quota remaining: {data['weekly_quota_remaining']}")
# amp[0]/pha[0] = radial (positive up) mm / degrees
# amp[1]/pha[1] = NS tangential (positive south)
# amp[2]/pha[2] = EW tangential (positive west)
for station in data["stations"]:
name = station["name"]
otl = station["otl"]
print(f"\n{name} (lat={station['lat']}, lon={station['lon']})")
for harm in info["harmonics"]:
amp = otl[harm]["amp"]
pha = otl[harm]["pha"]
print(f" {harm.upper():<4} "
f"U: {amp[0]:6.3f} mm / {pha[0]:7.2f}° "
f"N: {amp[1]:6.3f} mm / {pha[1]:7.2f}° "
f"E: {amp[2]:6.3f} mm / {pha[2]:7.2f}°")
{
"service": "OTL Provider — https://otl.teromovigo.com",
"created": "2026-06-14 16:42:11 UTC",
"tide_model": "FES2014b",
"earth_model": "STW105_disp",
"cmc": true,
"harmonics": ["m2", "s2", "n2", "k2", "k1", "o1", "p1", "q1", "mf", "mm", "ssa"],
"amp_units": "mm",
"pha_units": "degrees",
"components": ["amp[0]/pha[0]: radial, positive up",
"amp[1]/pha[1]: NS tangential, positive south",
"amp[2]/pha[2]: EW tangential, positive west"],
"phase_convention": "lag relative to Greenwich, positive lag",
"seawater_density": "1030 kg/m³",
"message": "The early bird gets the worm, but the second mouse gets the cheese."
}
Created: 2026-06-14 16:42:11 UTC
Tide model: FES2014b
Earth model: STW105_disp
CMC: True
Message: The early bird gets the worm, but the second mouse gets the cheese.
Stations used: 2
Weekly quota remaining: 4998
ONSA (lat=57.3958, lon=11.9253)
M2 U: 5.728 mm / 97.34° N: 1.832 mm / 106.52° E: 1.254 mm / 43.11°
S2 U: 1.748 mm / 124.18° N: 0.561 mm / 133.44° E: 0.382 mm / 69.87°
...
COIM (lat=40.2033, lon=-8.4103)
M2 U: 44.671 mm / 122.58° N: 9.341 mm / 108.73° E: 5.217 mm / 57.29°
S2 U: 13.621 mm / 149.34° N: 2.847 mm / 135.61° E: 1.590 mm / 84.15°
...
Pass "format": "blq" to receive the result as plain-text
BLQ format, ready for use in geodetic software such as GIPSY, BERNESE,
GAMIT, or CSRS-PPP. The response is plain text, not JSON.
import requests
BASE_URL = "https://otl.teromovigo.com"
API_KEY = "otl_your_api_key_here"
r = requests.post(
f"{BASE_URL}/v1/compute/",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"stations": [
{"name": "ONSA", "lat": 57.3958, "lon": 11.9253},
# or ECEF: {"name": "ONSA", "X": 3370658.4, "Y": 711917.2, "Z": 5349786.0}
{"name": "COIM", "lat": 40.2033, "lon": -8.4103},
],
"tide_model": "FES2014b", # "FES2014b" | "FES2022b" | "GOT5.5" | "TPXO10_atlas_v2"
"earth_model": "STW105_disp", # default — omit to use this
"cmc": True, # available for all four tide models
"format": "blq",
},
)
r.raise_for_status()
# Print BLQ to screen
print(r.text)
# Save to file
with open("ocean_loading.BLQ", "w") as f:
f.write(r.text)
print("Saved to ocean_loading.BLQ")
$$ Ocean loading displacement
$$
$$ OTL Provider (https://otl.teromovigo.com)
$$ Created by Bos and Scherneck
$$ 2026-06-14 16:42:11 UTC
$$
$$ "The early bird gets the worm, but the second mouse gets the cheese."
$$
$$ COLUMN ORDER: M2 S2 N2 K2 K1 O1 P1 Q1 MF MM SSA
$$
$$ ROW ORDER:
$$ AMPLITUDES (m)
$$ RADIAL
$$ TANGENTL EW
$$ TANGENTL NS
$$ PHASES (degrees)
$$ RADIAL
$$ TANGENTL EW
$$ TANGENTL NS
$$
$$ Displacement is defined positive in upwards, South and West direction.
$$ The phase lag is relative to Greenwich and lags positive. The STW105_disp
$$ Green's function is used.
$$
$$ CMC: YES (corr.tide centre of mass)
$$
$$ A constant seawater density of 1030 kg/m^3 is used.
$$
$$ FES2014b: m2 s2 n2 k2 k1 o1
$$ FES2014b: p1 q1 Mf Mm Ssa
$$
$$ CMC start : geocentre displacement coefficients
$$ CMC format: (harm, source, Gz_U, Gz_V, Gx_U, Gx_V, Gy_U, Gy_V) [metres]
$$ CMC conv : displacement = U*cos(wt) + V*sin(wt)
$$ CMC frequ : M2 FES2014b -1.2626E-03 -1.6550E-03 -1.3193E-03 8.8426E-04 1.2647E-03 1.5946E-04
$$ CMC frequ : S2 FES2014b ...
$$ CMC end :
$$
$$ END HEADER
$$
ONSA
$$ FES2014b ID:2026-06-14 16:42:11
$$ Computed using compute_otl_fft via REST API
$$ ONSA RADI TANG lon/lat: 11.9253 57.3958 10.800
.00573 .00175 .00103 .00047 .00174 .00107 .00057 .00024 .00010 .00007 .00005
.00125 .00038 .00022 .00010 .00050 .00031 .00016 .00007 .00003 .00002 .00001
.00183 .00056 .00033 .00015 .00063 .00039 .00021 .00009 .00004 .00003 .00002
97.34 124.18 79.52 127.21 -26.44 -37.82 -25.61 -53.17 87.34 91.22 58.14
43.11 69.87 25.09 72.08 -80.21 -91.64 -79.38 -106.99 33.52 37.41 4.33
106.52 133.44 88.71 136.44 -17.33 -28.71 -16.49 -44.06 96.43 100.31 67.23
$$
COIM
...
The account endpoint returns your remaining weekly quota and the last 50 compute requests. Every account receives a quota for maximal 5,000 station computations per week, which resets automatically every Monday at 00:00 UTC.
import requests
BASE_URL = "https://otl.teromovigo.com"
API_KEY = "otl_your_api_key_here"
r = requests.get(
f"{BASE_URL}/v1/account/",
headers={"Authorization": f"Bearer {API_KEY}"},
)
r.raise_for_status()
data = r.json()
print(f"Email: {data['email']}")
print(f"Member since: {data['member_since'][:10]}")
print(f"Stations remaining this week: {data['stations_remaining_this_week']}")
print("\nRecent usage:")
for entry in data["recent_usage"]:
print(f" {entry['date'][:10]} "
f"{entry['n_stations']:>4} station(s) "
f"{entry['tide_model']}")
Email: johndoe@anywhere.com
Member since: 2026-06-14
Stations remaining this week: 4996
Recent usage:
2026-06-14 2 station(s) FES2014b
2026-06-14 2 station(s) FES2014b
Every endpoint can be tested directly in your browser at /docs (Swagger UI) — no Python or terminal required. Below is a step-by-step walkthrough using the same three operations covered in this tutorial.
At the top-right of the Swagger page you will see an Authorize
button (it may look like a lock icon 🔒). Click it. A dialog appears with an
HTTPBearer field — paste your API key there
(e.g. otl_aBcDe…, without any Bearer prefix).
Click Authorize, then Close.
All protected endpoints will now send your key automatically.
POST /v1/auth/registerFind the auth section and click POST /v1/auth/register to expand it. Click Try it out, enter your email address in the request body, and click Execute. A verification email will be sent; click the link in it to activate your account and reveal your API key.
POST /v1/auth/recoverFind the auth section, expand POST /v1/auth/recover, and click Try it out. Enter your email address in the request body and click Execute. Your API key will be emailed to you. No password required.
POST /v1/computeFind the compute section, expand POST /v1/compute, and click Try it out. Paste the JSON below into the request body field and click Execute. The response will contain amplitudes and phases for all 11 tidal harmonics.
{"name": "COIM", …}, → no comma before ])# comment lines — JSON does not support comments{
"stations": [
{"name": "ONSA", "lat": 57.3958, "lon": 11.9253, "h": 10.8},
{"name": "COIM", "lat": 40.2033, "lon": -8.4103}
],
"tide_model": "FES2014b",
"earth_model": "STW105_disp",
"cmc": true,
"format": "json"
}
{
"info": {
"service": "OTL Provider — https://otl.teromovigo.com",
"created": "2026-06-14 16:42:11 UTC",
"tide_model": "FES2014b",
"earth_model": "STW105_disp",
"cmc": true,
"harmonics": ["m2", "s2", "n2", "k2", "k1", "o1", "p1", "q1", "mf", "mm", "ssa"],
"amp_units": "mm",
"pha_units": "degrees",
"components": ["amp[0]/pha[0]: radial, positive up",
"amp[1]/pha[1]: NS tangential, positive south",
"amp[2]/pha[2]: EW tangential, positive west"],
"phase_convention": "lag relative to Greenwich, positive lag",
"seawater_density": "1030 kg/m³",
"message": "The early bird gets the worm, but the second mouse gets the cheese."
},
"credits_used": 2,
"credits_remaining": 98,
"stations": [
{
"name": "ONSA", "lat": 57.3958, "lon": 11.9253,
"otl": {
"m2": {"amp": [5.728, 1.832, 1.254], "pha": [97.34, 106.52, 43.11]},
"s2": {"amp": [1.748, 0.561, 0.382], "pha": [124.18, 133.44, 69.87]},
...
}
},
...
]
}
GET /v1/accountFind the account section, expand GET /v1/account, and click Try it out, then Execute (no body needed — your API key from step A is sent automatically). The response shows your current credit balance and recent usage history.