# Authentication Token Refresh Operation
Source: https://api-docs.iklim.co/api-reference/authentication-service/authentication-token-refresh-operation
/open-api/en/auth-version-1.json post /v1/auth/refresh
When the Authentication Token expires, it is renewed using the refresh token
# Login Operation
Source: https://api-docs.iklim.co/api-reference/authentication-service/login-operation
/open-api/en/auth-version-1.json post /v1/auth/login
Login Operations are Made With Username And Password
# Logout Operation
Source: https://api-docs.iklim.co/api-reference/authentication-service/logout-operation
/open-api/en/auth-version-1.json post /v1/auth/logout
Logout Operations Puts JWT In Blacklist
# New User Registration
Source: https://api-docs.iklim.co/api-reference/authentication-service/new-user-registration
/open-api/en/auth-version-1.json post /v1/auth/register
New User Registration is Made
# Get route weather
Source: https://api-docs.iklim.co/api-reference/enroute/get-route-weather
/open-api/en/enroute_version-1.json post /v1/enroute/weather
Returns the latest weather data for a previously registered route.
# Register a route
Source: https://api-docs.iklim.co/api-reference/enroute/register-a-route
/open-api/en/enroute_version-1.json post /v1/enroute/register
Registers a new route with departure time and duration. Returns weather and location status for the current and upcoming segments.
# Create registration
Source: https://api-docs.iklim.co/api-reference/forecast-point-alarm-registrations/create-registration
/open-api/en/forecast-alarm_version-1.json post /v1/alarms/forecasts/register
Creates a forecast alarm registration with boundary, webhook and threshold filters.
# Delete registration
Source: https://api-docs.iklim.co/api-reference/forecast-point-alarm-registrations/delete-registration
/open-api/en/forecast-alarm_version-1.json delete /v1/alarms/forecasts/unregister/{registrationId}
Deletes a forecast alarm registration by UUID.
# Get by recipient ID
Source: https://api-docs.iklim.co/api-reference/forecast-point-alarm-registrations/get-by-recipient-id
/open-api/en/forecast-alarm_version-1.json get /v1/alarms/forecasts/get-by-recipient-id/{recipientId}
Returns all forecast alarm registrations for a given recipient ID.
# Get by registration ID
Source: https://api-docs.iklim.co/api-reference/forecast-point-alarm-registrations/get-by-registration-id
/open-api/en/forecast-alarm_version-1.json get /v1/alarms/forecasts/get-by-id/{registrationId}
Returns a single registration by UUID.
# Get city
Source: https://api-docs.iklim.co/api-reference/forecast-point-alarm-registrations/get-city
/open-api/en/forecast-alarm_version-1.json get /v1/alarms/forecasts/city/{cityId}
Returns city boundary info for the given city ID.
# Get district
Source: https://api-docs.iklim.co/api-reference/forecast-point-alarm-registrations/get-district
/open-api/en/forecast-alarm_version-1.json get /v1/alarms/forecasts/district/{districtId}
Returns district boundary info for the given district ID.
# List cities
Source: https://api-docs.iklim.co/api-reference/forecast-point-alarm-registrations/list-cities
/open-api/en/forecast-alarm_version-1.json get /v1/alarms/forecasts/{countryCode}/cities
Returns all cities of the given country. Supported countries: TUR (Türkiye), CTR (Northern Cyprus).
# List districts
Source: https://api-docs.iklim.co/api-reference/forecast-point-alarm-registrations/list-districts
/open-api/en/forecast-alarm_version-1.json get /v1/alarms/forecasts/districts/{cityId}
Returns all districts for the given city ID.
# List registrations (paged)
Source: https://api-docs.iklim.co/api-reference/forecast-point-alarm-registrations/list-registrations-paged
/open-api/en/forecast-alarm_version-1.json post /v1/alarms/forecasts/page
Returns forecast alarm registrations with pagination and filters.
# Update registration
Source: https://api-docs.iklim.co/api-reference/forecast-point-alarm-registrations/update-registration
/open-api/en/forecast-alarm_version-1.json patch /v1/alarms/forecasts/update
Updates an existing forecast alarm registration.
# Current Forecast Operation
Source: https://api-docs.iklim.co/api-reference/forecast-service/current-forecast-operation
/open-api/en/forecasts_version-1.json post /v1/forecasts/current
Returns the latest available weather metrics for the requested location.
# Daily Forecast Operation
Source: https://api-docs.iklim.co/api-reference/forecast-service/daily-forecast-operation
/open-api/en/forecasts_version-1.json post /v1/forecasts/daily
Returns aggregated daily weather metrics for the requested location.
# Hourly Forecast Operation
Source: https://api-docs.iklim.co/api-reference/forecast-service/hourly-forecast-operation
/open-api/en/forecasts_version-1.json post /v1/forecasts/hourly
Returns hour-by-hour weather metrics for the requested location and time span.
# Create registration
Source: https://api-docs.iklim.co/api-reference/geo-alarm-registrations/create-registration
/open-api/en/geo-alarm_version-1.json post /v1/alarms/geometries/register
Creates a registration with boundary, webhook and filters.
### Webhook lifecycle
Only one webhook configuration is stored per account.
1. **First registration:** Send the complete `webhook` configuration without `accountId`. The API associates and stores it for the authenticated user's account.
2. **Subsequent registrations:** Obtain the account ID from `GET /v1/users/me` and send only the account reference shown below.
```json
{
"webhook": {
"accountId": "2f04f1b5-1c0a-4c4e-b0a7-0ba6e9b7f2e1"
}
}
```
The API verifies that a webhook exists for the account and reuses its stored URL, authentication, request headers, and delivery policy. If no webhook exists for the supplied `accountId`, the registration fails. Use the update operation to modify an existing webhook.
# Delete registration
Source: https://api-docs.iklim.co/api-reference/geo-alarm-registrations/delete-registration
/open-api/en/geo-alarm_version-1.json delete /v1/alarms/geometries/unregister/{registrationId}
Deletes a registration by UUID.
# Get by recipient ID
Source: https://api-docs.iklim.co/api-reference/geo-alarm-registrations/get-by-recipient-id
/open-api/en/geo-alarm_version-1.json get /v1/alarms/geometries/get-by-recipient-id/{recipientId}
Lists registrations for a recipient. The recipient ID must be unique and comply with the US-ASCII character set.
# Get by registration ID
Source: https://api-docs.iklim.co/api-reference/geo-alarm-registrations/get-by-registration-id
/open-api/en/geo-alarm_version-1.json get /v1/alarms/geometries/get-by-id/{registrationId}
Returns a single registration by UUID.
# Get city
Source: https://api-docs.iklim.co/api-reference/geo-alarm-registrations/get-city
/open-api/en/geo-alarm_version-1.json get /v1/alarms/geometries/city/{cityId}
Returns city info for {cityId}.
# Get district
Source: https://api-docs.iklim.co/api-reference/geo-alarm-registrations/get-district
/open-api/en/geo-alarm_version-1.json get /v1/alarms/geometries/district/{districtId}
Returns district info for {districtId}.
# Get neighbourhood
Source: https://api-docs.iklim.co/api-reference/geo-alarm-registrations/get-neighbourhood
/open-api/en/geo-alarm_version-1.json get /v1/alarms/geometries/neighborhood/{neighborhoodId}
Returns neighbourhood info for the given neighbourhood ID. For CTR (Northern Cyprus), neighbourhoods are the sub-district level.
# List cities
Source: https://api-docs.iklim.co/api-reference/geo-alarm-registrations/list-cities
/open-api/en/geo-alarm_version-1.json get /v1/alarms/geometries/{countryCode}/cities
Returns all cities of the given country. Supported countries: TUR (Türkiye), CTR (Northern Cyprus).
# List districts
Source: https://api-docs.iklim.co/api-reference/geo-alarm-registrations/list-districts
/open-api/en/geo-alarm_version-1.json get /v1/alarms/geometries/districts/{cityId}
Returns districts for a city.
# List neighbourhoods
Source: https://api-docs.iklim.co/api-reference/geo-alarm-registrations/list-neighbourhoods
/open-api/en/geo-alarm_version-1.json get /v1/alarms/geometries/neighborhoods/{districtId}
Returns neighbourhoods for a district. For CTR (Northern Cyprus), neighbourhoods are the sub-district level.
# List registrations (paged)
Source: https://api-docs.iklim.co/api-reference/geo-alarm-registrations/list-registrations-paged
/open-api/en/geo-alarm_version-1.json post /v1/alarms/geometries/page
Returns registrations with pagination and filters.
# Update registration
Source: https://api-docs.iklim.co/api-reference/geo-alarm-registrations/update-registration
/open-api/en/geo-alarm_version-1.json patch /v1/alarms/geometries/update
Updates an existing registration.
# Page Operation
Source: https://api-docs.iklim.co/api-reference/lightning-service/page-operation
/open-api/en/lightnings_version-1.json post /v1/lightnings/page
Query Lightnings by Time Range With Pagination Support. Maximum backward interval is 5 days.
# Within Operation
Source: https://api-docs.iklim.co/api-reference/lightning-service/within-operation
/open-api/en/lightnings_version-1.json post /v1/lightnings/within
Query Lightnings Within Given Point and Radius. Maximum backward interval is 30 days, maximum radius is 50 km.
# Create a point alarm registration
Source: https://api-docs.iklim.co/api-reference/point-alarms/create-a-point-alarm-registration
/open-api/en/point-alarm_version-1.json post /v1/alarms/points/register
Creates a new point alarm registration within your allowed country/region boundaries.
# Delete (unregister) a point alarm registration
Source: https://api-docs.iklim.co/api-reference/point-alarms/delete-unregister-a-point-alarm-registration
/open-api/en/point-alarm_version-1.json delete /v1/alarms/points/unregister/{registrationId}
Deletes a point alarm registration using its unique identifier.
# Get registration by ID
Source: https://api-docs.iklim.co/api-reference/point-alarms/get-registration-by-id
/open-api/en/point-alarm_version-1.json get /v1/alarms/points/get-by-id/{registrationId}
Returns a single point alarm registration using its unique identifier.
# List point alarm registrations (paged)
Source: https://api-docs.iklim.co/api-reference/point-alarms/list-point-alarm-registrations-paged
/open-api/en/point-alarm_version-1.json post /v1/alarms/points/page
Returns a paged list of point alarm registrations filtered by recipient IDs and/or polygon intersection.
# List registrations by recipientId
Source: https://api-docs.iklim.co/api-reference/point-alarms/list-registrations-by-recipientid
/open-api/en/point-alarm_version-1.json get /v1/alarms/points/get-by-recipient-id/{recipientId}
Returns registrations for the given recipient
# Update a point alarm registration
Source: https://api-docs.iklim.co/api-reference/point-alarms/update-a-point-alarm-registration
/open-api/en/point-alarm_version-1.json patch /v1/alarms/points/update
Updates fields of an existing point alarm registration.
# Geometry Operation
Source: https://api-docs.iklim.co/api-reference/precipitation-api/geometry-operation
/open-api/en/precipitations_version-1.json post /v1/precipitations/geometry
Retrieves the protobuf-encoded geometry of the specified precipitation events, cropped to the given circular area. Users with power user role or above may omit the center point and radius to retrieve the full geometry without cropping.
# Page Operation
Source: https://api-docs.iklim.co/api-reference/precipitation-api/page-operation
/open-api/en/precipitations_version-1.json post /v1/precipitations/page
Retrieves a paginated list of precipitation records within the subscription's geographic boundaries for a specified time range.
# Within Operation
Source: https://api-docs.iklim.co/api-reference/precipitation-api/within-operation
/open-api/en/precipitations_version-1.json post /v1/precipitations/within
Retrieves precipitation data within a specified circular area and time range.
# Get Thunderstorm Details
Source: https://api-docs.iklim.co/api-reference/thunderstorm-service/get-thunderstorm-details
/open-api/en/thunderstorms_version-1.json post /v1/thunderstorms/get
Returns detailed information about a specific thunderstorm historic events.
# Thunderstorm List (Paged)
Source: https://api-docs.iklim.co/api-reference/thunderstorm-service/thunderstorm-list-paged
/open-api/en/thunderstorms_version-1.json post /v1/thunderstorms/page
Returns a paged list of thunderstorms (storm events) detected in the system. This endpoint allows querying data within a time range of 5 days based on the endTimeEpoch value.
# Thunderstorm List (Within Radius)
Source: https://api-docs.iklim.co/api-reference/thunderstorm-service/thunderstorm-list-within-radius
/open-api/en/thunderstorms_version-1.json post /v1/thunderstorms/within
Returns a paged list of thunderstorms within a given radius of a center point. This endpoint allows querying data within a maximum radius of 50 km and a time range of 30 days based on the endTimeEpoch value.
# Activate Account
Source: https://api-docs.iklim.co/api-reference/user-accounts-service/activate-account
/open-api/en/accounts-version-1.json get /v1/accounts/activate-account
Activates the account using user ID, account ID, and email verification token.
# Activate Phone Number
Source: https://api-docs.iklim.co/api-reference/user-accounts-service/activate-phone-number
/open-api/en/accounts-version-1.json get /v1/accounts/activate-phone-number
Activates the user's phone number using user ID, account ID, and phone verification token.
# Create Account
Source: https://api-docs.iklim.co/api-reference/user-accounts-service/create-account
/open-api/en/accounts-version-1.json post /v1/accounts/create
Creates a new account with the provided details.
# Get Account Details
Source: https://api-docs.iklim.co/api-reference/user-accounts-service/get-account-details
/open-api/en/accounts-version-1.json get /v1/accounts/{userId}
Retrieves detailed information of the account associated with the specified user ID.
# Request Account Activation
Source: https://api-docs.iklim.co/api-reference/user-accounts-service/request-account-activation
/open-api/en/accounts-version-1.json post /v1/accounts/account-activation-request
Initiates the process of account activation by sending an activation email to the user.
# Request Phone Number Activation
Source: https://api-docs.iklim.co/api-reference/user-accounts-service/request-phone-number-activation
/open-api/en/accounts-version-1.json post /v1/accounts/phone-number-activation-request
Starts the phone number activation process by sending a verification code to the user's phone.
# Update Account
Source: https://api-docs.iklim.co/api-reference/user-accounts-service/update-account
/open-api/en/accounts-version-1.json patch /v1/accounts/update
Updates account information such as email, phone, or other attributes.
# Update Subscription Plan
Source: https://api-docs.iklim.co/api-reference/user-accounts-service/update-subscription-plan
/open-api/en/accounts-version-1.json post /v1/accounts/update-subscription
Updates the account's subscription plan with the provided details.
# Change Password Operation
Source: https://api-docs.iklim.co/api-reference/user-service/change-password-operation
/open-api/en/auth-version-1.json post /v1/users/change-password
The operation that allows a user to change their own password. A user cannot change another user's password through this operation!
# Get Own User Information Operation
Source: https://api-docs.iklim.co/api-reference/user-service/get-own-user-information-operation
/open-api/en/auth-version-1.json get /v1/users/me
Retrieves only the requester's own user details without providing any token
# Get User Operation
Source: https://api-docs.iklim.co/api-reference/user-service/get-user-operation
/open-api/en/auth-version-1.json get /v1/users/{userId}
Can only be used by users with POWER_USER and higher roles. Retrieves user details with the given user identifier
# New User Creation Operation
Source: https://api-docs.iklim.co/api-reference/user-service/new-user-creation-operation
/open-api/en/auth-version-1.json post /v1/users/create
Can only be used by users with the ADMIN role. Creates a new user record with the provided details. If the password field is left blank, a random password of 12 alphanumeric characters is generated. After the user is created, a welcome email is sent to the relevant user's email address (username). Password information is included in this email.
# Pageable User Listing Operation
Source: https://api-docs.iklim.co/api-reference/user-service/pageable-user-listing-operation
/open-api/en/auth-version-1.json post /v1/users/page
Can only be used by users with the ADMIN role. Retrieves registered users in the system pageably according to the given filter and sorting options
# Password Reset Operation
Source: https://api-docs.iklim.co/api-reference/user-service/password-reset-operation
/open-api/en/auth-version-1.json post /v1/users/password-reset
Sent during the password reset request
# Password Reset Request Operation
Source: https://api-docs.iklim.co/api-reference/user-service/password-reset-request-operation
/open-api/en/auth-version-1.json post /v1/users/password-reset-request
This is the operation that receives the password reset request from users who have forgotten their password. A limit is applied to the password reset request for a user within a certain period. When this limit is exceeded, an email is not sent again and an error message is returned.
# User Unblocking Operation
Source: https://api-docs.iklim.co/api-reference/user-service/user-unblocking-operation
/open-api/en/auth-version-1.json get /v1/users/unblock/{userId}
Can only be used by users with the ADMIN role. A user who exceeds the number of unauthorized login attempts in the form of a brute-force attack is permanently blocked. This is the operation used to remove this block.
# User Update Operation
Source: https://api-docs.iklim.co/api-reference/user-service/user-update-operation
/open-api/en/auth-version-1.json patch /v1/users/update
An operation where only users with the ADMIN role can update roles, status, and password; only users with POWER_USER and higher roles, along with the requesting user themselves, can change the username; and users with other roles can update the remaining fields.
# Get Weather Icon
Source: https://api-docs.iklim.co/api-reference/weather-icons/get-weather-icon
/open-api/en/forecasts_version-1.json get /v1/weather-icons/{dayOrNight}/{iconName}
Serves the WEBP icon for the requested condition and day/night variant.
# Authentication
Source: https://api-docs.iklim.co/before-start/en/authentication
Authentication guarantees privacy of API Services by authenticating users. Authentication requires users to log in before accessing iklim.co API Services. This guide covers setup for each available handshake method.
🚨 All authorized endpoints require the `Authorization: Bearer ` header in every request.
Without it, the API responds with `401 - Unauthorized` and blocks the call.
Example header:
```ini theme={null}
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJtdXJhdG96ZGVtaXJAdGFybGEuaW8iLCJ1c2VySWQiOiJjOGI1OTQwZS01M2QyLTQwZGYtYTg4Yi0xMTBjODYxNTVjZmEiLCJyb2xlcyI6WyJBUElfVVNFUiIsIkFETUlOIl0sImxvY2FsZSI6InRyX1RSIiwidGltZVpvbmUiOiJFdXJvcGUvSXN0YW5idWwiLCJpYXQiOjE3NTMyMTA2MzQsImV4cCI6MTc1MzIxNDIzNH0.hfF1bjcNDhVthtpaZ_HkysA-zJH3VN1a6Sr59aRZbk0
```
# 1. Login Operation
> 🔐 Login operations always use a username and password pair.
> API Endpoint: `/v1/auth/login`
Example Request Payload:
```json theme={null}
{
"username": "name@domain.com",
"password": "password"
}
```
Example Success Response:
```json theme={null}
{
"username": "name@domain.com",
"type": "Bearer",
"accessToken": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJtdXJhdG96ZGVtaXJAdGFybGEuaW8iLCJ1c2VySWQiOiJjOGI1OTQwZS01M2QyLTQwZGYtYTg4Yi0xMTBjODYxNTVjZmEiLCJyb2xlcyI6WyJBUElfVVNFUiIsIkFETUlOIl0sImxvY2FsZSI6InRyX1RSIiwidGltZVpvbmUiOiJFdXJvcGUvSXN0YW5idWwiLCJpYXQiOjE3NTMyMDc1OTAsImV4cCI6MTc1MzIxMTE5MH0.fTCunl7i7yQ8nAuughBpvpRfdH8VFK8uhZWeTrDOPAc",
"refreshToken": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJtdXJhdG96ZGVtaXJAdGFybGEuaW8iLCJ1c2VySWQiOiJjOGI1OTQwZS01M2QyLTQwZGYtYTg4Yi0xMTBjODYxNTVjZmEiLCJsb2NhbGUiOiJ0cl9UUiIsInRpbWVab25lIjoiRXVyb3BlL0lzdGFuYnVsIiwiaWF0IjoxNzUzMjA3NTkwLCJleHAiOjE3NTMyOTM5OTB9.ozFiZzNPSUtzwvu3wuocA8q8t6MlqbyT6On2i0vSENQ"
}
```
⏳ Access tokens stay valid for 1 hour, while refresh tokens live for 1 full day.
Possible Error Responses:
```json theme={null}
{
"timestamp": "2025-04-14T16:58:22.22308754",
"status": 401,
"error": "Unauthorized",
"message": "Bad credentials",
"path": "/auth/login"
}
```
```json theme={null}
{
"timestamp": "2025-04-21T16:35:54.095818",
"status": 401,
"error": "Unauthorized",
"message": "User is disabled",
"path": "/auth/login"
}
```
# 2. Authentication Token Refresh Operation
> 🔁 When the access token expires, renew it with the refresh token.
> API Endpoint: `/v1/auth/refresh`
Example Request Payload:
```json theme={null}
{
"refreshToken": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJtdXJhdG96ZGVtaXJAdGFybGEuaW8iLCJ1c2VySWQiOiJjOGI1OTQwZS01M2QyLTQwZGYtYTg4Yi0xMTBjODYxNTVjZmEiLCJsb2NhbGUiOiJ0cl9UUiIsInRpbWVab25lIjoiRXVyb3BlL0lzdGFuYnVsIiwiaWF0IjoxNzUzMjA3NTkwLCJleHAiOjE3NTMyOTM5OTB9.ozFiZzNPSUtzwvu3wuocA8q8t6MlqbyT6On2i0vSENQ"
}
```
✅ Responses mirror the payload returned by the `Login Operation`.
⚠️ If you receive a `401 - Unauthorized` response, refresh your access token and retry.
🔁 Should refresh attempts also fail with `401`, perform a fresh login before proceeding.
# User Settings
Source: https://api-docs.iklim.co/before-start/en/final-briefing
This page provides important information about the details required to make API requests, such as language selection, user registration, account activation and account subscription processes.
# 1. API Language Selection
🌐 Use the `Accept-Language` header to receive API messages in different languages for login-free endpoints (any call that does not require an access token). Supported values are shown below, so pick what your automation prefers. 🎯
```
Accept-Language: tr
Accept-Language: en
```
## Login-free API endpoints 🔓
* Authorization Service ⚙️
* New User Registration - `/v1/auth/register`
* Login Process - `/v1/auth/login`
* Access Token Refresh - `/v1/auth/refresh`
* User Service 👥
* Password Reset Request `/v1/users/password-reset-request`
## User Language Selection
After a user logs in, the system reads the locale and language embedded inside the **Access Token** and automatically localizes the responses. Additionally, the ***User Update*** endpoint `/v1/users/update` lets a user (or an ADMIN acting for others) adjust language, locale, and timezone settings with a single call. 🛠️🌎
# 2. User Registration and Account Activation
Each step in the registration flow depends on the previous one and **must be completed in order**. Do not proceed to the next step until the current one succeeds. The `userId` returned in Step 1 and the `accountId` returned in Step 2 are required in every subsequent step — store both values.
* A new user registration is created via the **New User Registration** - `/v1/auth/register` API endpoint by providing the following information:
1. Username (email address)
2. Password
3. Name
4. Middle Name, if any
5. Surname
6. Language and Locale Preference
7. Time Zone Preference
* If another user already exists with the same username (email), the API responds with `400 - Bad Request` and `Existing User`. 🚫
* Successful registrations are created with the `INACTIVE` status until activation is complete. ⏳
* Submit an activation request via **Account Activation Request** - `/v1/accounts/account-activation-request` by providing:
1. User ID
2. Account Type
3. Mobile Phone Number *(optional)* — accepted formats: `905xxxxxxxxx`, `+905xxxxxxxxx`, or `5xxxxxxxxx`. Must be unique across all accounts; if the number is already registered to another account, the API responds with `400 - Bad Request`. 🚫
4. Location Address *(optional)*
5. Company *(optional)*
6. Industry *(optional)*
7. Link to Account Activation Page — provide any placeholder URL (e.g., `https://yourapp.com/activate-account`). The system automatically appends three query parameters to this link: `?u={userId}&a={accountId}&t={token}`, and embeds the resulting full URL in the activation email sent to the user. 📧
* The endpoint validates the payload (e.g., whether the user already has an account), creates the account when valid, and emails the activation link to the user. ✉️
* The response includes the **`accountId`** (UUID) — store this value, as it is required for all subsequent steps (Account Activation, Phone Verification, and Subscription Selection). 🔑
* It finalizes the registration of a new account via the **Account Activation** - `/v1/accounts/activate-account` API endpoint by entering the following information:
1. User ID
2. Account ID
3. Email Verification Token
* Activates a user account by verifying the provided email verification token. It validates the token, updates account and user status, and ensures the token has not expired. 🔐
* Once verified, the account is marked as email verified and token fields are cleared. ✅
A phone number verification request is made via the **Mobile Phone Number Activation Request** - `/v1/accounts/phone-number-activation-request` API endpoint by entering the following information:
1. User ID
2. Account ID
3. Link to Phone Number Activation Page
* Handles the activation process for a user's phone number. This includes validating the account, generating a phone number verification token, composing an activation message, and sending the activation SMS. 📲
* It is an optional step, this and the next step can be skipped, but the user will not receive SMS notifications if the phone number is not verified. 🚫
The phone number verification process is finalized by entering the following information via the **Mobile Phone Number Activation** - `/v1/accounts/activate-phone-number` API endpoint:
1. User ID
2. Account ID
3. Phone Number Validation Token
* Activates a phone number for a user by verifying the provided phone number verification token associated with their account. 🔁
The user cannot use paid services unless a subscription is selected, so complete the next section right after activation. 💡
* ⚠️ Until payment method integration is complete, users can only select the **`Trial`** plan. Paid plans (`Basic`, `Premium`, `Custom`) will become selectable once payment integration is enabled.
# 3. Subscription Operations
* Use **Update Subscription** - `/v1/accounts/update-subscription` to manage the plan for a user account by supplying:
* Plan Selection
1. User ID
2. Subscriber ID
3. Subscription Plan Name
* Creating a Custom Plan
1. User ID
2. Subscriber ID
3. Plan Details
1. Subscription Period
2. Limit Reset Recurrence
3. Geographical Boundary
4. API Service Limits
1. API Version
2. API Endpoint Name
3. Maximum Number of Calls
* The endpoint validates roles and account existence, sets the subscription plan, adjusts statuses if needed, and returns clear validation errors whenever something is missing. ✅📬
## 3.1. Subscription Plans 📦
| Plan | Period | Limit Profile | Geo Scope | Quick Notes |
| - | - | - | - | - |
| `None` | - | No quota applied | Not applicable | Default state for users without an active subscription. |
| `Trial` | 1 month | Daily cap: `5` calls, very low throughput | All regions | One-time trial per user—ideal for evaluations. |
| `Basic - Monthly` | 1 month | Monthly reset, limit count `1`, restricted volume | All regions | Budget-friendly option; renew every month to keep access. |
| `Basic - Annual` | 1 year | Annual quota pool with limited total calls | All regions | Spend the yearly bucket anytime within the year. |
| `Premium - Monthly` | 1 month | Monthly reset, higher allowance | All regions | Top-tier monthly access; renew each month. |
| `Premium - Annual` | 1 year | Annual quota with generous limits | All regions | Full-year premium bundle, use quota flexibly. |
| `Custom` | Configurable | Adjustable period, limit type, and call caps | Country/region boundaries configurable | Tailor every parameter (limits, geography, API mix) to match bespoke contracts. |
## 3.2. Geo-bounding Options for Custom Subscription Option 🗺️
1. Tümü
2. Akdeniz Region
3. Doğu Anadolu Region
4. Ege Region
5. Güneydoğu Anadolu Region
6. İç Anadolu Region
7. Marmara Region
8. Karadeniz Region
9. Kuzey Kıbrıs
1. All
2. Eastern
3. Western
4. Northern
5. Southern
1. Iran
1. All
# API Security
Source: https://api-docs.iklim.co/before-start/en/introduction
This page provides important information about the details you need to know before making API requests.
# Secure API Request Signing and Idempotency Mechanism
## Overview
🧩 This API leverages a robust security mechanism involving **HMAC-based request signing, timestamping, unique request identification (nonce)**, and **idempotency keys**. These mechanisms collectively ensure the integrity, authenticity, and non-replayability of client requests, as well as safe handling of duplicate submissions.
📘 This documentation explains each mechanism, their purposes, and how to implement the required client logic (e.g., in Postman or custom API clients).
## 1. HMAC Signature (`X-Signature` Header)
### What is it?
🔑 An **HMAC (Hash-based Message Authentication Code)** is a cryptographic signature generated using a shared secret and the request data. It ensures that the request cannot be tampered with, and its sender can be authenticated.
### Purpose
* **Integrity 🛡️:** Detects if any part of the request (method, URI, body, timestamp) has been altered.
* **Authentication 🔐:** Confirms the request is from a trusted client who knows the secret key.
* **Anti-Replay ♻️:** Tied to a timestamp and unique values, making replay attacks detectable.
### How is it used?
* The client constructs a string to sign, concatenating the HTTP method, full request path (including query), current timestamp ***in epoch milliseconds***, and resolved request body 🧾:
```ini theme={null}
dataToSign = HTTP_METHOD + "|" + PATH_WITH_QUERY + "|" + TIMESTAMP + "|" + BODY
```
* The client generates the HMAC SHA256 signature using the shared secret (kept in a secure vault, **never exposed**) 🔒:
```ini theme={null}
X-Signature = HMAC_SHA256(secret, dataToSign)
```
* This signature is sent in the request header `X-Signature` 🚀.
⚠️ Please ask us **HMAC Secret** before making your first API call.
### What if not used?
* Client will receive an error message with http status `400 - Bad Request`
```json theme={null}
{
"timestamp": "2025-04-14T16:58:10.414972912",
"status": 400,
"error": "Bad Request",
"message": "Missing signature, timestamp, or nonce headers",
"path": "/auth/login"
}
```
### What if signature is not valid?
* Client will receive an error message with http status `401 - Unauthorized`
```json theme={null}
{
"timestamp": "2025-04-14T16:57:50.083970026",
"status": 401,
"error": "Unauthorized",
"message": "Invalid request signature",
"path": "/auth/login"
}
```
## 2. Timestamp (`X-Timestamp` Header)
### What is it?
⏱️ The Unix timestamp (in milliseconds) at the moment the request is generated.
### Purpose
* **Anti-Replay 🛑:** Prevents an attacker from reusing a previously valid request, since the server can enforce strict time windows for valid requests.
### How is it used?
* The client generates the timestamp just before sending the request and includes it as the `X-Timestamp` header ⌚.
### What if not used?
* Client will receive an error message with http status `400 - Bad Request`
```json theme={null}
{
"timestamp": "2025-04-14T16:58:10.414972912",
"status": 400,
"error": "Bad Request",
"message": "Missing signature, timestamp, or nonce headers",
"path": "/auth/login"
}
```
## 3. Nonce (`X-Nonce` Header)
### What is it?
🎲 A **nonce** is a randomly generated unique identifier (UUID v4) included in each request.
### Purpose
* **Anti-Replay 🔁:** Further prevents request replay by uniquely identifying each request. Even if method, URI, and body are the same, the nonce will differ.
### How is it used?
* The client generates a new UUID v4 for every request and includes it as the `X-Nonce` header 🎯.
### What if not used?
* Client will receive an error message with http status `400 - Bad Request`
```json theme={null}
{
"timestamp": "2025-04-14T16:58:10.414972912",
"status": 400,
"error": "Bad Request",
"message": "Missing signature, timestamp, or nonce headers",
"path": "/auth/login"
}
```
### What if re-used?
* Client will receive an error message with http status `409 - Conflict`
```json theme={null}
{
"timestamp": "2025-04-14T16:57:21.168927321",
"status": 409,
"error": "Conflict",
"message": "Replay attack detected (nonce reused)",
"path": "/endpoint/uri/here"
}
```
## 4. Idempotency Key (`X-Idempotency-Key` Header)
### What is it?
🪪 A **unique identifier** (UUID v4) for each request, enabling the server to recognize and safely ignore duplicate submissions (e.g., from retrying a POST request).
### Purpose
* **Idempotency 🔂:** Guarantees that retrying a request due to network errors will not result in duplicate resource creation or side effects.
### How is it used?
* The client generates a new UUID v4 for each operation and sends it as the `X-Idempotency-Key` header 📮.
### What if not used?
* Client will receive an error message with http status `400 - Bad Request`
```json theme={null}
{
"timestamp": "2025-04-14T16:26:54.203118461",
"status": 400,
"error": "Bad Request",
"message": "Missing X-Idempotency-Key header",
"path": "/endpoint/uri/here"
}
```
### What if re-used?
* Client will receive an error message with http status `409 - Conflict`
```json theme={null}
{
"timestamp": "2025-04-14T16:56:28.671809875",
"status": 409,
"error": "Conflict",
"message": "Duplicate request detected (X-Idempotency-Key)",
"path": "/endpoint/uri/here"
}
```
* The `secret` used for HMAC **must never be shared or exposed in client code or documentation** 🔐.
* Always use HTTPS to prevent man-in-the-middle attacks 🌐.
* Servers enforce a maximum allowable time skew for the `X-Timestamp` and reject used/replayed `X-Nonce` or `X-Idempotency-Key` values within a certain window ⛔.
```ini theme={null}
X-Signature: 75c759570bbb5dfd40136cffd3dbaa51b28d4620e51359161686790adcc53bea
X-Timestamp: 1752751106704
X-Nonce: 684a0dca-bd6a-4056-a449-2567f9847f9c
X-Idempotency-Key: 777edc03-ad49-4c17-be6b-9baf05a1b9e0
```
### Summary Table 📊
| Header | Value/Format | Purpose |
| - | - | - |
| X-Signature | Hexadecimal string | Request authenticity/integrity |
| X-Timestamp | Milliseconds since epoch | Prevent replay |
| X-Nonce | UUID v4 | Uniqueness/anti-replay |
| X-Idempotency-Key | UUID v4 | Idempotency for retries |
# Service Details
Source: https://api-docs.iklim.co/pages/en/enroute
Weather-aware route monitoring for drivers. Register a route once, then poll for real-time and forecast weather conditions segment by segment as the journey unfolds.
The current segment always carries live radar-derived lightning, thunderstorm, and precipitation data — not model estimates.
Upcoming segments are enriched with hourly NWP forecast weather so drivers can plan for conditions hours away.
The route is split into Uber H3 hexagonal cells. Each cell gets its own ETA and independent weather query, keeping data fresh and spatially accurate.
***
## What is the Enroute API?
The Enroute API turns a planned route into a live weather-monitoring session. You provide the route geometry, departure time, and duration — the API splits the path into geographic segments, attaches scheduled arrival times to each one, and returns weather data for every segment on every subsequent poll.
**Two endpoints drive the entire lifecycle:**
| Endpoint | Purpose |
| - | - |
| `POST /v1/enroute/register` | Create a route session. Returns a `routeId` and the initial weather picture. |
| `POST /v1/enroute/weather` | Poll an active session with the driver's current location. Returns updated weather, position status, and any schedule changes. |
All timestamps in requests and responses are **epoch milliseconds** (64-bit integers). Never send ISO strings or fractional seconds.
***
## How segmentation works
When you register a route, the service decodes the route geometry (encoded polyline or coordinate list) and maps every point to an **Uber H3 hexagonal cell** at resolution 5. Consecutive points falling in the same cell are merged, yielding an ordered list of unique cells — the **segments**.
Each segment receives:
* **`eta`** — scheduled arrival time, interpolated from `departureTime + (distanceToEntry / totalDistance) × durationSeconds`
* **`etd`** — scheduled departure time (same interpolation using `distanceToExit`)
* **`distanceToEntry` / `distanceToExit`** — cumulative metres from route start
* **`arrivalPoint` / `departurePoint`** — the geographic coordinates where the route enters and exits the cell
* **`h3Address`** — the H3 cell identifier (used for weather queries)
H3 resolution 5 cells cover roughly **252 km²** each, comparable to a medium-sized city district. A typical 90-minute highway route passes through 7–10 segments.
**Why segment?**
Weather is spatially heterogeneous. Rain may be falling at your destination while skies are clear 80 km back. Segmentation lets the API query the right weather model cell for each portion of the journey and schedule those queries to the driver's actual ETA — not all at once.
***
## Nowcast vs Forecast
The response contains two structurally different weather payloads depending on the segment's time horizon.
### `currentSegment` — Nowcast + Forecast
The segment the driver is currently in (or the first segment, at registration time) receives **nowcast** data: real-time observations derived from live radar and sensor networks. This is the richest, most accurate weather picture available.
Nowcast includes three dedicated summary objects:
| Object | Source | What it tells you |
| - | - | - |
| `lightningSummary` | Lightning detection sensors | Flash counts, peak current, risk index, approach trend |
| `thunderstormSummary` | Radar storm tracking cells | Active storm list, threat boundary proximity, bearing, severity |
| `precipitationSummary` | Radar reflectivity | Current intensity, trend, expected start/end, forecast max |
**Why can upcoming segments not use nowcast?**
Each of the three nowcast services carries a different but complementary constraint:
* **Lightning:** Sensor networks record only events that have already occurred. They produce no forward-looking output whatsoever. A lightning nowcast for a cell the driver will reach two hours from now is physically impossible.
* **Thunderstorm:** Radar-based cell tracking observes the current position, speed, and direction of active storm cells. It generates no forecast projection; a meaningful threat assessment for a segment hours ahead cannot be derived from it.
* **Precipitation:** Alongside live radar observations, a very short-range NWP projection may also be available; this is what feeds the `expectedStartSec` / `expectedEndSec` fields in `precipitationSummary`. However, this horizon only extends slightly beyond the current observation window and does not represent conditions for a segment hours ahead.
In short: the value of all three services lies in answering **"right here, right now."** Once that question becomes meaningless for a future segment, multi-hour NWP forecast models take over.
### `upcomingSegments` — Forecast only
All segments after the current one carry NWP (Numerical Weather Prediction) hourly forecast data: temperature, apparent temperature, humidity, cloud cover, wind speed/gust/direction, precipitation, precipitation probability, snowfall, and visibility.
At registration time, if `departureTime` is within 60 minutes of `now`, the current segment also fetches nowcast data. If the departure is further in the future, only forecast data is returned for the first segment as well.
***
## Registering a route
### Request
```json theme={null}
POST /v1/enroute/register
Content-Type: application/json
Authorization: Bearer
{
"origin": { "lat": 41.0052, "lng": 39.7267 },
"destination": { "lat": 40.4608, "lng": 39.4818 },
"departureTime": 1776074400000,
"distanceMeters": 86000,
"durationSeconds": 5640,
"geometry": {
"encodedPolyline": "ywu~FmgpbBrA`AfBtAhD|BlDnCxBdBlBtAfChBjBpA...",
"polylinePrecision": 5
}
}
```
| Field | Type | Required | Description |
| - | - | - | - |
| `origin` | GeoPoint | ✅ | Starting coordinate `{ lat, lng }` |
| `destination` | GeoPoint | ✅ | Ending coordinate `{ lat, lng }` |
| `departureTime` | number | ✅ | Epoch milliseconds of planned departure |
| `distanceMeters` | number | ✅ | Total route length in metres |
| `durationSeconds` | number | ✅ | Estimated travel duration in seconds |
| `geometry.encodedPolyline` | string | ✅ (or coordinates) | Google-encoded polyline string |
| `geometry.polylinePrecision` | number | — | Precision factor (5 = standard, 6 = high-precision) |
| `geometry.coordinates` | GeoPoint\[] | ✅ (or polyline) | Explicit coordinate list instead of polyline |
| `waypoints` | GeoPoint\[] | — | Optional intermediate waypoints (informational) |
Either `encodedPolyline` (with `polylinePrecision`) or `coordinates` must be provided. Sending neither returns a 400 error.
### Response
```json theme={null}
{
"routeId": "84863286-c5f8-4a2c-9d40-837213e8c38e",
"expirationTime": 1776087240000,
"distanceUnit": "m",
"status": {
"progressPercentage": 0.0,
"distanceTraveled": 0.0,
"remainingDistance": 86000.0,
"timeElapsedSeconds": 0,
"estimatedTimeRemainingSeconds": 5640,
"scheduleDeviationSeconds": 0,
"expectedSegmentIndex": 0,
"estimatedArrivalTime": 1776079680000
},
"currentSegment": {
"index": 0,
"h3Address": "855b8c6bfffffff",
"eta": 1776074400000,
"etd": 1776075120000,
"distanceToEntry": 0.0,
"distanceToExit": 12200.0,
"arrivalPoint": { "lat": 41.0052, "lng": 39.7267 },
"departurePoint": { "lat": 40.9871, "lng": 39.6543 },
"weatherEvents": {
"temperature": 18.4,
"windSpeed": 3.2,
"windGust": 6.1,
"windDirection": 270,
"precipitation": 0.0,
"precipitationProbability": 12.0,
"humidity": 64.0,
"cloudCover": 35.0,
"visibility": 25000.0,
"lightningSummary": {
"windowMinutes": 60,
"bucketMinutes": 30,
"totalEventCount": 0,
"riskIndex": 0.0,
"riskLevel": "NONE",
"trend": "STABLE"
},
"thunderstormSummary": {
"windowMinutes": 60,
"bucketMinutes": 30,
"summary": {
"stormCount": 0,
"activeStorms": 0,
"insideAnyThreatBoundary": false,
"riskScore": 0,
"riskLevel": "NONE",
"trend": "STABLE"
}
},
"precipitationSummary": {
"windowMinutes": 60,
"bucketMinutes": 30,
"currentIntensity": null,
"isPrecipitating": false,
"riskLevel": "NONE",
"trend": "STABLE"
}
}
},
"upcomingSegments": [
{
"index": 1,
"h3Address": "855b8c67fffffff",
"eta": 1776075120000,
"etd": 1776076560000,
"distanceToEntry": 12200.0,
"distanceToExit": 24100.0,
"weatherEvents": {
"temperature": 16.1,
"windSpeed": 5.8,
"windGust": 12.3,
"windDirection": 285,
"precipitation": 1.4,
"precipitationProbability": 65.0,
"humidity": 78.0,
"cloudCover": 85.0,
"visibility": 12000.0
}
}
]
}
```
Save `routeId` from the registration response — it is the only key needed to poll the `/weather` endpoint throughout the journey. `expirationTime` tells you until when the session is valid; however, it is extended when segments are recalculated due to an off-schedule event (see [Off-schedule](#off-route-and-off-schedule)).
***
## Polling route weather
### Request
```json theme={null}
POST /v1/enroute/weather
Content-Type: application/json
Authorization: Bearer
{
"routeId": "84863286-c5f8-4a2c-9d40-837213e8c38e",
"currentLocation": { "lat": 40.9871, "lng": 39.6543 }
}
```
| Field | Type | Required | Description |
| - | - | - | - |
| `routeId` | UUID string | ✅ | Session identifier from `/register` |
| `currentLocation` | GeoPoint | ✅ | Driver's current GPS coordinate |
### Response
`GetRouteWeatherResponse` extends `RouteRegistrationResponse` and adds:
| Field | Type | Description |
| - | - | - |
| `segmentsRecalculated` | boolean | `true` if segments were recalculated due to an off-schedule event |
| `warning` | string | Human-readable message explaining why recalculation occurred |
All other fields (`routeId`, `expirationTime`, `distanceUnit`, `status`, `currentSegment`, `upcomingSegments`) are identical in structure to the registration response.
***
## Response field reference
### `LocationStatus`
| Field | Type | Description |
| - | - | - |
| `progressPercentage` | number | Journey completion percentage (0–100) |
| `distanceTraveled` | number | Metres traveled from route start |
| `remainingDistance` | number | Metres remaining to destination |
| `timeElapsedSeconds` | number | Seconds since `departureTime` |
| `estimatedTimeRemainingSeconds` | number | Remaining travel time based on current pace |
| `scheduleDeviationSeconds` | number | Positive = ahead of schedule; negative = behind schedule |
| `expectedSegmentIndex` | number | The segment index the driver *should* currently be in per the original schedule |
| `estimatedArrivalTime` | number | Estimated epoch-millisecond arrival time, capped at `expirationTime` |
`expectedSegmentIndex` reflects the route schedule, not GPS position. If the driver is in segment 2 but the schedule expected them to be in segment 3, this field shows `3`. Compare it with `currentSegment.index` to detect schedule drift.
### `BaseRouteSegment` (shared by `currentSegment` and `upcomingSegments`)
| Field | Type | Description |
| - | - | - |
| `index` | number | Zero-based segment position in the route |
| `h3Address` | string | Uber H3 cell identifier at resolution 5 |
| `eta` | number | Scheduled arrival epoch-milliseconds |
| `etd` | number | Scheduled departure epoch-milliseconds |
| `distanceToEntry` | number | Cumulative metres from route start to cell entry |
| `distanceToExit` | number | Cumulative metres from route start to cell exit |
| `arrivalPoint` | GeoPoint | Coordinate where the route enters the cell |
| `departurePoint` | GeoPoint | Coordinate where the route exits the cell |
### `ForecastWeatherEvents` (all segments)
| Field | Type | Unit | Description |
| - | - | - | - |
| `temperature` | number | °C | Air temperature at 2 m |
| `apparentTemperature` | number | °C | Feels-like temperature |
| `humidity` | number | % | Relative humidity |
| `cloudCover` | number | % | Sky cloud fraction |
| `windSpeed` | number | km/h | Wind speed at 10 m |
| `windGust` | number | km/h | Maximum wind gust |
| `windDirection` | number | ° | Meteorological wind direction (0 = N, 90 = E) |
| `snowFall` | number | cm | Snowfall accumulation |
| `precipitation` | number | mm | Precipitation accumulation |
| `precipitationProbability` | number | % | Model precipitation probability |
| `visibility` | number | m | Horizontal visibility |
***
## Nowcast summary fields
The `currentSegment.weatherEvents` object contains three additional summary objects when nowcast data is available.
### `LightningSummary`
Real-time lightning activity derived from ground-based electromagnetic sensors.
| Field | Type | Description |
| - | - | - |
| `windowMinutes` | number | Total observation window (e.g. 60 min) |
| `bucketMinutes` | number | Slot resolution used for aggregation (e.g. 30 min) |
| `totalEventCount` | number | All strike events within the window (in-cloud lightning + cloud-to-ground strikes) |
| `cgFlashCount` | number | Flash count — the most dangerous type for exposed workers and drivers |
| `icPulseCount` | number | Pulse count — indicator of storm electrification intensity |
| `lastEventAgeSec` | number | Seconds since the most recent event of any type |
| `lastFlashAgeSec` | number | Seconds since the most recent flash |
| `lastPulseAgeSec` | number | Seconds since the most recent pulse |
| `nearestEventDistance` | number | Metres to the closest event of any type |
| `nearestFlashDistance` | number | Metres to the closest flash |
| `nearestPulseDistance` | number | Metres to the closest pulse |
| `maxPeakCurrent` | number | Maximum peak current (kA) in the window — higher values indicate more destructive flashes |
| `avgPeakCurrent` | number | Average peak current (kA) |
| `avgSensorCount` | number | Average number of sensors that detected each event — higher = more accurate location |
| `confidenceScore` | number | 0–1 confidence score of detection quality |
| `confidenceLevel` | enum | `LOW` / `MEDIUM` / `HIGH` |
| `riskIndex` | number | Composite risk score (0–100) |
| `riskLevel` | enum | `NONE` / `LOW` / `MEDIUM` / `HIGH` / `EXTREME` |
| `trend` | enum | `UNKNOWN` / `DECREASING` / `STABLE` / `INCREASING` |
**What this means for a mobile app user:** Show a lightning shield icon with the `riskLevel` colour. If `riskLevel` is `HIGH` or `EXTREME`, surface an urgent alert. Use `nearestFlashDistance` and `lastFlashAgeSec` to give context: *"A flash was detected 3.2 km away 45 seconds ago — stay in your vehicle."*
***
### `ThunderstormSummary`
Storm cell tracking derived from multi-layer radar analysis.
#### `summary` object
| Field | Type | Description |
| - | - | - |
| `stormCount` | number | Total tracked storm cells in the observation window |
| `activeStorms` | number | Cells currently producing lightning or heavy precipitation |
| `insideAnyThreatBoundary` | boolean | `true` if the driver's current H3 cell overlaps any storm's threat polygon |
| `nearestThreatBoundaryDistance` | number | Metres to the closest storm threat boundary |
| `nearestStormCentroidDistance` | number | Metres to the closest storm cell centre |
| `maxSeverity` | enum | `LOW` / `NORMAL` / `HIGH` / `UNKNOWN` — highest severity among all tracked storms |
| `lastEventAge` | number | Seconds since the most recent storm event |
| `riskScore` | number | Composite risk score (0–100) |
| `riskLevel` | enum | `NONE` / `LOW` / `MEDIUM` / `HIGH` / `EXTREME` / `UNKNOWN` |
| `trend` | enum | `RISING` / `STABLE` / `FALLING` / `UNKNOWN` |
#### `activeStorms[]` — per-storm detail
| Field | Type | Description |
| - | - | - |
| `eventId` | string | Unique identifier of the tracked storm cell |
| `severity` | enum | `LOW` / `NORMAL` / `HIGH` / `UNKNOWN` |
| `eventStartUtcEpoch` | number | Epoch-milliseconds when this storm cell was first detected |
| `eventEndUtcEpoch` | number | Epoch-milliseconds of last recorded activity |
| `eventAge` | number | Seconds since the storm cell was first detected |
| `cell.area` | number | Storm cell area (km²) |
| `cell.speed` | number | Storm movement speed (km/h) |
| `cell.direction` | number | Storm movement direction (degrees, meteorological) |
| `cell.centroidDistance` | number | Metres from driver to storm cell centre |
| `cell.directionFromDriver` | enum | Compass bearing: `N`, `NNE`, `NE`, `ENE`, `E`, `ESE`, `SE`, `SSE`, `S`, `SSW`, `SW`, `WSW`, `W`, `WNW`, `NW`, `NNW` |
| `cell.bearingFromDriver` | number | Exact bearing in degrees from driver to storm centroid |
| `flashRates.inCloud` | number | Pulse rate (count/min) |
| `flashRates.cloudToGround` | number | Flash rate (count/min) |
| `flashRates.total` | number | Total strike rate (count/min) |
| `flashRates.cloudToGroundRatio` | number | Flash ratio (0–1); higher = more ground strikes |
| `threat.insideThreatPolygon` | boolean | `true` if driver is inside this storm's threat polygon |
| `threat.distanceToThreatBoundary` | number | Metres to this storm's threat polygon boundary |
| `threat.directionFromDriver` | enum | Compass direction to threat boundary |
| `threat.bearingFromDriver` | number | Exact bearing to threat boundary |
| `threat.approachState` | enum | `APPROACHING` / `MOVING_AWAY` / `STABLE` / `UNKNOWN` |
| `score.stormRiskScore` | number | Per-storm risk score (0–100) |
| `score.stormRiskLevel` | enum | `NONE` / `LOW` / `MEDIUM` / `HIGH` / `EXTREME` / `UNKNOWN` |
**What this means for a mobile app user:** If `insideAnyThreatBoundary` is `true`, this is an immediate safety alert — the driver is inside a storm's forecast impact zone. The `approachState: APPROACHING` flag combined with `nearestThreatBoundaryDistance` helps drivers decide whether to pull over or continue. Show the per-storm bearing so drivers understand which direction the storm is coming from.
***
### `PrecipitationSummary`
Radar-derived precipitation analysis for the current H3 cell.
| Field | Type | Description |
| - | - | - |
| `windowMinutes` | number | Observation window in minutes |
| `bucketMinutes` | number | Aggregation slot in minutes |
| `currentIntensity` | enum | Current intensity: `DRIZZLE` / `LIGHT` / `MODERATE` / `HEAVY` / `VERY_HEAVY` / `EXTREME`, or `null` if not precipitating |
| `isPrecipitating` | boolean | `true` if precipitation is currently detected over the cell |
| `radarTimeStamp` | number | Epoch-milliseconds of the radar scan used |
| `dataAgeSec` | number | Age of radar data in seconds — below 600 s is considered fresh |
| `radarSnapshotCount` | number | Number of radar scans included in the window |
| `maxIntensity` | enum | Maximum intensity recorded in the window |
| `trend` | enum | `INCREASING` / `STABLE` / `DECREASING` / `UNKNOWN` |
| `riskLevel` | enum | `NONE` / `LOW` / `MEDIUM` / `HIGH` / `EXTREME` |
| `forecastMaxIntensity` | enum | NWP model's predicted maximum intensity for the near future |
| `expectedEndSec` | number | Seconds until precipitation is expected to stop (if currently precipitating) |
| `expectedStartSec` | number | Seconds until precipitation is expected to start (if currently dry) |
**What this means for a mobile app user:** Surface `currentIntensity` as a weather badge. Use `expectedStartSec` to give advance warning — *"Heavy rain expected in 8 minutes"* — so drivers can prepare (wipers, speed reduction). `HEAVY` or above warrants a proactive push notification.
***
## Off-route and off-schedule
### Off-route
The `/weather` endpoint checks whether the driver's `currentLocation` is within **1,000 metres** of any segment of the registered route (perpendicular distance).
If the driver has deviated beyond this threshold:
* The route session is **immediately deleted** from cache.
* A **400 Bad Request** is returned with `error: "Off Route"`.
* The `routeId` is no longer valid.
* **The client must call `/register` again** with a new route.
```json theme={null}
{
"timestamp": "2026-04-14T15:26:45.404520747",
"status": 400,
"error": "Off Route",
"message": "Rota dışına 1.000 metreden daha fazla çıktığınız için mevcut rotanız iptal edildi, lütfen yeni bir rota için kayıt oluşturunuz!",
"path": "/v1/enroute/weather"
}
```
**UX guidance:** Listen for HTTP 400 with `error: "Off Route"`. Show the user a dialog: *"You have left the route. Please start navigation on your new route to continue weather monitoring."* Do not retry `/weather` with the same `routeId`.
### Off-schedule
If the driver's GPS position is significantly behind the expected schedule — specifically, if `now` is more than 10 minutes past the scheduled ETA of the segment *after* the driver's current segment — the service silently recalculates the remaining route segments from the driver's current position.
When recalculation occurs:
* **`segmentsRecalculated: true`** is set in the response.
* **`warning`** contains a human-readable explanation.
* Segment indices, ETAs, and distances are reset from the driver's current position.
* `currentSegment` reflects the new segment 0 starting from where the driver is now.
* The `expirationTime` is extended to new ETA + 2 hours.
When `segmentsRecalculated` is `true`, discard any cached segment data from previous responses and re-render the full segment list from the new response.
***
## Error reference
| HTTP Status | `error` | Trigger |
| - | - | - |
| 400 | `Off Route` | Driver is more than 1,000 m from the route. Session deleted. |
| 400 | `Invalid Route` | `routeId` not found, session expired, or route geometry invalid. |
| 409 | `Duplicate Route` | A session for the same origin → destination pair is already active. Use the existing `routeId`. |
| 402 | `Payment Required` | Subscription expired or exhausted. |
| 403 | `Forbidden` | No subscription for this endpoint, or queried location outside subscription boundaries. |
| 429 | `Too Many Requests` | Subscription rate limit exceeded. |
| 500 | `Data Provider Error` | A weather data provider was unreachable. Response includes source (Nowcast / Forecast) and service (Lightning, Thunderstorm, Precipitation, Current Forecast, Hourly Forecast) context. |
### Duplicate Route (409)
```json theme={null}
{
"timestamp": "2026-04-15T10:01:00.000000000",
"status": 409,
"error": "Duplicate Route",
"messages": [
"An active session already exists with routeId: 84863286-c5f8-4a2c-9d40-837213e8c38e",
"This session expires at: 2026-04-15T13:54:00Z",
"Use the existing routeId to continue polling /weather."
],
"path": "/v1/enroute/register"
}
```
### Expired or Not Found (400)
```json theme={null}
{
"timestamp": "2026-04-14T17:34:14.421986962",
"status": 400,
"error": "Invalid Route",
"message": "Route not found or the route session has expired.",
"path": "/v1/enroute/weather"
}
```
### Data Provider Error (500)
Returned when any of the underlying weather data services (lightning, thunderstorm, precipitation, or forecast) is unreachable. The `message` field identifies which source and service failed so you can log it and present a user-friendly fallback.
```json theme={null}
{
"timestamp": "2026-04-15T10:00:00.000000000",
"status": 500,
"error": "Data Provider Error",
"message": "A problem occurred while retrieving weather data (Nowcast Weather Events / Lightning). Please try again later.",
"path": "/v1/enroute/register"
}
```
```json theme={null}
{
"timestamp": "2026-04-15T10:00:00.000000000",
"status": 500,
"error": "Data Provider Error",
"message": "A problem occurred while retrieving weather data (Forecast Weather Events / Hourly Forecast). Please try again later.",
"path": "/v1/enroute/register"
}
```
***
## Developer notes
Every `Instant`-typed field in the API serializes as a 64-bit integer epoch millisecond — `routeId`, `expirationTime`, `eta`, `etd`, `estimatedArrivalTime`. Never parse these as seconds. Multiply by 1 and interpret directly as `Date(value)` in JavaScript or `Instant.ofEpochMilli(value)` in Java.
The API enforces deduplication on `(userId, originH3, destinationH3)`. If the driver tries to register the same route while an active session exists, a 409 Conflict is returned with the existing `routeId` and `expirationTime` in the `messages` array. Parse the `messages` array to extract the existing `routeId` and resume polling rather than treating the 409 as a fatal error.
Sessions expire at **ETA + 2 hours**. After expiration, calls to `/weather` with the old `routeId` return 400. Poll `/weather` before `expirationTime` to detect expiry proactively without waiting for an error.
There is no server-side push; all updates require a client-initiated `/weather` poll. Recommended polling interval: every **30–60 seconds** while the vehicle is moving. Reduce to every 5 minutes when stationary (e.g. at a traffic stop).
When `segmentsRecalculated: true`, the segment list has been rebuilt from the driver's current position. Segment `index` values restart from 0. Any UI element that cached previous segment indices (progress bars, segment list scrolling) must be re-initialized using the new response.
The `distanceUnit` field in the response is always `"m"`. All `distanceToEntry`, `distanceToExit`, and `remainingDistance` values are in metres. Convert to km or miles in your UI layer.
***
## Testing with Postman
The Postman visualization script below renders a human-readable route summary directly in the **Visualize** tab after each `/register` or `/weather` request — no need to read raw JSON to understand the current route state.
**How to use:**
1. Open the request in Postman (`POST /v1/enroute/register` or `POST /v1/enroute/weather`).
2. Go to **Scripts** → **Post-response** and paste the script below.
3. Send the request, then switch to the **Visualize** tab to see the summary.
```javascript theme={null}
// 1. Response (Cevap) Verisini Al
const responseData = pm.response.json();
// 2. Request (İstek) Verisini Al
let requestData = {};
if (pm.request.body && pm.request.body.raw) {
try {
requestData = JSON.parse(pm.request.body.raw);
} catch (e) {
console.log("Request body parse edilemedi: ", e);
}
}
// HTML/JS/CSS template for visualization
const template = `
H3 Weather Route
Detaylar
`;
// Hem response hem de request verisini Visualizer'a aktar
pm.visualizer.set(template, {
payload: JSON.stringify(responseData),
reqPayload: JSON.stringify(requestData)
});
```
# MCP Server
Source: https://api-docs.iklim.co/pages/en/mcp-server
Connect AI assistants like Claude to the iklim.co Weather API using the Model Context Protocol (MCP).
This guide is intended for **developers and technically proficient users**. Setting up the MCP server requires Node.js, a terminal, and basic familiarity with configuration files.
Every iklim.co API capability is exposed as an MCP tool — lightning, thunderstorm, precipitation, forecast, alarms, and more.
JWT tokens are acquired and refreshed automatically. Just provide your credentials and the server handles the rest.
Every request is signed with HMAC-SHA256. Credentials never travel in plain text and replay attacks are blocked with a per-request nonce.
## Overview
The iklim.co MCP Server implements the [Model Context Protocol](https://modelcontextprotocol.io) and exposes the full iklim.co REST API as **60 tools** across 10 categories. Any MCP-compatible AI client (Claude, OpenClaw, and others) can query live weather data, manage alarms, and handle user accounts through natural language.
| Category | Tools | Scope |
| - | :-: | - |
| ⚡ Lightning | 2 | Lightning strike data |
| 🌪️ Thunderstorm | 3 | Thunderstorm cell tracking |
| 🌧️ Precipitation | 3 | Radar precipitation data |
| 🌤️ Forecast | 3 | Hourly / daily / current weather |
| 👤 Auth & User | 11 | Authentication and user management |
| 🏢 Account | 8 | Account and subscription management |
| 📍 Point Alarms | 6 | GPS-based alert subscriptions |
| 🗺️ Geo Alarms | 12 | Boundary-based alerts + city/district/neighborhood catalog |
| 📅 Forecast Alarms | 10 | Threshold-based forecast alerts + city/district catalog |
| 🛣️ Enroute | 2 | Weather-aware route monitoring |
| **Total** | **60** | |
***
## Requirements
* **Node.js** >= 18 (ES2022 support required)
* **npm** >= 9
* iklim.co API credentials: HMAC secret, username, and password
***
## Installation
The source code is publicly available at [git.tarla.io/iklim.co/mcp-server](https://git.tarla.io/iklim.co/mcp-server).
```bash theme={null}
git clone https://git.tarla.io/iklim.co/mcp-server.git
cd mcp-server
npm install
npm run build
```
***
## Environment Variables
The following variables must be set before the server starts. For local development, create a `.env` file in the `mcp-server` directory (already in `.gitignore`):
```bash theme={null}
# .env
IKLIM_ENV=test # prod | test | local (used when IKLIM_BASE_URL is not set)
IKLIM_BASE_URL= # Optional. Overrides IKLIM_ENV when set
IKLIM_HMAC_SECRET= # Required. HMAC-SHA256 key for request signing
IKLIM_USERNAME= # Required. API account e-mail
IKLIM_PASSWORD= # Required. API account password
IKLIM_TOKEN_STORE_PATH= # Optional. Path for persisting access/refresh tokens
IKLIM_HTTP_LOG_PATH= # Optional. API request log file path
IKLIM_HTTP_LOG_MAX_BYTES=5242880 # Optional. Rotate threshold in bytes (default: 5 MB)
IKLIM_HTTP_LOG_MAX_FILES=5 # Optional. Number of rotated files to keep
IKLIM_HTTP_LOG_REQUEST_BODY_MAX_BYTES=16384 # Optional. Request body log size limit
IKLIM_HTTP_LOG_RESPONSE_BODY_MAX_BYTES=16384 # Optional. Response body log size limit
```
**Base URL by environment:**
| `IKLIM_ENV` | URL |
| - | - |
| `prod` | `https://api.iklim.co` |
| `test` | `https://api-test.iklim.co` |
| `local` | `http://localhost:8080` |
When `IKLIM_HTTP_LOG_PATH` is set, every API call is written as a single-line JSON log entry. Sensitive fields (`Authorization`, `X-Signature`, `password`, `token`, etc.) are automatically masked.
***
## Build & Run
```bash theme={null}
# Compile TypeScript (outputs to dist/)
npm run build
# Start the compiled server
npm start
# Development mode — no build step required
npm run dev
```
A successful start prints:
```
iklim.co MCP server running
```
The server communicates over **stdio** transport. It is designed to be managed by an MCP client, not run interactively in a terminal.
***
## MCP Client Configuration
### Claude CLI (`.mcp.json`)
Place a `.mcp.json` file in your project root. Claude CLI picks it up automatically:
```json theme={null}
{
"mcpServers": {
"iklim": {
"command": "node",
"args": ["/absolute/path/to/mcp-server/dist/index.js"],
"env": {
"IKLIM_ENV": "test",
"IKLIM_HMAC_SECRET": "",
"IKLIM_USERNAME": "",
"IKLIM_PASSWORD": ""
}
}
}
}
```
To register the server globally, add the same `mcpServers` block to `~/.claude/settings.json`.
### OpenClaw
The `openclaw mcp set` command does not support a separate `env` flag — pass everything as a single JSON object:
```bash theme={null}
openclaw mcp set iklim '{"type":"stdio","command":"node","args":["/absolute/path/to/mcp-server/dist/index.js"],"env":{"IKLIM_ENV":"test","IKLIM_HMAC_SECRET":"","IKLIM_USERNAME":"","IKLIM_PASSWORD":""}}'
```
Or edit `~/.openclaw/openclaw.json` directly:
```json theme={null}
{
"mcp": {
"iklim": {
"type": "stdio",
"command": "node",
"args": ["/absolute/path/to/mcp-server/dist/index.js"],
"env": {
"IKLIM_ENV": "test",
"IKLIM_HMAC_SECRET": "",
"IKLIM_USERNAME": "",
"IKLIM_PASSWORD": ""
}
}
}
}
```
### Other MCP Clients
Any client that supports the MCP stdio standard can connect. Required parameters:
| Parameter | Value |
| - | - |
| `transport` | `stdio` |
| `command` | `node` |
| `args` | `[""]` |
| `env` | The four variables above |
***
## Tool Catalog
### ⚡ Lightning
| Tool | Description |
| - | - |
| `get_lightnings_within` | Queries lightning strikes within a circular area defined by center coordinates and radius |
| `get_lightnings_page` | Returns paginated lightning data for a time interval |
### 🌪️ Thunderstorm
| Tool | Description |
| - | - |
| `get_thunderstorms_within` | Queries thunderstorm cells within a circular area |
| `get_thunderstorms_page` | Returns paginated thunderstorm data for a time interval |
| `get_thunderstorm_details` | Fetches historical details for a specific storm event by `eventId` |
### 🌧️ Precipitation
| Tool | Description |
| - | - |
| `get_precipitations_within` | Queries radar precipitation data within a circular area; optionally filter by `intensityThreshold` |
| `get_precipitations_page` | Returns paginated precipitation data; `intensityThreshold` is required |
| `get_precipitation_geometry` | Returns the polygon geometry of given precipitation `eventIds` (max 10) as a GeoJSON FeatureCollection; `latitude`/`longitude`/`radius` clip the result to a circular area and may be omitted only for power-user accounts |
**Intensity levels (lowest → highest):** `DRIZZLE` \< `LIGHT` \< `MODERATE` \< `HEAVY` \< `VERY_HEAVY` \< `EXTREME`
### 🌤️ Forecast
| Tool | Description |
| - | - |
| `get_hourly_forecast` | Hourly forecasts for 1–14 days; supports 53 selectable metrics |
| `get_daily_forecast` | Daily aggregate forecasts; same parameters as hourly (excluding solar panel fields) |
| `get_current_weather` | Most recent weather observation for a coordinate |
`WEATHER_ICON`, `TEMPERATURE`, `APPARENT_TEMPERATURE`, `DEW_POINT_TEMPERATURE`, `HUMIDITY`, `CLOUD_COVER`, `CLOUD_COVER_LOW`, `CLOUD_COVER_MID`, `CLOUD_COVER_HIGH`, `WIND_SPEED`, `WIND_GUST`, `WIND_DIRECTION`, `WIND_SPEED_AT_100M`, `WIND_DIRECTION_AT_100M`, `PRECIPITATION`, `RAIN`, `SHOWERS`, `SNOWFALL`, `SNOW_DEPTH`, `PRECIPITATION_PROBABILITY`, `WEATHER_CODE`, `PRESSURE_MSL`, `SURFACE_PRESSURE`, `VISIBILITY`, `EVAPOTRANSPIRATION`, `ET0_FAO_EVAPOTRANSPIRATION`, `VAPOUR_PRESSURE_DEFICIT`, `CAPE`, `LIFTED_INDEX`, `CONVECTIVE_INHIBITION`, `SUNSHINE_DURATION`, `SHORTWAVE_RADIATION`, `DIRECT_RADIATION`, `DIFFUSE_RADIATION`, `DIRECT_NORMAL_IRRADIANCE`, `GLOBAL_TILTED_IRRADIANCE`, `TERRESTRIAL_RADIATION`, `SHORTWAVE_RADIATION_INSTANT`, `DIRECT_RADIATION_INSTANT`, `DIFFUSE_RADIATION_INSTANT`, `DIRECT_NORMAL_IRRADIANCE_INSTANT`, `GLOBAL_TILTED_IRRADIANCE_INSTANT`, `TERRESTRIAL_RADIATION_INSTANT`, `SOIL_TEMPERATURE_0CM`, `SOIL_TEMPERATURE_6CM`, `SOIL_TEMPERATURE_18CM`, `SOIL_TEMPERATURE_54CM`, `SOIL_MOISTURE_0_TO_1CM`, `SOIL_MOISTURE_1_TO_3CM`, `SOIL_MOISTURE_3_TO_9CM`, `SOIL_MOISTURE_9_TO_27CM`, `SOIL_MOISTURE_27_TO_81CM`, `IS_DAY`
### 👤 Auth & User
| Tool | Description |
| - | - |
| `auth_register` | Creates a new user account |
| `auth_logout` | Invalidates the current JWT token |
| `user_get_me` | Returns the authenticated user's profile |
| `user_get` | Returns a user's details by `userId` |
| `user_create` | *(Admin)* Creates a new user with `roles` and `status` |
| `user_update` | *(Admin)* Updates user fields by `userId` |
| `user_list` | Returns a paginated user list; filterable by `roles`, `status` |
| `user_unblock` | Unblocks a blocked user |
| `user_change_password` | Changes password with `oldPassword` and `newPassword` |
| `user_password_reset_request` | Sends a password reset e-mail |
| `user_password_reset` | Updates password using a reset token |
### 🏢 Account
| Tool | Description |
| - | - |
| `account_get` | Returns account details by `userId` |
| `account_create` | Creates a new account (`INDIVIDUAL` or `ORGANIZATION`) |
| `account_update` | Updates account fields by `accountId` |
| `account_activation_request` | Sends an activation e-mail |
| `account_activate` | Activates the account using an e-mail verification token |
| `account_phone_activation_request` | Sends an SMS verification code |
| `account_activate_phone` | Verifies the phone number using an SMS token |
| `account_update_subscription` | Changes the subscription plan |
### 📍 Point Alarms
GPS-coordinate-based alert subscriptions for events within a configurable radius.
| Tool | Description |
| - | - |
| `point_alarm_register` | Creates a new point alarm |
| `point_alarm_update` | Updates an existing alarm |
| `point_alarm_delete` | Deletes an alarm |
| `point_alarm_get_by_id` | Returns a single alarm's details |
| `point_alarm_get_by_recipient` | Lists all alarms for a recipient |
| `point_alarm_list` | Paginated alarm list; filterable by `recipientIds` |
### 🗺️ Geo Alarms
Administrative boundary, polygon, or H3 address-based alert subscriptions.
Three boundary types are supported:
```json theme={null}
// Administrative boundary
{ "type": "ADMINISTRATIVE", "countryCode": "TUR", "cityId": 6, "districtId": 60 }
// Polygon
{ "type": "POLYGON", "polygon": { "exterior": [{"lat": 39.9, "lng": 32.8}, ...] } }
// H3 cell index
{ "type": "H3INDEX", "h3Address": "8f2830828052d25" }
```
`countryCode` is required for administrative boundaries: `TUR` (Türkiye) or `CTR` (Northern Cyprus). City, district and neighborhood IDs are scoped to a country, so take them from the catalog tools for that country. For `CTR`, the neighborhood level is the sub-district.
CRUD tools (`geo_alarm_register`, `geo_alarm_update`, `geo_alarm_delete`, `geo_alarm_get_by_id`, `geo_alarm_get_by_recipient`, `geo_alarm_list`) share the same signature as Point Alarms.
**Location catalog:**
| Tool | Description |
| - | - |
| `geo_alarm_list_cities` | Lists the cities of a country (`countryCode`: `TUR` or `CTR`) |
| `geo_alarm_get_city` | Returns city details by `cityId` |
| `geo_alarm_list_districts` | Lists districts by `cityId` |
| `geo_alarm_get_district` | Returns district details by `districtId` |
| `geo_alarm_list_neighborhoods` | Lists neighborhoods by `districtId` |
| `geo_alarm_get_neighborhood` | Returns neighborhood details by `neighborhoodId` |
### 📅 Forecast Alarms
Threshold-based alerts delivered at 04:00 UTC (morning) or 16:00 UTC (evening).
**Threshold parameters:**
| Parameter | Values |
| - | - |
| `precipitationThreshold` | Numeric value in mm |
| `snowFallThreshold` | `LIGHT` \| `MODERATE` \| `HEAVY` |
| `windGustThreshold` | `STRONG_WIND` \| `STORM` \| `SEVERE_STORM` \| `HURRICANE` |
| `hotTemperatureThreshold` | `HOT_SNAP` \| `HEAVY_HOT_SNAP` \| `EXTREME_HOT_SNAP` |
| `coldTemperatureThreshold` | `COLD_SNAP` \| `HEAVY_COLD_SNAP` \| `EXTREME_COLD_SNAP` |
CRUD tools follow the same signature as Point Alarms. Administrative boundaries require `countryCode` (`TUR` or `CTR`), e.g. `{ "type": "ADMINISTRATIVE", "countryCode": "CTR", "cityId": ..., "districtId": ... }`. Additional location catalog tools: `forecast_alarm_list_cities` (takes `countryCode`), `forecast_alarm_get_city`, `forecast_alarm_list_districts`, `forecast_alarm_get_district`.
### 🛣️ Enroute
Weather-aware route monitoring. Register a route once, then poll for segment-by-segment weather as the journey unfolds. See the Enroute API page for the full model.
| Tool | Description |
| - | - |
| `enroute_register_route` | Registers a route (origin, destination, departure time, distance, duration, geometry) and returns a `routeId` with the initial weather per segment |
| `enroute_get_route_weather` | Returns updated weather for a `routeId`; pass `currentLocation` to re-evaluate the current segment and recalculate ETAs |
***
## Architecture
```
src/
├── index.ts # MCP server bootstrap, tool routing
├── config.ts # Environment variable parsing
├── auth.ts # JWT token management (auto-refresh)
├── client.ts # HTTP API client (HMAC signing)
├── security.ts # HMAC-SHA256, nonce, idempotency key
└── tools/
├── lightnings.ts
├── thunderstorms.ts
├── precipitations.ts
├── forecasts.ts
├── auth.ts
├── accounts.ts
├── point-alarms.ts
├── geo-alarms.ts
├── forecast-alarms.ts
└── enroute.ts
```
**Request flow:**
```
MCP Client
│
▼
index.ts (CallToolRequestSchema)
│
▼
tools/.ts ← Zod validation
│
▼
client.ts (apiGet / apiPost / apiPatch / apiDelete)
│ ├── auth.ts → get valid JWT (auto-refresh if needed)
│ └── security.ts → generate HMAC-SHA256 signature
│
▼
iklim.co REST API
```
***
## Authentication & Security
Every API interaction uses two independent security layers: **JWT-based authentication** and **HMAC-SHA256 request signing**. Both are applied to every request.
### Automatic Auth Flow
The server logs in automatically on the first tool call. No manual login step is needed.
```
First tool call
│
▼
getValidAccessToken() ← auth.ts
│
├─ No token state → login()
│ POST /v1/auth/login { username, password }
│ ← { accessToken, refreshToken }
│ Decode JWT payload → calculate expiry
│ Save to tokenState
│
├─ accessToken expiring soon (< 30 s) → refresh()
│ POST /v1/auth/refresh { refreshToken }
│ ← { accessToken, refreshToken }
│ Update tokenState
│
└─ accessToken valid → return directly
```
The `login` and `refresh` endpoints do **not** include an `Authorization: Bearer` header — they are authenticated by HMAC signature alone.
### HTTP Request Headers
| Header | Value | Notes |
| - | - | - |
| `Content-Type` | `application/json` | Fixed |
| `Authorization` | `Bearer ` | Normal API requests only; omitted for login/refresh |
| `X-Signature` | hex string | HMAC-SHA256 signature |
| `X-Timestamp` | Unix epoch (ms) | `Date.now()` as a string |
| `X-Nonce` | UUID v4 | Unique per request — prevents replay attacks |
| `X-Idempotency-Key` | UUID v4 | `POST`, `PUT`, `PATCH`, and `DELETE` requests |
### HMAC-SHA256 Signature
The `X-Signature` value is the HMAC-SHA256 of four components joined by `|`:
```
data_to_sign = "METHOD|PATH_WITH_QUERY|TIMESTAMP|BODY"
X-Signature = HMAC-SHA256(data_to_sign, IKLIM_HMAC_SECRET) → hex
```
**Example — GET request:**
```
METHOD = "GET"
PATH = "/v1/users?pageNumber=0&pageSize=10"
TIMESTAMP = "1774349677000"
BODY = "" ← empty for GET
data_to_sign = "GET|/v1/users?pageNumber=0&pageSize=10|1774349677000|"
X-Signature = HMAC-SHA256(data_to_sign, secret) → "a3f9c2..."
```
**Example — POST request:**
```
METHOD = "POST"
PATH = "/v1/lightnings/within"
TIMESTAMP = "1774349677000"
BODY = '{"center":{"lat":39.87,"lng":32.74},"radius":50000,...}'
data_to_sign = "POST|/v1/lightnings/within|1774349677000|{\"center\":...}"
X-Signature = HMAC-SHA256(data_to_sign, secret) → "7be41d..."
```
### Security Recommendations
* Never commit `IKLIM_HMAC_SECRET` or `IKLIM_PASSWORD` to source control or git history
* In production, use system environment variables or a secrets manager instead of a `.env` file
* Use separate credentials for each environment (prod / test / local)
* Rotate the HMAC secret regularly
# Precipitation Geometry
Source: https://api-docs.iklim.co/pages/en/precipitation-geometry
Learn how to decode the protobuf-encoded geometry response from the /v1/precipitations/geometry endpoint and render it as standard GeoJSON.
Each polygon is pre-clipped to the exact circular area you queried — no post-processing needed on your side.
Protobuf encoding cuts payload size significantly compared to JSON, making it ideal for mobile and embedded clients.
Two decode steps are all it takes to go from raw bytes to shapes you can drop straight into geojson.io or Mapbox.
## What the endpoint returns
`POST /v1/precipitations/geometry` responds with a raw binary body (`Content-Type: application/x-protobuf`).
The bytes encode a `PrecipitationFeatureCollection` message — a list of `PrecipitationFeature` items, each carrying a cropped `MultiPolygon` geometry and typed precipitation properties.
Do **not** try to parse the response body as JSON or text. It is a protobuf binary stream and will look like garbage if treated as a string.
***
## Proto schema 📄
Below are the proto message definitions used by iklim.co for this endpoint.
```protobuf theme={null}
// Shared geometry primitives
message Coordinate {
double lng = 1;
double lat = 2;
}
message Ring {
repeated Coordinate coordinates = 1;
}
message PolygonShape {
repeated Ring rings = 1;
}
message MultiPolygonShape {
repeated PolygonShape polygons = 1;
}
message Geometry {
enum GeometryType {
POINT = 0;
LINE_STRING = 1;
POLYGON = 2;
MULTI_POINT = 3;
MULTI_LINE_STRING = 4;
MULTI_POLYGON = 5;
GEOMETRY_COLLECTION = 6;
}
GeometryType type = 1;
oneof shape {
Coordinate point = 2;
LineStringShape line_string = 3;
PolygonShape polygon = 4;
MultiPointShape multi_point = 5;
MultiLineStringShape multi_line_string = 6;
MultiPolygonShape multi_polygon = 7;
GeometryCollectionShape geometry_collection = 8;
}
}
// Precipitation-specific messages
message PrecipitationProperties {
string intensity = 1; // e.g. "DRIZZLE", "LIGHT", "MODERATE"
int64 radar_timestamp = 2; // epoch milliseconds
bool forecast = 3; // true = NWP (Numerical Weather Prediction) forecast, false = radar observation
}
message PrecipitationFeature {
string id = 1;
Geometry geometry = 2;
PrecipitationProperties properties = 3;
}
message PrecipitationFeatureCollection {
repeated PrecipitationFeature features = 1;
}
```
Copy the full `geojson.proto` file into your project before running the decoder steps below. The `protobufjs` loader needs the file at runtime.
***
## Decoding & converting to GeoJSON 🛠️
**Why two steps?** GeoJSON is a JSON text format (RFC 7946) — it has no official protobuf schema. Its coordinates are bare nested arrays (`[[lng, lat], ...]`), which protobuf cannot represent without a named wrapper message. iklim.co's binary schema uses typed messages (`Coordinate`, `Ring`, `PolygonShape`, …) optimised for compact encoding, so a structural reshaping step is always required after decoding.
```bash theme={null}
npm install protobufjs
# or
yarn add protobufjs
```
Tell `fetch` to read the response as an `ArrayBuffer`, then wrap it in a `Uint8Array` for the proto decoder.
```javascript theme={null}
const response = await fetch("/v1/precipitations/geometry", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer "
},
body: JSON.stringify({
eventIds: ["69baa6b76e0fe76a1957273f", "4a1c3e9d8f2b07e5c6d4a1b2"],
center: { lat: 36.80, lng: 32.33 },
radius: 25000
})
});
const buffer = await response.arrayBuffer();
const bytes = new Uint8Array(buffer);
```
Load `geojson.proto` and decode the bytes into a JavaScript object.
```javascript theme={null}
import protobuf from "protobufjs";
const root = await protobuf.load("geojson.proto");
const PrecipitationFeatureCollection = root.lookupType(
"PrecipitationFeatureCollection"
);
const fc = PrecipitationFeatureCollection.decode(bytes);
```
At this point `fc` looks like:
```json theme={null}
{
"features": [{
"id": "69baa6b76e0fe76a1957273f",
"geometry": {
"type": "MULTI_POLYGON",
"multiPolygon": {
"polygons": [{
"rings": [{
"coordinates": [
{ "lng": 32.33, "lat": 36.80 },
{ "lng": 32.34, "lat": 36.81 }
]
}]
}]
}
},
"properties": {
"intensity": "DRIZZLE",
"radarTimestamp": 1773846900000,
"forecast": true
}
}]
}
```
Transform the decoded object into RFC 7946-compliant GeoJSON.
```javascript theme={null}
function toGeoJson(fc) {
return {
type: "FeatureCollection",
features: fc.features.map(feature => ({
type: "Feature",
id: feature.id,
geometry: {
type: "MultiPolygon",
coordinates: feature.geometry.multiPolygon.polygons.map(polygon =>
polygon.rings.map(ring =>
ring.coordinates.map(coord => [coord.lng, coord.lat])
)
)
},
properties: {
intensity: feature.properties.intensity,
radarTimestamp: feature.properties.radarTimestamp,
forecast: feature.properties.forecast
}
}))
};
}
const geoJson = toGeoJson(fc);
console.log(JSON.stringify(geoJson, null, 2));
```
Drop this single async function into your project — it handles fetch, decode, and GeoJSON conversion end-to-end.
```javascript theme={null}
import protobuf from "protobufjs";
async function fetchPrecipitationGeoJson({ eventIds, center, radius, token }) {
// 1 — Fetch binary response
const response = await fetch("/v1/precipitations/geometry", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`
},
body: JSON.stringify({ eventIds, center, radius })
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const bytes = new Uint8Array(await response.arrayBuffer());
// 2 — Decode protobuf
const root = await protobuf.load("geojson.proto");
const PrecipitationFeatureCollection = root.lookupType(
"PrecipitationFeatureCollection"
);
const fc = PrecipitationFeatureCollection.decode(bytes);
// 3 — Convert to GeoJSON
return {
type: "FeatureCollection",
features: fc.features.map(f => ({
type: "Feature",
id: f.id,
geometry: {
type: "MultiPolygon",
coordinates: f.geometry.multiPolygon.polygons.map(poly =>
poly.rings.map(ring =>
ring.coordinates.map(c => [c.lng, c.lat])
)
)
},
properties: {
intensity: f.properties.intensity,
radarTimestamp: f.properties.radarTimestamp,
forecast: f.properties.forecast
}
}))
};
}
// Usage
const geoJson = await fetchPrecipitationGeoJson({
eventIds: ["69baa6b76e0fe76a1957273f", "4a1c3e9d8f2b07e5c6d4a1b2"],
center: { lat: 36.80, lng: 32.33 },
radius: 25000,
token: ""
});
```
The final GeoJSON object is RFC 7946-compliant and renders correctly in any GeoJSON viewer.
```json theme={null}
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": "69baa6b76e0fe76a1957273f",
"geometry": {
"type": "MultiPolygon",
"coordinates": [
[
[
[32.33, 36.80],
[32.34, 36.81],
[32.35, 36.80],
[32.33, 36.80]
]
]
]
},
"properties": {
"intensity": "DRIZZLE",
"radarTimestamp": 1773846900000,
"forecast": true
}
}
]
}
```
🧪 Copy the output above and paste it into **[geojson.io](https://geojson.io/)** to visually verify your geometry is correct before integrating with your map renderer.
***
## Field reference 📚
### `PrecipitationFeature`
| Field | Type | Description |
| - | - | - |
| `id` | string | Unique identifier of the radar geometry record. |
| `geometry` | object | Cropped `MultiPolygon` geometry (see below). |
| `properties` | object | Typed precipitation metadata. |
### `geometry` (MultiPolygon)
| Field | Path | Description |
| - | - | - |
| `type` | `geometry.type` | Always `MULTI_POLYGON` in the proto object; becomes `"MultiPolygon"` in GeoJSON. |
| `polygons` | `geometry.multiPolygon.polygons[]` | Array of polygons. Each polygon has one or more `rings`. |
| `rings` | `polygons[].rings[]` | First ring is the exterior boundary; additional rings are holes. |
| `coordinates` | `rings[].coordinates[]` | Array of `{ lng, lat }` objects → convert to `[lng, lat]` arrays for GeoJSON. |
### `properties`
| Field | Type | Description |
| - | - | - |
| `intensity` | string | Precipitation intensity label. See the `IntensityType` catalog below. |
| `radarTimestamp` | number | UTC epoch milliseconds of the radar scan that detected this event. |
| `forecast` | boolean | `true` = NWP (Numerical Weather Prediction) model forecast polygon; `false` = live radar observation. |
#### `IntensityType` catalog
| Value | Display name | Map color |
| - | - | - |
| `DRIZZLE` | Drizzle | `#7ae1e8` |
| `LIGHT` | Light | `#00c8d8` |
| `MODERATE` | Moderate | `#2d7bea` |
| `HEAVY` | Heavy | `#be46eb` |
| `VERY_HEAVY` | Very Heavy | `#e36546` |
| `EXTREME` | Extreme | `#ecd940` |
# Forecast Notification
Source: https://api-docs.iklim.co/webhook-messages/en/forecast
Multi-day weather forecast alarm payloads with per-metric violation details and district-level location context.
The alarm evaluates daily forecast data for up to 14 days ahead, so you can prepare before adverse conditions arrive.
Every payload carries precise coordinates alongside city and district identifiers, making geo-routing effortless.
Only metrics that actually breach your configured thresholds appear in `violations` — no noise, no guesswork.
```json theme={null}
{
"id": "a1b2c3d4-0000-4e5f-9876-000000000001",
"registrationId": "f7587d9e-2481-4b4c-818d-c8d1946851b7",
"recipientId": "customer-user-001",
"notificationTime": "2025-09-14T04:00:15.123",
"latitude": 39.9334,
"longitude": 32.8597,
"countryCode": "TUR",
"cityId": 6,
"city": "Ankara",
"districtId": 557,
"district": "Çankaya",
"distance": 1243.58,
"violations": [
{
"metric": "PRECIPITATION",
"unit": "mm",
"forecasts": [
{ "time": "2025-09-14T00:00:00Z", "value": 12.4 },
{ "time": "2025-09-15T00:00:00Z", "value": 18.1 }
]
},
{
"metric": "SNOWFALL",
"unit": "cm",
"forecasts": [
{ "time": "2025-09-14T00:00:00Z", "value": 8.5 }
]
},
{
"metric": "WIND_GUSTS_MAX",
"unit": "km/h",
"forecasts": [
{ "time": "2025-09-14T00:00:00Z", "value": 75.2 },
{ "time": "2025-09-15T00:00:00Z", "value": 82.0 }
]
},
{
"metric": "TEMPERATURE_MIN",
"unit": "°C",
"forecasts": [
{ "time": "2025-09-14T00:00:00Z", "value": -3.5 },
{ "time": "2025-09-15T00:00:00Z", "value": -5.1 }
]
},
{
"metric": "TEMPERATURE_MAX",
"unit": "°C",
"forecasts": [
{ "time": "2025-09-14T00:00:00Z", "value": 38.5 },
{ "time": "2025-09-15T00:00:00Z", "value": 40.1 }
]
}
]
}
```
| Field | Type | Description |
| - | - | - |
| `id` | string | Unique UUID generated for this specific alarm event delivery. |
| `registrationId` | string | UUID of the alarm registration that produced this delivery. |
| `recipientId` | string | Caller-supplied identifier that ties the notification back to your internal customer/asset record (no need to use human-readable values — hashed/anonymous IDs are fine for GDPR compliance); keep it unique per monitoring point because only one registration per `recipientId` is allowed for the same location, letting you route the alert without storing our `registrationId`. |
| `notificationTime` | string | ISO-8601 date-time (UTC) of when the alarm notification was dispatched (e.g. `2025-09-14T04:00:15.123`). |
| `latitude` | number | WGS84 latitude of the monitoring point. **`ADMINISTRATIVE`**: centroid of the registered district. **`POINT`**: the exact latitude supplied when the alarm was registered. |
| `longitude` | number | WGS84 longitude of the monitoring point. **`ADMINISTRATIVE`**: centroid of the registered district. **`POINT`**: the exact longitude supplied when the alarm was registered. |
| `countryCode` | string | ISO 3166-1 alpha-3 code of the country the registration belongs to: `TUR` (Türkiye) or `CTR` (Northern Cyprus, KKTC). `cityId` and `districtId` are unique only within a country, so always read them together with `countryCode`. |
| `cityId` | number | Numeric identifier of the city (province). **`ADMINISTRATIVE`**: the city ID supplied at registration. **`POINT`**: the city ID of the district whose forecast covers the registered coordinate. |
| `city` | string | Human-readable city name. Populated for `ADMINISTRATIVE` registrations; `null` for `POINT` registrations. |
| `districtId` | number | Numeric identifier of the district. **`ADMINISTRATIVE`**: the district ID supplied at registration. **`POINT`**: the district ID of the district whose forecast covers the registered coordinate. |
| `district` | string | Human-readable district name. Populated for `ADMINISTRATIVE` registrations; `null` for `POINT` registrations. |
| `distance` | number | Meters between the registered monitoring point and the district centroid used for forecast evaluation. For `ADMINISTRATIVE` registrations this value is effectively zero; for `POINT` registrations it reflects how far the registered coordinate is from the nearest district centroid. |
| `violations` | array | One entry per metric that breached the configured threshold; a single alarm can bundle multiple metrics simultaneously. |
#### `Metric` catalog — alarm-relevant values
| Value | Unit | Meaning |
| - | - | - |
| `PRECIPITATION` | mm | Total precipitation (rain + mixed) accumulated over the forecast interval. |
| `SNOWFALL` | cm | Snowfall depth accumulated over the forecast interval. |
| `WIND_GUSTS_MAX` | km/h | Daily maximum wind gust speed. |
| `TEMPERATURE_MIN` | °C | Daily minimum air temperature; triggers cold-snap alarms. |
| `TEMPERATURE_MAX` | °C | Daily maximum air temperature; triggers heat-wave alarms. |
#### `MetricForecasts` object
| Field | Type | Description |
| - | - | - |
| `violations[].metric` | enum | Metric identifier that exceeded the configured threshold (see catalog above). |
| `violations[].unit` | string | SI unit string for the metric values in this group (e.g. `mm`, `cm`, `km/h`, `°C`). |
| `violations[].forecasts` | array | Ordered list of forecast time slots during which the metric is predicted to breach the threshold. |
#### `Forecast` object
| Field | Type | Description |
| - | - | - |
| `forecasts[].time` | string | ISO-8601 timestamp (UTC) of the forecast time slot. |
| `forecasts[].value` | number | Forecast value for the metric in the units specified by `violations[].unit`. |
Use `registrationId` or `recipientId` to look up which of your assets or customers this alarm concerns — pair it with `city` / `district` for a quick human-readable summary.
Iterate over `violations` and map each `metric` to your internal risk policies. A single alarm can bundle multiple metrics (e.g. simultaneous heavy rain and strong gusts), so evaluate each entry independently.
Use `forecasts[].time` to schedule time-boxed actions — for example, send a heads-up notification at T-3h before the first threshold breach, and escalate if `value` keeps climbing across consecutive slots.
# Lightning Notification
Source: https://api-docs.iklim.co/webhook-messages/en/lightning
Deep dive into iklim.co lightning strike webhook payloads for automation-ready apps.
Every payload corresponds to a single lightning strike observed by our national detection grid.
Latitude/longitude pairs point to the geodesic centroid of the strike with meter-level precision.
Use the enriched attributes (current, multiplicity, distance) to decide whether to trigger claims or operational workflows.
```json theme={null}
{
"lightningId": "692c7dab7ca5904f700df66c",
"registrationId": "e71fedf8-a0a9-462e-a972-47dc8a5586df",
"recipientId": "recipient-04c2b9c7ad01",
"timeEpochMillis": 1764523421822,
"type": "FLASH_CLOUD_TO_GROUND",
"latitude": 41.0600156,
"longitude": 28.9795,
"peakCurrent": -7986,
"inCloudHeight": null,
"multiplicity": {
"inCloud": 0,
"cloudToGround": 1
},
"distance": 4994.386847196682
}
```
| Field | Type | Description |
| - | - | - |
| `lightningId` | string | Stable strike identifier generated by our detection platform. |
| `registrationId` | string | Subscription/registration that produced the webhook delivery. |
| `recipientId` | string | Caller-supplied identifier that points back to your system's asset/customer record (not required to be human readable—use hashed/anonymous IDs if needed for GDPR compliance); it must stay unique per geometry because we allow only one registration for the same area per `recipientId`, letting you locate the target without storing our `registrationId`. |
| `timeEpochMillis` | number | Millisecond UTC timestamp for when the strike occurred. |
| `type` | enum | Strike classification such as `FLASH_CLOUD_TO_GROUND` or `FLASH_IN_CLOUD`. |
| `latitude` | number | Decimal degree latitude of the strike centroid (WGS84). |
| `longitude` | number | Decimal degree longitude of the strike centroid (WGS84). |
| `peakCurrent` | number | Peak current in amperes (negative values represent negative polarity ground strokes). |
| `inCloudHeight` | number/null | Estimated altitude in meters for in-cloud events; `null` for pure cloud-to-ground flashes. |
| `multiplicity` | object | Nested counts describing how many sub-strokes occurred during the detection window. |
| `distance` | number | Approximate meters between the event and the registration centroid; `0` when the strike sits on the asset. |
#### `LightningType` catalog
| Value | Meaning |
| - | - |
| `FLASH_CLOUD_TO_GROUND` | Lightning channel that connects cloud charge directly to the ground; usually carries the strongest current and risk. |
| `PULSE_IN_CLOUD` | Electrical pulse that stays confined within or between clouds; useful for awareness but typically less damaging. |
| Field | Type | Description |
| - | - | - |
| `multiplicity.inCloud` | integer | Number of in-cloud sub-strokes contributing to the flash. |
| `multiplicity.cloudToGround` | integer | Number of cloud-to-ground return strokes. |
`FLASH_CLOUD_TO_GROUND` flashes usually drive the riskiest impacts—route them into high-priority queues, while in-cloud flashes can stay informational.
Combine `peakCurrent`, `distance`, and `multiplicity.*` to enrich your risk models (e.g., heavier current + short distance → immediate dispatch).
Store `lightningId` + coordinates so you can justify claim decisions or correlate against sensor data later.
# Precipitation Notification
Source: https://api-docs.iklim.co/webhook-messages/en/precipitation
Comprehensive webhook reference for rain, snow, and mixed precipitation alerts.
This webhook arrives when our nowcasting engines detect imminent precipitation over the registered polygon or point of interest.
Use it to nudge policyholders, pause outdoor jobs, or adjust underwriting exposures before the rain hits.
```json theme={null}
{
"precipitationId": "692d8218d4a02eb8afdf17a0",
"registrationId": "e71fedf8-a0a9-462e-a972-47dc8a5586df",
"recipientId": "recipient-04c2b9c7ad01",
"timeEpochMillis": 1764588600000,
"intensity": "HEAVY",
"centroid": {
"lat": 41.0151,
"lng": 28.9795
},
"distance": 1250.42
}
```
| Field | Type | Description |
| - | - | - |
| `precipitationId` | string | Stable identifier for this precipitation burst; use it for deduplication and historical lookups. |
| `registrationId` | string | ID of the webhook registration or subscription that the event belongs to. |
| `recipientId` | string | Caller-defined ID that ties the alert back to your internal customer/asset record (can be an anonymous/hashed key to meet GDPR compliance); it must remain unique per geometry because only one registration is allowed for the same area per `recipientId`, letting you route the alert without storing our `registrationId`. |
| `timeEpochMillis` | number | UTC timestamp in milliseconds that marks when the precipitation is predicted to reach the target centroid. |
| `intensity` | enum | Qualitative rate derived from radar reflectivity + sensor fusion. |
| `centroid` | object | Geographic center (latitude/longitude) of the impacted area. |
| `distance` | number | Meters between the centroid of the precipitation cell and your registration reference point. |
#### `centroid` object
| Field | Type | Description |
| - | - | - |
| `centroid.lat` | number | Latitude in decimal degrees (WGS84). |
| `centroid.lng` | number | Longitude in decimal degrees (WGS84). |
#### `PrecipitationIntensity` catalog
| Value | Meaning |
| - | - |
| `DRIZZLE` | Very light drops or mist-like precipitation ideal for heads-up only messaging. |
| `LIGHT` | Gentle rain/snow with minimal expected impact. |
| `MODERATE` | Sustained precipitation that can already reduce visibility or traction. |
| `HEAVY` | Intense shower that can trigger pooling, hail, or rapid accumulation. |
| `VERY_HEAVY` | Longer, near-severe bursts that typically overwhelm drainage. |
| `EXTREME` | Exceptional events requiring immediate escalations and risk mitigation. |
Confirm that the combination of `precipitationId` + `registrationId` has not been processed before to avoid duplicate downstream actions.
Map `intensity` to your internal severity scale (e.g., trigger low-level alerts for `LIGHT`, but escalate for `HEAVY` and `EXTREME`).
Send SMS/push notifications, schedule inspections, or store the event for exposure analytics together with `timeEpochMillis`.
# Thunderstorm Notification
Source: https://api-docs.iklim.co/webhook-messages/en/thunderstorm
Polygon-based thunderstorm lifecycle payload with severity, timing, and lightning cell analytics.
Describes the full thunderstorm window (start, end, severity) so you can orchestrate escalations minutes in advance.
Includes the exact exterior ring that intersects your registration, enabling map overlays out of the box.
Cell metrics expose flash rates, direction, and motion derived from real-time lightning telemetry.
```json theme={null}
{
"thunderstormId": "692d5b2cf4142f722546d77a",
"eventId": "EAF202512010907001",
"registrationId": "e71fedf8-a0a9-462e-a972-47dc8a5586df",
"recipientId": "recipient-04c2b9c7ad01",
"eventStartUtc": 1764580080,
"eventEndUtc": 1764582780,
"severity": "LOW",
"threshold": 3,
"speedKmh": 38.892,
"distance": 0,
"affectedPolygon": {
"exterior": [
{ "lng": 28.943784, "lat": 40.979168 },
{ "lng": 28.943784, "lat": 41.051032 },
{ "lng": 29.015216, "lat": 41.051032 },
{ "lng": 29.015216, "lat": 40.979168 },
{ "lng": 28.943784, "lat": 40.979168 }
],
"holes": []
},
"cell": {
"area": 8.999704133858742,
"speedKmh": 33.293898860271185,
"direction": 255,
"distance": null,
"centroid": {
"lng": 28.9795,
"lat": 41.0151
},
"polygon": {
"exterior": [
{ "lng": 28.961642, "lat": 41.0151 },
{ "lng": 28.970571, "lat": 41.033066 },
{ "lng": 28.988429, "lat": 41.033066 },
{ "lng": 28.997358, "lat": 41.0151 },
{ "lng": 28.988429, "lat": 40.997134 },
{ "lng": 28.970571, "lat": 40.997134 },
{ "lng": 28.961642, "lat": 41.0151 }
],
"holes": []
},
"flashRates": {
"inCloud": 4.33,
"cloudToGround": 0.34,
"total": 4.67
}
}
}
```
| Field | Type | Description |
| - | - | - |
| `thunderstormId` | string | Unique ID for this thunderstorm instance; use it to correlate updates. |
| `eventId` | string | Business-friendly identifier that aligns with the iklim.co event catalog. |
| `registrationId` | string | Subscription/registration that subscribed to the thunderstorm product. |
| `recipientId` | string | Caller-defined identifier that maps the notification back to your customer/asset record (feel free to use hashed/anonymized unique IDs instead of human labels for GDPR compliance); keep it unique per geometry because only one registration is allowed for the same area per `recipientId`, and use it when you prefer to locate your target without storing our `registrationId`. |
| `eventStartUtc` | number | UTC epoch seconds for when the storm will enter the protected area. |
| `eventEndUtc` | number | UTC epoch seconds for when the storm is expected to leave the area. |
| `severity` | enum | Discrete bucket such as `LOW`, `MEDIUM`, or `HIGH` based on flash density and speed. |
| `threshold` | number | Minimum lightning flash count per minute that qualifies the cluster as a thunderstorm. |
| `speedKmh` | number | Forecasted forward speed of the whole thunderstorm envelope in km/h—pair it with `direction` to project the track. |
| `direction` | number | Bearing in degrees (0° = North, clockwise) that shows where the envelope described by `speedKmh` is heading. |
| `distance` | number | Meters between the storm edge and your registration centroid (`0` when currently overhead). |
#### `ThunderstormSeverity` catalog
| Value | Meaning |
| - | - |
| `LOW` | Sporadic flashes with limited coverage; treat as FYI or low-touch workflows. |
| `MEDIUM` | Sustained lightning clusters capable of causing localized damage; escalate to mid-tier playbooks. |
| `HIGH` | Widespread, fast-moving cells with extreme flash rates; trigger your highest alert level immediately. |
#### `affectedPolygon`
| Field | Type | Description |
| - | - | - |
| `affectedPolygon.exterior[]` | `array` | Clockwise list of longitude/latitude vertices that intersect your registration boundary. |
| `affectedPolygon.holes[]` | `array` | Optional voids to subtract (usually empty). |
#### `cell`
| Field | Type | Description |
| - | - | - |
| `cell.area` | number | Cell area in square kilometers. |
| `cell.speedKmh` | number | Speed of the most active lightning cell (km/h); use with `cell.direction` to see how quickly the core is approaching or leaving. |
| `cell.direction` | number | Movement bearing in degrees (0° = North, clockwise) aligned with `cell.speedKmh`, highlighting the lightning core's heading. |
| `cell.distance` | number/null | Kilometers from the cell centroid to the registered asset; `null` when it overlaps. |
| `cell.centroid.lng` | number | Longitude of the lightning cell centroid. |
| `cell.centroid.lat` | number | Latitude of the lightning cell centroid. |
| `cell.polygon.exterior[]` | `array` | High-resolution outline of the most energetic cell. |
| `cell.polygon.holes[]` | `array` | Optional interior voids for the cell polygon. |
| `cell.flashRates.inCloud` | number | In-cloud flashes per minute. |
| `cell.flashRates.cloudToGround` | number | Cloud-to-ground flashes per minute. |
| `cell.flashRates.total` | number | Sum of all flash types per minute. |
Convert `eventStartUtc`/`eventEndUtc` to your user's timezone and show a friendly countdown in customer apps.
Overlay `affectedPolygon` + `cell.polygon` on your GIS layer to highlight precisely where the danger lies.
When `severity` ≥ `MEDIUM` and `distance` ≤ 5 km, automatically notify high-value customers or halt risky operations.
# Webhook Endpoint Guide
Source: https://api-docs.iklim.co/webhook-messages/en/webhook-endpoint-guide
Authentication and response requirements shared across all iklim.co webhook alarm deliveries.
## Incoming request authentication
When iklim.co delivers an alarm to your webhook endpoint, it attaches the credentials you configured during alarm registration. The exact mechanism depends on the authentication type you selected:
| Auth type | How credentials are sent |
| - | - |
| **Basic** | `Authorization: Basic ` header |
| **API Key — as header** | `X-API-Key: ` header |
| **API Key — as query parameter** | `?apiKey=` appended to the URL |
| **JWT** | `Authorization: Bearer ` header |
| **HMAC Signature** | `X-Client-Id`, `X-Signature`, `X-HmacAlgorithm`, `X-Timestamp`, `X-Nonce` headers |
If you chose **API Key** authentication, exactly one of the two delivery modes (`asHeader` or `asQueryParameter`) is active — never both at the same time.
***
## Your endpoint's response
After receiving an alarm payload, your webhook endpoint must respond with HTTP `200` and a JSON body conforming to the `AlarmReceived` schema. iklim.co uses both the HTTP status code and the body-level `status` field to decide whether delivery was successful or should be retried.
```json theme={null}
{
"id": "a1b2c3d4-0000-4e5f-9876-000000000001",
"status": "SUCCESS",
"timeEpochMillis": 1726286415123
}
```
| Field | Type | Description |
| - | - | - |
| `id` | string | Echo back the `id` from the received alarm payload exactly as-is. iklim.co compares this value against the dispatched alarm's `id` for correlation — a mismatch is treated as a transient failure and triggers a retry. |
| `status` | enum | Processing outcome reported by your endpoint (see status table below). |
| `timeEpochMillis` | number | Unix epoch timestamp in milliseconds at which your endpoint processed the alarm. |
#### `Status` values
| Value | Meaning | iklim.co action |
| - | - | - |
| `SUCCESS` | Alarm was received and processed successfully. | Stop retrying, move to the next alarm. |
| `CANCELLED` | Your system has opted out of further deliveries for this alarm. | Stop retrying, move to the next alarm. |
| `FAILURE` | Transient error on your side (e.g. downstream service unavailable). | Run retry policy. |
| `TIMEOUT` | Your endpoint timed out internally while processing the alarm. | Run retry policy. |
| `UNKNOWN` | Unexpected condition; cause is unclear. | Run retry policy. |
iklim.co evaluates the HTTP status code **before** inspecting the response body. Non-2xx responses never reach body-level evaluation.
| HTTP | Body status | Reason | Action |
| - | - | - | - |
| 200 | `SUCCESS` | Processed successfully | Stop retrying |
| 200 | `CANCELLED` | Endpoint opted out | Stop retrying |
| 200 | `FAILURE` | Transient recipient-side error | Retry |
| 200 | `TIMEOUT` | Recipient-side timeout | Retry |
| 200 | `UNKNOWN` | Unexpected recipient-side error | Retry |
| 200 | id mismatch or null body | Correlation check failed | Retry |
| 400 | — | Payload corrupted / missing fields | Retry |
| 401 | — | Authorization invalid or missing | Stop retrying |
| 403 | — | Access denied | Stop retrying |
| 404 | — | Endpoint resource not found | Stop retrying |
| 405 | — | HTTP method not allowed | Stop retrying |
| 429 | — | Rate limit exceeded | Retry |
| 500 | — | Unexpected server-side error | Retry |
| 502 | — | Invalid upstream response | Retry |
| 503 | — | Service temporarily unavailable | Retry |
| 504 | — | Upstream gateway timeout | Retry |