# OverDrive Library Title Availability Scraper (`automation-lab/overdrive-libby-library-title-availability`) Actor

Export public OverDrive library title availability by supplied library slugs and title IDs, including copies, holds, wait estimates and formats. No login, search, borrowing or downloads.

- **URL**: https://apify.com/automation-lab/overdrive-libby-library-title-availability.md
- **Developed by:** [Automation Lab](https://apify.com/automation-lab) (community)
- **Categories:** Education
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.68 / 1,000 title availability snapshots

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## OverDrive Library Title Availability Scraper

Export **overdrive library title availability** for known titles across public library catalogs. Supply library slugs and numeric OverDrive title IDs to collect current anonymous copy counts, holds, wait estimates, and formats where exposed. No library card, login, search, borrowing, or content downloads.

One row represents one library/title snapshot. All exposed formats are included in that row. Counts belong to the title, not to separate delivery formats.

### Who is it for?

- Library collection researchers comparing digital inventory across named systems.
- Publishers and authors checking copies and hold queues for known editions.
- Data teams maintaining a recurring availability spreadsheet.
- Readers researching public catalog availability without connecting a patron account.

### Why use this Actor?

Exact IDs keep a recurring panel consistent: you do not need to rerun broad searches or resolve similarly named editions every time. Duplicate IDs are removed, null values stay distinct from zero, and each row includes a collection timestamp and source link.

This is a focused lookup tool, not a catalog discovery engine. You bring the title IDs and libraries; the Actor collects the current public snapshots. It does not save historical changes or send alerts itself.

### Getting started

1. Open a public library catalog, for example `lapl.overdrive.com`.
2. Copy its library slug (`lapl`).
3. From a title URL ending in `/media/12715834`, copy the numeric ID.
4. Enter the arrays below and run the Actor.
5. Inspect the default dataset or download JSON/CSV from Apify.

```json
{
  "libraryIds": ["lapl"],
  "titleIds": ["12715834", "4252045"],
  "maxItems": 10
}
```

The first example title was owned by Los Angeles Public Library at verification; the second returned an unowned title. Catalog inventory changes independently of this Actor.

### Inputs

| Field | Behavior |
| --- | --- |
| `libraryIds` | Required, 1–20 public library slugs. Lowercased and trimmed; duplicates removed. No URLs or library-card IDs. |
| `titleIds` | Required, 1–200 numeric media IDs as strings, positive 32-bit integers. Trimmed and deduplicated. No ISBN, title text, URLs, or search terms. |
| `maxItems` | Global saved-row limit, default 10, range 1–1000. No zero/unlimited mode. |

Every distinct library is paired with every distinct title. Libraries are processed in input order, then titles in input order. A missing title does not consume the row limit. A title that exists but is not owned can still produce a useful snapshot with `isOwned: false` and null numeric counts.

Unknown fields and malformed identifiers fail validation instead of being silently ignored. There are no proxy, query, keyword, account, download, or historical-date fields.

### Extracted data

| Fields | Meaning |
| --- | --- |
| `libraryId`, `libraryName` | Requested normalized slug and public display name |
| `titleId`, `title`, `creator` | Exact title ID, name, and first credited creator |
| `mediaType` | Source type, such as `ebook` or `audiobook` |
| `isOwned`, `isAvailable`, `isHoldable` | Public source flags; null if absent |
| `ownedCopies`, `availableCopies` | Title-level normal inventory counts, null if absent |
| `holdsCount` | Exposed title-level hold queue count |
| `estimatedWaitDays` | Source estimate in days, not a guaranteed wait |
| `availabilityType` | Source lending category when present |
| `formats` | Array of format IDs, names, and ISBNs when present |
| `sourceUrl`, `scrapedAt` | Public catalog link and UTC snapshot timestamp |

Lucky-day inventory is not merged into normal copy counts. Formats such as Kindle, EPUB, and OverDrive Read can represent delivery options for the same title; their count is not a count of independent copies.

### Output example

A representative public snapshot from the example input:

```json
{
  "libraryId": "lapl",
  "libraryName": "Los Angeles Public Library",
  "titleId": "12715834",
  "title": "Rick Steves Snapshot Madrid & Toledo",
  "creator": "Rick Steves",
  "mediaType": "ebook",
  "isOwned": true,
  "isAvailable": true,
  "isHoldable": true,
  "ownedCopies": 2,
  "availableCopies": 2,
  "holdsCount": 0,
  "estimatedWaitDays": 7,
  "availabilityType": "normal",
  "formats": [
    { "id": "ebook-kindle", "name": "Kindle Book", "isbn": null },
    { "id": "ebook-overdrive", "name": "OverDrive Read", "isbn": "9781641717342" },
    { "id": "ebook-epub-adobe", "name": "EPUB eBook", "isbn": "9781641717342" },
    { "id": "ebook-kobo", "name": "Kobo eBook", "isbn": null }
  ],
  "sourceUrl": "https://lapl.overdrive.com/media/12715834",
  "scrapedAt": "2026-10-02T20:11:35.087Z"
}
```

Notice that the source can expose a wait estimate even while a title is available. The Actor preserves that estimate rather than replacing it with zero. Missing counts are `null`, not inferred inventory.

### How much does it cost to check OverDrive library title availability?

Pay-per-event pricing uses a **$0.06 start fee** once per valid run plus one **item** event per saved library/title snapshot. Formats have no separate event charge. Unknown title IDs have no item charge; a valid run with no matching titles still incurs its start fee.

| Apify tier | Price per snapshot |
| --- | --- |
| FREE | $0.00322 |
| BRONZE | $0.0028 |
| SILVER | $0.002184 |
| GOLD | $0.00168 |
| PLATINUM | $0.00168 |
| DIAMOND | $0.00168 |

At BRONZE, 1 snapshot costs $0.0628; 10 cost $0.088; 100 cost $0.34. These examples describe Actor event prices, not a separate proxy fee. Check the live pricing panel for your applicable tier and platform terms.

Set Apify's maximum charge limit to bound spending. A billing limit can stop collection before the requested snapshot limit. A run interrupted by timeout or upstream failure can leave partial output; do not interpret it as a complete panel.

### OverDrive library title availability limitations

- Public anonymous inventory is not a promise that your patron account can borrow a title.
- Consortium, advantage, visitor, restricted, and special lending rights can differ by account.
- Wait estimates are source estimates, not a queue-position calculation for your account.
- A numeric ID identifies a specific edition/media record; another edition may have another ID.
- The Actor does not search, discover new titles, borrow, reserve, log in, or download books/audio.
- It collects current snapshots only. It cannot request historical inventory.
- No guarantee is made that every library or title exposes all fields.

### Reliability and troubleshooting

Requests use the public structured catalog endpoint directly, one at a time, paced at approximately two requests per second. Transient network errors, HTTP 429, and server failures receive at most three total attempts with bounded backoff. Authentication failures, challenge responses, malformed JSON, and parser mismatches fail visibly rather than producing fabricated empty data.

A title-not-found response is skipped and logged. An invalid or inactive library fails the run. A later failure can leave previously saved rows in the dataset, so inspect the terminal run status before treating the export as complete.

If a library slug fails, confirm the slug from its public OverDrive URL. If a title returns no row, confirm the numeric media ID rather than an ISBN or a reserve UUID. If counts are null, compare the public catalog: not-owned and missing inventory are different from zero available copies.

### Integrations

Schedule recurring Apify runs with the same ID panel, then export each run to a spreadsheet, database, or object store. Compare snapshots downstream using `libraryId` plus `titleId`; retain `scrapedAt` to distinguish observations.

An Apify webhook can deliver successful-run notifications to Make, Zapier, or your application. Keep full exports outside a language model's context and perform diffing downstream. The Actor itself does not configure schedules, store historical panels, or send change alerts.

CSV exports are useful for scalar fields. JSON preserves the nested format list. Fetch only the fields your downstream workflow needs.

### API usage

Use your own Apify API token via an authorization header. This starts one authorized run and waits briefly; retain the returned run ID and inspect that same run until terminal before retrieving its dataset.

```bash
curl -X POST \
  'https://api.apify.com/v2/acts/automation-lab~overdrive-libby-library-title-availability/runs?waitForFinish=30' \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"libraryIds":["lapl"],"titleIds":["12715834"],"maxItems":1}'
```

JavaScript using the official client:

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/overdrive-libby-library-title-availability')
  .call({ libraryIds: ['lapl'], titleIds: ['12715834'], maxItems: 1 });
if (run.status !== 'SUCCEEDED') throw new Error(`Run ${run.id}: ${run.status}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems({ limit: 20 });
console.log(items);
```

Python using the official client:

```python
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/overdrive-libby-library-title-availability').call(
    run_input={'libraryIds': ['lapl'], 'titleIds': ['12715834'], 'maxItems': 1})
if run['status'] != 'SUCCEEDED':
    raise RuntimeError(f"Run {run['id']}: {run['status']}")
items = client.dataset(run['defaultDatasetId']).list_items(limit=20).items
print(items)
```

### MCP use

Select a stable Actor-specific endpoint rather than the full Store:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/overdrive-libby-library-title-availability"
```

For Claude Desktop, Cursor, and VS Code with remote HTTP MCP support:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/overdrive-libby-library-title-availability",
      "headers": { "Authorization": "Bearer YOUR_APIFY_TOKEN" }
    }
  }
}
```

Example prompt: “Check title 12715834 in lapl with maxItems 1. Show copies, holds, and the collection timestamp. Explain missing values without guessing.”

Discover actual tool names and schemas with scoped `tools/list`. Actor selection also adds run/data helper tools; it does not expose exactly one tool. Discovery alone is not authorization to run or abort anything.

For an authorized invocation, start once and retain the returned run ID and storage IDs. They are metadata, not rows. If the call remains nonterminal, check that same run with bounded 2/4/8-second backoff capped at 10 seconds and a total 120-second deadline. Set client request timeout above the chosen server wait plus transport margin. Recover the known run after a client timeout rather than restarting; if its ID is unknown, report uncertainty. At deadline report pending with the run ID.

After success, request untransformed dataset JSON in pages of 20 rows, explicit offsets, and selected fields. As an example consumer budget, admit at most 100 source rows and 64 KiB of serialized UTF-8 results into model context, whichever comes first. Enforce bytes host-side, including oversized rows; disclose omissions or summaries. If the client cannot intercept oversized responses, do not claim a hard byte guarantee. Track source offsets rather than transformed row counts and report the continuation offset and truncation. Full exports remain in storage. Failed-run rows are partial.

These are consumer context choices, not collection limits or promised token savings. Client-side discovery caching and prompt caching vary by client.

### Legality and diagnostics

Use public catalog metadata responsibly and comply with applicable terms and law. No private circulation records or patron data are collected. This Actor is independent and is not affiliated with OverDrive or Libby. Public access does not establish a legal right to redistribute every field.

No AI inference service is used at runtime. Apify hosts your input, dataset and logs under your account's storage and retention controls; delete these in Console or through the API when no longer needed. No cross-run cache or private patron data is retained by Actor code.

Failed operations send sanitized diagnostic input, exceptions and Actor/build/run IDs to our private GlitchTip service for repair. Secret fields and URL queries are removed; reports are retained for 30 days.

For help, open an issue on this Actor's Apify Store page with a safe input example and run link. Do not share library-card details or credentials.

### FAQ

**Can I search by ISBN or title?** No. Provide the exact numeric media IDs from public catalog URLs.

**Does zero available copies mean the library owns zero?** No. Inspect `ownedCopies` and `isOwned` independently. A missing number is null and must not be converted to zero.

**Are formats independently available?** The source exposes title-level inventory. Formats are delivery metadata; this Actor does not assign copies to individual formats.

**Why can an unowned title still return a row?** The public catalog can identify titles outside a library's owned collection. The row explicitly preserves ownership and missing inventory.

**Can I compare repeated runs?** Yes, downstream by library/title key and timestamp. History and alerts are not built into this Actor.

### Related workflows

This Actor is intentionally standalone: general book/catalog metadata products do not expose library-specific holdings. Use Apify datasets and your existing spreadsheet/database workflow for panel history rather than treating unrelated catalog records as equivalent availability evidence.

# Changelog

This Actor's version history is a separate document: https://apify.com/automation-lab/overdrive-libby-library-title-availability/changelog.md

# Actor input Schema

## `libraryIds` (type: `array`):

1–20 public OverDrive library slugs, e.g. lapl from lapl.overdrive.com. Whitespace is trimmed and case normalized; duplicate slugs are removed. Each library is paired with every title ID. URLs and account/card identifiers are not accepted.

## `titleIds` (type: `array`):

1–200 positive 32-bit numeric OverDrive media IDs as strings, taken from /media/ID catalog URLs. Whitespace is trimmed and duplicates removed. Exact ID lookup only, not ISBN, title text, or search. Unknown IDs are skipped without a record charge; unowned titles can return a snapshot with null counts.

## `maxItems` (type: `integer`):

Global limit on distinct library/title rows saved, default 10, range 1–1000. Processed in library order then title order after deduplication. Missing titles do not consume this limit. Zero/unlimited is not supported. Formats are included in each row, not separate billed records.

## Actor input object example

```json
{
  "libraryIds": [
    "lapl"
  ],
  "titleIds": [
    "12715834",
    "4252045"
  ],
  "maxItems": 10
}
```

# Actor output Schema

## `dataset` (type: `string`):

Export the default dataset as JSON; missing numeric counts remain null.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "libraryIds": [
        "lapl"
    ],
    "titleIds": [
        "12715834",
        "4252045"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/overdrive-libby-library-title-availability").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "libraryIds": ["lapl"],
    "titleIds": [
        "12715834",
        "4252045",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/overdrive-libby-library-title-availability").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "libraryIds": [
    "lapl"
  ],
  "titleIds": [
    "12715834",
    "4252045"
  ]
}' |
apify call automation-lab/overdrive-libby-library-title-availability --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/overdrive-libby-library-title-availability"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/IdLM2GxxUeIcd8Ujt/builds/I6tx8vqY0mUedgqgY/openapi.json
