In 2025, I was on a mission to add as many library cards as possible to Libby to accelerate my audiobook addiction. When I added my 39th library card, Libby started failing my requests. After a tortuous few days/weeks of debugging, I determined that the JWT token which Libby uses to store data was now too big for their nginx server to handle. 💥
I wrote some tools. I determined that the few thousand tags I had been painstakingly applying to books was lost, at least to me – I was unable to re-authenticate with Libby using my now compromised JWT. I could never get the full list. I mourned my loss, wrote a blog post in November, reset my account, re-added some (not all) library cards, decided to stop using tags if they were going to be a fragile mechanism and moved on with my life.
At some point this year, Libby either increased the header limitations of their nginx deployment AND/OR updated their JWT format. Probably both! Suddenly, I was able to re-authenticate with the broken JWT and export all my tags! I finished the tools I had started, used them to export and then re-import those tags into the account I was currently using and considered myself lucky!
Then I saw the post about the partner libraries… If Libby was adding partner libraries for everyone…
OH NO!
So back in I went. Will the addition of potentially dozens of partner libraries I had been avoiding once again throw me over the limit?!? Enquiring minds (me) wanted to know!
TL;DR: NOTHING TO WORRY ABOUT.
Libby changed the shape of the data they store in their JWT!
The original JWT embedded every linked card in a verbose chip.accounts[] structure. Each card was represented as a named account object, approximately like this:
{
"ag": 123,
"id": "account-id",
"typ": "library",
"cards": [
{
"id": "card-id",
"name": "My card name",
"lib": {
"id": "website-id",
"key": "library-slug"
}
}
]
}
name (this is where many bytes were wasted).The refreshed identity JWT still includes a grant for every linked card, but uses chip.cards[]: a compact positional tuple.
[
"account-id",
"card-id",
"ils-id",
false,
"website-id",
"library-slug"
]
I measured 89 real compact card grants across two refreshed Libby identities (46 cards and 43 cards).
The JWT has a fixed non-card envelope of about 610 bytes: its signed header, signature, and claims such as issuer, expiry, and chip identifiers. A useful approximation is:
JWT size ≈ 610 bytes + (number of cards × bytes per card)
For comparison, the observed legacy card representation was:
chip.accounts[]: 118–139 raw JSON bytes; 157–185 JWT on-wire bytes per cardchip.cards[]: 54–83 raw JSON bytes; 72–111 JWT on-wire bytes per cardThe legacy-format “typical” figure uses its observed approximate cost of 167 bytes/card. The new figures use the measured distribution above.
A typical account has roughly eight times the theoretical card headroom: about 45 cards in the former 8 KiB/legacy-format world, versus about 382 cards using the current compact JWT under the current Libby-core header limit.
The relevant request carries its JWT like this:
curl -sS -D- "https://sentry.libbyapp.com/chip/sync" \
-H "Accept: application/json" \
-H "Authorization: Bearer <YOUR_JWT>"
A malformed-but-small bearer reaches the application and is rejected as unauthenticated:
curl -sS -D- "https://sentry.libbyapp.com/chip/sync" \
-H "Accept: application/json" \
-H "Authorization: Bearer not-a-valid-jwt"
# HTTP/1.1 403 Forbidden
# {"result":"missing_chip"}
The request-header boundary was tested by increasing an invalid bearer value. The behavior changes sharply:
Authorization line length: 32,766 bytes → 403 Authorization line length: 32,767 bytes → 400
The difference matters:
chip.cards[] form.My original failure was real, but the conditions behind it have materially improved. Libby still carries linked-card grants inside its identity token; the change is that those grants are now encoded more efficiently, and Libby accepts a substantially larger authorization header. For ordinary multi-library users, the practical danger zone has moved from “a few dozen cards may be risky” to “hundreds of normally sized cards.”