Schema validation failure in Action Configuration

I'm looking to add a custom Action to our Salesforce integration, and I'm running into the following error in the configuration tab:

JSON failed schema validation for the following reasons: the following keywords are unknown and will be ignored: [ContactId]

I have two questions, based on the following scenario:

I'm looking to pass the Salesforce.ContactId returned in the standard Salesforce integration, based on the caller's ANI. In my web service I'm calling it 'patientId'. My first questions is, in the Action Configuration, is there a standard variable that I can use for that Id, or do I call it essentially whatever I want, provided that in Architect I have a variable of the same name that I pass to the Action?

My second question relates specifically to that ^ schema error: If my Action Configuration request is as follows (below), is that where the validation's [ContactId] is coming from? I have not configured the response yet, so ContactId isn't referenced there, nor is it referenced in the Contract.

{
"requestUrlTemplate": "https://test.salesforce.com/services/{InventoryService}", "requestType": "POST", "headers": {}, "requestTemplate": "{\"contactId\": \"{input.CONTACT_ID}"}"
}

Ah - it is in the Input Contract, which I understood to be where I described the input parameters (1 in this case) for the service. What should this look like instead?
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Patient Inventory request",
"description": "Schema to retrieve Delivery Schedule details via Contact Id",
"type": "object",
"required": [
"ContactId"
],
"properties": {},
"ContactId": {
"type": "string",
"description": "Salesforce Contact Id"
},
"additionalProperties": true
}

In the Architect call flow, the Salesforce Contact.Id returned from getContactByPhoneNumber returns Contact.Id, among other things/values. That is what I need to pass to my web service in the custom Action that I'm trying to create. How do I define the Contract for that Action so that I can reference it?

The full error is:

Failed Validation of contract.input.inputSchema as a simple properties schema. Must be an Object with properties and no sub-objects. JSON failed schema validation of contract.input.inputSchema for the following reasons: the following keywords are unknown and will be ignored: [ContactId]

The above should be

So the ContactId should be inside the properties value.

This should solve your issue.

And indeed it did -- thanks, Trey!

While I have your attention (and before I look it up, 'cause it's late), a couple of the values I'm looking to return are dates, which aren't valid JSON primitives apparently - If I want to be able to read these as text-to-speech to the caller, is returning them as strings the way to go?

Yes, that is correct. Have the custom action return a String, then in Architect you can use the ToAudioDateTime(Task.CustomActionOutputString) for a generic date time readback.

There is also a few different ways to get a new DateTime with in Architect. You could try the following example:
ToAudioDateTime(ToDateTime(Task.CustomActionOutputString), Format.DateTime.timeOnly) given a string like "101217".

Correct, within an Architect flow the conversion of a string to a DateTime can be accomplished using various functions. Like Trey showed, there's:

ToDateTime(<date_time_string>)

where the dateTime string needs to have the following format:

The String must be in the following format: YYYY-MM-DDThh:mm:ss[Z|(+|-)hh:mm] where YYYY = 4 digit year, MM = 2 digit month, DD = 2 digit day, hh = 2 digit hour, mm = 2 digit minute and ss = 2 digit second. T is a separator indicating where the time portion starts. [Z|(+|-)hh:mm] represents the offset of the date time from Coordinated Universal Time where Z indicates Coordinated Universal Time (UTC) itself. If "[+|-]hh:mm" is used instead of Z that specifies an offset from UTC.

If the string doesn't have that format, Architect also has a MakeDateTime function that can be used to create a DateTime value. There is an implementation of MakeDateTime that takes a year, month and day as integer values. It looks like this:

MakeDateTime(<year_int>, <month_int>, <day_int>)

Assuming the returned string has a format of "MMDDYYYY" in a Task.ReturnedDateStr string variable in the Architect flow, you can convert that to a DateTime with the following expression:

MakeDateTime(ToInt(Substring(Task.ReturnedDateStr, 4, 4)), ToInt(Substring(Task.ReturnedDateStr, 0, 2)), ToInt(Substring(Task.ReturnedDateStr, 2, 2)))

In the event you're not guaranteed to get a string that's 8 characters in length, you could add some basic checks like this:

If(IsSet(Task.ReturnedDateStr) and Length(Task.ReturnedDateStr)==8, MakeDateTime(ToInt(Substring(Task.ReturnedDateStr, 4, 4)), ToInt(Substring(Task.ReturnedDateStr, 0, 2)), ToInt(Substring(Task.ReturnedDateStr, 2, 2))), ToDateTime(NOT_SET))

