Rest V1 Create or Update End Point: "BILLING Region is invalid" Bug?

Looking for a little help here.

Would love it if Rest V2 had an createWithDupeCheck (upsert) functionality so I didn’t have to bother with v1.

When retrieve a contact via V1 endpoint, I get the following for a billing address:
[
“line1” => “542 S. MAIN CT”,
“line2” => “”,
“locality” => “Anaheim”,
“region” => “CA”,
“field” => “BILLING”,
“postal_code” => “92808”,
“zip_code” => “92808”,
“zip_four” => null,
“country_code” => “USA”,
]

I want to create (or update) a related contact at the same address. So I create the contact payload, and keep the address the same, and make my PUT request to /v1/contacts with:

[
“line1” => “542 S. MAIN CT”,
“locality” => “Anaheim”,
“region” => “CA”,
“postal_code” => “92808”,
“country_code” => “USA”,
“field” => “BILLING”,
]

This results in a 400 Bad Request with the response: {“message”:“BILLING Region is invalid”}

I am returning the EXACT region that was provided. No modifications.

When I use the V2 endpoint to create a new contact, I can use the exact same data and it works just fine. But now I am making two calls: #1 check if a contact exists, #2 create the contact OR #2 update the contact.

Hi @Timothy_Withers, thanks for the detailed write-up!

For V1 (PUT /v1/contacts), the endpoint only accepts regions that are in su the supported region list for the given country.

You can fetch those using these endpoints:

We’re also aware of the V2 gap for a single-call create/update (upsert). It’s on our radar, and I’ll share updates as we have them.

Ah. I will have to test it out then. Strange that the contacts end point return a value that is not accept on a post request. I feel like the GET shouldn’t return “CA” and “USA” if it isn’t allowed on a POST/PUT.

1 Like

@OmarAlmonte If the expected behavior for that endpoint is to validate the input against a list of acceptable values, is it also expected behavior that a GET request for the same object would return an invalid value for that property?

@Todd_Stoker @Timothy_Withers The issue is usually how the address was originally created.

Classic UI and XML-RPC allow free-text regions with no standardization, so you can retrieve a region value that isn’t actually valid per V1’s supported region list. When you later do a PUT /v1/contacts, V1 enforces validation and rejects it with “BILLING Region is invalid”, even if you’re sending back the exact same value.

If the address was originally created via the REST APIs or the new UI, the region would have been validated up front and you could safely reuse the value returned by GET in POST/PUT/PATCH.

That does make sense.

When creating the contact with v2, I didn’t get any errors in regards to the address region being invalid.

I guess I need to double check that it created the contact with the address/region and didn’t just ignore the region.

@OmarAlmonte Additionally, the Keap UI doesn’t enforce these validation rules (e.g. in the UI, State/Region is an input that accepts any arbitrary string). I can appreciate the situation with XML, but the inconsistency across “current” contact update/create mechanisms is a problem that should be addressed. And even if this weren’t the case and the UI enforced a consistent set of rules, being unable to trust the values returned from the GET endpoint in a subsequent create/update is problematic and should receive scrutiny at Keap.

@Todd_Stoker @Timothy_Withers That’s the classic UI, and like I mentioned, if a contact was originally created via XML-RPC or in the Classic UI, there’s no field validation. In the new UI and REST APIs, field validation does exist. I can bring this up to the team so they can take a look, but mapping these values on our end can be tricky since those fields could contain any string. for example, we can’t automatically map “The moon” (like in your example) to a real region.

Sorry, I missed the detail regarding the Classic UI. This detail adds additional complexity. And third-party developers also cannot automatically map “The moon” to a real region. It’s a problem because actual, real-world customer data includes variations of real states/regions (like misspellings and abbreviations).

Right, and that’s exactly one of the benefits of standardization, everything becomes consistent.

Ok, we might be off in our tests, because we were getting inconsistent behavior and perhaps we weren’t tracking classic vs new…anyway, I’d like to make sure I’ve grokked this so we can validate:

  • No validation for region/state and country:
    • Classic UI
    • XML, any operation
  • Validates region/state and country:
    • New UI
    • v1/contacts PUT, POST, PATCH
    • v2/contacts POST, PATCH (and PUT if/when available?)

Is this accurate?

That’s correct.

Hey Omar, sorry to keep harping on this. We spent the better part of a few hours testing V1 and V2 addresses. Here are our findings we would like you to validate:

v1 GET contact:

  • Address contains keys country_code and region
  • country_code is either
    • the ISO 3166-1 alpha 3 code (“USA” for “United States”, “MEX” for “Mexico”, etc)
    • empty
  • region is either:
    • the proper name for the state/region (“Arizona”, “California”, etc)
    • some random string that was saved in the contact record
  • region is never returned as the ISO 3166-2 code

v1 POST/PATCH contact:

  • country_code must be a valid ISO 3166-1 alpha 3 code
    • If invalid, error is thrown.
  • region must be a valid ISO 3166-2 code that matches the country_code OR the proper name for the state/region.
    • If I use the 3166-2 code (“US-CA”), it will show the proper name in app (“California”)
    • If invalid, error is thrown.
    • If contact doesn’t have a country_code, error is thrown

V2 GET contact:

  • Address contains keys 1 or more of the following:
    • region
    • region_code
    • country
    • country_code
  • country_code and region_code are only shown when they are valid ISO 3166 representations (“USA”, “US-AZ”)
  • country is the proper country name, and is only shown when country_code is present
  • region could be anything
    • If region_code is set, then it is the proper name of the region
    • If region_code is not set, then it will be whatever is entered

V2 POST/PATCH contact:

  • If country_code is passed, it must be a valid ISO 3166-1 alpha 3 code
    • If country_code is invalid, and error is thrown
  • If country and country_code is passed, country is ignored as long as the country_code is valid.
    • country is updated to the proper country name.
  • If country alone is passed, country_code and country are deleted from the contact record.
    • No error is thrown.
  • If region_code is passed, it must be a valid region_code belonging to a valid country_code
    • If invalid, an error is thrown
  • If region_code and region is passed, region is ignored as long as the region_code is valid.
    • region is updated to the proper region name.
  • If region alone is passed:
    • If country_code is set and valid:
      • If region is a proper region name (“Arizona”)
        • region_code is updated to the correct ISO-3166-2 code (“US-AZ”)
      • If region is a valid ISO 3166-2 code
        • region is set to the proper region name
        • region_code is set to the ISO 3166-2 code
      • If region is not a proper region name, and region is not a valid ISO 3166-2 code:
        • region is set to the value entered
        • region_code is deleted (if it was set)
    • If country_code is not set, region is set to whatever value is entered

V2 feels a bit more lenient as far as regions go. But it is worrisome that any update to country without country_code deletes the country code and country entirely, rather than simply ignoring it or throwing an error.

The takeaway that I am sensing is that country_code will always be a valid/safe ISO 3166-1 alpha 3 code if it is set, where as region is a bag of mysteries.

Hi @Timothy_Withers,

Thanks for the detailed breakdown. As long as valid ISO codes are used, everything will remain consistent. I see your concern with countries being removed if country_code isn’t provided, I’ll circle this back with the team and get back to you.

@OmarAlmonte For clarity about why this is problematic: we’re trying to migrate production applications, so weird behavior like this is extremely painful – it takes hours to troubleshoot and diagnose. All the while, users of our applications suffer. Stuff like this might seem small, but it has an enormous impact on a production application.