PHPackages                             inwebo/save-page-now-2 - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. inwebo/save-page-now-2

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

inwebo/save-page-now-2
======================

Capture a web page as it appears now for use as a trusted citation in the future.

1.0.0(1mo ago)12MITPHPPHP ^8.1CI failing

Since Apr 22Pushed 1mo agoCompare

[ Source](https://github.com/inwebo/save-page-now-2)[ Packagist](https://packagist.org/packages/inwebo/save-page-now-2)[ Docs](https://github.com/inwebo/save-page-now-2)[ RSS](/packages/inwebo-save-page-now-2/feed)WikiDiscussions master Synced 1w ago

READMEChangelogDependencies (4)Versions (2)Used By (0)

save-page-now-2
===============

[](#save-page-now-2)

PHP 8.1+ client library for the **Save Page Now 2 (SPN2) API** provided by the [Internet Archive](https://wayback-api.archive.org/save).

Official Documentation
----------------------

[](#official-documentation)

- [Google Doc (Vangelis Banos, Internet Archive)](https://docs.google.com/document/d/1Nsv52MvSjbLb2PCpHlat0gkzw0EvtSgpKHu4mk0MnrA/) ← Most comprehensive reference
- [Readable Mirror Gist](https://gist.github.com/regstuff/82e690db2f1d91ba59f6681c1abad6cf)

---

Installation
------------

[](#installation)

```
composer require inwebo/save-page-now-2
```

Obtain your S3 API keys at .

---

Quick Start
-----------

[](#quick-start)

```
use Inwebo\SavePageNow2\Auth\S3Credentials;
use Inwebo\SavePageNow2\Capture\CaptureOptionsBuilder;
use Inwebo\SavePageNow2\Response\JobStatus;
use Inwebo\SavePageNow2\SavePageNow2Client;
use Symfony\Component\HttpClient\HttpClient;

$client = new SavePageNow2Client(
    HttpClient::create(),
    new S3Credentials('my-access-key', 'my-secret'),
);

// 1. Submit a URL
$options = (new CaptureOptionsBuilder())
    ->withSkipFirstArchive()   // Faster
    ->withJsBehaviorTimeout(0) // No JS execution
    ->build();

$job = $client->capture('https://example.com/', $options);
echo "Job started: {$job->jobId}\n";

// 2. Poll until completion
do {
    sleep(5);
    $status = $client->getStatus($job->jobId);
} while ($status->getStatus() === JobStatus::Pending);

// 3. Result
if ($status->getStatus() === JobStatus::Success) {
    echo "✅ Archived: {$status->getWaybackUrl()}\n";
} else {
    echo "❌ Error: {$status->getMessage()} ({$status->getStatusExt()})\n";
}
```

---

Architecture
------------

[](#architecture)

```
src/
├── Auth/
│   ├── AuthInterface.php          Generic authentication interface
│   ├── S3Credentials.php          Authorization: LOW key:secret (recommended)
│   └── CookieCredentials.php      logged-in-user + logged-in-sig (fallback)
│
├── Capture/
│   ├── CaptureOptions.php         Readonly Value object — all POST parameters
│   └── CaptureOptionsBuilder.php  Immutable fluent builder
│
├── Response/
│   ├── JobStatus.php              Enum: Pending | Success | Error
│   ├── CaptureJobResponse.php     POST /save response {url, job_id}
│   ├── UserStatusResponse.php     GET /save/status/user {available, processing}
│   ├── SystemStatusResponse.php   GET /save/status/system {status}
│   └── Status/
│       ├── StatusResponseInterface.php
│       ├── StatusResponseFactory.php   Dispatches JSON → correct implementation
│       ├── PendingStatusResponse.php
│       ├── SuccessStatusResponse.php   + getWaybackUrl()
│       └── ErrorStatusResponse.php     + getStatusExt(), getMessage()
│
├── Exception/
│   ├── SavePageNowException.php       Base exception
│   ├── ApiException.php               Unexpected / malformed response
│   ├── AuthenticationException.php    HTTP 401 / error:unauthorized
│   ├── UserSessionLimitException.php  error:user-session-limit
│   └── NetworkException.php           Symfony transport error
│
├── SavePageNow2Interface.php      Public client contract
└── SavePageNow2Client.php         Symfony HttpClient implementation

```

---

Complete API
------------

[](#complete-api)

### `capture(string $url, ?CaptureOptions $options = null): CaptureJobResponse`

[](#capturestring-url-captureoptions-options--null-capturejobresponse)

Submits a URL for archiving. Returns a `job_id` immediately.

### `getStatus(string $jobId): StatusResponseInterface`

[](#getstatusstring-jobid-statusresponseinterface)

Returns a `PendingStatusResponse`, `SuccessStatusResponse`, or `ErrorStatusResponse`.

### `getStatuses(array $jobIds): array`

[](#getstatusesarray-jobids-arraystring-statusresponseinterface)

Retrieves the status of multiple jobs in a single request.

### `getOutlinksStatus(string $parentJobId): array`

[](#getoutlinksstatusstring-parentjobid-arraystring-statusresponseinterface)

Retrieves the status of all outlinks for a parent job (requires `capture_outlinks=1`).

### `getUserStatus(): UserStatusResponse`

[](#getuserstatus-userstatusresponse)

Active and available sessions for the authenticated user.

### `getSystemStatus(): SystemStatusResponse`

[](#getsystemstatus-systemstatusresponse)

Overall health of the SPN2 service.

---

Capture Options
---------------

[](#capture-options)

Builder methodAPI ParameterDescription`withCaptureAll()``capture_all=1`Also captures 4xx/5xx pages`withCaptureOutlinks()``capture_outlinks=1`Automatically archives outlinks`withCaptureScreenshot()``capture_screenshot=1`Captures a full-page PNG screenshot`withDelayWbAvailability()``delay_wb_availability=1`Available in ~12h (reduces server load)`withForceGet()``force_get=1`Forces a simple GET (no headless browser)`withSkipFirstArchive()``skip_first_archive=1`Skips the "first archive" check (faster)`withIfNotArchivedWithin(string)``if_not_archived_within`Only archives if older than e.g., `"3d 5h"``withOutlinksAvailability()``outlinks_availability=1`Returns the last snapshot timestamp for each outlink`withEmailResult()``email_result=1`Sends an email report`withJsBehaviorTimeout(int $s)``js_behavior_timeout=N`JS execution time after loading (0–30s)`withCaptureCookie(string)``capture_cookie`Additional HTTP cookie for the target`withTargetCredentials(string, string)``target_username/password`Credentials for the target's auth forms---

Error Handling
--------------

[](#error-handling)

```
use Inwebo\SavePageNow2\Exception\AuthenticationException;
use Inwebo\SavePageNow2\Exception\UserSessionLimitException;
use Inwebo\SavePageNow2\Exception\NetworkException;
use Inwebo\SavePageNow2\Exception\ApiException;

try {
    $job = $client->capture('https://example.com/');
} catch (AuthenticationException $e) {
    // Invalid or expired S3 keys
} catch (UserSessionLimitException $e) {
    // 12 simultaneous captures reached (auth) / 6 (anonymous)
} catch (NetworkException $e) {
    // Transport issue (timeout, DNS...)
} catch (ApiException $e) {
    // Unexpected API response
}
```

Detailed error codes from `status_ext` (e.g., `error:not-found`, `error:too-many-daily-captures`) are available via `ErrorStatusResponse::getStatusExt()`.

---

Testing
-------

[](#testing)

Run tests using the included PHPUnit runner:

```
composer phpunit
```

```
Save Page Now 2 — Test Suite
========================================
..................................................... 53 / 53
OK (53 tests, 97 assertions)

```

---

License
-------

[](#license)

MIT

###  Health Score

37

—

LowBetter than 81% of packages

Maintenance90

Actively maintained with recent releases

Popularity4

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity42

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

48d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/845359?v=4)[Inwebo Veritas](/maintainers/inwebo)[@inwebo](https://github.com/inwebo)

---

Top Contributors

[![inwebo](https://avatars.githubusercontent.com/u/845359?v=4)](https://github.com/inwebo "inwebo (2 commits)")

---

Tags

internet-archivesave-page-now-2spn2internet-archivesave page now 2spn2

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/inwebo-save-page-now-2/health.svg)

```
[![Health](https://phpackages.com/badges/inwebo-save-page-now-2/health.svg)](https://phpackages.com/packages/inwebo-save-page-now-2)
```

###  Alternatives

[symfony/asset-mapper

Maps directories of assets &amp; makes them available in a public directory with versioned filenames.

1668.1M208](/packages/symfony-asset-mapper)[internal/dload

Downloads binaries.

102187.3k17](/packages/internal-dload)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
