Developers

API update: stricter validation and clearer 422 errors

The Hipcall API now validates every request against its OpenAPI spec. Unknown fields, bad enums and malformed filters return 422 instead of being ignored.

Onur Ozgur OZKAN Onur Ozgur OZKAN
· 7 min read

If you integrate with the Hipcall API, one thing is changing and it’s worth 10 minutes of your attention: the API now validates every request against its published OpenAPI specification. Input the server can’t process returns 422 instead of being quietly dropped.

Most well-formed integrations won’t notice. But if your code has been sending a field name with a typo in it, or a filter value the API never understood, you’ve been getting 200 responses that didn’t do what you assumed. Those requests now fail loudly.

This post is a map of what changed, so you can check your integration before your users do.

Terminal comparing a silent 200 response with a 422 error naming the field

The problem this fixes

Here’s the case that prompted the work. Ask for dispositions with a mistyped filter value:

# "success" misspelled as "succes"
curl -H "Authorization: Bearer $TOKEN" \
  "https://api.hipcall.com/api/v3/dispositions?outcome=succes"

The old behaviour: 200 OK, and the complete unfiltered list. The filter wasn’t understood, so it wasn’t applied, and the response looked exactly like a successful filtered query. If you then counted those rows, or synced them, or showed them to a user, you were working with wrong data and had no way to know.

That’s worse than an error. An error you handle; a plausible wrong answer you propagate.

The same request now returns 422:

{
  "errors": {
    "outcome": ["Invalid value for enum"]
  }
}

Same error shape you already parse.

The rule, in one sentence

If the API understood your request but can’t process it, you get a 422 naming the field. It no longer guesses, defaults, or ignores.

6 things follow from that:

  1. Unknown fields are rejected rather than skipped.
  2. No silent type coercion—except one that’s now explicitly documented (see external_id).
  3. Enum values are validated, never defaulted.
  4. A filter that can’t be applied fails the request instead of returning everything.
  5. Validation happens before any write, so there are no half-applied payloads.
  6. Errors name the offending field.

What now returns 422

Unknown query parameters

Previously ignored. This is the change with the widest reach, because a typo in a parameter name used to be invisible.

# Was: 200 with every contact. Now: 422.
curl -H "Authorization: Bearer $TOKEN" \
  "https://api.hipcall.com/api/v3/contacts?life_cycle_i=3"

Unknown body fields

Same rule for writes. A misspelled key used to return 200 having changed nothing at all:

curl -X PATCH \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"first_nam": "Jane"}' \
  "https://api.hipcall.com/api/v3/contacts/123"
{
  "errors": {
    "first_nam": ["Unexpected field: first_nam"]
  }
}

A PATCH whose body contains only unrecognised keys is now a 422 too. “I did nothing” was never a success.

Pagination on endpoints that never paginated

A few endpoints return a small fixed collection and accept no limit or offset—dispositions, call cards, the contact-centre vocabularies, your profile. They used to accept those parameters and ignore them, which quietly implied you were paging through results you were in fact receiving whole. They now say so with a 422.

Filter operators

Bracket-notation filters validate their operators against the documented set:

# Valid—unchanged
"?started_at[gte]=2026-01-01T00:00:00Z"

# Unknown operator: was a silently dropped rule, now 422
"?started_at[invalid]=2026-01-01T00:00:00Z"

The wire format hasn’t changed. If your filters work today, they keep working.

Field-level changes worth checking

external_id

If you use external_id to keep your CRM in lockstep with Hipcall—the pattern in syncing CRM companies with external_id—read this one carefully.

You sendBeforeNow
"CRM-4821"storedstored (unchanged)
4821 (integer)stored as "4821"stored as "4821" (unchanged, now documented)
nullclears the fieldclears the field (unchanged)
""silently became null422
" "silently became null422
["a"], {"k":"v"}, truesilently became null422

The integer coercion stays, and is now declared in the schema as oneOf: [string, integer]—so 4821 and "4821" are the same value. What changed is that meaningless values are rejected instead of turning into null behind your back.

To clear the field, send null. An empty string no longer does it.

phones and emails on update

These were accepted by POST and silently discarded by PATCH. If you’ve been sending them in an update expecting them to apply, they never did.

# Now returns 422 naming "phones"
curl -X PATCH \
  -H "Content-Type: application/json" \
  -d '{"first_name":"Jane","phones":[{"number":"+905551112233","country":"TR"}]}' \
  "https://api.hipcall.com/api/v3/contacts/123"

Use the sub-resources instead: /contacts/:id/phones and /contacts/:id/emails, and the same pair under /companies/:id/.

Attribution tracking

POST /attribution used to store null for an unrecognised device or an unparseable occurredAt. Both are now validated—device accepts mobile, tablet, or desktop.

If your tracker is a fire-and-forget browser beacon that ignores the response, this is the one to check by hand. A beacon won’t tell you it started failing.

Error format

The shape is unchanged: an object keyed by field name, exactly what you parse today.

{ "errors": { "outcome": ["Invalid value for enum"] } }

2 details worth knowing.

Nested fields carry a JSON pointer in the message, so an array index isn’t lost:

{ "errors": { "phones": ["#/phones/0/number: Missing field: number"] } }

Two endpoints wrap their body in a data envelopePOST /tasks and POST /tasks/:id/comments. Errors there are keyed under data, with the field named in the pointer:

{ "errors": { "data": ["#/data/name: Missing field: name"] } }

If you parse errors["name"] on those two endpoints, switch to reading errors["data"].

Send a JSON content type

Write endpoints declare a JSON request body. If your client sends application/x-www-form-urlencoded or a multipart body, those requests now return 422.

Content-Type: application/json

application/json; charset=utf-8 is fine. This is the most common reason a previously working integration starts failing, and it’s a one-line fix in most HTTP clients.

What hasn’t changed

Worth stating plainly, because “stricter validation” makes people nervous about things that are fine:

  • Authentication still comes first. An unauthenticated request returns 401, not 422. Validation can’t be used to probe which parameters exist.
  • Numeric strings still work. {"company_id": "123"} is still accepted where an integer is expected.
  • Existing length, format and enum rules are unchanged. If a 300-character name was rejected before, it’s rejected the same way now.
  • Valid filter requests are untouched. Bracket notation, comma-separated in lists, pagination—all identical.
  • No endpoint changed its URL, its method, or its success response body.

A 5-minute checklist

  1. Set Content-Type: application/json on every write request.
  2. Grep your integration for field names and compare them against the spec at /api/openapi. Typos are the number one cause of the new 422s.
  3. Search for external_id assignments that could produce "" or a whitespace-only string. Send null to clear.
  4. Check any PATCH that includes phones or emails and move it to the sub-resource endpoint.
  5. Log your 422 rate for a week after you deploy. A step change points straight at the offending call.

The OpenAPI specification at /api/openapi is the enforced contract now, not documentation that drifted alongside the code. If the spec and the API disagree, that’s a bug and we want to hear about it. The rollout is phased across endpoints, so treat the spec as the source of truth for whichever ones you depend on.

Full reference and guides live in the developer hub. If something breaks and the reason isn’t obvious from the error body, send us the request and the response—we’ll tell you exactly which rule it hit.

Analyze the content of this post with

Written by

Onur Ozgur OZKAN

Onur Ozgur OZKAN

Co-founder & CEO

The Hipcall team builds an all-in-one communication platform for sales and support teams, combining business phone, call centre, CRM, and helpdesk into a single workspace.

Stay in the loop

Get the latest product updates, tips, and industry insights delivered to your inbox.

Try for Free