Issue Nº 57 — Nov 12, 2026

Hebcal documents 90 requests per 10 seconds, but its headers say two other limits

Issue fifty-seven covers Hebcal in the Calendar category. It converts dates between the Gregorian and Hebrew calendars, lists holidays and gives prayer times. It needs no key and sends a wildcard CORS header. We queried it live on 12 November 2026. The docs page gives one rate limit, and the response headers give two others. The cache lifetime depends on one thing: does the request name a date?

Three rate-limit numbers

The developer docs say: "You may receive a 429 “Too Many Requests” error if your client makes more than 90 requests in a 10-second window." The live headers do not match this text.

GET /hebcal, /zmanim and /shabbat sent ratelimit-policy: "default";q=150;w=10. That is 150 requests in 10 seconds. GET /converter sent "default";q=60;w=3. That is 60 requests in 3 seconds, or 20 a second. The first policy allows 15 a second.

We sent 70 parallel calls to /converter, and all 70 returned 200. We did not time the spread, so this proves nothing about enforcement. Read the ratelimit header on each reply. Do not trust the docs number.

A dated request caches for longer

The same route can send different cache headers. The change comes from the date parameters, not the path.

GET /converter?cfg=json&gy=2026&gm=11&gd=12&g2h=1 returned cache-control: public, max-age=31536000, which is one year. With no date, /converter?cfg=json&g2h=1 does not answer at all. It returns a 302 redirect to the dated URL for today, with private, max-age=1200. So the redirect caches for 20 minutes, and the dated answer behind it caches for a year. At 00:21 UTC on 12 November, the redirect still pointed to 11 November. The server uses a time zone behind UTC.

GET /zmanim?cfg=json&geonameid=281184&date=2026-11-12 returned max-age=2592000, which is 30 days. The same call with no date returned no cache-control header. GET /shabbat?cfg=json&geonameid=281184&gy=2026&gm=11&gd=13 returned max-age=604800, which is 7 days. With no date, /shabbat also returned no cache-control header.

GET /hebcal?v=1&cfg=json&maj=on&year=2026&geo=none returned max-age=604800, or 7 days. Without year it returned max-age=32400, which is 9 hours.

This makes sense. An answer for a fixed date does not change. An answer for today changes every day. If you cache replies yourself, send an explicit date, or follow the header on each reply.

Leap years and the Adar month

Hebrew year 5787 is a leap year with two Adar months. GET /converter?cfg=json&hy=5787&hm=Adar&hd=1&h2g=1 returned hm: "Adar II" and the Gregorian date 2027-03-10. The reply has no warning.

This is documented. The converter docs say: "In leap years, specifying Adar is the same as specifying Adar2." Use Adar1 to get 2027-02-08 for the first month. In year 5786, which is not a leap year, hm=Adar returned 2026-02-18.

A day number past the end of the month gives an error. hm=Adar&hd=30 in 5787 returned: Hebrew day 30 out of valid range 1-29 for Adar II 5787.

The range field shows the items, not your request

GET /hebcal?v=1&cfg=json&maj=on&year=2026&geo=none returned range.start 2026-03-02, not 2026-01-01. The first item is Erev Purim on that date, and the last is Chanukah: 8th Day on 2026-12-12. The range end is also that last date, not 2026-12-31.

Adding min=on moved the start to 2026-02-02, the date of the first extra item. Sending start=2026-01-01 and end=2026-12-31 changed nothing. We found no text in the docs about this. We only report what we saw: range follows the first and last item returned.

If you show the range to a user, build it from your own request parameters.

Errors, formats and CORS

Bad input on the JSON routes gives a JSON error. A Gregorian month of 13 on /converter returned 400 with {"error":"Gregorian month out of valid range 1-12: 13"}. A geonameid of 99999999 on /zmanim and on /shabbat returned 404 with {"error":"Sorry, can't find geonameid: 99999999"}.

An unknown path such as /nope returned 404 with an HTML page. Leaving out cfg=json on /converter returned an HTML page, not JSON. Check the content type before you parse.

cfg=xml on /converter returned text/xml. cfg=rss and cfg=ics on /hebcal returned application/rss+xml and text/calendar. So the API is not JSON only.

We sent Origin: https://example.com with each request. The JSON replies, errors included, had access-control-allow-origin: *. The HTML error page for /nope had no such header.

Hebrew Calendar, by the numbers

Rendered live from the atlas entry
AuthenticationNone required
HTTPSSupported
CORSEnabled
PricingFree
FormatsJSON
Rate limit

90 requests in a 10-second window; exceeding it returns HTTP 429 Too Many Requests — per hebcal.com/home/developer-apis (live-fetched).

Key endpoints
  • GET/converter
  • GET/hebcal
  • GET/zmanim
jewish-calendarholidayszmanimkeyless

Sources

Facts checked Nov 2026