# Philippine Supreme Court Decisions Scraper (`fanndev/ph-court-decisions-scraper`) Actor

Full-text search and bulk export of Philippine Supreme Court decisions from the Judiciary's official free E-Library (1996-present). Returns case number, parties, division, ponente, promulgation date, Philippine Reports citation and the complete opinion text. No account needed.

- **URL**: https://apify.com/fanndev/ph-court-decisions-scraper.md
- **Developed by:** [Faisal Ahdan naufal](https://apify.com/fanndev) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.40 / 1,000 results

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Philippine Supreme Court Decisions Scraper

Search and export **Philippine Supreme Court decisions** from the Judiciary's
own free archive — the [Supreme Court E-Library](https://elibrary.judiciary.gov.ph).
No account, no API key, no login.

You get the case number, the parties, the deciding division, the ponente, the
promulgation date, the Philippine Reports citation and the **complete opinion
text** — as the Court itself prints them.

### What you can do

| Mode | Use it for |
|---|---|
| **Full-text search** | Every decision mentioning a doctrine, statute, party or phrase — `malversation`, `probable cause`, `syndicated estafa`. Powered by the E-Library's own dtSearch engine, with keyword-in-context snippets and per-document hit counts. |
| **Browse by year** | Bulk-harvest the archive month by month for a year range (monthly listings run **1996 to present**). |
| **Full decision (by ID)** | Pull the complete text of specific decisions you already have IDs for. |

Search reaches further back than browse — a 1991 decision was returned from
the decisions index during testing.

### Example input

Find recent decisions on a doctrine, with full opinions:

```json
{
  "mode": "search",
  "searchPhrase": "probable cause",
  "searchCorpus": "sc_decision",
  "searchType": "phrase",
  "maxResults": 50,
  "fetchFullText": true
}
```

Bulk-harvest a year:

```json
{ "mode": "browse", "fromYear": 2025, "toYear": 2026, "maxResults": 500 }
```

Fetch specific decisions:

```json
{ "mode": "decision", "docIds": ["70008", "69244", "70515"] }
```

### Example output

```json
{
  "_source": "S1-decision",
  "docId": "69244",
  "caseNumber": "G.R. No. 235790",
  "caseTitle": "PEOPLE OF THE PHILIPPINES, PLAINTIFF-APPELLEE, VS. MONICO D. SANTOS AND FRANCIS H. CANOZA, ACCUSED, MONICO D. SANTOS, ACCUSED-APPELLANT.",
  "documentType": "DECISION",
  "division": "SECOND DIVISION",
  "ponente": "LEONEN, SAJ.",
  "reporterCitation": "930 Phil. 215",
  "decisionDate": "2022-09-21",
  "decisionDateText": "September 21, 2022",
  "fullTextLength": 41129,
  "fullText": "930 Phil. 215 SECOND DIVISION [ G.R. No. 235790, September 21, 2022 ] …",
  "documentUrl": "https://elibrary.judiciary.gov.ph/thebookshelf/showdocs/1/69244"
}
```

Set `fetchFullText: false` (the default) in search/browse mode for fast,
cheap summary rows without the opinion text.

### Good to know

- **Three of the portal's eight search corpora are broken on the
  judiciary's own servers** — `sc_resolution`, `administrative_matter` and
  `bar_matter` answer with dtSearch's `Unable to access index` error. The
  actor reports that as an explicit `search_index_unavailable` record instead
  of pretending there were no matches. `sc_decision` (the default) and
  `sc_all` work fine.
- **dtSearch stops counting at 5,000 matches** per query, so very broad
  searches report 5,000 as the total. Narrow the phrase or use `browse` for
  exhaustive coverage.
- **Search results and portal pages use different document IDs.** A search
  row carries `dtSearchDocId` (the engine's numbering) and links to the
  engine's document view; `docId` (the portal's numbering, usable with
  `mode: "decision"`) appears on browse and decision records. They are not
  interchangeable.
- Decisions not yet published in Philippine Reports have no
  `reporterCitation` / `reporterPdfUrl` — that is expected, not an error.
- Full opinions are long: 20,000–40,000 characters is typical, but en banc
  and consolidated cases run far bigger — one was measured at **2,379,838
  characters (2.4 MB)** in a single record. Budget accordingly when
  `fetchFullText` is on.

### Reliability

- **HTTP-only.** No browser, no login, no credentials. The site runs no WAF
  and imposes no rate limit; `maxConcurrency` still defaults to a polite 4.
- **Never fails silently.** Unknown document IDs soft-404 with HTTP 200 on
  this portal; the actor detects that and returns `_error: "decision_not_found"`
  rather than an empty-looking record. Every failure mode emits a row.
- **Raw passthrough.** Values are the portal's own — nothing renamed into a
  different taxonomy, nothing re-derived.

### Data source & limits

Data comes from the Supreme Court E-Library, the Philippine Judiciary's
official public archive of its decisions. The site publishes no `robots.txt`
policy (it 404s) and requires no authentication. Court decisions are public
records. You are responsible for how you use the output.

# Actor input Schema

## `mode` (type: `string`):

"search" runs a full-text query against the Supreme Court E-Library's own dtSearch engine. "browse" walks the archive month by month for a year range. "decision" fetches the full text of specific document ID(s) directly.

## `searchPhrase` (type: `string`):

"search" mode -- the full-text query, e.g. "malversation", "probable cause", a party name or a doctrine. Required in this mode.

## `searchCorpus` (type: `string`):

"search" mode -- which of the E-Library's dtSearch indexes to query. NOTE: sc\_resolution, administrative\_matter and bar\_matter were unreadable server-side at build time (the portal's own search returns "$E 0001 Unable to access index" for them); a run against one returns an explicit search\_index\_unavailable diagnostic rather than a silent zero.

## `searchType` (type: `string`):

"search" mode -- how dtSearch interprets the phrase: any of the words, all of the words, the exact phrase, or a boolean expression (AND / OR / NOT / w-within).

## `fromYear` (type: `integer`):

"browse" mode -- earliest year to walk. The archive's monthly listing starts at 1996.

## `toYear` (type: `integer`):

"browse" mode -- latest year to walk. Years are visited newest-first, and each month is paged until it runs out.

## `docIds` (type: `array`):

"decision" mode -- the numeric document ids from the portal's own /thebookshelf/showdocs/1/<id> URLs, e.g. "70008". A full URL may be pasted instead of the bare id. These appear as the "docId" field of any browse result.

## `maxResults` (type: `integer`):

"search" and "browse" modes -- maximum number of decisions to return. Pages are followed automatically until this cap or the end of the result set. dtSearch itself stops counting at 5,000 matches per query.

## `fetchFullText` (type: `boolean`):

"search" and "browse" modes -- when true, every result is additionally fetched for the complete opinion (division, ponente, document type, Philippine Reports citation and the full text, frequently 20,000-350,000 characters). When false (default), only the summary row is returned, which is far faster and cheaper for large result sets.

## `maxConcurrency` (type: `integer`):

How many decision pages to fetch in parallel when fetchFullText is on, or how many ids to fetch at once in "decision" mode. Kept deliberately low by default -- this is a small government library server.

## `proxyConfiguration` (type: `object`):

Apify Proxy configuration. Residential is on by default.

## Actor input object example

```json
{
  "mode": "search",
  "searchPhrase": "malversation",
  "searchCorpus": "sc_decision",
  "searchType": "anywords",
  "fromYear": 2026,
  "toYear": 2026,
  "docIds": [
    "70008"
  ],
  "maxResults": 50,
  "fetchFullText": false,
  "maxConcurrency": 4,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `results` (type: `string`):

All scraped decision records produced by this run.

# 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 = {
    "mode": "search",
    "searchPhrase": "malversation",
    "searchCorpus": "sc_decision",
    "searchType": "anywords",
    "fromYear": 2026,
    "toYear": 2026,
    "docIds": [
        "70008"
    ],
    "maxResults": 50,
    "fetchFullText": false,
    "maxConcurrency": 4,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("fanndev/ph-court-decisions-scraper").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 = {
    "mode": "search",
    "searchPhrase": "malversation",
    "searchCorpus": "sc_decision",
    "searchType": "anywords",
    "fromYear": 2026,
    "toYear": 2026,
    "docIds": ["70008"],
    "maxResults": 50,
    "fetchFullText": False,
    "maxConcurrency": 4,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("fanndev/ph-court-decisions-scraper").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 '{
  "mode": "search",
  "searchPhrase": "malversation",
  "searchCorpus": "sc_decision",
  "searchType": "anywords",
  "fromYear": 2026,
  "toYear": 2026,
  "docIds": [
    "70008"
  ],
  "maxResults": 50,
  "fetchFullText": false,
  "maxConcurrency": 4,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call fanndev/ph-court-decisions-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,fanndev/ph-court-decisions-scraper"
        }
    }
}
```

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/WJkiUMEZxQseSKAwx/builds/JBIhh3v3e2m0zS2YI/openapi.json
