REST system¶
The REST system represents a REST service (i.e. a web server) serving HTTP requests from a base url using the REST vocabulary of GET, PUT, POST, PATCH, OPTIONS and HEAD.
It is used by the REST sink.
It supports the HTTP
and HTTPS
protocols. It provides session handling, connection pooling and authentication
services to sources and sinks which need to communicate with a HTTP server.
The REST system is an extension of the URL system, so all configuration properties of the URL system apply. We’ll only cover the REST system specific properties in this section.
Prototype¶
{
"_id": "id-of-system",
"name": "Name of system",
"type": "system:rest",
"url_pattern": "http://host:port/path/%s",
"verify_ssl": false,
"custom_ca_pem_chain": "-----BEGIN CERTIFICATE-----\nMIIGYTCCB[...]\n-----END CERTIFICATE-----\n",
"username": null,
"password": null,
"authentication": "basic",
"jwt_token": null,
"connect_timeout": 60,
"read_timeout": 1800,
"rate_limiting_retries": 3,
"rate_limiting_delay": 60,
"json_content_types": ["application/jsonish"]
"headers": {
"MY_HEADER": "some-value",
"MY_OTHER_HEADER": "$ENV(key-for-other-value)",
"MY_SECRET_HEADER": "$SECRET(secret-key)"
},
"operations": {
"get-operation": {
"url" : "/a/service/that/supports/get/{{ _id }}",
"method": "GET",
"next_page_link": {{ body.pagination.next }},
"next_page_termination_strategy": ["next-page-link-empty", "same-next-page-request", "same-response"]
"id_expression": {{ id }},
"updated_expression": {{ updated }},
"payload_property": "result",
"response_property": "response",
"since_property_name": "updated",
"since_property_location": "query|header|manual"
},
"delete-operation": {
"url" : "/a/service/that/supports/delete/{{ _id }}",
"method": "DELETE",
"rate_limiting_retries": 3,
"rate_limiting_delay": 60
},
"put-operation": {
"url" : "/some/service/that/supports/put",
"method": "PUT",
"headers": {
"Content-type": "application/json"
},
"payload-type": "json"
},
"post-operation": {
"url" : "/some/service/that/supports/post",
"method": "POST",
"payload-type": "form"
},
"patch-operation": {
"url" : "/some/service/that/supports/patch",
"headers": {
"Content-type": "application/xml"
},
"method": "PATCH"
}
}
}
Properties¶
Property |
Type |
Description |
Default |
Req |
---|---|---|---|---|
|
The REST system extends the URL system, so any property from the URL system can be applied. |
|||
|
Dict<String,String> |
A optional set of header values to set as defaults in the requests made using the REST system. Both keys and values must
evaluate to strings. Note that any “Authorization” header provided in this object is automatically overwritten
when using the |
||
|
Object |
An object containing the registered operations allowed for the REST service. See the Operation properties section for details.
Note that you can also define an |
||
|
Integer |
If set and the REST service returns a HTTP 429 error code, the request will be retried the number of times
indicated. The time between retries can be adjusted by setting |
||
|
Integer |
If |
1 |
|
|
Array of strings |
This property can be used to supply the REST source and transform a list of response “content-type” strings that represent valid JSON content that should be parsed as such. The content-type “application/json” is always included. |
[“application/json”] |
|
|
Object |
See the custom authentication section |
Operation properties¶
You can register as many named “operations” as you like with the system (even using the same type of “method”). A operation configuration looks like:
Property |
Type |
Description |
Default |
Req |
---|---|---|---|---|
|
String |
A URL or URL part. The property supports the |
Yes |
|
|
String |
A enumeration of |
Yes |
|
|
Dict<String,String> |
An optional object that contain key-value mappings for the HTTP request header. Entries in this dictionary
will override any default |
||
|
Objects |
An optional object that contain key-value mappings for any HTTP parameters. The property supports the
|
||
|
Enum<String> |
A enumeration of “text”, “json”, “json-transit”, “form” and “multipart-form”, that denotes how to treat the
|
|
|
|
Object |
The properties mapping used as default values for the emitted entities. Note that if both are present the properties in the emitted entity takes precedence. Also note that this property can be defined in the REST source, REST transform and REST sink configuration as well. The configuration in pipes will take precedence if both are defined. |
||
|
Object, string or array |
The value to use as payload for the operation. Note that this property can be defined in the REST source,
REST transform and REST sink configuration as well, but only the
|
||
|
String |
The name of the property to put the response in when emitting entities. Note that this property can be defined in the REST source and REST transform configuration as well. The configuration in pipes will take precedence if both are defined. |
||
|
String |
The name of the property to put the response headers in when emitting entities. Note that this property can be defined in the REST source and REST transform configuration as well. The configuration in pipes will take precedence if both are defined. |
||
|
String |
The name of the property to put the response status code in when emitting entities. Note that this property can be defined in the REST source and REST transform configuration as well. The configuration in pipes will take precedence if both are defined. |
||
|
String |
The JSON response sub-property to use as the source of the emitted entities. Note that this property can be defined in the REST source and REST transform configuration as well. It will be ignored by the REST sink. The configuration in pipes will take precedence if both are defined. |
||
|
String |
The property supports the |
||
|
Enum<String> or array of Enum<String> |
Enumeration of |
|
|
|
Integer |
An integer indicating the number of entities contained in a paged response. This property must be set if the
|
||
|
String |
An expression in the form of single values or value ranges of HTTP status codes that will be allowed to be passed
through by the transform. The values are either comma separated integer values or a range of values with a hyphen separator
(i.e. a single Note This operation property can only be used with the REST transform. Warning If you allow other status codes than the default, make sure that these are dealt with downstream. |
|
|
|
String |
An expression in the form of single values or value ranges of HTTP status codes that will be ignored by the
transform. HTTP responses with status codes matching this list will result in the response being omitted from
the result. The values are either comma separated integer values or a range of values with a hyphen separator
(i.e. a single Note This operation property can only be used with the REST transform. Warning Any response with status codes listed here will be discarded with no traces to be found, making it next to impossible to audit the pipe. |
||
|
String |
The property supports the |
||
|
String |
The property supports the |
||
|
String |
The property supports the |
||
|
String |
The name of the property to relay continuation information. This is only relevant if |
|
|
|
String |
A enumeration of |
|
|
|
Integer |
If set and the REST service returns a HTTP 429 error code, the request will be retried the number of times
indicated. The time between retries can be adjusted by setting |
||
|
Integer |
If |
1 |
Custom authentication (experimental)¶
The custom_auth
section can be used for authentication towards systems that use some form of token authentication.
This requires more configuration than oauth2
authentication, but it is a lot more flexible. The general idea
is to create an operation in the operations section that points to an endpoint used for
fetching an access token, and the custom_auth
section describes how to parse the response from that operation so
that the token can be used in other operations.
Up to two operations can be performed during the authentication flow. The get_token_operation
is required, while
an optional get_refresh_token_operation
is also available. If the latter is used, it will be executed before the
get_token_operation
. The responses from both these operations are available in the Jinja environment in the token
object and properties can be accessed using dotted notation. For example, if the response is expected to contain a token under the
access_token
property, it can be used with {{ token.access_token }}
.
These standardized expiry-related properties are also added to the token
object whenever a new token is fetched:
Property |
Description |
---|---|
|
A Unix epoch in seconds for when the access token expires, e.g. |
|
A human-readable version of |
|
Like |
|
Like |
There are several examples here of using custom_auth
towards various systems. Some of
these examples make use of our Jinja filters for more advanced configuration.
The custom_auth
section uses the following sub-properties:
Property |
Type |
Description |
Default |
Req |
---|---|---|---|---|
|
String |
This must point to an operation in the |
Yes |
|
|
String |
If the token is expected to contain a timestamp for when the access token expires,
this should be set to the name of the property that contains that timestamp. This needs to be a Jinja expression,
e.g. |
Yes, if |
|
|
String |
If the token is expected to contain the amount of time until the token expires, this
should be set to the name of the property that contains that value. The evaluated value must be in seconds.
If the provided value is not in seconds, you can use Jinja expressions to do the conversion (e.g. if a token
contains a property |
Yes, if |
|
|
Integer |
This option sets how many seconds in advance to refresh a token before it expires. |
30 |
No |
|
Object |
If the provider uses tokens that work similar to refresh tokens, initial refresh tokens are sometimes required.
These should be provided as an object where the key is the name of the refresh token property, and the value
is the refresh token that you’ve been given. These are immediately made available in the |
No |
|
|
String |
This must point to an operation in the |
No |
|
|
String |
Like |
Yes, if |
|
|
String |
Like |
Yes, if |
|
|
String |
Deprecated. Set to the name of the property inside the expected response from |
No |
|
|
String |
Deprecated. Some providers can grant tokens that behave similar to OAuth2 refresh tokens. If that is the case,
this can be set to the name of the property that can contain a new refresh token, e.g. Warning For on-premise single subscriptions, new refresh tokens are only kept in memory. This means that pipes will start
failing after a reboot if a new refresh token was previously fetched. The |
No |
Notes on Jinja templates¶
(experimental)
The payload
, headers
and params
operation configuration properties are objects where the properties can be
templated using Jinja (both the key and the values) with various dynamically bound parameters. This makes it possible to construct
these request parameters dynamically. You can also control whether a particular property is included in the final
object by injecting a special marker constant "sesam:markskip"
using conditional logic. If this marker is present in the
rendered template, then the property is omitted from its parent object. Note that you can use this marker in both keys and values.
An example:
{
"_id": "our-rest-service",
"name": "Our REST service",
"url_pattern": "http://our.domain.com/api/%s",
"type": "system:rest",
"operations": {
"post-operation": {
"url" : "{{ properties.url }}/some-path",
"method": "POST",
"payload-type": "json",
"payload": {
"key": "value",
"conditional_key": "{% if entity.conditional_property is defined %}{{ entity.conditional_property }}{% else %}sesam:markskip{% endif %}",
"some_other_key{% if entity.other_conditional_property is not defined %}sesam:markskip{% endif %}": "other_value"
}
}
..
(experimental)
You can use the special marker "sesam:markjson"
to construct JSON objects, lists or single values from a templated string in the payload
, headers
and params
operation configuration properties. It can be used to cast Jinja templated strings to JSON data types or construct objects or lists with conditional Jinja logic.
An example:
{
"_id": "our-rest-service",
"name": "Our REST service",
"url_pattern": "http://our.domain.com/api/%s",
"type": "system:rest",
"operations": {
"post-operation": {
"url" : "{{ properties.url }}/some-path",
"method": "POST",
"payload-type": "json",
"payload": {
"key": "{{ properties.integer_property }}system:markjson",
"some_other_key": "[{{ properties.arg1, \"literal value \"}}]sesam:markjson"
}
}
..
Result payload object:
..
"payload": {
"key": 10,
"some_other_key": [1.2, \"literal value \"]
}
..
When using the custom_auth
feature, the response properties from the authentication request(s) are available
under the token
object. Use this to construct the payload/headers/parameters for the operations, e.g. for a
system that uses the bearer token format:
{
"_id": "webcrm",
"type": "system:rest",
"url_pattern": "https://api.webcrm.com/%s",
"headers": {
"Authorization": "Bearer {{ token.access_token }}"
},
..
See the example configurations for more examples on systems that use custom_auth
.
Example configuration¶
{
"_id": "our-rest-service",
"name": "Our REST service",
"url_pattern": "http://our.domain.com/api/%s",
"type": "system:rest",
"operations": {
"get-men": {
"url" : "men/{{ properties.collection_name }}/men/{{ since }}",
"method": "GET"
},
"get-man": {
"url" : "men/{{ properties.collection_name }}/{{ _id }}",
"method": "GET"
},
"get-woman": {
"url" : "women/{{ properties.collection_name }}/{{ _id }}",
"method": "GET"
},
"delete-man": {
"url" : "men/{{ properties.collection_name }}/{{ _id }}",
"method": "DELETE"
},
"delete-woman": {
"url" : "women/{{ properties.collection_name }}/{{ _id }}",
"method": "DELETE"
},
"update-man": {
"url" : "men/{{ properties.collection_name }}/",
"method": "POST",
"headers": {
"Content-type": "application/xml"
}
},
"update-woman": {
"url" : "women/{{ properties.collection_name }}/",
"method": "POST",
"headers": {
"Content-type": "application/json"
},
"payload-type": "json"
},
"form-operation": {
"url" : "men/{{ properties.collection_name }}/submit-form",
"method": "POST",
"payload-type": "form"
},
"multipart-form-operation": {
"url" : "men/{{ properties.collection_name }}/submit-multipart-form",
"method": "POST",
"payload-type": "multipart-form"
}
}
}
Example configurations with custom authentication¶
These are examples on how to use the custom_auth
functionality towards various systems.
Tripletex¶
Tripletex uses basic authentication with “0” as the username. The authorization header needs to be constructed using Base64 encoding and bytes conversion. Additionally, an expiration date must be set when requesting a new access token:
{
"_id": "tripletex",
"type": "system:rest",
"url_pattern": "https://api.tripletex.io/v2/%s",
"verify_ssl": true,
"headers": {
"Authorization": "Basic {{ ( ('0:' + token.token) | bytes | base64_encode) }}"
},
"custom_auth": {
"get_token_operation": "fetch-session-token",
"expires_at_expression": "{{ token.expirationDate }}"
},
"operations": {
"contact-list": {
"id_expression": "{{ id }}",
"method": "GET",
"next_page_termination_strategy": "empty-result",
"payload_property": "values",
"url": "contact?fields=*,changes"
},
"fetch-session-token": {
"method": "PUT",
"params": {
"consumerToken": "$SECRET(consumer_token)",
"employeeToken": "$SECRET(employee_token)",
"expirationDate": "{{ (now() + timedelta(hours=48)).strftime('%Y-%m-%d') }}"
},
"payload_property": "value",
"url": "token/session/:create"
}
}
}
WebCRM¶
Uses a bearer token, with the expiration time in seconds provided in the property ExpiresIn
:
{
"_id": "webcrm",
"type": "system:rest",
"url_pattern": "https://api.webcrm.com/%s",
"verify_ssl": true,
"headers": {
"Authorization": "Bearer {{ token.AccessToken }}"
},
"custom_auth": {
"get_token_operation": "fetch-access-token",
"expires_in_expression": "{{ token.ExpiresIn }}"
},
"operations": {
"fetch-access-token": {
"url": "Auth/ApiLogin",
"method": "POST",
"payload": {
"authCode": "$SECRET(application_token)"
}
},
"get-operation": {
"headers": {
"Authorization": "Bearer {{ token.AccessToken }}"
},
"id_expression": "{{ PersonId }}",
"url": "Persons?Page=1&Size=10",
"method": "GET"
}
}
}
Membercare¶
The authorization header is different from the typical bearer token format:
{
"_id": "membercare",
"type": "system:rest",
"url_pattern": "https://customer-test.membercare.no/api/%s",
"verify_ssl": true,
"custom_auth": {
"expires_at_expression": "{{ token.expiration }}",
"get_token_operation": "fetch-access-token"
},
"headers": {
"token": "{{ token.value }}"
},
"operations": {
"companies-list": {
"id_expression": "{{ debtorAccountNumber }}",
"method": "GET",
"payload_property": "result",
"since_property_name": "changedAfter",
"updated_expression": "{{ lastChange }}",
"url": "v1/companies"
},
"fetch-access-token": {
"headers": {
"accept": "text/plain"
},
"method": "GET",
"params": {
"clientApiKey": "$SECRET(api_key)",
"personToImpersonate": "person-to-impersonate"
},
"url": "v1/token"
},
"persons-list": {
"id_expression": "{{ debtorAccountNumber }}",
"method": "GET",
"next_page_link": "{{ body.nextPageUrl }}",
"payload_property": "result",
"url": "v1/persons"
}
}
}
Hubspot¶
Hubspot uses OAuth2, meaning that using our OAuth2 machinery (see the URL system) works perfectly
fine. This just demonstrates that you can also use custom_auth
in a way that works towards OAuth2 systems using the
ìnitial_refresh_token
property, and then using {{ token.refresh_token }}
inside the payload:
{
"_id": "hubspot",
"type": "system:rest",
"url_pattern": "https://api.hubapi.com/%s",
"verify_ssl": true,
"headers": {
"Authorization": "Bearer {{ token.access_token }}",
"Content-Type": "application/json"
},
"custom_auth": {
"get_token_operation": "fetch-access-token",
"expires_in_expression": "{{ token.expires_in }}",
"initial_refresh_token": {
"refresh_token": "$SECRET(refresh_token)"
}
},
"operations": {
"fetch-access-token": {
"url": "oauth/v1/token",
"method": "POST",
"headers": {
"content-type": "application/x-www-form-urlencoded"
},
"payload": {
"grant_type": "refresh_token",
"refresh_token": "{{ token.refresh_token }}",
"client_id": "$SECRET(client_id)",
"client_secret": "$SECRET(client_secret)"
}
},
"company-list": {
"id_expression": "{{ id }}",
"method": "GET",
"next_page_link": "{{ body.paging.next.link.split('?')[0]~'?after='~body.paging.next.after }}",
"params": {
"associations": "contacts,companies,deals,tickets,products,quotes",
"properties": "hs_merged_object_ids,jobtitle,firstname,lastname,email,date_of_birth,mobilephone,work_email,hs_analytics_first_timestamp,hs_analytics_last_timestamp,hs_analytics_last_visit_timestamp,hs_analytics_num_page_views,hs_analytics_num_visits,engagements_last_meeting_booked,engagements_last_meeting_booked_campaign,engagements_last_meeting_booked_source,hs_last_booked_meeting_date,hs_last_logged_call_date,hs_last_open_task_date,hs_last_sales_activity_timestamp,hs_lastmodifieddate,notes_last_contacted,notes_last_updated,notes_next_activity_date,num_contacted_notes,about_us,address,address2,annualrevenue,city,closedate,country,createdate,days_to_close,description,domain,engagements_last_meeting_booked_medium,first_contact_createdate,founded_year,hs_analytics_last_touch_converting_campaign,hs_analytics_source,hs_analytics_source_data_1,hs_analytics_source_data_2,hs_createdate,hs_num_child_companies,hs_object_id,hs_parent_company_id,industry,is_public,lifecyclestage,name,num_associated_contacts,numberofemployees,phone,state,timezone,total_money_raised,total_revenue,type,web_technologies,website,zip,hs_analytics_first_touch_converting_campaign,hs_analytics_first_visit_timestamp,first_deal_created_date,hs_num_open_deals,hs_total_deal_value,num_associated_deals,recent_deal_amount,recent_deal_close_date,hs_lead_status,hubspot_owner_assigneddate,hubspot_owner_id,hubspot_team_id,facebook_company_page,facebookfans,googleplus_page,linkedin_company_page,linkedinbio,twitterbio,twitterfollowers,twitterhandle,hs_ideal_customer_profile,hs_is_target_account,hs_num_blockers,hs_num_contacts_with_buying_roles,hs_num_decision_makers"
},
"payload_property": "results",
"url": "crm/v3/objects/company"
}
}
}
ArcGIS-UN¶
The token provider for ArcGIS-UN sets the expiry as a Unix epoch under the expires
property. The REST system expects
the expiry to be given as a date, so we need to do some filtering first using the datetime and datetime_format
Jinja filters (these filters mimic the datetime and
datetime-format DTL functions, respectively). Note that the value of expires
is in milliseconds, and the filters expect the value to be in nanoseconds.
{
"_id": "arcgis-un",
"type": "system:rest",
"custom_auth": {
"expires_at_expression": "{{ (token.expires * 1000 * 1000) | datetime | datetime_format }}",
"get_token_operation": "get-token"
},
"headers": {
"Authorization": "Bearer {{ token.token }}"
},
"operations": {
"get-sources-layers": {
"method": "GET",
"url": "server/rest/services/BUN/bun_edit/FeatureServer/queryDataElements?layers=&f=json"
},
"get-token": {
"method": "POST",
"payload": {
"client": "referer",
"expiration": 60,
"f": "json",
"password": "$SECRET(arcgis_un_rest_password)",
"referer": "https://sesam.io",
"username": "$SECRET(arcgis_un_rest_username)"
},
"url": "portal/sharing/rest/generateToken"
}
},
"url_pattern": "https://test.domain.io/%s",
"verify_ssl": true
}
Solteq¶
Requires two REST operations for fetching the access token. The first operation (get_refresh_token_operation
)
is used to fetch a WCToken
and WCTrustedToken
. These are both required in the second operation
(get_token_operation
) when fetching the access token. This behaviour is similar to refresh tokens in OAuth2 systems.
Note that the lifetimes of the WCToken
and WCTrustedToken
are opaque, so we need to make some assumptions on
when we need to refresh them. We set refresh_token_expires_in_expression
to {{ 600 }}
so that we attempt to
refresh it every 10 minutes. The expiry of the access token itself is encoded in the access token (which is a JWT),
but we do not support decoding this at the moment. It has been set to 1 hour in this example configuration, but
you should experiment with different values to find out what works best.
{
"_id": "solteq",
"type": "system:rest",
"custom_auth": {
"expires_in_expression": "{{ 3600 }}",
"get_refresh_token_operation": "fetch-WC-tokens",
"get_token_operation": "fetch-access-token",
"refresh_token_expires_in_expression": "{{ 600 }}",
},
"headers": {
"Authorization": "Bearer {{ token.access_token }}"
},
"operations": {
"fetch-WC-tokens": {
"method": "POST",
"payload": {
"logonId": "$SECRET(logon_id)",
"logonPassword": "$SECRET(logon_password)"
},
"payload-type": "json",
"url": "loginidentity"
},
"fetch-access-token": {
"headers": {
"WCToken": "{{ token.WCToken }}",
"WCTrustedToken": "{{ token.WCTrustedToken }}"
},
"method": "POST",
"url": "loginidentity/jwtToken"
},
"get-addresses": {
"method": "GET",
"url": "addresses"
}
},
"url_pattern": "http://solteq.site/%s"
}