PPactDocs
Developers

Back up Pact data nightly to your S3

Page through every record and write a dated JSON snapshot to your own S3 bucket.

Keep an independent copy of your CRM data in storage you control. This recipe exports accounts, contacts, and deals to a dated prefix in S3, on a schedule.

Estimated time: ~30 minutes to set up; runs unattended after that.

1 — Use a read-only, rate-limited key

Create an API key with only read:accounts, read:contacts, and read:deals, and give it a modest per-key rate limit so the nightly job never competes with interactive users. Store it as a secret in your job runner — never in code.

2 — Page through everything and upload

python
import datetime as dt
import json, os
import boto3, requests

API = "https://app.pact.place"
H = {"Authorization": f"Bearer {os.environ['PACT_API_KEY']}"}
BUCKET = os.environ["BACKUP_BUCKET"]
s3 = boto3.client("s3")

def fetch_all(entity, page_size=200):
    offset, out = 0, []
    while True:
        res = requests.get(
            f"{API}/v1/{entity}",
            params={"limit": page_size, "offset": offset},
            headers=H, timeout=30,
        )
        res.raise_for_status()
        page = res.json()
        out.extend(page["items"])
        offset += page_size
        if offset >= page["total"] or not page["items"]:
            return out

def main():
    day = dt.datetime.now(dt.UTC).strftime("%Y/%m/%d")
    for entity in ("companies", "contacts", "opportunities"):
        records = fetch_all(entity)
        s3.put_object(
            Bucket=BUCKET,
            Key=f"pact-backup/{day}/{entity}.json",
            Body=json.dumps(records).encode(),
            ContentType="application/json",
        )
        print(f"backed up {len(records)} {entity} → s3://{BUCKET}/pact-backup/{day}/{entity}.json")

if __name__ == "__main__":
    main()

3 — Schedule it

Run the script from cron, a GitHub Actions scheduled workflow, or a Lambda on an EventBridge rule. Once a day off-peak is plenty for most workspaces.

Respect the rate limit

This script pages sequentially, which keeps it well under any tier's limit. If you backfill in parallel, watch X-RateLimit-Remaining and back off on 429 — see rate limits and pagination.

4 — Lifecycle your backups

Add an S3 lifecycle rule to expire or transition old snapshots to cheaper storage so the bucket doesn't grow forever. Daily JSON compresses well — enable bucket-level compression or gzip before upload if size matters.

What's next?