Xink API reference

Version: 1.1
Date:      Nov 7, 2023

Overview

Xink provides REST API for external clients.
All traffic between a client application and Xink service (Xink Developer API) is transported by encrypted HTTP requests and responses.

Authentication/Authorization

We use OAuth2 to control access to Xink developer API; external clients should use ClientCredentialsGrant in particular. The client should request the access token from the Xink authorization server, then use the token in all subsequent requests to the API.

In Xink admin you create an API user. Navigate to ‘preferences-> admins’ and add an API user. Give the user proper permissions and a good password. The ‘user’ referred to in this documentation is an API user.

Authorization server URI
Authorization endpoint
/OAuth/Authorize
Token endpoint
/OAuth/Token

Here is a C# sample: 

var authorizationServerUri = new Uri("https://auth.xink.io");
var authorizationServer = new AuthorizationServerDescription
{
    AuthorizationEndpoint = new Uri(authorizationServerUri, "/OAuth/Authorize"),
    TokenEndpoint = new Uri(authorizationServerUri, "/OAuth/Token")
};
_webServerClient = new WebServerClient(authorizationServer, "some-id-here@SRV", "secretpassword");

var state = _webServerClient.GetClientAccessToken(new[] { "employeeread", "employeesignaturesread" });
_accessToken = state.AccessToken;
C


Please note scope names “employeeread” and “employeesignaturesread”, these are required to get employee list and signatures. Each operation may require some scope.

By default token is valid for 2 hours since authorization request. On later stages, this value might be changed to 10-15 minutes.

API Service Host Name

The service host name is DC-specific. For now it is possible to use hardcoded host name. It would be e.xink.io for US, e-eu.xink.io for EU, e-uk.xink.io for UK, e-ca.xink.io for CA and e-au.xink.io for AU.

It is also possible to send GET request to https://auth.xink.io/Discovery end point, providing Authorization header with Bearer auth token obtained in the section above. The end point returns JSON, including APIRoot field that links the API user to proper datacenter.

Summary of All Available API Controllers

Account

[HttpGet, Route("api/1.0/account/info")]

Employee

[HttpGet, Route("api/1.0/employee/list/{offset:int=0}/{limit:int=500}")]
[HttpGet, Route("api/1.0/employee/{email}/fields")]
[HttpGet, Route("api/1.0/employee/{email}/signatures")]
[HttpPost, Route("api/1.0/employee/upload")]
[HttpPost, Route("api/1.0/employee/delete")]

Image

[HttpPost, Route("api/1.0/image")]
[HttpGet, Route("api/1.0/image/list/{offset:int=0}/{limit:int=500}")]

Signature

[HttpGet, Route("api/1.0/signature/list/{offset:int=0}/{limit:int=500}")]
[HttpPost, Route("api/1.0/signature/")]
[HttpPost, Route("api/1.0/signature/{id}/update")]
[HttpDelete, Route("api/1.0/signature/{id}")] [HttpPost, Route("api/1.0/signature/{id}/delete")]

Signature Import

[HttpPost, Route("api/1.0/signatureimport/{id}/image")]
[HttpPost, Route("api/1.0/signatureimport/{id}")]

Rerouting

[HttpGet, Route("api/1.0/rerouting/status")]
[HttpPost, Route("api/1.0/rerouting/clearmap")]
[HttpPost, Route("api/1.0/rerouting/replacemap")]

Managed Accounts

[HttpGet, Route("/api/1.0/managedAccounts/list")]

Examples of API Controller’s Requests-Results

Scopes

  • employeeread
  • employeefieldsread
  • employeectrl (WRITE)
  • employeesignaturesread
  • signaturectrl (WRITE)
Account

[HttpGet, Route("api/1.0/account/info")]
Scope: employeeread

{
"CompanyName": "Some Company",
"LicensedEdition": "Xink Campaign",
"LicensedCount": 6,
"CreatedDate": "2013-02-07T07:37:52.78",
"RenewalDate": null,
"InvoiceEmail": "some@some.com",
"TotalEmployees": 530,
"Signatures": 32,
"DefaultSignature": null,
"ReplySignature": null,
"RunningCampaigns": [
{
"Name": "disclaimer2",
"ContentText": null,
"ContentHTML": "disclaimer2",
"Active": true,
"TrackClicks": true,
"Weight": 2,
"Notes": null,
"EffectiveActive": true,
"SignaturesCount": 0,
"ClickCount": 0,
"ViewCount": 8
},
. . .
{
"Name": "Subscribe",
"ContentText": null,
"ContentHTML": "<a href=\"https://subscribe.some.com/subscribe-feeds.aspx?utm_medium=email&amp;utm_campaign=NewsSubscribers&amp;utm_source=email%20signature\"><img alt=\"\" border=\"0\" height=\"151\" src=\"/Images/Get/G3/p83.gif\" width=\"300\" /></a>",
"Active": true,
"TrackClicks": true,
"Weight": 1,
"Notes": null,
"EffectiveActive": true,
"SignaturesCount": 1,
"ClickCount": 11,
"ViewCount": 15
}
],
"UpcomingCampaigns": [],
"TopCampaigns": [],
"DisabledCampaigns": [
{
"Name": "__custom_tracking_link_01",
"ContentText": null,
"ContentHTML": null,
"Active": false,
"TrackClicks": true,
"Weight": 1,
"Notes": null,
"EffectiveActive": false,
"SignaturesCount": 1,
"ClickCount": 0,
"ViewCount": 53
},
. . .
{
"Name": "new campaign",
"ContentText": null,
"ContentHTML": null,
"Active": false,
"TrackClicks": true,
"Weight": 1,
"Notes": null,
"EffectiveActive": false,
"SignaturesCount": 0,
"ClickCount": 0,
"ViewCount": 0
}
],
"DateFrom": "2023-07-09T00:00:00+00:00"
}
Employee

