PreArrive Developers

PreArrive REST API v1

Server-to-server integration for Pro accounts and API partners. Push reservations, pull certificates, react to signed status events.

What you can do

List/create/update properties and packets; push reservations and send packets; check signed status; pull signed certificates (PDF + JSON audit trail); react to lifecycle events via webhooks.

What it is NOT

Not a PMS, channel manager, marketplace OAuth surface, payment collector, platform writeback tool, ID verifier, or smart-lock operator. Keys are server-to-server only — issued by the Pro account owner.

Stability

Stable endpoints follow the v1 backward-compat promise. Beta endpoints may change with 30 days' notice (clearly marked).

Proof standard

Partner integrations can inspect the sample certificate and verification flow today. Public metrics and customer stories wait for cohort size, date range, permission, and anonymization.

Read the proof policy

Getting started

Five minutes from key creation to first authenticated call.

1. Get a key

Sign in to your Pro account → Settings → Public REST API. Click Create key, pick secret (full access) or restricted (scoped), copy the value. The plaintext is shown once.

2. Make a call

curl https://api.prearrive.com/v1/properties \
  -H "Authorization: Bearer prearrive_live_sk_..."

3. Inspect the response

{
  "object": "list",
  "data": [
    {
      "id": "prop_4Xz9c2K8b7Lm9nQpRsTuV",
      "object": "property",
      "name": "Brickell 2BR",
      "address": "123 SW 8th St, Miami, FL 33130",
      "subdomain": "brickell-2br",
      "created_at": "2026-04-12T18:02:11Z",
      "updated_at": "2026-05-08T15:31:00Z"
    }
  ],
  "has_more": false,
  "url": "/v1/properties"
}

Common integration recipes

Most partner builds start with the same narrow flow: create the stay, send the guest link, wait for signed status, react to arrival-release events, and retrieve the certificate. The API does not replace your PMS, guidebook, lock system, claims workflow, or booking platform.

Create and send a reservation

Use this when a PMS, co-hosting dashboard, guidebook app, or internal tool already has the guest and stay dates.

POST /v1/reservations
{
  "property_id": "prop_4Xz9c2K8b7Lm9nQpRsTuV",
  "guest_name": "Maya Chen",
  "guest_email": "maya@example.com",
  "checkin_date": "2026-06-14",
  "checkout_date": "2026-06-17",
  "source": "airbnb"
}

POST /v1/reservations/res_K2jH4g5F6d7S8a9G0h1F/send

Check signed status and pull the certificate

Polling is fine for internal tools. Webhooks are better when another system should act immediately. The reservation carries selfie_required — a snapshot of the property's photo-at-signing requirement at send time — so you can tell whether the guest faces a photo step before they can sign.

GET /v1/reservations/res_K2jH4g5F6d7S8a9G0h1F

# response includes: "selfie_required": true,
#                    "status": "sent", ...
# wait for status: signed and signature_id
GET /v1/certificates/sig_pQrStUvWxYzAbCdEfGhI
GET /v1/certificates/sig_pQrStUvWxYzAbCdEfGhI/download

Release arrival details after signature

Set packet.arrival_release, subscribe to arrival-detail lifecycle events, verify the signature, then release partner-owned door codes, WiFi, or guidebook sections when PreArrive emits the release event.

PreArrive-Event: reservation.arrival_details_released
PreArrive-Signature: t=1748764800,v1=...

{
  "type": "reservation.arrival_details_released",
  "data": {
    "arrival_release": {
      "state": "released",
      "released": ["wifi", "door_code"],
      "withheld": []
    }
  }
}

Build against test mode first

Test keys use separate data, skip real guest emails, and expose email previews so you can verify copy before live traffic.

Read test-mode guide · Idempotency · Versioning

Authentication

Every request must include a bearer token:

Authorization: Bearer prearrive_<env>_<type>_<random>

Keys are team-scoped (one team per key) and owner-issued (only the team owner can create them). Restricted keys carry a list of scopes like properties:read, reservations:send. Calling an endpoint outside your scopes returns 403 permission_error.

