vaktija.eu API
Prayer times for mosque screens, websites, apps and automation, from the same data as vaktija.eu and the Vaktija apps.
- Base URL
https://api.vaktija.eu/v4- Format
- JSON over HTTPS,
GETrequests only - Access
- API key from [email protected]
Quick start
- Find the id of your location in the location list (no key needed).
- Ask for an API key at [email protected].
- Request the prayer times with your key:
curl -H "api-key: YOUR_API_KEY" \
https://api.vaktija.eu/v4/locations/7/todayAuthentication
Every endpoint that returns prayer times checks your API key. Send it in the api-key header with each request. The location list needs no key.
- A key is issued for specific locations. Requests for any other location answer
403. - Keep the key secret. Call the API from your server or device, never from a web page that visitors can read. Browsers on other websites cannot call the API.
- If a key leaks, tell us and we will replace it.
Getting a key
Write to [email protected] with:
- your organisation and what you are building
- the locations you need, by id from the location list
- roughly how many screens, devices or users will use it
Endpoints
| Endpoint | Key | Returns |
|---|---|---|
GET /locations | No | Every location with id, name, slug, coordinates and country |
GET /locations/{id}/today | Yes | Today's times by id |
GET /locations/slug/{slug}/today | Yes | Today's times by slug |
GET /locations/{id} | Yes | Times for every day of the year by id |
GET /locations/slug/{slug} | Yes | Times for every day of the year by slug |
Paths are relative to https://api.vaktija.eu/v4. A location can be named by its id (7) or its slug (ulm); we recommend the id. Nothing else is available to partners.
List locations
Every location we publish prayer times for, so you can find the ids to ask for. The list is large (about 5,400 locations, 170 KB compressed) and rarely changes: load it when you set up a location, not on every start.
curl https://api.vaktija.eu/v4/locations{
"status": "success",
"data": [
{
"id": 7,
"name": "Ulm",
"slug": "ulm",
"latitude": 48.4011,
"longitude": 9.9876,
"country": { "id": 1, "name": "germany", "slug": "germany", "code": "DE" }
},
{
"id": 132,
"name": "Sarajevo",
"slug": "sarajevo",
"latitude": 43.8563,
"longitude": 18.4131,
"country": { "id": 5, "name": "bosnia", "slug": "bosnia", "code": "BA" }
}
],
"meta": { "data_version": "3.16" }
}| Field | Type | Description |
|---|---|---|
id | integer | Location id, used in the other endpoints |
name | string | Display name in the local spelling |
slug | string | URL-safe name, also used in the other endpoints |
latitude, longitude | number | Position of the location |
country.code | string | ISO 3166-1 alpha-2 country code |
country.id, name, slug | integer, string | Our country id and lower-case identifiers (bosnia) |
Today's times
The times for the current date at the location.
curl -H "api-key: YOUR_API_KEY" \
https://api.vaktija.eu/v4/locations/7/today{
"status": "success",
"data": {
"date": "2026-10-30",
"prayers": {
"fajr": "05:13:00",
"sunrise": "06:56:00",
"dhuhr": "12:09:00",
"asr": "14:42:00",
"maghrib": "17:12:00",
"isha": "18:42:00"
},
"location": {
"id": 7,
"name": "Ulm",
"slug": "ulm",
"timezone": "Europe/Berlin"
}
},
"meta": { "data_version": "3.16" }
}| Field | Type | Description |
|---|---|---|
date | string | The date these times are for, YYYY-MM-DD, at the location |
prayers.fajr … isha | string | Six times, HH:MM:SS (see Times and time zones) |
location.id, name, slug | integer, string | The location, as in the location list |
location.timezone | string | IANA time zone of the location, e.g. Europe/Berlin |
Full year
The times for every day of the year in one response: fetch it once, store it and work from your copy (see Caching and updates).
curl -H "api-key: YOUR_API_KEY" \
https://api.vaktija.eu/v4/locations/7{
"status": "success",
"data": {
"id": 7,
"name": "Ulm",
"slug": "ulm",
"latitude": 48.4011,
"longitude": 9.9876,
"timezone": "Europe/Berlin",
"months": {
"1": {
"days": {
"1": {
"day": 1,
"month": 1,
"fajr": "06:20:00",
"sunrise": "08:05:00",
"dhuhr": "12:29:00",
"asr": "14:22:00",
"maghrib": "16:43:00",
"isha": "18:13:00"
},
"2": {
"day": 2,
"month": 1,
"fajr": "06:20:00",
"sunrise": "08:05:00",
"dhuhr": "12:29:00",
"asr": "14:23:00",
"maghrib": "16:44:00",
"isha": "18:14:00"
}
}
}
}
},
"meta": { "data_version": "3.16" }
}| Field | Type | Description |
|---|---|---|
id, name, slug | integer, string | The location, as in the location list |
latitude, longitude | number | Position of the location |
timezone | string | IANA time zone of the location, e.g. Europe/Berlin |
months | object | Keyed by month, "1" to "12" |
months[m].days | object | Keyed by day of the month, "1" to "31" |
days[d].day, month | integer | The day and month of the entry |
days[d].fajr … isha | string | Six times, HH:MM:SS |
Look a day up by month and day. Use the date at the location, not your server's:
// The date at the location, which can differ from your server's date
const today = new Intl.DateTimeFormat('en-CA', { timeZone: data.timezone }).format(new Date())
const [, month, day] = today.split('-').map(Number)
const times = data.months[month].days[day]
console.log(times.fajr) // "06:20:00"- The calendar includes 29 February; other years simply skip it.
- The calendar has no year: it is the current year's. The times around the change to and from summer time (late March and late October) are adjusted every year, and
meta.data_versionchanges when they are.
Times and time zones
- Times are the local time at the location, 24-hour
HH:MM:SS, with summer time already applied. Show them as they are. timezoneis the location's IANA time zone. You only need it to turn a time into an exact moment on a computer in another time zone, for example a server that sends push notifications.sunriseis not a prayer: it marks the end of the time for Fajr.
Caching and updates
Prayer times rarely change: when a community corrects its calendar, and once a year around the summer time changes. Please do not request the same data more often than you need it.
- Fetch the full year of each of your locations once and store it.
- Once a day, make one request, for example today's times of one of your locations, and compare
meta.data_versionwith the value you stored. It is the same for every location, so one request a day is enough however many locations you have. - When
data_versionhas changed, times were corrected: fetch the full year of each location again. - Work out today's times and the next prayer from your copy. Do not call the API every minute.
- If the API cannot be reached, keep using your copy.
// Once a day, for all your locations together
const response = await fetch('https://api.vaktija.eu/v4/locations/7/today', {
headers: { 'api-key': process.env.VAKTIJA_API_KEY },
})
const { meta } = await response.json()
if (meta.data_version !== stored.dataVersion) {
// Times were corrected: fetch the full year of each location again
}Treat data_version as text and only check whether it changed; do not compare it as a number.
Rate limits
300 requests per 30 seconds per IP address. Following the advice above, you will stay far below it. Every response carries:
| Field | Type | Description |
|---|---|---|
X-RateLimit-Limit | header | Requests allowed per window |
X-RateLimit-Remaining | header | Requests left in the current window |
X-RateLimit-Reset | header | Seconds until the window resets |
Retry-After | header | Only on 429: seconds to wait before the next request |
Errors
Errors use the HTTP status code and a JSON body of the same shape. Decide on statusCode; the message is for people and may change.
{
"statusCode": 403,
"error": "Forbidden",
"message": "This API key may not read this location"
}401 Unauthorized
No api-key header was sent.
{
"statusCode": 401,
"error": "Unauthorized",
"message": "API key required (api-key header)"
}401 Unauthorized
The key is unknown or has been revoked.
{
"statusCode": 401,
"error": "Unauthorized",
"message": "Invalid or revoked API key"
}403 Forbidden
Your key is not issued for this location. Locations that do not exist answer the same way.
{
"statusCode": 403,
"error": "Forbidden",
"message": "This API key may not read this location"
}403 Forbidden
The endpoint is not available to your key, for example /v4/countries.
{
"statusCode": 403,
"error": "Forbidden",
"message": "This API key may not use this route"
}404 Not Found
The endpoint does not exist.
{
"statusCode": 404,
"error": "Not Found",
"message": "Cannot GET /v4/locations/7/tomorrow"
}429 Too Many Requests
You went over the rate limit. Wait for the number of seconds in the Retry-After header.
{
"statusCode": 429,
"error": "Too Many Requests",
"message": "Too many requests"
}500 Internal Server Error
Something went wrong on our side. Try again later.
{
"statusCode": 500,
"error": "Internal Server Error",
"message": "Internal server error"
}502, 503, 504
The service is briefly unreachable. The body may be an HTML page instead of JSON. Retry with growing pauses and keep using your stored data meanwhile.
Versioning
- This is version 4: every path starts with
/v4. - We may add fields to responses at any time. Ignore the ones you do not use.
- We do not remove or rename fields within a version. Breaking changes come as a new version, announced by email to everyone with a key.
Contact
For a key, questions, or a key that has leaked, write to [email protected].