REST Type for POST API calls with body

#1

Hi,

I am trying to replicate such call using the REST type :

curl -X POST \
  'https://a-random-url?apikey=<apiKey>' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -H 'cache-control: no-cache' \
  -d 'grant_type=client_credentials&client_id=<clientId>&client_secret=<clientSecret>&access_token_manager_id=PAccessToken'

Based on the available documentation (https://environment_url/api/1/TENANT/TAG/documentation/topic/REST) and a few community posts, I have created the following type:

@rest(url="https://a-random-url")
type PiWriteBackGetTokenAPI mixes REST {

    @rest(method="POST")
    getAuthenticationToken: function (
        @rest(header = true, headerName = 'Content-Type') contentType : !string,
        @rest(parameterName= 'apikey') apiKey: !string,
        body: !PiWriteBackGetTokenAPIBody
    ) : PiWriteBackGetTokenAPIResponse
}

And the body looking as follow:

type PiWriteBackGetTokenAPIBody {

    /**
    * Method used for granting access
    *
    * By default it is set as 'client_credentials'
    */
    grant_type: !string default 'client_credentials'

    /**
    * Client ID, read from secure location
    */
    client_id: !string

    /**
    * Client Secret, read from secure location
    */
    client_secret: !string

    /**
    * Access Token Manager
    *
    * By default it is set as 'PAccessToken'
    */
    access_token_manager_id: !string default 'PAccessToken'

However, despite working credentials (tested in Postman), the call fails :

PiWriteBackGetTokenAPI.getAuthenticationToken('application/x-www-form-urlencoded', 'apiKey', PiWriteBackGetTokenAPIBody.make('client_credentials', 'client_id', 'client_secret', 'PAccessToken'))

In Splunk, it is hard to see what is actually passed during the API calls. Copy below one of the message :

2019-04-08T14:03:39.464Z level=INFO thread="Hannibal-18" logger=c3.action a_id="8221.3968028" a_rid="8221.3968028" 
rootReq,t_tenant=compressors,t_module=-type-PiWriteBackGetTokenAPI,t_tag=dev7,t_type=PiWriteBackGetTokenAPI,t_action=getAuthenticationToken,a_st=Failed,a_t=0.214599,a_cpu=0.214494,a_self=0.214005,a_self_cpu=0.214005,a_wt=0.000105,a_io=0,a_sql=0,a_kv=0,a_objs=0,a_self_io=0,a_self_sql=0,a_self_kv=0,a_cache_key="",a_user="laura.foulquier@shell.com",a_func="1127.1069"
Failed action input:
a_param_contentType="application/x-www-form-urlencoded"
a_param_apiKey="apiKey"
a_param_body={
  "type" : "PiWriteBackGetTokenAPIBody",
  "grant_type" : "client_credentials",
  "client_id" : "clientId",
  "client_secret" : "clientSecret",
  "access_token_manager_id" : "PAccessToken"
}
a_param_contentType="application/x-www-form-urlencoded"
a_param_apiKey="apiKey"
a_param_body={
  "type" : "PiWriteBackGetTokenAPIBody",
  "grant_type" : "client_credentials",
  "client_id" : "clientId",
  "client_secret" : "clientSecret",
  "access_token_manager_id" : "PAccessToken"
}

Would there be any available documentation about how to best build a POST API call with a body ?
Also, is there a way to see in Splunk the full API call ? (Final URL, headers, etc … something similar of the cURL posted at the start of this message ?)

1 Like
#2

I might be completely wrong here, but it seems you are setting the content-type as "application/x-www-form-urlencoded" but you are passing in JSON…
Could you test with "application/json" ?

#3

No luck …
I have also tried with passing all fields (client_secret, client_id, grant_type, access_token_manager_id) as parameterName, unsuccessfully

#4

What error do you get?
I see an extra apostrophe in the first @rest annotation (on the type)

#5

Apologies, it was a typo (which did not exist in the original code).
I am getting this error :

{
    "error_description": "Invalid client or client credentials",
    "error": "invalid_client"
}

Unfortunately this is not very representative, as you would get this error if the credentials were wrong but also if the request has not been send correctly (a missing field, an extra one, etc …).
Hence why I am trying to see what request has actually been send.

#6

I think your problem actually comes from the function declaration in the type.
For a normal (non-REST) function, foo: function(a: string, b: int, c: double), the way it is exposed by C3 is:

  • a REST endpoint on which you can do a POST:
    https://env.c3-e.com/api/1/tenant/tag/MyType?action=foo
  • with the body of the request as {a:..., b,..., c:...}

Following the same philosophy, I believe your function should have the following signature:

@rest(url="https://a-random-url")
type PiWriteBackGetTokenAPI mixes REST {

    @rest(method="POST")
    getAuthenticationToken: function (
        @rest(header = true, headerName = 'Content-Type')
        contentType : !string,
        @rest(parameterName= 'apikey') 
        apiKey: !string,
        grant_type: !string,
        client_id: !string,
        client_secret: !string,
        access_token_manager_id: !string
    ) : PiWriteBackGetTokenAPIResponse
}
#7

From the documentation on the @rest annotation (see c3ShowType(Ann.Rest) ), I see a couple of things you could try:

  1. use the @rest(contentType='FORM_URLENCODED') instead of defining a specific field
  2. change the annotation for apiKey to be @rest(queryParameter=true), since you want apiKey to be a parameter in the query string
  3. the other parameters should stay as @lpoirier mentioned, i.e. directly as parameters to the function

You can also find examples of POST requests in some types that mix REST, for example see c3ShowFunc(GitHub,'createCommit')

#8

Using Splunk, I am now first trying to get the URL right:
The closest I have managed is :
httpclient_target=https://a-random-url/?apikey=<apiKey>
But I am trying to get : https://a-random-url?apikey=<apiKey> (without the extra β€˜/’)

I have tried with the following code to remove the extra β€˜/’, without success :

@rest(method="POST")
    getAuthenticationToken: function (
        @rest(header=true, headerName='Content-Type')
        contentType:                !string,
        @rest(queryParameter=true, partOfURI=true, uriPrefix="", parameterName= 'apikey') 
        apiKey:                     !string,
        grant_type:                 !string,
        client_id:                  !string,
        client_secret:              !string,
        access_token_manager_id:    !string
    ) : PiWriteBackGetTokenAPIResponse

(It comes back with the following URL : https://a-random-url/<apiKey> )

Any idea on how to remove the extra β€˜/’ that looks added by default ?

#9

I’m hoping your real URL behind a-random-url is not simply the root address but is something like https://root/a/random/url.
In that case, you could define @rest(url="https://root") at the type level, and then add @rest(uri="a/random/url", method="POST") before the function.

1 Like
#10

Thanks, this is the URL sorted.
Still getting the same error but at least the final URL is now looking good in Splunk

#11

Thank you for your help, the following code seems to have made the trick:

@rest(url="https://root")
type PiWriteBackGetTokenAPI mixes REST {

    @rest(uri="a/random/url", method="POST", contentType="application/x-www-form-urlencoded")
    getAuthenticationToken: function (
        @rest(queryParameter=true, parameterName= 'apikey') 
        apiKey:                     !string,
        grant_type:                 !string,
        client_id:                  !string,
        client_secret:              !string,
        access_token_manager_id:    !string
    ) : PiWriteBackGetTokenAPIResponse
}
3 Likes
#12

Cool! Little hint, you might try adding a ! to your return type i.e.

@rest(url="https://root")
type PiWriteBackGetTokenAPI mixes REST {

    @rest(uri="a/random/url", method="POST", contentType="application/x-www-form-urlencoded")
    getAuthenticationToken: function (
        @rest(queryParameter=true, parameterName= 'apikey') 
        apiKey:                     !string,
        grant_type:                 !string,
        client_id:                  !string,
        client_secret:              !string,
        access_token_manager_id:    !string
    ) : !PiWriteBackGetTokenAPIResponse
}

That way, C3 will throw an error if the REST api ever fails to return that object instead of you needing to check in your logic yourself :+1:

2 Likes