Skip to content
DevelopersREST contract
Documentation menu Open Integrations

Custom CRM

REST contract

Implement the six fixed operations on your customer-owned HTTPS bridge. Pongg appends each POST path to the configured base URL.

Contract at a glance

Every request includes tenant_id and contract_version:pongg.crm.v1. Enforce the binding between that tenant and the presented credential. Return JSON objects. Contact upsert is required, together with at least one of leads.create or appointments.create.

Download the complete OpenAPI 3.1 document; it contains required fields, optional fields and synthetic examples.

cURL
curl --request POST "$BRIDGE_BASE_URL/capabilities" \
  --header "Authorization: Bearer $BRIDGE_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{"tenant_id":"YOUR_PONGG_TENANT_ID","contract_version":"pongg.crm.v1"}'

REST operations

Every operation is an HTTPS POST. Pongg appends the path to your configured base URL. For a base of https://crm.example.com/pongg/v1, capabilities is https://crm.example.com/pongg/v1/capabilities. All examples below are synthetic.

Every body contains tenant_id and contract_version. All three write operations also carry the same opaque 64-character hexadecimal key in the Idempotency-Key header and idempotency_key body field. Reads do not carry a write key.

POST /capabilities

Return the exact contract version and only the supported operation names. Include contacts.upsert and at least one of leads.create or appointments.create. If you advertise availability.list, also implement appointments.create and return booking_closes_availability:true for the same calendar.

Request JSON
Contract example
{
  "tenant_id": "tenant-example-001",
  "contract_version": "pongg.crm.v1"
}
Response JSON
Contract example
{
  "contract_version": "pongg.crm.v1",
  "operations": [
    "contacts.search",
    "contacts.upsert",
    "leads.create",
    "availability.list",
    "appointments.create"
  ],
  "booking_closes_availability": true
}

Both search fields are strings and may be empty. Return contact:null when there is no match, or a nonblank durable contact ID. Do not invent a match.

Request JSON
Contract example
{
  "tenant_id": "tenant-example-001",
  "contract_version": "pongg.crm.v1",
  "phone": "+12145550148",
  "email": "jordan@example.com"
}
Response JSON
Contract example
{
  "contact": {
    "id": "contact-example-001"
  }
}
Contract example
{
  "contact": null
}

POST /contacts/upsert

Match phone or email identity before creating a contact. Return a durable nonblank ID and a real boolean is_new. String values such as "false" do not satisfy the response.

