xChange Platform API Documentation

Swagger Spec

Getting Started

Before you begin you will need to have a client ID and secret provisioned for you by the xChange Platform team. You'll need those two values to use the service. The configuration of this client during provisioning controls the routing of your data.

Samples below are provided in C#. The IdentityModel package is used here for simplicity. It is recommended but not required.

Obtain an Access Token

The Epicor Identity Provider is used for authentication and authorization. Once you authenticate against the token-granting endpoint, you will be granted an access token that will be your authorization. The OAuth client credential flow is used (sometimes referred to as 2-legged OAuth).

Note: The token endpoint domain is different from the API endpoints.

async Task<string> RequestTokenAsync()
{
    var authHttpClient = new HttpClient();

    var tokenRequest = new ClientCredentialsTokenRequest
    {
        Address = "https://login.epicor.com/connect/token",
        ClientId = "My Client ID",
        ClientSecret = "My Secret"
    };

    var tokenResponse = await authHttpClient.RequestClientCredentialsTokenAsync(tokenRequest);

    if (tokenResponse.IsError)
    {
        throw new Exception(tokenResponse.Error);
    }

    var expiration = DateTimeOffset.UtcNow.AddSeconds(tokenResponse.ExpiresIn);
    return tokenResponse.AccessToken;
}

You can save the token for re-use. Either keep track of its expiration time (default is 1 hour) or respond to a 401 response by requesting a new access token and retrying.

Inbound Transaction Endpoint

Post Transaction Data

Send data for integration using your access token for authorization.

A successful response indicates your data has been queued for processing by HQ.

Note: The size of the request is limited to 10MB.

class InboundTransaction
{
    public string Content { get; set; }
}

async Task SendTransactionAsync(string accessToken, string content)
{
    var transactionHttpClient = new HttpClient();

    transactionHttpClient.SetBearerToken(accessToken);

    var inboundTransactionRequest = new InboundTransaction
    {
        Content = content
    };

    var jsonBody = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(inboundTransaction),
        System.Text.Encoding.UTF8, "application/json");

    var response = await transactionHttpClient.PostAsync("https://xchange.epicor.com/xchange/v1/InboundTransaction", jsonBody);

    if (!response.IsSuccessStatusCode)
        throw new Exception($"Send responded with error code {response.StatusCode}");
}

Complete Example

using System;
using System.Net.Http;
using System.Threading.Tasks;
using IdentityModel.Client;

namespace OneEDISource.HQxChange.Tester
{
    public class InboundTransactionExample
    {
        const string TokenEndpoint = "https://login.epicor.com/connect/token";
        const string ClientId = "My Client ID";
        const string ClientSecret = "My Secret";
        const string InboundTransactionEndpoint = "https://xchange.epicor.com/xchange/v1/InboundTransaction";

        public async Task SendTransactionAsync()
        {
            string accessToken = await RequestTokenAsync();
            string payloadData = System.IO.File.ReadAllText("C:\\EDI.txt");
            await SendTransactionAsync(accessToken, payloadData);
        }

        async Task<string> RequestTokenAsync()
        {
            var authHttpClient = new HttpClient();

            var tokenRequest = new ClientCredentialsTokenRequest
            {
                Address = TokenEndpoint,
                ClientId = ClientId,
                ClientSecret = ClientSecret
            };

            var tokenResponse = await authHttpClient.RequestClientCredentialsTokenAsync(tokenRequest);

            if (tokenResponse.IsError)
            {
                throw new Exception(tokenResponse.Error);
            }

            var expiration = DateTimeOffset.UtcNow.AddSeconds(tokenResponse.ExpiresIn);
            return tokenResponse.AccessToken;
        }

        class InboundTransaction
        {
            public string Content { get; set; }
        }

        async Task SendTransactionAsync(string token, string content)
        {
            var transactionHttpClient = new HttpClient();
            
            transactionHttpClient.SetBearerToken(token);

            var inboundTransactionRequest = new InboundTransaction
            {
                Content = content
            };

            var postBody = CreateJsonContent(inboundTransactionRequest);

            var response = await transactionHttpClient.PostAsync(InboundTransactionEndpoint, postBody);

            if (!response.IsSuccessStatusCode)
                throw new Exception($"Send responded with error code {response.StatusCode}");                
        }