to help guard against the MakeDateTime or ToInt function calls causing the flow to go in to error. The ToDateTime(NOT_SET) at the end of the above expression is the DateTime value to return if the string isn't 8 characters in length.

And then, like Trey also showed above, you can pass that DateTime value returned from the expression off to either ToAudioDate or ToAudioDateTime functions which at runtime will read back the DateTime value on the call in a format appropriate to the current language in which the call flow is running.

Within Architect you can see more information on the above functions and their various implementations / overloads in Expression Help.

Jim

Thanks Jim -- that's a really helpful document!

Now that I've updated both Input & Output contracts for the action, I've moved on to my first tests of the action, using a Salesforce ContactId from our sandbox instance. The operations are passing until I get to #7. Resolve Request Body Template. Here's the error:

{
"status": 500,
"code": "invalid.substitution",
"message": "Substitution values invalid in action config. Reference ${input.CONTACT_ID} evaluated to null when attempting to render at BodyTemplate[line 1, column 16]",
"messageParams": {},
"contextId": "db98df92-ed9a-4c25-9c33-7872b098ed65",
"details": [],
"errors": []
}

It looks like this is referring back to the Action.Configuration request (below). Should I replace "input.CONTACT_ID" between the brackets with "ContactId", the way it's defined in the Input Contract?

{
"requestUrlTemplate": "https://test.salesforce.com/services/Soap/c/41.0/0DF2C000000009c/InventoryService.getInventorySchema",
"requestType": "POST",
"headers": {},
"requestTemplate": "{"contactId": "${input.CONTACT_ID}"}"
}

If you don't need to manipulate the data returned from Salesforce, you can try the following in the Response Configuration:

{
  "translationMap": {},
  "successTemplate": "${rawResult}"
}

and the requestTemplate field should be "requestTemplate": "${input.rawRequest}"

Earlier in this thread the name from your input schema looked like 'ContactId', while the error references 'CONTACT_ID'. Try switching your requestTemplate to use ${input.ContactId}.

Thanks Jason -- right on the money! Past that hurdle, moving on - it doesn't look like the web service URL is correct:
{
"status": 403,
"code": "not.authorized",
"message": "You are not authorized to perform the requested action.",
"messageParams": {},
"contextId": "e98498a6-6088-4482-b65f-89b584f51452",
"details": [],
"errors": [
{
"status": 403,
"code": "NOT_AUTHORIZED",
"message": "REST call for action execute failed. Message:Unable to get IP for provided target host [e98498a6-6088-4482-b65f-89b584f51452] [e98498a6-6088-4482-b65f-89b584f51452]",
"messageParams": {},
"details": [],
"errors": []
}
]
}

The issue is that you have "https//test.salesforce.com" at the beginning of your requestUrlTemplate. As part of the authentication process Salesforce returns the base URL to use for API access. We automatically prepend that URL to the URL template. See if removing "https//test.salesforce.com" from the beginning of your requestUrlTemplate fixes this issue for you and I will enter a ticket for us to handle a situation like this better.

For context, the URL that salesforce returns us is typically of the form https://aa11.salesforce.com where the 'aa' could be any letter combination and the '11' could be any set of numbers.

That makes sense - the sandbox instances start with 'cs##', production starts w/ 'na##' (at least for (n)orth (a)merica).

Sadly (it would seem), now the notifications are getting really cryptic, but perhaps I can start leveraging the Salesforce community for this one:

{
"status": 500,
"code": "internal.server.error",
"message": "The server encountered an unexpected condition which prevented it from fulfilling the request.",
"messageParams": {},
"contextId": "6911ff8c-e780-447b-8bd1-002254f1b290",
"details": [],
"errors": [
{
"status": 500,
"code": "INTERNAL_SERVER_ERROR",
"message": "REST call for action execute failed. Message:Request to backend service failed.\n<?xml version=\"1.0\" encoding=\"UTF-8\"?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">soapenv:Bodysoapenv:Faultsoapenv:Clientcontent-type of the request should be text/xml</soapenv:Fault></soapenv:Body></soapenv:Envelope>\n [6911ff8c-e780-447b-8bd1-002254f1b290] [6911ff8c-e780-447b-8bd1-002254f1b290]",
"messageParams": {},
"details": [],
"errors": []
}
]
}