Optional per-key IP allowlist (CIDR or plain v4) blocks any request from outside the listed ranges.

Rotation

Hit Rotate in the dashboard to mint a successor with the same name/scopes/IP. The old key keeps working for 24 hours so you can swap it in production without downtime; calls during the grace return a Deprecation: true header and write an audit event so you can see the old key still in use.

Errors

Every non-2xx response uses the same envelope:

{
  "error": {
    "type": "invalid_request_error",
    "code": "missing_required_field",
    "message": "Field `guest_email` is required.",
    "param": "guest_email",
    "doc_url": "https://developers.prearrive.com/errors/missing_required_field",
    "request_id": "req_K2jH4g5F6d7..."
  }
}

The request_id (a req_… value, also returned in the X-Request-Id response header) — include it in any support ticket. Field-level errors add a param naming the offending field. Common types:

TypeStatusWhen
invalid_request_error400Bad JSON, missing/invalid field, business rule.
authentication_error401API key missing, malformed, revoked, wrong environment.
permission_error403Key valid but missing the required scope.
not_found_error404Resource id doesn't exist or isn't owned by this team.
conflict_error409Concurrent edit (etag mismatch), uniqueness violation.
plan_limit_error402Plan cap reached. Add an add-on or upgrade.
rate_limit_error429Too many requests. Respect Retry-After.

Rate limits

Pro accounts: 100 req/min reads, 30 req/min writes, sliding window. Every response carries:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 73
X-RateLimit-Reset: 1748764800

429s include Retry-After in seconds. Burst allowance: 2× the per-minute limit over a 10-second window.

Pagination

Cursor-based. List endpoints accept limit (1–100, default 25), starting_after, and ending_before — pass the id of an item from the current page to navigate. Cursors are stable across mutations; offset is not.

Webhooks

Register an endpoint in the dashboard (Settings → Public REST API → Webhooks) or via the API. Every event POSTs JSON to your URL with a signed header:

POST /your-receiver HTTP/1.1
Host: hooks.example.com
Content-Type: application/json
PreArrive-Signature: t=1748764800,v1=5257a869e7ec...
PreArrive-Event: reservation.signed
PreArrive-Event-Id: evt_pQrStUvWxYzAbCdEfGhI
PreArrive-Delivery-Id: evt_K2jH4g5F6d7S8a9G0h1F
PreArrive-Attempt: 1

{ "id":"evt_pQrSt...", "type":"reservation.signed", "data":{ ... } }

Verifying the signature (Node)

import crypto from 'node:crypto';

function verify(header, rawBody, secret) {
  const parts = Object.fromEntries(
    header.split(',').map(p => p.split('=', 2))
  );
  const t = parseInt(parts.t, 10);
  if (Math.abs(Date.now()/1000 - t) > 300) return false;       // 5-min replay window
  const expected = crypto.createHmac('sha256', secret)
    .update(`${t}.${rawBody}`).digest('hex');
  // Header may carry two v1 sigs during 24h rotation grace.
  const sigs = header.split(',').filter(p => p.startsWith('v1='))
    .map(p => p.slice(3));
  return sigs.some(s =>
    s.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(s, 'hex'),
                           Buffer.from(expected, 'hex')));
}

Verifying the signature (Python)

import hmac, hashlib, time
def verify(header, raw_body, secret):
    parts = dict(p.split('=', 1) for p in header.split(','))
    t = int(parts['t'])
    if abs(time.time() - t) > 300: return False
    expected = hmac.new(secret.encode(), f"{t}.{raw_body}".encode(),
                        hashlib.sha256).hexdigest()
    sigs = [p[3:] for p in header.split(',') if p.startswith('v1=')]
    return any(hmac.compare_digest(s, expected) for s in sigs)

Rotating the signing secret

