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
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.