Takbeer Time · API

Takbeer Time API

A public, JSON REST API for masjid prayer times, jamat times, jamaat times, and iqamah updates. Build a widget, a Slack bot, a dashboard, or a masjid-finder for your community website.

Introduction

Base URL:

https://takbeertime.com/api

The base URL returns discovery metadata for clients and uptime checks:

curl "https://takbeertime.com/api"

All requests and responses are JSON. The API is read-mostly and most endpoints don't need authentication — read paths are open so a directory app can ship without account plumbing. Write paths (creating masjids, submitting timings, voting, suggesting updates) need a signed-in user.

No ads, ever. The API is free for any genuine use. Sadqa fe sabilillah. Heavy commercial use? Drop us a note so we know what to plan capacity for.

Authentication

Pass the Takbeer Time app JWT returned by /auth/register, /auth/login, or /auth/google in the Authorization header:

Authorization: Bearer <app-jwt>

The backend also accepts a verified Firebase ID token on authenticated routes for compatibility, but the app JWT is the normal client contract. Anonymous calls to read endpoints return public fields only — calls to write or to /users/me/* return 401 without a token.

For local development, the server accepts an X-Dev-User-Email header in lieu of an Authorization bearer token, but only when DEV_AUTH_USER_EMAIL is set in the deploy. Production never accepts it.

POST /auth/register Public 5 / hr / IP

Create an email/password account and return { token, user }. If a dev-created user exists without a password, this claims the account; Google-only or deleted accounts return 409.

{
  "email": "[email protected]",
  "password": "at-least-8-characters",
  "fullName": "User Name"
}
POST /auth/login Public 10 / 15 min / IP

Sign in with email/password and return { token, user }.

{ "email": "[email protected]", "password": "..." }
POST /auth/google Public 20 / 15 min / IP

Exchange a Firebase Google sign-in ID token for the same Takbeer Time app JWT used by email/password auth.

{ "idToken": "firebase-google-id-token" }

Rate limits

Per-user (or per-IP for anonymous), one-hour rolling window. RateLimit-* headers report current state on every response.

EndpointCap (per hour)
POST /submissions30
POST /submissions/:id/vote60
POST /suggestions10
POST /mosques5
POST /auth/login10 per 15 minutes per IP
POST /auth/google20 per 15 minutes per IP
POST /auth/register5 per IP
POST /account-deletion-request1 pending request per email per hour

Exceeding the cap returns 429 Too Many Requests. Read endpoints are not rate-limited at present.

Errors

Every error response is JSON with at least error and message fields.

{
  "error": "Validation Error",
  "message": "lat is required"
}
StatusMeaning
400Validation failed (missing/invalid params)
401Not authenticated (missing or expired token)
403Authenticated, but not allowed for this resource
404Resource not found (or soft-deleted)
429Rate limit exceeded
500Server error — please report

Response shape

Paginated endpoints return:

{
  "data": [ ... ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "totalCount": 281696,
    "totalPages": 14085,
    "hasMore": true
  }
}

Times are stored and returned as 24-hour HH:mm strings. Maghrib is not stored as a clock time — it's astronomical sunset for the masjid's coordinates plus a per-masjid maghribOffset (number of minutes after sunset that takbeer is called). Compute Maghrib client-side; the API only carries the offset.

Masjids

GET /mosques/recent Public

Latest active masjids added to the directory. Returns up to 12 records for "recently added" widgets and community dashboards.

GET /mosques/nearby Public

Find masjids within a radius of a coordinate, sorted by distance. The active prayer schedule is included on each result so you can label markers without a second round-trip.

Query params

NameType
latnumberRequired. Latitude (-90..90).
lngnumberRequired. Longitude (-180..180).
radiusnumberSearch radius in meters. Default 5000, max 200000.
limitnumberDefault 20, max 100.

Example

curl "https://takbeertime.com/api/mosques/nearby?lat=33.7295&lng=73.0372&radius=5000"
GET /mosques Public

Paginated, filterable list. Use this when you don't have coordinates — e.g. to enumerate all masjids in a city.

Query params

citystringFilter by city name (case-insensitive).
countrystringFilter by country.
searchstringTrigram-matched search across name + city.
pagenumber1-indexed. Default 1.
limitnumberDefault 20, max 100.
GET /mosques/:id Public

Full masjid record: location, amenities, the active prayer schedule, the ranked keepers list, and the effective times for the calling user (their preferred keeper, or the top-rated keeper if no preference set).

Response (excerpt)

{
  "id": "...",
  "name": "Faisal Mosque",
  "latitude": 33.7295,
  "longitude": 73.0372,
  "city": "Islamabad",
  "country": "Pakistan",
  "amenities": ["parking", "wudhu", "women_section"],
  "prayerSchedules": [{
    "timings": { "fajr": "05:00", "dhuhr": "13:30", "maghribOffset": 3, ... },
    "verificationStatus": "verified"
  }],
  "keepers": [...],
  "effectiveTimings": { "fajr": "05:00", ... },
  "effectiveKeeperId": "...",
  "effectiveKeeperName": "..."
}
GET /mosques/:id/keepers Public

Ranked list of contributors who've submitted timings for this masjid. Each keeper's rating is computed as voteScore × recencyWeight × verifiedBoost × repBoost × keeperBoost. The top-rated keeper's submission is the default for users who haven't picked otherwise.

GET /mosques/:id/consensus Public

Per-prayer consensus over recent submissions. Shows the time, confidence (0..1), and contributor count for each prayer. Useful for masjids that don't yet have an active schedule — surface "the community thinks Fajr is around 05:00 (62% confidence)" instead of nothing.

GET /mosques/:id/reviews Public

Paginated reviews for a masjid. Sorted by helpful-count then recency.

POST /mosques Auth 5 / hr

Add a new masjid to the directory. The caller must be signed in; abuse is rate-limited.

Body

{
  "name": "Masjid Al-Falah",
  "latitude": 33.7295,
  "longitude": 73.0372,
  "city": "Islamabad",
  "country": "Pakistan",
  "addressLine1": "Block 4, Garden Town"   // optional
}
PUT /mosques/:id Auth

Edit a masjid created by the authenticated user. Returns 403 if another contributor owns the masjid record.

Body

Any field accepted by POST /mosques may be sent as a partial update. If either coordinate changes, send both latitude and longitude.

POST /mosques/:id/reviews Auth

Create or update the authenticated user's review for a masjid.

{
  "rating": 5,
  "timingAccuracyRating": 5,
  "cleanlinessRating": 4,
  "accessibilityRating": 4,
  "title": "Reliable timings",
  "reviewText": "The posted jamaat times match the board."
}
POST /mosques/:id/favorite Auth

Toggle: if not already favorited, save it; if already, remove. Returns { favorite: true|false }.

Submissions

A "submission" is a contributor's proposed jamat times for a specific masjid. Multiple users can submit; the consensus algorithm + per-keeper rating decide which becomes the active schedule.

GET /submissions Public

Paginated submissions, filterable by masjid id and status.

Query params

mosqueIduuidRestrict to a single masjid.
statusstringpending | approved | rejected.
page, limitnumberStandard pagination.
GET /submissions/:id Public

A single submission with the contributor's profile (name, reputation, verified flag).

POST /submissions Auth 30 / hr

Submit timings for a masjid. After creation the consensus pipeline runs — your submission may immediately become the active schedule if confidence is high enough.

Body

{
  "mosqueId": "uuid",
  "timings": {
    "fajr":   "05:00",
    "dhuhr":  "13:30",
    "asr":    "17:00",
    "isha":   "20:00",
    "jummah": ["13:30"],
    "maghribOffset": 3        // minutes after astronomical sunset
  },
  "notes": "winter schedule"  // optional
}
Don't send maghrib as HH:MM. Maghrib is computed client-side from the masjid's coordinates + the offset you submit. The server silently strips any maghrib field.
POST /submissions/:id/vote Auth 60 / hr

Up- or down-vote a submission. Voting on your own submission returns 400.

Body

{ "voteType": "upvote" | "downvote" | "report",
  "reportReason": "..."  // required when voteType is "report" }
POST /submissions/:id/review Auth

Verified contributors can approve or reject a pending timing submission. Approval installs a verified active schedule and rewards the submitter.

Body

{
  "action": "approve" | "reject",
  "reviewNotes": "Checked against the masjid board"
}

Suggestions

A direct user-to-keeper proposal: "I think Fajr should be 05:16 instead of 05:00, can you update your record?" The keeper sees it in their inbox and accepts or declines. Accepting writes the suggested values to the masjid's active schedule (the keeper is authoritative over their own record — no consensus floor needed).

POST /suggestions Auth 10 / hr

Send a suggestion to a specific keeper (the user whose timings are currently active for the masjid, or anyone in the keeper list).

Body

{
  "toUserId": "uuid",        // recipient keeper
  "mosqueId": "uuid",
  "timings":  { "fajr": "05:16", "maghribOffset": 5 },
  "notes":    "Ramadan schedule starts tomorrow"
}
GET /suggestions/inbox Auth

Pending suggestions sent to the authenticated user. Each suggestion includes the sender, the masjid, the proposed times, AND the masjid's current active timings — so a UI can render an old → new diff in one round-trip.

POST /suggestions/:id/accept Auth

Mark accepted, install the proposed timings on the masjid's active schedule (merged over current values), and create a TimingSubmission as the keeper for the audit trail. Returns 403 if the calling user isn't the recipient.

POST /suggestions/:id/decline Auth

Mark declined with optional note. No schedule change.

Body

{ "note": "These times are wrong, please re-check" }  // optional

Users

GET /users/me Auth

The authenticated user's profile, default mosque, and counts.

PUT /users/me Auth

Update profile fields.

Body (all optional)

{
  "fullName":         "Junaid Qazi",
  "phoneNumber":      "+92...",
  "preferredLanguage": "ur",
  "defaultMosqueId":   "uuid"   // null clears it
}
GET /users/me/favorites Auth

Paginated list of mosques the user has favorited.

PUT /users/me/preferred-keeper Auth

Set or clear the user's preferred keeper for one masjid. The masjid detail's effectiveTimings use this keeper's submission when set.

{ "mosqueId": "uuid", "keeperUserId": "uuid" | null }
PUT /users/me/reminder-prefs Auth

Mirror the user's reminder preferences server-side so they sync across devices.

{
  "enabled":       true,
  "perPrayer":     { "fajr": 10, "dhuhr": 10, "asr": 10, "maghrib": 10, "isha": 10, "jummah": 0 },
  "prayerEnabled": { "fajr": true, "dhuhr": true, ... }
}
GET /users/me/notifications Auth

Paginated notifications (timing changes, new keepers, etc.). Add ?unread=true to filter.

PATCH /users/me/notifications/:id/read Auth

Mark one notification as read. Returns 404 if the notification does not belong to the authenticated user.

POST /users/me/notifications/read-all Auth

Mark all unread notifications for the authenticated user as read. Returns { marked: number }.

DELETE /users/me Auth

Self-service account deletion. Soft-deletes the user, clears favorites and reminder preferences, removes sent pending suggestions, and keeps community timing submissions for continuity.

Schedules

Prayer schedules are derived state — a snapshot of a masjid's timings at a point in time, with a verification status (pending or verified) and a validity range. Most apps don't need these endpoints; /mosques/:id already includes the active schedule. Use these when you want history.

GET /schedules Public

Paginated list of schedules across masjids.

GET /schedules/:id Public

A single schedule with its verifier and contributor.

GET /schedules/mosque/:mosqueId/active Public

The currently active schedule for a masjid. 404 if the masjid has none.

Compliance

POST /account-deletion-request Public 1 / hr / email

Auth-less account deletion request for users who cannot access the app. The response does not reveal whether the email belongs to an account.

{
  "email": "[email protected]",
  "reason": "I can no longer access the app"
}