To swap a webhook signing secret with zero dropped events, POST /v1/webhook_endpoints/<id>/rotate. The response returns the new signing_secret in plaintext once (store it now — it is never shown again) plus a rotation_grace_until timestamp. For the 24-hour grace window, deliveries are signed with both the new and old secrets — that second v1= in the signature header — so a receiver that still has the old secret keeps validating while you roll out the new one.

POST /v1/webhook_endpoints/whe_K2jH4g5F6d7S8a9G0h1F/rotate

{
  "object": "webhook_endpoint",
  "id": "whe_K2jH4g5F6d7S8a9G0h1F",
  "signing_secret": "whsec_…",            // shown once
  "signing_secret_warning": "Store this secret now — it will not be shown again.",
  "rotation_grace_until": "2026-06-03T18:00:00Z"
}

Swap flow: (1) call rotate and capture the new secret; (2) deploy it to your receiver's config; (3) keep accepting either secret until rotation_grace_until passes (the dual-signing already covers this — the verifier loops over every v1=); (4) after the window, the old secret stops being signed with automatically. No second call needed.

Retries

Non-2xx responses are retried at 1m, 5m, 30m, 2h, 6h, 12h, 24h, 24h, 24h after the previous failure. After 9 failures, the endpoint is auto-disabled and the team owner is emailed. Re-enable in the dashboard. Inspect every attempt via GET /v1/webhook_endpoints/<id>/deliveries; POST .../redeliver queues a fresh attempt.

Idempotency (dedup retries)

Because non-2xx responses are retried, the same event can hit your receiver more than once. Each event carries a stable PreArrive-Event-Id (the id in the body, identical across every retry) and a per-attempt PreArrive-Delivery-Id + PreArrive-Attempt counter. Dedup on PreArrive-Event-Id: record the id the first time you process an event, and no-op if you've seen it before.

const eventId = req.headers['prearrive-event-id'];   // evt_… — stable across retries
if (await seen(eventId)) return res.sendStatus(200);  // already handled; ack so retries stop
await process(JSON.parse(rawBody));
await markSeen(eventId);
res.sendStatus(200);

Always return 2xx once you've durably accepted the event — that's what stops the retry curve. Use PreArrive-Delivery-Id / PreArrive-Attempt only for logging which attempt arrived, not for dedup.

Events partners commonly subscribe to

Current events include reservation lifecycle events, certificate creation, property and packet changes, and sync-source lifecycle events. Start with the narrowest set your workflow needs.

reservation.created
reservation.sent
reservation.clicked
reservation.signed
reservation.cancelled
reservation.arrival_details_held
reservation.arrival_details_released
reservation.arrival_details_viewed
reservation.arrival_details_override_released
certificate.created
property.created
property.updated
packet.updated
sync_source.created
sync_source.updated
sync_source.run.completed

What a sync run touches

On each iCal run, a new feed event inserts a synced reservation; a modified event (e.g. a guest reschedule) upserts — the check-in/check-out date and time refresh so downstream timing (auto-send, reminders, late alerts) tracks the new dates. A re-sync never overwrites guest name/email (enriched separately) or the packet lifecycle (status, sent, signed), and it will not touch a reservation once it is signed, redacted, or under legal hold — those rows are frozen as evidence. A booking that simply disappears from the feed is not auto-cancelled on every run; cancellation is gated on a healthy (non-empty) feed.

Partner contact

Self-serve Pro covers API keys, webhooks, test mode, idempotency, and the public reference. Partner distribution, DPA review, co-selling, or a larger rollout may need a separate agreement.

Partner pages

Use-case pages for PMS and co-hosting tools, guidebooks, smart-lock workflows, insurance and claims-prep services, and internal tools.

Open partner pages

Send API context

The contact form route preselects API / partner integration and passes the partner source through to the support inbox.

Contact partnerships

API reference

Full reference for all 34 endpoints with hyperlinkable schemas and a try-it panel — rendered live from the OpenAPI spec.

Open API reference → View openapi.yaml Download Postman collection

Questions? Hit reply on any PreArrive email or contact us.