Vaktija.eu
Bosna i Hercegovina flag

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, GET requests only
Access
API key from [email protected]

Quick start

  1. Find the id of your location in the location list (no key needed).
  2. Ask for an API key at [email protected].
  3. Request the prayer times with your key:
Request
cURL
curl -H "api-key: YOUR_API_KEY" \
  https://api.vaktija.eu/v4/locations/7/today

Authentication

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

EndpointKeyReturns
GET /locationsNoEvery location with id, name, slug, coordinates and country
GET /locations/{id}/todayYesToday's times by id
GET /locations/slug/{slug}/todayYesToday's times by slug
GET /locations/{id}YesTimes for every day of the year by id
GET /locations/slug/{slug}YesTimes 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

GET/v4/locations
No API key needed

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.

Request
cURL
curl https://api.vaktija.eu/v4/locations
200 OK (shortened)
{
  "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" }
}
FieldTypeDescription
idintegerLocation id, used in the other endpoints
namestringDisplay name in the local spelling
slugstringURL-safe name, also used in the other endpoints
latitude, longitudenumberPosition of the location
country.codestringISO 3166-1 alpha-2 country code
country.id, name, sluginteger, stringOur country id and lower-case identifiers (bosnia)

Today's times

GET/v4/locations/{id}/today
GET/v4/locations/slug/{slug}/today
API key required

The times for the current date at the location.

Request
cURL
curl -H "api-key: YOUR_API_KEY" \
  https://api.vaktija.eu/v4/locations/7/today
200 OK
{
  "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" }
}
FieldTypeDescription
datestringThe date these times are for, YYYY-MM-DD, at the location
prayers.fajr … ishastringSix times, HH:MM:SS (see Times and time zones)
location.id, name, sluginteger, stringThe location, as in the location list
location.timezonestringIANA time zone of the location, e.g. Europe/Berlin

Full year

GET/v4/locations/{id}
GET/v4/locations/slug/{slug}
API key required

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).

Request
cURL
curl -H "api-key: YOUR_API_KEY" \
  https://api.vaktija.eu/v4/locations/7
200 OK (shortened to two days)
{
  "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" }
}
FieldTypeDescription
id, name, sluginteger, stringThe location, as in the location list
latitude, longitudenumberPosition of the location
timezonestringIANA time zone of the location, e.g. Europe/Berlin
monthsobjectKeyed by month, "1" to "12"
months[m].daysobjectKeyed by day of the month, "1" to "31"
days[d].day, monthintegerThe day and month of the entry
days[d].fajr … ishastringSix times, HH:MM:SS

Look a day up by month and day. Use the date at the location, not your server's:

Node.js
Node.js
// 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_version changes 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.
  • timezone is 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.
  • sunrise is 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.

  1. Fetch the full year of each of your locations once and store it.
  2. Once a day, make one request, for example today's times of one of your locations, and compare meta.data_version with the value you stored. It is the same for every location, so one request a day is enough however many locations you have.
  3. When data_version has changed, times were corrected: fetch the full year of each location again.
  4. Work out today's times and the next prayer from your copy. Do not call the API every minute.
  5. If the API cannot be reached, keep using your copy.
Node.js
Node.js
// 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:

FieldTypeDescription
X-RateLimit-LimitheaderRequests allowed per window
X-RateLimit-RemainingheaderRequests left in the current window
X-RateLimit-ResetheaderSeconds until the window resets
Retry-AfterheaderOnly 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.

Error body
{
  "statusCode": 403,
  "error": "Forbidden",
  "message": "This API key may not read this location"
}

401 Unauthorized
No api-key header was sent.

Response
{
  "statusCode": 401,
  "error": "Unauthorized",
  "message": "API key required (api-key header)"
}

401 Unauthorized
The key is unknown or has been revoked.

Response
{
  "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.

Response
{
  "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.

Response
{
  "statusCode": 403,
  "error": "Forbidden",
  "message": "This API key may not use this route"
}

404 Not Found
The endpoint does not exist.

Response
{
  "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.

Response
{
  "statusCode": 429,
  "error": "Too Many Requests",
  "message": "Too many requests"
}

500 Internal Server Error
Something went wrong on our side. Try again later.

Response
{
  "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].