

## Laravel API Contract

The WordPress plugin treats Laravel as a black box. These are the exact endpoints WP will call and the exact contract each must fulfil. Laravel owns all PDF generation, S3 storage, and signed URL issuance — WP never touches a PDF file directly.

All requests are routed through `AgencyPulseWP_Api_Client::request()` from the `agency-pulse-pro` connector plugin. This handles:

- JWT acquisition and storage (`agencypulsewp_connector_state`)
- Automatic token refresh on soft-expiry or `401`/`403` response
- `Authorization: Bearer {JWT}` header injection
- Base URL (`APWP_CONNECT_URL`) and `/api/` prefix

This means **no separate endpoint URL or API key is stored or configured by this plugin**. The Pro connector is a hard runtime dependency — if it is not active and connected, the signing API calls will fail gracefully with a `WP_Error`.

```php
// Example call pattern used in class-sign-api.php
AgencyPulseWP_Api_Client::request( 'POST', 'plugin/invoice/sign', [
    'body' => wp_json_encode( $payload ),
] );
```

Laravel must reject requests where the JWT is invalid or expired with `401 Unauthorized`.

---

### POST `/api/plugin/invoice/sign`

Called by WP after a client submits a valid signature.

**Request body (JSON):**

```json
{
  "invoice_id":       123,
  "invoice_html":     "<html>…full snapshot HTML…</html>",
  "signature_image":  "data:image/png;base64,…",
  "signed_at":        "2026-03-27T14:32:00Z",
  "signed_ip":        "203.0.113.42",
  "client_name":      "Acme Corp",
  "invoice_number":   "INV-000042"
}
```

**What Laravel must do:**

1. Render the invoice HTML into a PDF (headless Chrome / Puppeteer / Browsershot or equivalent)
2. Overlay the signature image in the designated signature area
3. Append an audit footer: invoice number, signed date/time, IP address, SHA-256 hash placeholder
4. Compute `SHA-256` hash of the final PDF bytes
5. Upload the PDF to the private S3 bucket under `invoices/{invoice_id}/signed-{invoice_number}.pdf`
6. Return the response below

**Success response `200`:**

```json
{
  "s3_path":    "invoices/123/signed-INV-000042.pdf",
  "pdf_hash":   "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
  "expires_in": 7
}
```

`expires_in` is the number of days the signed URL will be valid when requested via the signed-URL endpoint. WP stores `s3_path` and `pdf_hash`; it does not store the PDF itself.

**Error response `422`:**

```json
{
  "error": "human-readable reason"
}
```

---

### POST `/api/plugin/invoice/signed-url`

Called by WP when an admin or client requests to download the signed PDF.

**Request body (JSON):**

```json
{
  "s3_path":    "invoices/123/signed-INV-000042.pdf",
  "expires_in": 7
}
```

`expires_in` is passed from the WP setting `apss_signed_url_ttl_days` (default 7). Laravel uses it to set the S3 pre-signed URL TTL.

**What Laravel must do:**

1. Verify `s3_path` exists in the private bucket
2. Generate a pre-signed S3 URL valid for `expires_in` days
3. Return the URL

**Success response `200`:**

```json
{
  "signed_url": "https://s3.amazonaws.com/bucket/invoices/123/signed-INV-000042.pdf?X-Amz-Signature=…",
  "expires_at": "2026-04-03T14:32:00Z"
}
```

WP immediately redirects the browser to `signed_url`. The URL is never stored.

**Error response `404`:**

```json
{
  "error": "File not found in storage"
}
```

---

### Authentication & Security Notes

- Authentication is handled entirely by the Pro connector JWT flow — no separate API key is stored by this plugin
- All WP → Laravel calls are server-to-server via `AgencyPulseWP_Api_Client` — no client browser ever calls Laravel directly
- Laravel should validate `invoice_id` is a positive integer and `s3_path` belongs to the expected prefix (`invoices/`) to prevent path traversal
- The S3 bucket must be **private** — no public read access; all client access goes through signed URLs only
- Laravel should log every sign and download request with timestamp, IP, and invoice ID for the audit trail

---

### New WP Setting Required

| Option key | Type | Purpose |
|-----------|------|---------|
| `apss_signed_url_ttl_days` | int (default 7) | Days a signed download URL remains valid; sent to Laravel on every download request |

`apss_laravel_endpoint` and `apss_laravel_api_key` are **not used** — connection is handled by the Pro connector.