[HttpGet, Route("api/1.0/employee/list/{offset:int=0}/{limit:int=500}")]

Example: https://app.xink.io/api/1.0/employee/list/0/3
Response contains Offset and Limit parameters values used in the request (or defaults if no parameters were supplied); and actual array of Items, each represents one employee.

Scope: employeeread

{
"Limit": 500,
"Offset": 0,
"SearchQuery": null,
"Items": [
{
"Email": "john@doe.com",
"FirstName": "John",
"LastName": "Doe",
"DisplayName": "Jonny D"
},
{
"Email": "maria@doe.com",
"FirstName": "Maria",
"LastName": "Doe",
"DisplayName": "Maria D"
},
. . .

 

[HttpGet, Route("api/1.0/employee/{email}/fields")]
Scope: employeefieldsread

{
"Fields": [
{
"Code": "Country",
"DisplayName": "Country",
"FieldValue": null,
"IsImage": false
},
{
"Code": "DisplayName",
"DisplayName": "Display Name",
"FieldValue": "somename",
"IsImage": false
},
. . .
{
"Code": "LastName",
"DisplayName": "Last Name",
"FieldValue": "somename",
"IsImage": false
},
{
"Code": "Email",
"DisplayName": "Employee's Email",
"FieldValue": "test@some.com",
"IsImage": false
},
. . .

 

[HttpGet, Route("api/1.0/employee/{email}/signatures")]
Scope: employeesignaturesread

{
"Clean": false,
"SetDefault": true,
"SetReply": false,
"Signatures": [
{
"Name": "",
"HTML": null,
"Text": null,
"DefaultNew": false,
"DefaultReply": true
},
{
"Name": "(KM) 3-4-Sterne Offerte",
"HTML": "<p style=\"font-family:arial,helvetica,sans-serif; font-size:9pt !important;\"><img alt=\"\" border=\"0\" height=:9pt…!”
"Text": null,
"DefaultNew": false,
"DefaultReply": false
},
. . .
  • Clean flag represents value of “Delete all existing email signatures in Outlook” preference.
  • Signature supposed to be downloaded directly into “Signatures” folder of Outlook user profile. Each signature must be saved into a file named exactly as field Name value with .htm extension added.

[HttpPost, Route("api/1.0/employee/upload")]
Scope: employeectrl

Request

{
"Items": [
{
"Email":"user1@testapi.com",
"FirstName":"John",
"LastName":"Doe",
"DisplayName":"John Doe",
"Fields": [
{
"Code":"City",
"FieldValue":"New York"
},
{
"Code":"Country",
"FieldValue":"USA"
}
]
},
{
"Email":"user2@testapi.com",
"FirstName":"Peter",
"LastName":"Johnson",
"DisplayName":"",
"Fields": [
{
"Code":"Department",
"FieldValue":"Sales"
},
{
"Code":"Facebook",
"FieldValue":""
}
]
}
]
}

Response

       

{
"Items": [
"user1@testapi.com: OK",
"user2@testapi.com: OK"
]
}

 

[[HttpPost, Route("api/1.0/employee/delete")]
Scope: employeectrl

Request

{
"Items": [
{
"Email":"user1@testapi.com"
},
{
"Email":"user2@testapi.com"
}
]
}

Response

 
None
Image

Signature may contain images, like company logo, employee avatar or whatever else. Xink stores those images and performs several action to prepare Outlook application to use them properly.
Field Name provides exact file name for corresponding image. Images supposed to be saved in the same directory as signatures. It is important; signature HTML refers images just by file name.
Xink server is responsible for making image names unique. If in any case image names are repeated, it is guaranteed that these are references to the same image.
There is no dedicated authorization scope for image access. It is enough to provide a token generated for valid client account.

[HttpPost, Route("api/1.0/image")]
Content-Type: multipart/form-data
Key: file
Value: someImg.png

Response

       
{
"AccountUniqueName": "BT4",
"ImageUniqueName": "i",
"RelativeImageUrl": "https://i.xink.io/Images/Get/BT4/i.png",
"Name": "img21.png",
"DateCreated": "2023-10-10T09:18:18.21",
"MD5": "3A16278045564DAEIUB5A52B23C8B",
"SHA1": "A327FCE8098NIJN1E39D2683616DE71CBA4"
}

 

[HttpGet, Route("api/1.0/image/list/{offset:int=0}/{limit:int=500}")]
Scope: signaturectrl

       
{
"Limit": 500,
"Offset": 0,
"Items": [
{
"AccountUniqueName": "B4",
"ImageUniqueName": "i6",
"RelativeImageUrl": "https://i.xink.io/Images/Get/B4/i6.png",
"Name": "image001.png",
"DateCreated": "2014-06-18T11:59:38.843",
"MD5": "255F0174C3EOKN230283222FEE",
"SHA1": "89E5C303A9D90283NEED815735FF2C5239871"
},
{
"AccountUniqueName": "B3",
"ImageUniqueName": "i1",
"RelativeImageUrl": "https://i.xink.io/Images/Get/B3/i1.png",
"Name": "image002.png",
"DateCreated": "2014-06-18T12:19:18.76",
"MD5": "51BCBB0923NJEB223A2BA122E0",
"SHA1": "FC4907WOEINR0293712AF8BF8D9C727D15"
},
. . .
Signature

[HttpGet, Route("api/1.0/signature/list/{offset:int=0}/{limit:int=500}")]
Scope: signaturectrl

       
{
"Limit": 500,
"Offset": 0,
"Items": [
{
"SignatureID": "372236",
"Name": "eMailSignature",
"ContentText": "((FirstName)) ((LastName))\r\n((JobTitle))\r\n\r\n((Company))\r\n((Department))\r\nEmail: ((Email))\r\nPhone: ((DirectPhone))\r\n((WebSite))",
"ContentHTML": "
\r\n((FirstName)) ((LastName))
\r\n((JobTitle))
\r\n\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\t\r\n\t\t\r\n\t\r\n
 
\r\n",
"DefaultNew": false,
"DefaultReply": false,
"Available": false,
"Public": false,
"Notes": null
},
. . .

 

[HttpPost, Route("api/1.0/signature/")]
Name (string, required): The name of the signature.
ContentText (string, required): The text content of the signature.
ContentHTML (string, required): The HTML content of the signature.
Notes (string, optional): Optional notes or additional information about the signature.

Request

       
{
"Name": "MySignature",
"ContentText": "Hello, this is my signature text.",
"ContentHTML": "

Hello, this is my HTML signature.

",
"Notes": "Optional notes about the signature"

}

Response

       
{
"SignatureID": "12345"
}

 

[HttpPost, Route("api/1.0/signature/{id}/update")]
Scope: signaturectrl

Request

       
{
"Name": "MySignature-UPDATED",
"ContentText": "Hello, this is my signature text UPDATED.",
"ContentHTML": "

Hello, this is my HTML signature UPDATED.

",
"Notes": "Optional notes about the signature UPDATED"

}

Response

       
{
"SignatureID": "12345"
}

[HttpDelete, Route("api/1.0/signature/{id}")] [HttpPost, Route("api/1.0/signature/{id}/delete")]

Signature Import

[HttpPost, Route("api/1.0/signatureimport/{id}/image")]
[HttpPost, Route("api/1.0/signatureimport/{id}")]

Rerouting
 

[HttpGet, Route("api/1.0/rerouting/status")]



{
"StatusCode": 0,
"Domains": [],
"IsSMTPCacheReady": true
}

[HttpPost, Route("api/1.0/rerouting/clearmap")]
[HttpPost, Route("api/1.0/rerouting/replacemap")]

Managed Accounts

[HttpGet, Route("/api/1.0/managedAccounts/list")]

{
"Items": [
{
"id": 1,
"name": "Account1",
"email": "admin1@xink.io",
"employees": 1,
"licenses": 0,
"using": 0,
"note": "4",
"folders": [],
"createdDate": "2019-02-22T08:45:29.36",
"endDate": null
},
{
"id": 2,
"name": "Account2",
"email": "admin2@xink.io",
"employees": 1,
"licenses": 0,
"using": 0,
"note": "note",
"folders": [
"folder1"
],
"createdDate": "2019-04-24T15:22:24.157",
"endDate": "2019-05-08T00:00:00"
},
{
"id": 3,
"name": "Account3",
"email": "admin3@xink.io",
"employees": 1,
"licenses": 0,
"using": 0,
"note": "2",
"folders": [
"folder1",
"folder2"
],
"createdDate": "2019-06-18T06:58:56.243",
"endDate": null
}
]
}

Did you find it helpful? Yes No

Send feedback
Sorry we couldn't be helpful. Help us improve this article with your feedback.