        StringContent CreateJsonContent(InboundTransaction inboundTransaction)
        {
            string json = Newtonsoft.Json.JsonConvert.SerializeObject(inboundTransaction);
            return new StringContent(json, System.Text.Encoding.UTF8, "application/json");
        }
    }
}

Message Feed Endpoints

The message feed endpoints provide a way for API consumers to accept delivery of messages sent by HQ.

Check for Messages

A GET request with no path parameter returns a list of available messages waiting to be downloaded and can be thought of as your inbox. You will need a message ID from this list to interact with a specific message in the next steps.

An empty array will be returned if you have no messages to receive.

Messages not accepted by their expiration time will be purged form the system.

class Message
{
    public Guid Id { get; set; }
    public DateTimeOffset CreatedTimeUtc { get; set; }
}

async Task<Message[]> CheckForMessagesAsync()
{
    HttpResponseMessage messagesResponse = await httpClient.GetAsync("https://xchange.epicor.com/xchange/v1/MessageFeed");
    string messagesContent = await messagesResponse.Content?.ReadAsStringAsync();
    Message[] messages = JsonConvert.DeserializeObject<Message[]>(messagesContent);
    return messages;
}

Download Message

Download the contents of a specific message by using its ID retrieved in the previous step.

async Task<string> DownloadMessageContent(Guid id)
{
    var messageResponse = await httpClient.GetAsync($"{MessageFeedEndpoint}/{id}");
    return await messageResponse.Content?.ReadAsStringAsync();
}

Delete Message

Remove a specific message from your inbox, indicating that you have downloaded and processed it. It will no longer appear in the list of available messages or be available for download. This cannot be undone.

async Task DeleteMessage(Guid id)
{
    await httpClient.DeleteAsync($"{MessageFeedEndpoint}/{id}");
}

Complete Example

public class Message
{
    public Guid Id { get; set; }
    public DateTimeOffset CreatedTimeUtc { get; set; }
}

public class MessageFeedExample
{
    const string MessageFeedEndpoint = "https://xchange.epicor.com/xchange/v1/MessageFeed";

    HttpClient httpClient = new HttpClient();

    public async Task ProcessAllMessages(string accessToken)
    {
        httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
        Message[] messages = await CheckForMessagesAsync();
        foreach(Message message in messages)
        {
            string data = await DownloadMessageContentAsync(message.Id);
            Process(data);
            await DeleteMessageAsync(message.Id);
        }
    }

    async Task<Message[]> CheckForMessagesAsync()
    {
        HttpResponseMessage messagesResponse = await httpClient.GetAsync(MessageFeedEndpoint);
        string messageContent = await messagesResponse.Content?.ReadAsStringAsync();
        Message[] messages = JsonConvert.DeserializeObject<Message[]>(messageContent);
        return messages;
    }

    async Task<string> DownloadMessageContentAsync(Guid id)
    {
        var messageResponse = await httpClient.GetAsync($"{MessageFeedEndpoint}/{id}");
        return await messageResponse.Content?.ReadAsStringAsync();
    }

    void Process(string data)
    {
        //TODO: handle message data
    }

    async Task DeleteMessageAsync(Guid id)
    {
        await httpClient.DeleteAsync($"{MessageFeedEndpoint}/{id}");
    }

TLS Protocol Support

TLS protocol and cipher suite selection is Mozilla's recommended configuration for intermediate compatibility. Source

You may encounter problems using legacy clients such as Windows XP or older versions of Safari. A "handshake failure" message could mean your client does not support one of these cipher suites.

Supported TLS Protocols and Cipher Suites

TLS 1.2
ECDHE-RSA-AES128-GCM-SHA256
ECDHE-RSA-AES256-GCM-SHA384
ECDHE-RSA-CHACHA20-POLY1305
DHE-RSA-AES128-GCM-SHA256
DHE-RSA-AES256-GCM-SHA384
TLS 1.3
TLS_AES_128_GCM_SHA256
TLS_AES_256_GCM_SHA384
TLS_CHACHA20_POLY1305_SHA256