For the Headers in the Input Configuration, try adding

"Content-Type": "text/xml"

Like this?

{
"requestUrlTemplate": "/services/Soap/c/41.0/0DF2C000000009c/inventory_schema_retrieval/getInventorySchema",
"requestType": "POST",
"headers": {"Content-Type": "text/xml"},
"requestTemplate": "{"contactId": "${input.ContactId}"}"
}

Looks like we've moved the chains somewhat:

{
"status": 500,
"code": "internal.server.error",
"message": "The server encountered an unexpected condition which prevented it from fulfilling the request.",
"messageParams": {},
"contextId": "bcd46666-63a5-4995-a8d7-bb197eb9171a",
"details": [],
"errors": [
{
"status": 500,
"code": "INTERNAL_SERVER_ERROR",
"message": "REST call for action execute failed. Message:Request to backend service failed.\n<?xml version=\"1.0\" encoding=\"UTF-8\"?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">soapenv:Bodysoapenv:Faultsoapenv:ClientSOAPAction HTTP header missing</soapenv:Fault></soapenv:Body></soapenv:Envelope>\n [bcd46666-63a5-4995-a8d7-bb197eb9171a] [bcd46666-63a5-4995-a8d7-bb197eb9171a]",
"messageParams": {},
"details": [],
"errors": []
}
]
}

.... Okay, from the Salesforce community at StackExchange I need to add a header to my request. What's the syntax for having multiple headers? This isn't it, apparently

{
  "requestUrlTemplate": "/services/Soap/c/41.0/0DF2C000000009c/inventory_schema_retrieval/getInventorySchema",
  "requestType": "POST",
  "headers": {"Content-Type": "text/xml"},
             {"SOAPAction": "anything"},
  "requestTemplate": "{\"contactId\": \"${input.ContactId}\"}"
}

Ah - this looks better

"headers": {"Content-Type": "text/xml",
"SOAPAction": "anything"},

(apparently the content of 'SOAPAction' doesn't matter, so long as it's not empty)

[BTW, thank you for your patience as I work through this; high-profile project, wouldn't you know...]

Still getting errors; cross-posting in StackExchange - currently "Content is not allowed in prolog." Accessing Apex webservice from 3rd party application

Hi Duncan. I was looking over this post and was wondering if you could tell us what you're trying to do in Salesforce with the data action? I noticed you were using the SOAP API for your interactions. That will make the action setup much more difficult because that's an XML based API.

The Salesforce REST API is far better suited for use with data actions. Depending on what you're trying to do I'd recommend trying with the REST API, which supports native json.

The service is (trying to) use REST; seeing as I haven't done much at all w/ webservices, I'm not finding it easy to dig through the documentation to figure out the proper URL.

Having said that, I now see a reference to '/services/apexrest/'... which is likely closer to what I need - I still haven't come across an example of how my class/method names should appear thereafter. I'll try right after and see how that goes.

Updated header to Content-Type application/json, removed SOAPAction...

Storming ahead, moved down to Operation 11. Validate output against schema

{
"status": 400,
"code": "invalid.schema",
"message": "JSON failed schema validation for the following reasons: Missing: InventorySchema",
"messageParams": {},
"contextId": "e0ce2570-f0d5-4f45-8de1-7cff48d9f7c8",
"details": [],
"errors": []
}

This ^ makes me think that I've skipped a level in my response definition, because those individual fields are values within a larger/outer wrapper(?), so I should define that in addition. I wonder what that's going to look like.

I'm hoping to return an object that looks like this:

    webservice String patientID {get;set;}
    webservice String shipDate {get;set;}
    webservice String deliveryDate {get;set;}
    webservice Boolean isPureflow {get;set;}
    webservice Boolean isPureflowwWarmer {get;set;}
    webservice Boolean hasWastelines {get;set;}
                    
    global InventorySchema() {
        patientID = '';
        shipDate = null;
        deliveryDate = null;
        isPureflow = false;
        isPureflowwWarmer = false;
        hasWastelines = false;
    }

(though I hope all those values are populated and not null or false, clearly)

Okay, fine - I'll answer the question - I'm using the ContactId to retrieve custom details from the Contact & related records, which I plan to leverage in an IVR to prompt the caller for additional information, which I then plan to use to submit records back into Salesforce (and hopefully be able to end the call at that point).