API Documentation

The VerityPay API is a REST/JSON API designed for tenant-scoped automation. This section is the first stop for any integration.

  • Authentication — bearer tokens, abilities, tenant resolution.
  • Endpoints — full reference of available endpoints.
  • Webhooks — outbound event deliveries: envelope, signature verification, retries.
  • Sandbox → Production — how environments work and the go-live migration checklist.
  • Postman & MCP — import-ready collection and AI-agent integration.

Conventions at a glance

Property Value
Base URL https://app.veritypay.com/api/v1
Transport HTTPS only (TLS 1.2+)
Authentication Bearer token (Sanctum personal access token)
Tenant resolution Encoded in token via central token_routes
Default content type application/json
API version v1 (in path)
Idempotency Idempotency-Key header on writes (24h)
Request correlation X-Request-Id header (set or echoed back)
Error envelope { "error": { "code", "message" } }

Versioning

Every endpoint lives under /api/v1/.... Breaking changes ship as a new major version (/api/v2/...); both versions run side by side during the deprecation window. Non-breaking additions land in v1.

A change is breaking if it removes a field, narrows a field's accepted values, or changes a field's type. Adding new optional request fields, new optional response fields, new endpoints, or new abilities is not breaking.

Request format

POST /api/v1/contacts HTTP/1.1
Host: app.veritypay.com
Authorization: Bearer vp_pat_••••••••••••••••••••
Accept: application/json
Content-Type: application/json
Idempotency-Key: 4f4f8b5e-4c5e-4d3a-8e9c-b3a3f2e1c0d5

{
  "first_name": "Jane",
  "last_name": "Doe",
  "phone": "+15551234567"
}

Response format

Successful responses return JSON resources. Collection endpoints follow Laravel API Resources pagination:

{
  "data": [ /* ... */ ],
  "links": { "first": "...", "last": "...", "prev": null, "next": "..." },
  "meta":  { "current_page": 1, "per_page": 25, "total": 142 }
}

Error envelope

All /api/v1/* errors return a stable JSON shape so you can switch on error.code:

{
  "error": {
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "details": { "phone": ["The phone field is required."] },
    "request_id": "01HZK8XZTQ2C5J7YTX3J7Z3VYW"
  }
}
HTTP error.code When
400 bad_request Malformed request body (e.g., invalid JSON).
401 unauthenticated Missing / invalid bearer token.
401 invalid_signature Signed-URL signature missing or invalid.
403 forbidden Token lacks the required ability.
404 resource_not_found Object does not exist or is not in this tenant.
404 route_not_found Endpoint does not exist.
405 method_not_allowed HTTP method not allowed on this endpoint.
409 idempotency_conflict Same Idempotency-Key reused with a different body.
413 batch_payload_too_large Batch ingest body exceeds the 5 MB payload cap; split the batch.
419 csrf_token_mismatch (Web only — never on JSON API.)
422 validation_failed Request body validation failed; see details.
422 invalid_transition State machine refused the transition (e.g. cancel after paid).
429 rate_limited Throttled; respect Retry-After.
429 batch_concurrency_limit_reached Tenant already has the maximum batches in flight; retry after Retry-After.
5xx server_error Unhandled server-side error; report with request_id.

The envelope always carries error.request_id when one was assigned. Always log this — support cannot triage without it.

Rate limits

Surface Limit
Authenticated API 60 requests / minute / token
Batch ingest submissions 10 requests / minute / tenant by default (plus max 2 batches in flight; both per-tenant configurable — see Batch ingest)
Unauthenticated API 30 requests / minute / IP
Webhook receivers 120 requests / minute / IP

Rate-limit headers (X-RateLimit-Limit, X-RateLimit-Remaining) and a Retry-After header on 429s let you back off cleanly. Build retry-with-jitter into any production client.

Idempotency

Every writing call (POST/PUT/PATCH) accepts an Idempotency-Key header. Send a fresh, opaque, ≤128-char key per logical operation:

Idempotency-Key: 4f4f8b5e-4c5e-4d3a-8e9c-b3a3f2e1c0d5

VerityPay caches the response for 24 hours. Replays with the same key + same body return the original response. Replays with the same key + different body return 409 idempotency_conflict. Use UUID v4s — never derive the key from request contents.

Request correlation

Every request gets an X-Request-Id header. If your client supplies one (≤64 chars, ULID or UUID), the server echoes it back; otherwise the server assigns one. The same value appears on every error envelope (error.request_id), every audit log row, and every internal log line — making cross-system debugging trivial.

Best practices for integration teams

A short opinionated checklist that has saved teams real engineering hours:

  1. Generate one API token per service, not one per developer. Document which token belongs to which workload in your secrets manager.
  2. Grant the minimum abilities the workload needs (see Authentication). It's cheaper to add an ability later than to debug a leaked over-privileged token.
  3. Always send Idempotency-Key on writes. Networks lie. Retries are inevitable. Without a key you will eventually create duplicate payables.
  4. Always log request_id alongside your own correlation IDs. When something breaks, support can pivot from your trace ID directly to ours.
  5. Always handle 429 with exponential backoff + jitter. Spinning on a limited endpoint just delays recovery for everyone, including you.
  6. Pin the API version in code. Don't dynamically resolve /api/v1 vs /api/v2; make the upgrade an explicit, reviewed deploy.
  7. Validate webhook signatures before trusting payloads. VerityPay's outbound webhooks are HMAC-signed — verify every delivery per the Webhooks guide before acting on it.
  8. Treat phone numbers as the canonical identity in this platform. Always store them E.164-formatted on your side and let VerityPay normalise on ingest.
  9. Soft-delete first, hard-delete only on tenant offboarding. The API supports restore for transient mistakes — use it instead of recreating.
  10. Smoke-test in your tenant before promoting. Each tenant has its own dataset; a token tested in staging will not work in production.