Request JSON
Contract example
{
  "tenant_id": "tenant-example-001",
  "contract_version": "pongg.crm.v1",
  "contact": {
    "full_name": "Jordan Example",
    "first_name": "Jordan",
    "last_name": "Example",
    "phones": [
      "+12145550148"
    ],
    "emails": [
      "jordan@example.com"
    ],
    "address": "100 Example St, Dallas TX 75201",
    "source": "Pongg (phone)",
    "tags": [
      "Pongg"
    ],
    "call_id": "example-call-001",
    "postal_code": "75201"
  },
  "idempotency_key": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
Response JSON
Contract example
{
  "id": "contact-example-001",
  "is_new": false
}

POST /availability

The window includes the requested interval, IANA zone, weekly windows, duration and job type. Weekly windows are [day, openingMinute, closingMinute], with 0=Monday; an empty array means not supplied. slot_minutes can be 0 when unspecified. Return at most 50 real openings inside the interval, with end after start. Pongg sorts and deduplicates slots by instant. Service IDs or arrival-window metadata in a slot response are not consumed by this bridge.

Request JSON
Contract example
{
  "tenant_id": "tenant-example-001",
  "contract_version": "pongg.crm.v1",
  "window": {
    "start": "2026-10-20T00:00:00+00:00",
    "end": "2026-10-21T00:00:00+00:00",
    "timezone": "America/Chicago",
    "windows": [
      [
        1,
        420,
        1080
      ]
    ],
    "slot_minutes": 60,
    "job_type": "Roof repair"
  }
}
Response JSON
Contract example
{
  "slots": [
    {
      "start": "2026-10-20T15:00:00+00:00",
      "end": "2026-10-20T16:00:00+00:00",
      "timezone": "America/Chicago"
    }
  ]
}

POST /appointments

Check capacity and create the appointment atomically. Return a durable ID, confirmed:true, exact requested start/end instants and a valid IANA timezone. An unconfirmed request, shifted time, empty ID or missing timestamp is a failed booking. Return HTTP 409 if the slot is taken.

Request JSON
Contract example
{
  "tenant_id": "tenant-example-001",
  "contract_version": "pongg.crm.v1",
  "appointment": {
    "contact_external_id": "contact-example-001",
    "timezone": "America/Chicago",
    "start": "2026-10-20T15:00:00+00:00",
    "end": "2026-10-20T16:00:00+00:00",
    "slot_text": "Tuesday at 10 AM Central",
    "job_type": "Roof repair",
    "address": "100 Example St, Dallas TX 75201",
    "notes": "Synthetic example: inspect a small roof leak.",
    "title": "Roof repair visit",
    "source": "Pongg (phone)",
    "location": {
      "id": "dallas",
      "name": "Dallas office"
    },
    "service_id": "repair-inspection"
  },
  "idempotency_key": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
Response JSON
Contract example
{
  "id": "appointment-example-001",
  "confirmed": true,
  "start": "2026-10-20T15:00:00+00:00",
  "end": "2026-10-20T16:00:00+00:00",
  "timezone": "America/Chicago"
}

POST /leads

Create or return the existing lead for the same idempotency key. Return its durable nonblank ID. A generic ok:true acknowledgement is not a receipt.

Request JSON
Contract example
{
  "tenant_id": "tenant-example-001",
  "contract_version": "pongg.crm.v1",
  "lead": {
    "contact": {
      "full_name": "Jordan Example",
      "first_name": "Jordan",
      "last_name": "Example",
      "phones": [
        "+12145550148"
      ],
      "emails": [
        "jordan@example.com"
      ],
      "address": "100 Example St, Dallas TX 75201",
      "source": "Pongg (phone)",
      "tags": [
        "Pongg"
      ],
      "call_id": "example-call-001",
      "postal_code": "75201"
    },
    "job_type": "Roof repair",
    "summary": "Caller requested an inspection.",
    "notes": "Synthetic example only.",
    "urgency": "routine",
    "source": "Pongg (phone)",
    "call_id": "example-call-001",
    "location": {
      "id": "dallas",
      "name": "Dallas office"
    }
  },
  "idempotency_key": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
Response JSON
Contract example
{
  "id": "lead-example-001"
}

Receipts, time and retries

Use explicit UTC offsets in every timestamp and an IANA timezone such as America/Chicago. Appointment input is checked before sending; a naive or nonpositive interval is refused. A receipt must match both requested instants exactly. A prior availability read does not reserve capacity.

Persist a unique idempotency-key-to-receipt mapping in the same CRM transaction as each write. Replaying a write must return the original record. Contact keys are based on normalized identity; appointment keys use tenant, contact, UTC interval, service or job type and location; lead keys use call ID and job type when available. Mutable descriptions do not change those appointment or call-based lead keys. Treat keys as opaque.

Only the capabilities probe is part of connecting. The bridge must actually honor identity matching, idempotency, permissions and atomic slot closure. Test those behaviors with your team before relying on live calls.

Security and limits

  • Use public HTTPS with a verifiable certificate. Every DNS answer must be public; the selected address is pinned while TLS verifies the original hostname.
  • Redirects, proxies, URL credentials, query strings and fragments are not supported. Use a clean endpoint path.
  • Each HTTP exchange has a 10-second total deadline. Request and response bodies are each limited to 1 MiB. Return uncompressed JSON or uncompressed MCP SSE.
  • Bind each credential to its tenant. Reject a tenant mismatch even if the incoming phone number or CRM record looks valid.
  • Store and rotate credentials securely. Pongg uses its tenant-bound encrypted integration store; never paste production secrets into examples, URLs or support messages.
  • Keep caller data limited to the workflow your company authorized. Free-form notes and summaries need the same access controls as CRM records.

Failure responses

ResponsePongg behaviorBridge responsibility
409 conflictPermanent failure for this operation; no appointment is confirmedUse for a taken slot. Do not return a successful receipt for a different time.
401Service-account OAuth refreshes the grant and retries the same operation once; a second rejection stopsReturn the same write receipt if the first request already committed.
Other 4xx / redirectsPermanent configuration or data failureCorrect the input or configuration. Do not depend on redirect following.
429 / 5xx / network timeoutTransient failure; post-call worker retries under its policyHonor idempotency even when a response was lost after the write committed.
Invalid receipt / malformed JSONRejected; not a confirmed writeReturn the complete object documented for the operation.

Do not rely on a particular remote error-body schema: Pongg surfaces a controlled error, not your raw body. In-call operations are time-bounded and do not wait through a post-call worker retry schedule. A successful capabilities probe never proves the write path.

Search documentation