Added

Interactions API: Granular Validation

Granular Validation support added to Interactions API

POST /interactions now validates each interaction in a request individually and returns a structured result describing exactly what happened to every record. Previously a single invalid interaction rejected the entire request; now the valid interactions in a batch are published even when others are rejected.

New status code semantics:

  • 200 — every interaction was valid and published.
  • 207 — some interactions were published and some were rejected.
  • 400 — every interaction was rejected (returned as the same batch body), or the request itself was invalid (empty, or more than 100 interactions), which returns a plain error instead.
  • Precise, per-record error fields. Each rejected error has a field and an errorMessage.

If anything other than a 200 is treated as a hard failure by the client they will need to update to handle the new 207 code.

Previous Response Examples

All interactions valid - 200 Echoed the request back.

{
  "interactions": [
    {
      "interactionId": "3f2a9c7e-1b4d-4c8a-9e2f-6a7b8c9d0e1f",
      "person": [{ "id": "80001", "type": "ngpvan" }],
      "channel": { "type": "phone_number", "value": "+12025550176" },
      "stateCode": "OH",
      "attemptDateTime": "2026-07-27T08:00:00Z",
      "method": "email",
      "committee": [{ "id": "harbor-city-dems", "type": "committee_id" }],
      "canvasser": [{ "id": "4400", "type": "canvasser_id" }],
      "vendorSource": "CanvassKit",
      "outcome": "successful_contact",
      "jsonMetadata": "{\"seq\": 0, \"campaign\": \"fall-gotv\"}"
    }
  ]
}

Any interactions invalid - 400 Bad Request Any invalid interaction returned a 400 with a flat validation-error array.

[
  {
        "propertyName": "Interactions[3].StateCode",
        "errorMessage": "State code must be a valid two-letter state code.",
        "attemptedValue": "ca",
        "customState": null,
        "severity": "Error",
        "errorCode": "RegularExpressionValidator",
        "formattedMessagePlaceholderValues": {
            "RegularExpression": "^[A-Z]{2}$",
            "PropertyName": "State Code",
            "PropertyValue": "ca",
            "PropertyPath": "Interactions[3].StateCode",
            "CollectionIndex": 3
        }
    },
    {
        "propertyName": "Interactions[7].Method",
        "errorMessage": "'Method' must not be empty.",
        "attemptedValue": "unknown",
        "customState": null,
        "severity": "Error",
        "errorCode": "NotEmptyValidator",
        "formattedMessagePlaceholderValues": {
            "PropertyName": "Method",
            "PropertyValue": "unknown",
            "PropertyPath": "Interactions[7].Method",
            "CollectionIndex": 7
        }
    },
    {
        "propertyName": "Interactions[7].Method",
        "errorMessage": "'unknown' is not a valid interaction method.",
        "attemptedValue": "unknown",
        "customState": null,
        "severity": "Error",
        "errorCode": "NotEqualValidator",
        "formattedMessagePlaceholderValues": {
            "ComparisonValue": "unknown",
            "ComparisonProperty": "",
            "PropertyName": "Method",
            "PropertyValue": "unknown",
            "PropertyPath": "Interactions[7].Method",
            "CollectionIndex": 7
        }
    }
]

New Response Examples

All interactions valid — 200

{
  "correlationId": "11534196688560234",
  "totalInteractions": 1,
  "acceptedInteractions": {
    "count": 1,
    "data": [
      { 
        "interactionId": "3f2a9c7e-1b4d-4c8a-9e2f-6a7b8c9d0e1f", 		
        "index": 0 	
			}
    ]
  },
  "rejectedInteractions": { "count": 0, "data": [] }
}

Some interactions rejected — 207

Two interactions were submitted; the one at index 1 was rejected (invalid state code). The interaction at index 0 was still published.

{
  "correlationId": "11534196688560235",
  "totalInteractions": 2,
  "acceptedInteractions": {
    "count": 1,
    "data": [
      { "interactionId": "3f2a9c7e-1b4d-4c8a-9e2f-6a7b8c9d0e1f", "index": 0 }
    ]
  },
  "rejectedInteractions": {
    "count": 1,
    "data": [
      {
        "interactionId": "8d1e5f42-9a3b-4c17-8e6d-2f0a1b3c4d5e",
        "index": 1,
        "errors": [
          {
            "field": "stateCode",
            "errorMessage": "State code must be a valid two-letter state code."
          }
        ]
      }
    ]
  }
}

All interactions rejected — 400

The same batch body is returned with an empty acceptedInteractions.data. Note the nested, indexed field path on the outcome-detail error.

{
  "correlationId": "11534196688560236",
  "totalInteractions": 1,
  "acceptedInteractions": { "count": 0, "data": [] },
  "rejectedInteractions": {
    "count": 1,
    "data": [
      {
        "interactionId": "8d1e5f42-9a3b-4c17-8e6d-2f0a1b3c4d5
e",
        "index": 0,
        "errors": [
          {
            "field": "outcomesDetailed[0].value.questionId",
            "errorMessage": "'QuestionId' is required to be a
 positive integer when VAN is included as a destination."
          }
        ]
      }
    ]
  }
}

See the API Examples and the API Reference for full details.