API Reference

Using the API

As previously mentioned, the API is implemented as a collection of REST endpoints. As such, it provides great flexibility as to the types of clients that can use the API and developers are free to choose from any number of available REST client libraries depending on their platform and preferences.

Authentication

Credentials Auth Provider (default)

Authenticaticate with username/password credentials by POST-ing to the /auth service

curl -H "Content-Type: application/json" \
-X POST -d '{"username":"xyz","password":"xyz"}' \
http://localhost:3000/auth

You can similarly logout by invoking a GET or POST request to /auth/logout

Security

In case of a public domain scenario, the API will be consumed by terminals installed in different locations, outside the secure environment of a private network. In order to allow calls originating only from specific terminals and no one else unauthorized, the API supports two extra security layers: Digital signature and IP control.

Digital Signature

The digital signature uses a private/public key encryption scheme and applies only to /auth service. The private key is used by the caller to sign the authentication GET request URI, or the POST request payload. The signature produced must be added to the request header named as X-Signature. In addition the public key must be registered to the API configuration file encoded as base64:

<WebService>
    <Config>
        <AccessControl>
            <AccessRule Type="PublicKey">
-----BEGIN CERTIFICATE-----
MIIDkTCCAnmgAwIBAgIJANJ4QeZx/BJXMA0GCSqGSIb3DQEBCwUAMF8xCzAJBgNV
BAYTAmdyMQ8wDQYDVQQIDAZhdGhlbnMxDzANBgNVBAcMBmF0aGVuczEWMBQGA1UE
CgwNc2luZ3VsYXJsb2dpYzEWMBQGA1UECwwNc2luZ3VsYXJsb2dpYzAeFw0xNzA4
MTQxNTQ1NDFaFw0xODA4MDUxNTQ1NDFaMF8xCzAJBgNVBAYTAmdyMQ8wDQYDVQQI
rp4GfcENZ2X0fwi0Gl8CFrYgYPp7spGMTJbAOsVZTb80bAC4iqcYloRQBPOJCUPt
YzEWMBQGA1UECwwNc2luZ3VsYXJsb2dpYzCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAK1Mt+tmAy93p+9M5hUT8DJjhEaP/dTuxoxhytnMx7C2LgsI9vQV
dbUpAUFlXo3qqIkvQvQW37exxfCIJfiBQUocbTZX8udzSY4ORPFq3hqVgrwL2dMk
fMh28S+Xd0ZvbujvbpE2h4Cuu0AQpKmIVmwLO/nZcQx3DHkCRJN+JQPXw8oZy+0o
/RvbZ+zO0BGml4VFgMEjyrpSGzYCRZFKytbd3yqDxotfkLixIRneW++vhtq6gKFy
sqdpUl3UNW+VJr5JqnIHVYV1xCwNb6cGckVKKwBZhvXYdZTwxuQvHtNO2K7bJWEE
hcGK1ZBcwFb+MpEzsVvH2nIPzqT5lNkA2QsCAwEAAaNQME4wHQYDVR0OBBYEFN6z
DAZhdGhlbnMxDzANBgNVBAcMBmF0aGVuczEWMBQGA1UECgwNc2luZ3VsYXJsb2dp
QsO0LuGVnqahzCJ51iXQTWkPMB8GA1UdIwQYMBaAFN6zQsO0LuGVnqahzCJ51iXQ
TWkPMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAIe46sCX1yO2d//n
w8KL6ViZGdTZxdZrsly4NsGI3e5L0Zxxe2e7p2bKF9wpEGX3wWtCp2jVi6ayt+7P
BYup6tg7PAM6R+2XUJpQDgr8ol4FzZlLaJNg6jNlKEjDl6TrKY/EHwk5ol8jZDS+
yrpF4Vtnx8e/d9dytl4GDTeyTtaDw7sQJtVj/9bu+gKDDDgHXypFOY1Gk8ZlKPw6
PJ2m7U9ssWxxJT9SMFWDmf38u35wvRbBWtbf8PdPqk7TVZ4unf0uAELV04Sdd0rb
s5qUS4c=
-----END CERTIFICATE-----       
            </AccessRule>
        </AccessControl>
    </Config>
</WebService>

IP Control

The IP control mechanism is also defined at the API configuration file:

<WebService>
    <Config>
        <AccessControl>
            <AccessRule Type="ClientIP" Access="Allow">
            85.20.1.100,
            102.76.*
            </AccessRule>
        </AccessControl>
    </Config>
</WebService>

An IP rule can be of Allow or Deny access type. Ιt will be applied to the containing list of comma separated IPs and IP masks.

Error Handling

The API will return conventional HTTP response codes to indicate the success or failure of an API request. In general, codes in the 2xx range indicate success, codes in the 4xx range indicate an error that failed given the information provided (e.g., authentication error), and codes in the 5xx range indicate a server error.

Code HTTP status summary
200 OK Everything worked as expected.
400 Bad Request The request was unacceptable, often due to missing a required parameter.
401 Unauthorized No valid API key provided.
403 Forbidden Access denied by security policy.
404 Not Found The requested resource doesn’t exist.
50x Server Errors

Failure responses will always return an error code, a friendly error message, and detailed descriptions of all errors that occurred. There are cases where more than one error messages will be returned.

{
  "ErrorCode": "Error code",
  "Message": "A human friendly error message",
  "Errors": [
    {
      "ErrorCode": "ErrorCode",
      "FieldName": "FieldName",
      "Message": "For multiple detailed validation errors"
    }
  ]
}

Enumerations

An Enumeration is a complete, ordered list of named values. It is a well-known construct of many popular languages. Within the Galaxy API, Enumerations are used in a variety of ways, mainly to group, identify and specify properties of various data entities. In simple terms, they are very frequently used as values for entity fields.

Every Business Domain will provide us with a list of all exposed Enumerations at /api/{DomainName}/enums/ :

[
  {
    "Name": "Active",
    "Uri": "/api/glx/enums/active"
  },
  {
    "Name": "IsOpen",
    "Uri": "/api/glx/enums/isopen"
  },
  {
    "Name": "IsSystem",
    "Uri": "/api/glx/enums/issystem"
  },
  {
    "Name": "ABMeunCalcType",
    "Uri": "/api/glx/enums/abmeuncalctype"
  },
  {
    "Name": "ABMeunRelType",
    "Uri": "/api/glx/enums/abmeunreltype"
  }
]

The response is a list of all available enumerations, along with a link to a URI that will return each Enumeration’s contents.

For example, this is the response when invoking the Active enumeration’s URI /api/glx/enums/active :

[
  {
    "Code": "Inactive",
    "Value": "0",
    "DisplayValue": "Ανενεργός"
  },
  {
    "Code": "Active",
    "Value": "1",
    "DisplayValue": "Ενεργός"
  }
]

The response is the same for all Enumerations. It is an array of objects, including a Code, actual numeric Value, and DisplayValue for each member of the Enumeration.

Note: Enumerations are a code artifact, and are thus static, in the sense that they cannot be altered without re-deploying a Business Domain. Thus, the API does not provide any additional endpoints to administer enumerations’ content.

Lookups

Lookups are semantically similar to Enumerations, with one important distinction. Not all Lookups look the same, i.e. have the same fields.

Actually, Lookups originate from a database, and are administrable from the Galaxy back-end. They can have almost any schema and oftentimes they originate from Data Entities themselves. Typically, their ID is used in fields of other Data Entities to indicate relationships or grouping.

As expected, every Business Domain will list it’s Lookups under /api/{DomainName}/lookups . The list returned is identical to the list of enumerations we san earlier, containing a Name, and a URI to follow:

[
  {
    "Name": "AcquaintSource",
    "Uri": "/api/glx/lookups/acquaintsource"
  },
  {
    "Name": "AddressType",
    "Uri": "/api/glx/lookups/addresstype"
  },
  {
    "Name": "ApprovalGroup",
    "Uri": "/api/glx/lookups/approvalgroup"
  },
  {
    "Name": "ApprovTempl",
    "Uri": "/api/glx/lookups/approvtempl"
  }
]

To illustrate the fact that Lookups don’t share a common schema, let’s follow the first two links in our list, and examine the data returned.

The AcquaintSource lookup lives at /api/glx/lookups/acquaintsource :

[
  {
    "ID": "9b296f6e-d01e-4253-8ed5-1497ef1c6987",
    "RevNum": 2,
    "Code": "001",
    "Description": "Advertisment"
  },
  {
    "ID": "c1765851-30fe-4774-91c2-25683da26b52",
    "RevNum": 1,
    "Code": "004",
    "Description": "Other"
  },
  {
    "ID": "b577e7fa-e355-482e-b90d-4e7bdb7e8c1f",
    "RevNum": 2,
    "Code": "002",
    "Description": "Other customer"
  },
  {
    "ID": "6aecdf40-ed22-4cf3-a623-733c66da9b00",
    "RevNum": 1,
    "Code": "003",
    "Description": "Campaign"
  }
]

And the AddressType lookup /api/glx/lookups/addresstype :

[
  {
    "ID": "8d037b23-8479-4572-ab7d-594786e61a3f",
    "RevNum": 4,
    "Reserved": 0,
    "Code": "003",
    "Description": "Παράδοσης"
  },
  {
    "ID": "7b280bc5-3d34-487b-afe5-6d6d5f73f9cb",
    "RevNum": 3,
    "Reserved": 0,
    "Code": "002",
    "Description": "Αποθήκης"
  },
  {
    "ID": "70e85b80-c209-44ba-aac7-a78867980973",
    "RevNum": 2,
    "Reserved": 0,
    "Code": "001",
    "Description": "Τιμόλογησης"
  }
] 

We can see that although very similar, the two responses are actually different.

Lookups and Depth

As mentioned earlier, the list returned when we retrieve a Lookup can originate from a variety of sources, and does not have a flat schema requirement.

Let’s examine the response for the /api/glx/lookups/bank lookup:

[
  {
    "ID": "20185ae4-d9da-4f48-ac06-1555cf700ccb",
    "RevNum": 13,
    "Code": "ΚΥΠ",
    "Description": "Κύπρου ",
    "BIC": null,
    "URL": null,
    "Branches": [
      {
        "ID": "8e8927c2-950d-49ce-9cd4-e50195f3eb47",
        "BankID": "20185ae4-d9da-4f48-ac06-1555cf700ccb",
        "Code": "001",
        "Description": "Κατάστημα Λευκωσίας",
        "BIC": "51",
        "Street": null,
        "CntrID": "7ecdf343-7e70-426b-a7ec-22d077c7307d",
        "PrefID": "c2733c0e-5d3e-427e-a522-dc5e2a95c64f",
        "RegnID": "0e416737-f98e-4604-8b2a-0a8ca621c827",
        "StreetNumber": null,
        "PostalCode": null,
        "Phone1": null,
        "Phone2": null,
        "Email": null,
        "Fax": null
      }
    ]
  },
    ... 
]

The response is an array of Bank objects, however, notice that now we got a two-level response. Each Bank object also contains an array of nested Branch objects.

There might be cases where we want to expand or limit the depth of the payload returned. If there is no need for the bank branch data in my use case, it would be better not to incurr the extra overhead of fetching the related branches.

The API supports the depth URL parameter to control the depth of nesting we require. The default value is 1 level of nesting.

Let’s invoke the same lookup, with a depth of zero, and compare responses /api/glx/lookups/bank.json?depth=0 :

[
  {
    "ID": "20185ae4-d9da-4f48-ac06-1555cf700ccb",
    "RevNum": 13,
    "Code": "ΚΥΠ",
    "Description": "Κύπρου ",
    "BIC": null,
    "URL": null,
    "Branches": []
  },
    ...
]

As expected, the same list was returned, only now all Branches arrays in the payload are empty.

Views

So far we’ve been able to retrieve rather static lists of values and objects from the API, with very limited capabilities regarding the content returned.

Views come to expand our “list” capabilities, providing:

  • Filtering
  • Field Selection
  • Ordering
  • Paging
  • Payload & Query Metadata Descriptions

Every Business Domain will list it’s Views under /api/{DomainName}/views/ . The list is identical to enums & lookups, so we’ll move on to something more interesting.

Let see the contents of a View at /api/glx/views/salesentry :

[
  {
    "ID": "275c34ef-42c1-4ba2-be67-2a1972d06407",
    "TentID": "f3f3ff0e-82e5-4789-8ccd-0e14024733b9",
    "CoetID": "3ae05b32-0cc1-4282-929c-59a2dc9f7467",
    "CurrIDTran": "9efe2b51-f986-4071-9ce4-65d42075740b",
    "CurrIDEntity": "9efe2b51-f986-4071-9ce4-65d42075740b",
    "TotalValue": 111.5,
    "CoetCode": "6100",
    "CoetDescription": "Τιμολόγιο Πώλησης",
    "TRADEDATE": "2017-01-04T00:00:00.0",
    "OFFICIALDATE": "2017-01-04T00:00:00.0",
    "ORIGIN": 3,
    "SOURCETYPE": 1,
    "STATUS": 1,
    "TRADECODE": "6100 - Α6100 - 000233",
    "CompID": "59c7f302-f5b0-45f6-8a32-6c7f64e91c00",
    "CmpsID": "67a55e4f-9e78-4ea5-adae-b1b4299e8c39",
    "COMPTITLE": "Best Market SA ",
    "CMPSDESCRIPTION": "Best Market Αθήνα",
    "FSYRDESCRIPTION": "2017",
    "TSSCDESCRIPTION": "Σενάριο Παραστατικών Πωλήσεων (Προχ-Ενημ)",
    "TSSSDESCRIPTION": "Ενημερωμένο",
    "ISOFFICIAL": 1,
    "DNUMDISPLAYCODE": "Α6100",
    "ISPRINTED": 0,
    "EMAILSENT": 0,
    "ICTRANS": 0,
    "GLUPDATED": 0,
    "CustCode": "00000002",
    "DebtCode": null,
    "TrdrName": "Βασιλείου Γεώργιος",
    "CurDescription": "Ευρώ",
    "WAREHSDESCR": "κανονικά",
    "ROUTEDESCR": null,
    "NODEDESCR1": null,
    "NODEDESCR2": null,
    "NODEDESCR3": null,
    "NODEDESCR4": null,
    "NODEDESCR5": null
  },
  {
    "ID": "65493d3a-4f62-487a-b425-b411eaaac2b9",
    "TentID": "b672287b-3526-4cca-9146-822496fdfc3f",
    "CoetID": "85354cb0-0077-46b9-a07a-0afe20cee6ef",
    "CurrIDTran": "9efe2b51-f986-4071-9ce4-65d42075740b",
    "CurrIDEntity": "9efe2b51-f986-4071-9ce4-65d42075740b",
    "TotalValue": 596.55,
    "CoetCode": "6201",
    "CoetDescription": "Τιμολόγιο Πώλησης - Δελτίο Αποστολής (Offline Λογιστική)",
    "TRADEDATE": "2017-01-11T00:00:00.0",
    "OFFICIALDATE": "2017-01-11T00:00:00.0",
    "ORIGIN": 3,
    "SOURCETYPE": 1,
    "STATUS": 1,
    "TRADECODE": "6201 - A - 002205",
    "CompID": "59c7f302-f5b0-45f6-8a32-6c7f64e91c00",
    "CmpsID": "67a55e4f-9e78-4ea5-adae-b1b4299e8c39",
    "COMPTITLE": "Best Market SA ",
    "CMPSDESCRIPTION": "Best Market Αθήνα",
    "FSYRDESCRIPTION": "2017",
    "TSSCDESCRIPTION": "Παραστατικά Τιμολόγησης",
    "TSSSDESCRIPTION": "Ενημερωμένο",
    "ISOFFICIAL": 1,
    "DNUMDISPLAYCODE": "A",
    "ISPRINTED": 0,
    "EMAILSENT": 0,
    "ICTRANS": 0,
    "GLUPDATED": 1,
    "CustCode": "000000004",
    "DebtCode": null,
    "TrdrName": "Frank test",
    "CurDescription": "Ευρώ",
    "WAREHSDESCR": "κανονικά",
    "ROUTEDESCR": null,
    "NODEDESCR1": null,
    "NODEDESCR2": null,
    "NODEDESCR3": null,
    "NODEDESCR4": null,
    "NODEDESCR5": null
  }
]

This response bears little resemblance to any list we’ve seen so far. Actually, every view will most probably return a completely different payload ! It gets worse. Can I, as a developer use any fields for filtering the results ? And what about sorting ?

View Metadata

To assist developers, the API provides metadata for each and every View, detailing:

  • All Fields returned
  • All Filters that can be applied, in great detail
  • Any Sort Fields that can be applied

The metadata URI for a View, is the view URI followed by /metadata, in it’s generic form /api/{DomainName}/views/{ViewName}/metadata/

Let’s see what the metadata looks like for the SalesEntry View, at /api/glx/views/salesentry/metadata/ :

{
  "RelativeViews": [
        {
          "Name": "SalesEntryTransf",
          "Uri": "/api/glx/views/salesentrytransf"
        }
  ],
  "CustomViews": [],
  "Fields": [
        {
          "Name": "CoetCode",
          "DataType": "String",
          "AcceptedValues": null,
          "Description": "Κωδικός τύπου",
          "Width": 100,
          "Editor": "None"
        },
        {
          "Name": "CoetDescription",
          "DataType": "String",
          "AcceptedValues": null,
          "Description": "Περιγραφή τύπου",
          "Width": 200,
          "Editor": "None"
        },
        {
          "Name": "STATUS",
          "DataType": "Int16",
          "AcceptedValues": {
                "AcceptedValuesList": "/api/glx/enums/tradestatus",
                "DisplayField1": "Code",
                "DisplayField2": null,
                "KeyField": "Value",
                "SortFields": null
          },
          "Description": "Κατάσταση",
          "Width": 100,
          "Editor": "None"
        }
  ],
  "Filters": [
        {
          "Name": "QCoetID",
          "Description": "Τύπος",
          "Kind": "Quick",
          "DisplayAs": "MultiCheck",
          "Required": false,
          "Visible": true,
          "DataType": "String",
          "PredefinedValues": null,
          "AcceptedValues": {
                "AcceptedValuesList": "/api/glx/lookups/salesentrytype",
                "DisplayField1": "Code",
                "DisplayField2": "Description",
                "KeyField": "ID",
                "SortFields": [
                  {
                    "Name": "Code",
                    "Direction": "Ascending"
                  }
                ]
          },
          "AllowedOperators": [
                "Equal",
                "NotIn"
          ]
        },
        {
          "Name": "QTRADECODE",
          "Description": "Παραστατικό",
          "Kind": "Quick",
          "DisplayAs": "Text",
          "Required": false,
          "Visible": true,
          "DataType": "String",
          "PredefinedValues": null,
          "AcceptedValues": null,
          "AllowedOperators": [
                "Equal",
                "Greater",
                "GreaterOrEqual",
                "Less",
                "LessOrEqual",
                "NotEqual",
                "Like",
                "In",
                "NotIn"
          ]
        },
        {
          "Name": "FKIND",
          "Description": "Είδος",
          "Kind": "Misc",
          "DisplayAs": "MultiCheckEnumeration",
          "Required": false,
          "Visible": true,
          "DataType": "String",
          "PredefinedValues": null,
          "AcceptedValues": {
                "AcceptedValuesList": "/api/glx/enums/commercialentrykind",
                "DisplayField1": "Code",
                "DisplayField2": null,
                "KeyField": "Value",
                "SortFields": null
          },
          "AllowedOperators": [
                "In",
                "NotIn"
          ]
        }
  ],
  "IdentityField": "ID",
  "SortByFields": [
        "TRADEDATE"
  ],
  "Name": "SalesEntry",
  "Uri": "/api/glx/views/salesentry"
}

As you can see, the View metadata is quite comprehensive, informing us of various aspects regarding the View, its fields, filters we can use, and how.

View Field Metadata

Appart from a name and a data type, the API provides clients with a multitude of instructions and information, including:

  • A range of accepted values for the field, including the URI where the list of values can be retrieved, along with instructions on which list fields are to be used for the actual value, for display in a UI, and whether the list can be sorted and by what fields.

  • A preferred UI width for the field

  • The type of preferred editor UI control

    {
        "Name": "STATUS",
        "DataType": "Int16",
        "AcceptedValues": {
            "AcceptedValuesList": "/api/glx/enums/tradestatus",
            "DisplayField1": "Code",
            "DisplayField2": null,
            "KeyField": "Value",
            "SortFields": null
        },
        "Description": "Κατάσταση",
        "Width": 100,
        "Editor": "None"
    }
    

View Filter Metadata

Filter metadata is equally comprehensive, providing details on:

  • Name, data type and a UI description for the filter
  • A grouping indicator, the filter Kind, Quick or Misc
  • A UI control instruction in DisplayAs
  • A range of predefined values, if any
  • The URI where we can retrieve a list of accepted values, including Key and Display fields, like in fields
  • The allowed operators that we can use for the filter

    {
        "Name": "FKIND",
        "Description": "Είδος",
        "Kind": "Misc",
        "DisplayAs": "MultiCheckEnumeration",
        "Required": false,
        "Visible": true,
        "DataType": "String",
        "PredefinedValues": null,
        "AcceptedValues": {
            "AcceptedValuesList": "/api/glx/enums/commercialentrykind",
            "DisplayField1": "Code",
            "DisplayField2": null,
            "KeyField": "Value",
            "SortFields": null
        },
        "AllowedOperators": [
            "In",
            "NotIn"
        ]
    }
    

View Metadata

In addition to information we get about fields and filters, View metadata includes several fields that relate to the view itself. Those include:

  • Relative Views, views that display similar or related / complementary information
  • Custom Views, based on the View we’re examining. Those are user customizations, based on the original view
  • Dynamic Views, detail views filtered by the View’s master row data
  • The IdentityField, the field that contains a unique id for the row
  • SortByFields, an array of field names that we can sort on

Selecting View Fields

There may be the case where we want to specify the fields returned from our View invocation. The API provides the fields URL parameter for that purpose.

Consider this URL, which invokes the same salesentry View, but limits the fields returned at /api/glx/views/salesentry.json?fields=ID,TentID :

[
  {
    "ID": "275c34ef-42c1-4ba2-be67-2a1972d06407",
    "TentID": "f3f3ff0e-82e5-4789-8ccd-0e14024733b9"
  },
  {
    "ID": "65493d3a-4f62-487a-b425-b411eaaac2b9",
    "TentID": "b672287b-3526-4cca-9146-822496fdfc3f"
  }
]

Sorting View Results

Sorting on Views is achieved using the sortBy URL parameter. The value for the parameter follows a specific pattern: {FieldName}.{asc || desc}

If we were to sort the SalesEntry View on the CoetDescription field, ascending, we would follow /api/glx/views/salesentry.json?fields=ID,CoetDescription&sortBy=CoetDescription.asc:

[
  {
    "ID": "02aa6816-103a-4551-81a7-9e33f0b678d2",
    "CoetDescription": "Copy of Παραγγελία Πωλήσεων"
  },
  {
    "ID": "0bedafd0-d56c-415c-ba2c-68e17b65396e",
    "CoetDescription": "Copy of Παραγγελία Πωλήσεων"
  },
  {
    "ID": "17ad59cf-81b8-41f8-8f91-3de3779dab16",
    "CoetDescription": "Copy of Παραγγελία Πωλήσεων"
  }
]

Try inverting the qualifier to descending, and observe the results, following /api/glx/views/salesentry.json?fields=ID,CoetDescription&sortBy=CoetDescription.desc:

[
  {
    "ID": "65493d3a-4f62-487a-b425-b411eaaac2b9",
    "CoetDescription": "Τιμολόγιο Πώλησης - Δελτίο Αποστολής (Offline Λογιστική)"
  },
  {
    "ID": "52f5190e-43a4-4940-9520-fb494751dc1c",
    "CoetDescription": "Τιμολόγιο Πώλησης - Δελτίο Αποστολής (Offline Λογιστική)"
  },
  {
    "ID": "58cf0ffc-fa84-43de-840f-c6b12b797de0",
    "CoetDescription": "Τιμολόγιο Πώλησης"
  }
]

Note: Please note that the illustrated payloads are partial, for brevity.

Paging Views

Paging is achieved using the skip and take URL parameters. Quite self-explaining, they are similar to skip and take in some versions of SQL (mySQL most notable). Skip instructs the query to skip N number of items, and take instructs it to only retrieve X number of items.

Let’s use our SalesEntry View to fetch the first 5 items, sorted by CoetDescription, selecting only the ID and CoetDescriptions fields /api/glx/views/salesentry.json?fields=ID,CoetDescription&sortBy=CoetDescription.desc&skip=0&take=5:

 [
  {
    "ID": "65493d3a-4f62-487a-b425-b411eaaac2b9",
    "CoetDescription": "Τιμολόγιο Πώλησης - Δελτίο Αποστολής (Offline Λογιστική)"
  },
  {
    "ID": "52f5190e-43a4-4940-9520-fb494751dc1c",
    "CoetDescription": "Τιμολόγιο Πώλησης - Δελτίο Αποστολής (Offline Λογιστική)"
  },
  {
    "ID": "58cf0ffc-fa84-43de-840f-c6b12b797de0",
    "CoetDescription": "Τιμολόγιο Πώλησης"
  },
  {
    "ID": "b6066e6e-acba-4b28-8cfd-ad3e14424ead",
    "CoetDescription": "Τιμολόγιο Πώλησης"
  },
  {
    "ID": "adde2e82-208d-42db-990e-1a571bb9d19c",
    "CoetDescription": "Τιμολόγιο Πώλησης"
  }
]

Now, progressively increase the skip parameter, and decrease the take parameter. If paging works, we will see the first item disappear on each try:

Request:

/api/glx/views/salesentry.json?fields=ID,CoetDescription&sortBy=CoetDescription.desc&skip=1&take=4

Response:

[
  {
    "ID": "52f5190e-43a4-4940-9520-fb494751dc1c",
    "CoetDescription": "Τιμολόγιο Πώλησης - Δελτίο Αποστολής (Offline Λογιστική)"
  },
  {
    "ID": "58cf0ffc-fa84-43de-840f-c6b12b797de0",
    "CoetDescription": "Τιμολόγιο Πώλησης"
  },
  {
    "ID": "b6066e6e-acba-4b28-8cfd-ad3e14424ead",
    "CoetDescription": "Τιμολόγιο Πώλησης"
  },
  {
    "ID": "adde2e82-208d-42db-990e-1a571bb9d19c",
    "CoetDescription": "Τιμολόγιο Πώλησης"
  }
]

Request:

/api/glx/views/salesentry.json?fields=ID,CoetDescription&sortBy=CoetDescription.desc&skip=2&take=3

Response:

[
  {
    "ID": "58cf0ffc-fa84-43de-840f-c6b12b797de0",
    "CoetDescription": "Τιμολόγιο Πώλησης"
  },
  {
    "ID": "b6066e6e-acba-4b28-8cfd-ad3e14424ead",
    "CoetDescription": "Τιμολόγιο Πώλησης"
  },
  {
    "ID": "adde2e82-208d-42db-990e-1a571bb9d19c",
    "CoetDescription": "Τιμολόγιο Πώλησης"
  }
]

Note: Full-blown paging is supported only on versions of SQL Server greater or equal to SQL Server 2012. In previous versions of SQL Server the skip parameter will be ignored. However, take will be taken into account when performing the query, and only a certain number of result rows will be retrieved.

Using Filters

We saw in the View metadata for SalesEntry that there are filters defined for the view. In this section we will use them to filter the results we need from the View.

Consider the following filter definition:

{
    "Name": "QTRADECODE",
    "Description": "Παραστατικό",
    "FieldName": "TRADECODE",
    "Kind": "Quick",
    "DisplayAs": "Text",
    "Required": false,
    "Visible": true,
    "DataType": "String",
    "PredefinedValues": null,
    "AcceptedValues": null,
    "AllowedOperators": [
        "Equal",
        "Greater",
        "GreaterOrEqual",
        "Less",
        "LessOrEqual",
        "NotEqual",
        "Like",
        "In",
        "NotIn"
    ]
}

The DisplayAs and DataType properties inform us that the value for the field will be a string, and preferrably, we would like the filter rendered as a Text field in the UI. It also instructs us to use one of several allowed operators that the filter supports.

To better understand filtering, lets fetch the View data with only the fields we will be using for the rest of the section. Those are ID, CoetID and TradeCode. Let’s also limit the results returned to three, so the payloads remain readable:

Request:

/api/glx/views/salesentry.json?fields=ID,CoetID,TradeCode&sortBy=CoetDescription.desc&take=3

Response:

[
    {
        "ID": "65493d3a-4f62-487a-b425-b411eaaac2b9",
        "CoetID": "85354cb0-0077-46b9-a07a-0afe20cee6ef",
        "TRADECODE": "6201 - A - 002205"
    },
    {
        "ID": "52f5190e-43a4-4940-9520-fb494751dc1c",
        "CoetID": "85354cb0-0077-46b9-a07a-0afe20cee6ef",
        "TRADECODE": "6201 - A - 002204"
    },
    {
        "ID": "58cf0ffc-fa84-43de-840f-c6b12b797de0",
        "CoetID": "3ae05b32-0cc1-4282-929c-59a2dc9f7467",
        "TRADECODE": "6100 - Α6100 - 000234"
    }
]

Let’s try to locate entries whose TradeCode contains ‘6201’.

Request:

/api/glx/views/salesentry.json?fields=ID,CoetID,TradeCode&take=3&filters={QTRADECODE:[6201,Like]}

Response:

[
    {
        "ID": "65493d3a-4f62-487a-b425-b411eaaac2b9",
        "CoetID": "85354cb0-0077-46b9-a07a-0afe20cee6ef",
        "TRADECODE": "6201 - A - 002205"
    },
    {
        "ID": "52f5190e-43a4-4940-9520-fb494751dc1c",
        "CoetID": "85354cb0-0077-46b9-a07a-0afe20cee6ef",
        "TRADECODE": "6201 - A - 002204"
    }
]

Note: Clearly, our filter worked, albeit with unusual semantics for the ‘Like’ operator. Galaxy will use the Like operator as “contains”, rather than the typical SQL semantics where we would have to add wildcard characters to achieve the same result.

Examine the ending part of our request URL: filters={QTRADECODE:[6201,Like]}

In it’s generic form, this would look like: filters={<Filter_Name>:[<Filter_Value>,<Filter_Operator>]}

Upon closer inspection, this looks very similar to a JSON object, with a single property key, whose value is an array. In reality, this is exactly how the API will parse this!

The immediate side-effect of this fact, is that we can use multiple filters in the same request. On the next section, we will use another filter, to further limit our results. However, before we do that, we need to get acquainted with another filter type, a filter that will accept only one of a range of specific values.

Filters with limited range of AcceptedValues

Consider the following filter definition, present in our SalesEntry View.

{
    "Name": "QCoetID",
    "Description": "Τύπος",
    "FieldName": "CoetID",
    "Kind": "Quick",
    "DisplayAs": "MultiCheck",
    "Required": false,
    "Visible": true,
    "DataType": "String",
    "PredefinedValues": null,
    "AcceptedValues": {
        "AcceptedValuesList": "/api/glx/lookups/salesentrytype",
        "DisplayField1": "Code",
        "DisplayField2": "Description",
        "KeyField": "ID",
        "ParentField": null,
        "SortFields": [
            {
            "Name": "Code",
            "Direction": "Ascending"
            }
        ]
    },
    "AllowedOperators": [
        "Equal",
        "NotIn"
    ]
}

This adds an interesting twist to filters. The AcceptedValues object informs us that the filter will only accept a value that originates in a Lookup. It will tell us two display fields to use in our UI, and the field that will actually be used as our filter value.

Let’s examine the lookup contents at /api/glx/lookups/salesentrytype:

[
  {
    "ID": "85354cb0-0077-46b9-a07a-0afe20cee6ef",
    "Code": "6201",
    "Description": "Τιμολόγιο Πώλησης - Δελτίο Αποστολής (Offline Λογιστική)",
    ...
  },
  {
    "ID": "83a79223-5604-4703-92ca-0b760a0d4e5a",
    "Code": "6201-01",
    "Description": "Τιμολόγιο Πώλησης - Δελτίο Αποστολής (Offline Λογιστική & Εγκρίσεις)",
    ...
  },
...
]

Note: Again, for the sake of brevity this is a partial payload that only illustrates the fields we’re interested in. The actual Lookup retrieves much more data.

As we can see, the fields we found from our filter metadata exist on the Lookup. Now, let’s try to use some of that data to filter our original SalesEntry view. We want to add a second filter to our existing query, to only retrieve entries whose CoetID is equal to ‘85354cb0-0077-46b9-a07a-0afe20cee6ef’ - the first salesentrytype ID we found.

Request:

/api/glx/views/salesentry.json?fields=ID,CoetID,TradeCode&take=3&filters={QTRADECODE:[6201,Like],QCoetID:[85354cb0-0077-46b9-a07a-0afe20cee6ef,In]}

Response:

[
    {
        "ID": "65493d3a-4f62-487a-b425-b411eaaac2b9",
        "CoetID": "85354cb0-0077-46b9-a07a-0afe20cee6ef",
        "TRADECODE": "6201 - A - 002205"
    },
    {
        "ID": "52f5190e-43a4-4940-9520-fb494751dc1c",
        "CoetID": "85354cb0-0077-46b9-a07a-0afe20cee6ef",
        "TRADECODE": "6201 - A - 002204"
    }
]

If we inspect the CoetID field in our response, it is unclear that our second filter worked. Let make sure. If we invert the operator, and use NotIn, we can expect that we will be getting back zero rows - both of our items have the same CoetID, and only these also contain 6201 in their TradeCode. Let’s try it.

Request:

/api/glx/views/salesentry.json?fields=ID,CoetID,TradeCode&take=3&filters={QTRADECODE:[6201,Like],QCoetID:[85354cb0-0077-46b9-a07a-0afe20cee6ef,NotIn]}

Response:

[]

Views have been our most involved section still. We haven’t exhausted the subject of filtering, as there are many filter types, each with its own idiocyncrasies. Those will be summarized in Appendix A, Filters and Filter Types.

Dynamic Views

Galaxy users are able to create detail views under a View, having related content with View’s master rows. These are called Dynamic Views and listed under each View URI adding the /dynviews suffix:

/api/{DomainName}/views/{ViewName}/dynviews

and under the DynamicViews section of View metadata. Dynamic views for the Item View of Glx business domain, are listed at /api/glx/views/item/dynviews:

[
    {
        "Name": "Company balances per branch",
        "Uri": "/api/glx/views/item/{KeyValue}/dynviews/Company+balances+per+branch"
    },
    {
        "Name": "Company balances per warehouse \\ Location",
        "Uri": "/api/glx/views/item/{KeyValue}/dynviews/Company+balances+per+warehouse+%5c+Location"
    },
    {
        "Name": "Balances per company and branch",
        "Uri": "/api/glx/views/item/{KeyValue}/dynviews/Balances+per+company+and+branch"
    },
    {
        "Name": "Balances per company \\ warehouse \\ location",
        "Uri": "/api/glx/views/item/{KeyValue}/dynviews/Balances+per+company+%5c+warehouse+%5c+location"
    }
]

In order to list dynamic view’s contents we can use the following URI, providing the row KeyValue of master View:

/api/{DomainName}/views/{ViewName}/{KeyValue}/dynviews/{DynamicViewName}

As in View, dynamic view’s metadata are listed under:

/api/{DomainName}/views/{ViewName}/dynviews/{DynamicViewName}/metadata

Services

Services are endpoints that don’t have a direct relationship with a Database, and only expose remotely callable Business Operations.

Every Business Domain will list it’s exposed services under /api/{DomainName}/services :

[
  {
    "Domain": "glx",
    "Name": "ArtiusConnectivity",
    "Uri": "/api/glx/services/artiusconnectivity"
  },
  {
    "Domain": "glx",
    "Name": "Assignments",
    "Uri": "/api/glx/services/assignments"
  },
  {
    "Domain": "glx",
    "Name": "Connectivity",
    "Uri": "/api/glx/services/connectivity"
  },
  {
    "Domain": "glx",
    "Name": "DataTalkObjects",
    "Uri": "/api/glx/services/datatalkobjects"
  },
  {
    "Domain": "glx",
    "Name": "ExtCall",
    "Uri": "/api/glx/services/extcall"
  },
  {
    "Domain": "glx",
    "Name": "HT4HotelsConnectivity",
    "Uri": "/api/glx/services/ht4hotelsconnectivity"
  },
  {
    "Domain": "glx",
    "Name": "IS.TaxationCalc",
    "Uri": "/api/glx/services/is.taxationcalc"
  },
  {
    "Domain": "glx",
    "Name": "Messenger",
    "Uri": "/api/glx/services/messenger"
  },
    ...
]

Service Metadata

Obviously, each business domain provides a variety of services, to cater for business or system needs. Let’s examine the URL for the Messenger service. It is in essence a chat service /api/glx/services/messenger :

[
  {
    "Arguments": {
      "Parameters": [
        {
          "Type": "string, null",
          "IsArray": false,
          "Name": "userName",
          "Required": true,
          "DefaultValue": null
        }
      ]
    },
    "Name": "GetUserOptions",
    "Uri": "/api/glx/services/messenger/getuseroptions"
  },
  {
    "Arguments": {
      "Parameters": [
        {
          "Type": "string, null",
          "IsArray": false,
          "Name": "conversationID",
          "Required": true,
          "DefaultValue": null
        }
      ]
    },
    "Name": "GetConversation",
    "Uri": "/api/glx/services/messenger/getconversation"
  },
  {
    "Arguments": {
      "Parameters": [
        {
          "Type": "integer",
          "IsArray": false,
          "Name": "top",
          "Required": true,
          "DefaultValue": null
        },
        {
          "Type": "string, null",
          "IsArray": false,
          "Name": "fromUser",
          "Required": true,
          "DefaultValue": null
        },
        {
          "Type": "string, null",
          "IsArray": false,
          "Name": "toUser",
          "Required": true,
          "DefaultValue": null
        }
      ]
    },
    "Name": "GetOldMessages",
    "Uri": "/api/glx/services/messenger/getoldmessages"
  },
  {
    "Arguments": {
      "Parameters": []
    },
    "Name": "ClearMessages",
    "Uri": "/api/glx/services/messenger/clearmessages"
  },
  {
    "Arguments": {
      "Parameters": [
        {
          "Type": "string, null",
          "IsArray": false,
          "Name": "userName",
          "Required": true,
          "DefaultValue": null
        },
        {
          "Type": "string, null",
          "IsArray": false,
          "Name": "computerName",
          "Required": true,
          "DefaultValue": null
        }
      ]
    },
    "Name": "GetMessages",
    "Uri": "/api/glx/services/messenger/getmessages"
  },
  {
    "Arguments": {
      "Parameters": [
        {
          "Type": "string, null",
          "IsArray": false,
          "Name": "message",
          "Required": true,
          "DefaultValue": null
        },
        {
          "Type": "string, null",
          "IsArray": false,
          "Name": "fromUser",
          "Required": true,
          "DefaultValue": null
        },
        {
          "Type": "string, null",
          "IsArray": true,
          "Name": "toUsers",
          "Required": true,
          "DefaultValue": null
        },
        {
          "Type": "string, null",
          "IsArray": false,
          "Name": "conversationID",
          "Required": true,
          "DefaultValue": null
        }
      ]
    },
    "Name": "SendMessage",
    "Uri": "/api/glx/services/messenger/sendmessage"
  },
  {
    "Arguments": {
        "Parameters": [
         {
            "Type": "boolean",
            "IsArray": false,
            "Name": "includeInactive",
            "Required": true,
            "DefaultValue": null
         }
        ]
    },
    "Name": "GetUsers",
    "Uri": "/api/glx/services/messenger/getusers"
  }
]

A Service URI will return a description of all operations exposed by the service. It lists a Name for the operation, a URI to call, and a description of all parameters per call.

Invoking a Service Operation

To invoke an operation, we need to do a POST request to the operation’s URI, with the parameters encoded as a JSON string in the request body, and a Content-Type of application/json.

For example, to invoke the GetUsers operation:

Property Description
URI /api/glx/services/messenger/getusers
METHOD POST
BODY { “includeInactive” : true }
Content-Type application/json

Using CURL:

curl -H "Content-Type: application/json" \
-X POST -d '{ "includeInactive" : true }' \
http://localhost:3000/api/glx/services/messenger/getusers

Upon POST-ing our request to the API, the service responds with:

[
  {
    "UserName": "config1",
    "Description": "config1",
    "ComputerName": null,
    "LastUpdate": "0001-01-01T00:00:00.0000000",
    "Status": "Offline"
  },
  {
    "ID": null,
    "UserName": "ikourt",
    "Description": "ikourt",
    "ComputerName": null,
    "LastUpdate": "0001-01-01T00:00:00.0000000",
    "Status": "Offline"
  },
  {
    "ID": null,
    "UserName": "@Service",
    "Description": "@Service",
    "ComputerName": null,
    "LastUpdate": "0001-01-01T00:00:00.0000000",
    "Status": "Offline"
  },
  {
    "ID": null,
    "UserName": "nmn1",
    "Description": "nmn1",
    "ComputerName": null,
    "LastUpdate": "0001-01-01T00:00:00.0000000",
    "Status": "Offline"
 }
]

All Service invocations work the same way. Clients should examine the metadata for each service and construct their calls accordingly, since each Service provides its own, unique functionality.

Entities

So far, the objects we’ve encountered in the API were either data-related ( Enums, Lookups & Views ), or Remote Procedure Call “servers” (Services).

Entities are objects with a direct link to the Database, exposing a queryable list of items and CRUD operations, but - just like Services - they also expose Business Operations to the client. In that sense, an Entity is a mix of Views and Services.

Every Business Domain lists the Entities it exposes at /api/{DomainName}/entities, using the familiar list schema we’ve seen in all object lists so far, including a Name and a URI:

[
      {
        "Domain": "glx",
        "Name": "Item",
        "Uri": "/api/glx/entities/item"
      },
      {
        "Domain": "glx",
        "Name": "Company",
        "Uri": "/api/glx/entities/company"
      },
        ...
]

Entity Default Collections

Invoking the Entity URI, will always return a collection of items /api/glx/entities/company:

[
    {
        "ID": "9ca0b73e-f6e6-46c9-a554-0c8752730ffc",
        "Code": "004",
        "Title": "Best Market Services ΟΕ",
        "DistinctiveTitle": null,
        "TIN": "094007989",
        "Active": 1
    },
    {
        "ID": "dd32e7b3-036c-41df-9cd2-294e96415d4c",
        "Code": "006",
        "Title": "SingularLogic",
        "DistinctiveTitle": "SingularLogic",
        "TIN": null,
        "Active": 1
    },
        ...
]

Interestingly, the contents and schema of the collection returned, is actually a View. This means that we get all the functionality exposed by Views here too. For example, let’s examine the metadata for the Company list we got back at /api/glx/entities/company/metadata:

{
  "RelativeViews": [],
  "CustomViews": [],
  "Fields": [
        {
          "Name": "Code",
          "DataType": "String",
          "AcceptedValues": null,
          "Description": "Κωδικός",
          "Width": 120,
          "Editor": "None",
          "DecimalsProvider": null
        },
        {
          "Name": "Title",
          "DataType": "String",
          "AcceptedValues": null,
          "Description": "Τίτλος",
          "Width": 200,
          "Editor": "None",
          "DecimalsProvider": null
        },
            ...
  ],
  "Filters": [
        {
          "Name": "QCode",
          "Description": "Κωδικός",
          "FieldName": "Code",
          "Kind": "Quick",
          "DisplayAs": "Text",
          "Required": false,
          "Visible": true,
          "DataType": "String",
          "PredefinedValues": null,
          "AcceptedValues": null,
          "AllowedOperators": [
                "Equal",
                "Greater",
                "GreaterOrEqual",
                "Less",
                "LessOrEqual",
                "NotEqual",
                "Like",
                "In",
                "NotIn"
            ]
        },
            ...
  ],
  "IdentityField": "ID",
  "SortByFields": [
"Code"
  ],
  "Name": "Company",
  "Uri": "/api/glx/views/company"
}

Entity Custom Views

Using the Galaxy client, users are free to create custom variations of Views, for personal or public use. In simple terms, we could have more than a single View returning Company data. There custom views are listed under the Entity URI by adding the suffix /myviews, and they are also included in the CustomViews section of View metadata.

In it’s generic form: /api/{DomainName}/entities/{EntityName}/myviews.

Let’s see the list of custom views defined for the Item entity, present in the Glx business domain at /api/glx/entities/item/myviews:

[
    {
        "Name": "Browser Personalization",
        "Uri": "/api/glx/entities/item/myviews/custom+view"
    },
    {
        "Name": "Browser Personalization",
        "Uri": "/api/glx/entities/item/myviews/custom+view+(public)"
    },
    {
        "Name": "Browser Personalization",
        "Uri": "/api/glx/entities/item/myviews/items+with+images+(public)"
    },
    {
        "Name": "Browser Personalization",
        "Uri": "/api/glx/entities/item/myviews/actvesql+(public)"
    }
]

These URIs behave just like any other view URI, supporting metadata, filtering, sorting and all other useful features we saw in the Views section.

Entity CRUD Operations

We saw earlier that Entities are representations of collections, originating from the database, that support CRUD operations.

So, we should be able to retrieve an Entity by its ID. The generic form of the “GetByID” URI is /api/{DomainName}/entities/{EntityName}/{ID}.

Let’s try to retrieve the details of the first Company in the companies list we visited before by applying the URI:

Request:

/api/glx/entities/company/9ca0b73e-f6e6-46c9-a554-0c8752730ffc

Response:

{
  "ID": "9ca0b73e-f6e6-46c9-a554-0c8752730ffc",
  "RevNum": 50,
  "IsSystem": 0,
  "Active": 1,
  "ActiveDate": "2015-04-24T00:00:00",
  "Code": "004",
  "Title": "Best Market Services ΟΕ",
  "DistinctiveTitle": null,
  "CmpgID": null,
  "ClgtID": "a6fa8eb8-12a2-4c3e-8f6e-3bf8f61f2652",
  "StartDate": null 
  "Sites": [
        {
          "ID": "f15c912a-9aa4-4652-95c4-64aa92d50286",
          "IsSystem": 0,
          "Code": "003",
          "Description": "Συν.Αιγάλεω",
          "Active": 1,
          "ActiveDate": "2011-04-13T00:00:00",

          "CompanyTitle": "Best Market Services ΟΕ",
          "Warehouses": [ ... ],
          "SiteLocation": [
                {
                  "ID": "a58407a1-ceec-4a9e-90e6-0243016e817d",
                  "CmpsID": "f15c912a-9aa4-4652-95c4-64aa92d50286",
                  "AsLoID": "ab76a143-4b65-4e3e-ab35-1b10a1a46ba1",
                  "AsLoCode": "ΕΣ_Σ",
                  "AsLoDescr": "Χώρος συνεργείου"
                }
          ],
          "WorkshopUnits": [ ... ],
          "SocialInsInstitution": [ ... ]
        }
  ],
  "TaxData": [ ... ],
  "TaxCredentials": [
        {
          "ID": "88664eee-55aa-483f-96af-3f6b65787ffe",
          "CompID": "9ca0b73e-f6e6-46c9-a554-0c8752730ffc",
          "TaxService": 3,
        }
  ],
  "usEmail2": null,
  "usEMail3": null
}

Although the above payload is partial ( the actual payload is very large for reading as an example ), it is clear that it bears no resemblance to the metadata we saw for the default collection.

This is because the view could be almost any query in the database. Still, in order to create or update rows, we need to know the complete schema.

For that purpose, the API provides metadata for Entities. Furthermore, to make it easier for developers to create the required json payload, the API also provides a template per Entity type. The template is a default JSON payload for a given Entity, initialized with any possible default values.

Entity Template and Metadata

To retrieve the Entity Template, append /template to the Entity URI. For example, to retrieve the template for a Company, we need to perform a GET request at /api/glx/entities/company/template :

{
    "ID": "5564ab3e-ab9f-44b1-b707-249339833147",
    "RevNum": 1,
    "IsSystem": 0,
    "Active": 1,
    "ActiveDate": null,
    "Code": null,
    "Title": null,
    "DistinctiveTitle": null,
    "CmpgID": null,
    "ClgtID": null,
    "StartDate": null,
    "TIN": null,
    "TaxoID": null,
    "TaxRegNum": null,
    "TaxRecNum": null,
    "SscoID": null,
    "SocSecNum": null,
    "Comment": null,
    "EANCode": null,
    "ProfID": null,
    "RelTrdrID": null,
    "OilCompany": 0,
    "GROblTaxReg": 0,
    "Taxisnetuser": null,
    "Taxisnetpass": null,
    "Taxationuser": null,
    "Taxationpass": null,
    "myGlxUser": null,
    "myGlxPass": null,
    "VatSubOnCol": 0,
    "VatSubOnColDate": null,
    "IExchangeSender": null,
    "myGlxProvID": null,
    "InternalFlags": null,
    "iExhPrincipal": null,
    "iExhTenant": null,
    "myGlxSelected": null,
    "Sites": [],
    "TaxData": [],
    "TaxCredentials": [],
    "usEmail2": null,
    "usEMail3": null
}

This payload is much more similar to the JSON we got previously for a Company by ID. However, the keen eye will notice that although this template is correct, we don’t get any indication of the schema in the Sites, TaxData & TaxCredentials arrays.

To alleviate this inconsistency ( that there might be properties in the Entity schema that are not required, so the Template doesn’t include them ), the API also provides full metadata for the Entity Template, including all detail objects to any depth.

To get the Template Metadata URI, we just append /metadata to the template URI we just used, giving us /api/glx/entities/company/template/metadata or, in its generic form /api/{DomainName}/entities/{EntityName}/template/metadata:

{
  "PKey": "ID",
  "definitions": {
        "gxCompanySite": {
          "PKey": "ID",
          "definitions": {
                "gxWarehouse": { ... },
                "gxCompanySiteLoc":{ ... },
                "gxWorkshopUnit": { ... },
                "gxSocialInsInstDet": { ... }
          },
          "FKey": "CompID",
          "properties": { ... },
          "required":[ ... ]
        },
        "gxCompanyTaxData": { ... },
        "gxCompTaxCred":{ ... }
  },
  "properties": {
    "ID": {
      "type": "string"
    },
    "RevNum": {
      "type": "integer"
    },
    "IsSystem": {
      "type": "integer"
    },
    "Active": {
      "type": "integer"
    },
    "ActiveDate": {
      "type": "object"
    },
    "Code": {
      "type": "string"
    },
    "Title": {
      "type": "string"
    },
    "DistinctiveTitle": {
      "type": [
            "string",
            "null"
      ]
    },
    "Sites": {
      "type": [
            "array",
            "null"
      ],
      "items": {
        "$ref": "#/definitions/gxCompanySite"
      }
    },
    "TaxData": {
      "type": [
            "array",
            "null"
      ],
      "items": {
            "$ref": "#/definitions/gxCompanyTaxData"
      }
    },
    "TaxCredentials": {
      "type": [
            "array",
            "null"
      ],
      "items": {
            "$ref": "#/definitions/gxCompTaxCred"
      }
    }
  },
  "required": [
        "ID",
        "RevNum",
        "IsSystem",
        "Active",
        "ActiveDate",
        "Code",
        "Title",
        "OilCompany",
        "GROblTaxReg",
        "VatSubOnCol"
  ]
}

The format of the metadata might seem daunting at first. Luckily, it follows a standard schema format, called JSchema, popular with REST clients and many modern programming languages. Chances are that no matter what platform, the developer can find an open-source library that parses and uses JSchema.

The structure is actually simple. Every Schema contains a properties collection, where for each property we get the possible data types ( null is considered a type in JSchema ).

For array or object properties, we get a JSchema inside the definitions, present in the beggining of the payload. The structure is recursive, so it can describe object trees of arbitrary depth in a standard way.

Typically, the developer will use a JSchema parser to read the schema and create default instances of any object described therein, that can later be used in calls to the API.

Next, we will use the Entity Template to insert a new Company in our back-end, retrieve it by ID, update, and finally delete it.

Create a new Entity instance

In the previous section, we retrieved the Entity Template for companies. Now, we will use it to create a new company in our back-end.

To insert a new company in our back-end, we need to POST our JSON payload to the Entity’s base URI, in our case /api/glx/entities/company:

Request:

{
    "ID": "5564ab3e-ab9f-44b1-b707-249339833147",
    "RevNum": 1,
    "IsSystem": 0,
    "Active": 1,
    "ActiveDate": "2017-03-15T14:35:21",
    "Code": "008",
    "Title": "API Test Company",
    "DistinctiveTitle": "API Test Company",
    "CmpgID": null,
    "ClgtID": null,
    "StartDate": null,
    "TIN": null,
    "TaxoID": null,
    "TaxRegNum": null,
    "TaxRecNum": null,
    "SscoID": null,
    "SocSecNum": null,
    "Comment": null,
    "EANCode": null,
    "ProfID": null,
    "RelTrdrID": null,
    "OilCompany": 0,
    "GROblTaxReg": 0,
    "Taxisnetuser": null,
    "Taxisnetpass": null,
    "Taxationuser": null,
    "Taxationpass": null,
    "myGlxUser": null,
    "myGlxPass": null,
    "VatSubOnCol": 0,
    "VatSubOnColDate": null,
    "IExchangeSender": null,
    "myGlxProvID": null,
    "InternalFlags": null,
    "iExhPrincipal": null,
    "iExhTenant": null,
    "myGlxSelected": null,
    "Sites": [],
    "TaxData": [],
    "TaxCredentials": [],
    "usEmail2": null,
    "usEMail3": null
}

Note: If we examine the Entity metadata, we will see that there are ten required fields. These are the only ones we will be posting values for, to illustrate the point that, the template will always inform us of the minimum set of data we need to post to the server to create a new entity instance.

Upon receiving our request, the server responds with an Http Status: 200 (OK), and a detailed response object.

Response:

{
  "Messages": [
        {
          "Kind": "Warning",
          "Message": "[Company] Validation of Tax Identification Number (TIN)."
        },
        {
          "Kind": "Warning",
          "Message": "[Company] TIN field is left blank."
        },
        {
          "Kind": "Information",
          "Message": "[Warehouse locations] The array was posted successfully"
        },
        {
          "Kind": "Information",
          "Message": "[Company] The entry was posted successfully"
        }
  ],
  "ResourceURI": "/api/glx/entities/company/5564ab3e-ab9f-44b1-b707-249339833147",
  "ResourceID": "5564ab3e-ab9f-44b1-b707-249339833147"
}

In the response, even if successful, we might retrieve various warnings or informational messages meant for the user to see but, most importantly, we can retrieve the URI for our newly created Company in the ResourceURI field.

Updating an Entity

Let’s use that Resource URI value, to retrieve our Company data from /api/glx/entities/company/5564ab3e-ab9f-44b1-b707-249339833147. You will notice that the back-end has automatically created a CompanySite for us. Such business rules exist in many of Galaxy’s objects, to ensure business functionality and data integrity and consistency.

Let’s try to change the Company Title and update our entity. Change the Title in our JSON payload to “API Test Company 1”.

Now, we need to do a PUT request at the Entity URI to update. Remember, our Entity URI is now suffixed with the Entity ID /api/glx/entities/company/5564ab3e-ab9f-44b1-b707-249339833147.

Once again, the server responds with an Http Status 200 (OK), and our response payload is:

{
  "Messages": [
        {
          "Kind": "Information",
          "Message": "[Company] The entry was posted successfully"
        }
  ],
  "ResourceURI": "/api/glx/entities/company/5564ab3e-ab9f-44b1-b707-249339833147",
  "ResourceID": "5564ab3e-ab9f-44b1-b707-249339833147"
}

Using the PATCH Http Verb for partial updates

For many people, posting the entire JSON payload of our Company to change just one field might seem like overkill and inefficient bandwidth and resource use. Many people in the REST community felt the same way, so a new way to partially update objects is being proposed, using the PATCH Http Verb.

Note: There is still no clear standard for using the PATCH verb with REST services. The Galaxy API implements RFC 6902, which specifies a payload containing instructions on how to change the original document.

This documentation will not delve into the structure of a PATCH document, developers are encouraged to read RFC 6902 in the link provided, and implement support for creating their payloads.

Let’s see how we could accomplish the same update on our Company, using JSON PATCH:

Request:

[{ "op":"replace", "path":"/Title", "value":"API Test Company 2" }]

Now, we need to do a PATCH Http request to the Entity URI with our patch instructions document.

Response:

As we’d expect, the server responds with an Http Status 200 (OK) and our familiar operation response payload.

{
  "Messages": [
        {
          "Kind": "Information",
          "Message": "[Company] The entry was posted successfully"
        }
  ],
  "ResourceURI": "/entities/company/5564ab3e-ab9f-44b1-b707-249339833147",
  "ResourceID": "5564ab3e-ab9f-44b1-b707-249339833147"
}

Deleting an Entity

Deleting an Entity is as simple as performing a DELETE Http request to the Entity URI - including the Entity ID.

Let delete our entity. Issue an Http DELETE request to /api/glx/entities/company/5564ab3e-ab9f-44b1-b707-249339833147.

The server responds with an Http Status 200 (OK), and no content. DELETE requests typically don’t include content in their response.

Entity Actions

An Entity is in essence an extension of a Service class. It also exposes callable Business Operations, identical in structure and use to those of a Service.

Every Entity will list its operations under /actions. In its generic form the URI that lists all actions exposed by an Entity is /api/{DomainName}/entities/{EntityName}/actions.

Let’s examine the actions of the Company entity we’ve been using, at /api/glx/entities/company/actions:

[
    ...
    {
        "Arguments": {
            "Parameters": [
                {
                    "Type": "string, null",
                    "IsArray": false,
                    "Name": "compid",
                    "Required": true,
                    "DefaultValue": null
                },
                {
                    "Type": "boolean",
                    "IsArray": false,
                    "Name": "distTitle",
                    "Required": true,
                    "DefaultValue": null,
                    "ClrType": null
                }
            ]
        },
        "Name": "CompanyTitle",
        "Uri": "/api/glx/entities/company/{ID}/actions/companytitle"
    },
    {
        "Arguments": {
            "Parameters": [
                {
                    "Type": "string, null",
                    "IsArray": false,
                    "Name": "cmpsid",
                    "Required": true,
                    "DefaultValue": null,
                    "ClrType": null
                }
            ]
        },
        "Name": "CompanySiteDescription",
        "Uri": "/api/glx/entities/company/{ID}/actions/companysitedescription"
    },
    ...
]

Notice that this payload is identical to the one we get for Service operations. Invoking those operations is again a matter of performing a POST Http Request to the action URI, with a JSON payload that specifies our parameter values.

The second operation in our list returns the description of a Company Site for a Company. We’ll use it to illustrate calling an Entity action.

Note: To verify the results we’ll cheat a bit here. There is a lookup we saw earlier that returns all Companies, along with their Company Sites, under /api/glx/lookups/company. We’ll be using the first company in that list to verify the result of our action invocation. Invoke the list in your browser, and pick the first Company ID, and it’s first Site ID to use as parameters for calling our action.

Request (POST):

/api/glx/entities/company/9ca0b73e-f6e6-46c9-a554-0c8752730ffc/actions/companysitedescription

JSON Payload:

{ "cmpsid" : "f15c912a-9aa4-4652-95c4-64aa92d50286" }

Response:

"Συν.Αιγάλεω"

To verify the answer, lets take a look at an extract from the companies lookup payload:

{
    "ID": "9ca0b73e-f6e6-46c9-a554-0c8752730ffc",
    "RevNum": 50,
    "IsSystem": 0,
    "Active": 1,
    "ActiveDate": "2015-04-24T00:00:00",
    "Code": "004",
    "Title": "Best Market Services ΟΕ",
    "DistinctiveTitle": null,
    "CmpgID": null,
    "ClgtID": "a6fa8eb8-12a2-4c3e-8f6e-3bf8f61f2652",
    "StartDate": null,
    "TIN": "094007989",
        ...
    "Sites": [
        {
        "ID": "f15c912a-9aa4-4652-95c4-64aa92d50286",
        "IsSystem": 0,
        "Code": "003",
        "Description": "Συν.Αιγάλεω",
        "Active": 1,
        "ActiveDate": "2011-04-13T00:00:00", 

    ...

Entity Dynamic Details

The Galaxy platform was designed to be as flexible as possible. Sometimes, users would like to store detail data for some of the out-of-the-box Entities that is not covered in some way by the default objects. So Galaxy allows the user to define their own dynamic detail tables, and attach them to any entity’s child collections.

For the sake of illustrating Dynamic Details, we have added a Dynamic Detail table under the Activities Entity, in the CRM Business Domain.

Entity Dynamic Details Metadata

Every Entity will list dynamic details’ metadata under /api/{DomainName}/entities/{EntityName}/dyndetails/metadata, in our case this URI is /api/crm/entities/activities/dyndetails/metadata:

[
    {
        "title": "dycmActivitiesDynDetails",
        "type": "object",
        "properties": {
            "dyID": {
                "type": "string"
            },
            "cmActivitiesID": {
                "type": "string"
            },
            "dycmActDetDescription": {
                "type": "string"
            }
        },
        "required": [
        "dyID",
        "cmActivitiesID",
        "dycmActDetDescription"
        ]
    }
]

Note: The response is an array of JSchema metadata. It’s the same format we already used before when examining Entity Template Metadata.

Entity Dynamic Details Template

Typically in the API, where there is Template Metadata, there is a Template call to retrieve a default initialized instance. Dynamic Details’ template always contains a reference to the parent, so in order to retrieve a template we must include the parent Entity ID in the URL:

/api/{DomainName}/entities/{EntityName}/{EntityID}/dyndetails/{DynamicDetailTableName}/template

Request (GET):

/api/crm/entities/activities/3227ccb6-72f9-41e6-ae55-05b787c6a173/dyndetails/dycmActivitiesDynDetails/template

Response:

{
    "dyID": null,
    "cmActivitiesID": "3227ccb6-72f9-41e6-ae55-05b787c6a173",
    "dycmActDetDescription": null
}

Dynamic Detail CRUD operations

Dynamic Details are in essence sub-collections of our original Entity instance. The API provides full CRUD functionality for them, just like we do for Entities.

Insert a new Dynamic Detail row

We have a template for our dynamic detail, let’s use it to insert a new row under our chosen activity instance. We can accomplish that by issuing a POST request to:

/api/{DomainName}/entities/{EntityName}/{EntityID}/dyndetails/{DynamicDetailTableName}/

Request (POST):

/api/crm/entities/activities/3227ccb6-72f9-41e6-ae55-05b787c6a173/dyndetails/dycmActivitiesDynDetails

Payload:

{
    "dyID": "996fc264-446c-4eb0-9270-487396464bb3",
    "cmActivitiesID": "3227ccb6-72f9-41e6-ae55-05b787c6a173",
    "dycmActDetDescription": "First DynDetail Row"
}

Response: Http Status 200 (OK)

Retrieving Dynamic Details’ Content

Let’s now validate that we have indeed inserted a new row in the dycmActivitiesDynDetails dynamic detail for our Activity.

Every Entity instance will list it’s dynamic details’ table contents at /api/{DomainName}/entities/{EntityName}/{EntityID}/dyndetails/{DynamicDetailTableName}

Or, in our case /api/crm/entities/activities/3227ccb6-72f9-41e6-ae55-05b787c6a173/dyndetails/dycmActivitiesDynDetails (GET) :

Response:

[
  {
    "dyID": "996fc264-446c-4eb0-9270-487396464bb3",
    "cmActivitiesID": "3227ccb6-72f9-41e6-ae55-05b787c6a173",
    "dycmActDetDescription": "First DynDetail Row"
  }
]

Updating Dynamic Details

One would expect that in order to update a row in our Dynamic Details table, we would issue a PUT request to our dynamic detail table URI, suffixed with the detail ID. This is exactly the case.

So, in order to update our newly created row:

Request (PUT):

/api/crm/entities/activities/3227ccb6-72f9-41e6-ae55-05b787c6a173/dyndetails/dycmActivitiesDynDetails/996fc264-446c-4eb0-9270-487396464bb3

Payload:

{
  "dyID": "996fc264-446c-4eb0-9270-487396464bb3",
  "cmActivitiesID": "3227ccb6-72f9-41e6-ae55-05b787c6a173",
  "dycmActDetDescription": "First DynDetail Row Changed"
}

Response: Http Status 200 (OK)

Note: Retrieve the contents of our detail table using our previous URI, and validate that the description has indeed changed.

Retrieving Entity and Dynamic Detail Data in a single call

At this point, the inquisitive reader might be thinking that it is not efficient to perform a request to retrieve entity data, and a second one to retrieve dynamic details.

The API allows the caller to retrieve both Entity and Dynamic Details’ data by appending a URL parameter to the Entity URI called “includeDynDetails” with a value of “true”.

Let’s try it /api/crm/entities/activities/3227ccb6-72f9-41e6-ae55-05b787c6a173.json?includeDynDetails=true:

{
  "ID": "3227ccb6-72f9-41e6-ae55-05b787c6a173",
  "ActivityTypeID": "9cf4259f-fb78-4374-8253-608260460033",
  "CreatorID": "dfc21bdc-b753-43ad-bb37-3b6c8942e358",
  "OwnerID": "dfc21bdc-b753-43ad-bb37-3b6c8942e358",
  "AccountID": null,
  "ContactPersontID": null,
  "OriginActivityID": null,
  "RevNum": 4,
  "ReferenceNum": null,
  "Subject": "Έργο σε γήπεδο ΕΠΟ",
  "Description": "Έργο σε γήπεδο ΕΠΟ",

  "ActivitiesToFollow": [],
  "DynamicDetails": {
    "dycmActivitiesDynDetails": [
      {
        "dyID": "996fc264-446c-4eb0-9270-487396464bb3",
        "cmActivitiesID": "3227ccb6-72f9-41e6-ae55-05b787c6a173",
        "dycmActDetDescription": "First DynDetail Row Changed"
      }
    ]
  }
} 

Notice the “DynamicDetails” property in the payload. It contains* an array of rows for each Dynamic Detail table defined for our Entity*.

Note: Clients cannot insert or update Dynamic Details data by POST or PUT -ing it along with Entity data. The request will at best ignore dynamic details or fail, depending on the undelying configuration of the Galaxy platform.

Deleting Dynamic Detail data

As you would expect, issuing a DELETE request to the Dynamic Details row URI will delete the row.

Request (DELETE):

/api/crm/entities/activities/3227ccb6-72f9-41e6-ae55-05b787c6a173/dyndetails/dycmActivitiesDynDetails/996fc264-446c-4eb0-9270-487396464bb3

Response: Http Status 200 (OK)

As a convenience method, the API allows clients to delete all data for a Dynamic Detail Table by issuing a DELETE request to the Dynamic Detail Table URI.

Request (DELETE):

/api/crm/entities/activities/3227ccb6-72f9-41e6-ae55-05b787c6a173/dyndetails/dycmActivitiesDynDetails/

Response: Http Status Code 200 (OK)

Issue a GET request on the same URI to retrieve all data, and validate that we have indeed erased all rows for our Dynamic Detail Table.

Reports

Every Business Domain will list it’s Reports under /api/{DomainName}/reports. The list returned is identical to list of enumerations, containing a Name and a URI:

[
    {
        "Name": "AccountFinExport",
        "Uri": "/api/glx/reports/accountfinexport"
    },
    {
        "Name": "AccountLedgerExport",
        "Uri": "/api/glx/reports/accountledgerexport"
    },
    ...
]

Report metadata

To fetch a report’s default view metadata we do a GET request to URI /api/{DomainName}/reports/{ReportName}/metadata

Request:

/api/glx/reports/allocamountbalances/metadata

Response:

    {
        "CustomViews": [],
        "Filters": [
            {
                "Name": "FCompID",
                "Description": "Εταιρία",
                "FieldName": "CompID",
                "Kind": "Misc",
                "DisplayAs": "LookupSelector",
                "Behaviour": null,
                "Required": true,
                "Visible": true,
                "DataType": "Guid",
                "PredefinedValues": {
                    "From": "59c7f302-f5b0-45f6-8a32-6c7f64e91c00",
                    "To": "59c7f302-f5b0-45f6-8a32-6c7f64e91c00",
                    "Value": "59c7f302-f5b0-45f6-8a32-6c7f64e91c00"
                },
                "AcceptedValues": {
                    "AcceptedValuesList": "/api/glx/lookups/company",
                    "DisplayField1": "Code",
                    "DisplayField2": "Title",
                    "KeyField": "ID",
                    "IndexField": null,
                    "ParentField": null,
                    "SortFields": [
                        {
                            "Name": "IsSystem",
                            "Direction": "Descending"
                        },
                        {
                            "Name": "Code",
                            "Direction": "Ascending"
                        },
                        {
                            "Name": "Title",
                            "Direction": "Ascending"
                        }
                    ]
                },
                "AllowedOperators": [
                    "Equal",
                    "NotEqual"
                ],
                "UseRange": false,
                "AllowNullState": true
            }
            ...
        ],
        "CurrentUserReportView": {
            "Name": "AllocAmountBalances",
            "Uri": "/api/glx/reports/AllocAmountBalances"
        },
        "Title": "Προορισμός ποσών επιμερισμού",
        "Description": "Προορισμός ποσών επιμερισμού",
        "Name": "AllocAmountBalances",
        "Uri": "/api/glx/reports/allocamountbalances"
    }

To fetch a report’s custom view metadata we do a GET request to URI /api/{DomainName}/reports/{ReportName}/custom/{CustomReportViewName}/metadata

Report execution

To execute a report we need to do a POST request to report’s URI as it returned in the reports list /api/{DomainName}/reports/{ReportName}. The report will be executed using the default view and filters. Post data contain the filters will be used during execution.

{
    "filters": [
        {
            "field name": "[value,operator]"
        },
        ...
    ]
}

e.g.

{
    "filters": [
        {
            "fltBankCode": "[0001,LessOrEqual]"
        },
        {
            "fltBankCode": "[0001,GreaterOrEqual]"
        }
    ]
}

Request:

/api/glx/reports/allocamountbalances

Response:

{
    "DocumentId": "02ae5862-59b8-4c55-969b-d9648afa4e2f",
    "Pages": [
        {
            "Height": 793.7008,
            "Width": 1122.52
        },
        ...
    ],
    "Title": "Προορισμός ποσών επιμερισμού",
    "Name": "gxAllocAmountBalances",
    "Uri": "/api/glx/reports/data/02ae5862-59b8-4c55-969b-d9648afa4e2f"
}

To execute a report using a custom view we need to do a POST request to report’s URI /api/{DomainName}/reports/{ReportName}/custom/{CustomReportViewName}

Report fetch data

To fetch the executed report’s data we do a GET request to report’s execution uri. The response will provide the first page of the executed report.

Request:

/api/glx/reports/data/02ae5862-59b8-4c55-969b-d9648afa4e2f

Response:

{
    "Title": "Προορισμός ποσών επιμερισμού",
    "TotalPages": 4,
    "DocumentId": "02ae5862-59b8-4c55-969b-d9648afa4e2f",
    "PageContents": [
        {
            "Content": "\ufeff<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\" ... </table>",
            "PageNumber": 1
        }
    ]
}

Optional url query parameters Use the page parameter to get specific pages of the executed report. The format is comma separated pages and / or page range e.g. page=1,3,5,10-15,20-25. Use the format parameter to get the report data to a specific format, valid values are pdf, excel, html. Additionally to the format parameter is the attachment which defines if the response will be saved to disk (value=true) or the browser will try to render the response (value=false).

Reports history

To fetch a list of executed reports and refetch its data do a GET request to URI /api/{DomainName}/reports/history

Request:

/api/glx/reports/history

Response:

[
    {
        "StatusID": 2,
        "Status": "Finished",
        "Title": "Προορισμός ποσών επιμερισμού on 25/2/2019 3:31:01 μμ",
        "Description": "Κατάσταση: Finished, Λεπτομέρειες: Σελίδες: 1 (00:00:03)",
        "Name": "02ae5862-59b8-4c55-969b-d9648afa4e2f",
        "Uri": "/api/glx/reports/data/02ae5862-59b8-4c55-969b-d9648afa4e2f"
    },
    ...
]

You can use the response’s Uri to fetch the executed report’s data.

Documents

Documents are files and folders that build a diretcory tree attached to any application’s entity. Documents are supported by document providers and consist of providers, provider’s setup items, private and public folders, other folders as subfolders to private and public folders and finally files contained in folders. Available providers are Database, FTP, SSH FTP, SharePoint, Azure and Network Share. Notice that DataBase document providers do not contain setup items unlike the rest providers.

Important: Documents have been implemented and setup on the client side. Because the API is running on the server side there is a need to setup the client providers and their setup items on the server side also.

Documents list

To get an entity’s documents do a GET request to URI /api/{DomainName}/documents/{EntityID}

Request:

/api/glx/documents/779bc63d-b354-423c-a648-1a2346c6f82b

Response:

[
    {
        "Files": [],
        "ID": "9E49E69D-B13D-4A76-A9DE-9AA1EA770064",
        "ParentID": null,
        "RelativeID": "779bc63d-b354-423c-a648-1a2346c6f82b",
        "Description": "FTP document provider",
        "Kind": 102,
        "Size": 0,
        "Revision": 0,
        "RevisionID": null,
        "FileName": null,
        "LinkID": null,
        "OwnerID": null,
        "Name": "9E49E69D-B13D-4A76-A9DE-9AA1EA770064",
        "Uri": null
    },
    {
        "Files": [],
        "ID": "a59be590-de91-478d-b99a-4301007e7766",
        "ParentID": "9E49E69D-B13D-4A76-A9DE-9AA1EA770064",
        "RelativeID": "779bc63d-b354-423c-a648-1a2346c6f82b",
        "Description": "FTP_site",
        "Kind": 102,
        "Size": 0,
        "Revision": 0,
        "RevisionID": null,
        "FileName": null,
        "LinkID": null,
        "OwnerID": null,
        "Name": "a59be590-de91-478d-b99a-4301007e7766",
        "Uri": null
    },
    {
        "Files": [],
        "ID": "f1d1638c-cdfe-47cc-a824-a14c53d0d1a6",
        "ParentID": "a59be590-de91-478d-b99a-4301007e7766",
        "RelativeID": "779bc63d-b354-423c-a648-1a2346c6f82b",
        "Description": "Public",
        "Kind": 102,
        "Size": 0,
        "Revision": 0,
        "RevisionID": null,
        "FileName": null,
        "LinkID": null,
        "OwnerID": null,
        "Name": "f1d1638c-cdfe-47cc-a824-a14c53d0d1a6",
        "Uri": null
    },
    {
        "Files": [
            {
                "ID": "0a2e64f0-32e5-4341-b5bd-d20f11ba39c7",
                "ParentID": "bd443d5d-655c-4f12-8d3c-736ff042e926",
                "RelativeID": "779bc63d-b354-423c-a648-1a2346c6f82b",
                "Description": "FTP_site:Borland Delphi 7.msi",
                "Kind": 2,
                "Size": 5818880,
                "Revision": 0,
                "RevisionID": null,
                "FileName": "/Entities/779bc63d-b354-423c-a648-1a2346c6f82b/pcl/file1.pdf",
                "LinkID": "",
                "OwnerID": "55014932-3b1b-4ad6-a602-579d20b3ec43",
                "Name": "0a2e64f0-32e5-4341-b5bd-d20f11ba39c7",
                "Uri": "/api/glx/documents/DownloadFile/0a2e64f0-32e5-4341-b5bd-d20f11ba39c7"
            }
        ],
        "ID": "bd443d5d-655c-4f12-8d3c-736ff042e926",
        "ParentID": "f1d1638c-cdfe-47cc-a824-a14c53d0d1a6",
        "RelativeID": "779bc63d-b354-423c-a648-1a2346c6f82b",
        "Description": "FTP_site:pcl",
        "Kind": 102,
        "Size": 0,
        "Revision": 0,
        "RevisionID": null,
        "FileName": "",
        "LinkID": "",
        "OwnerID": "55014932-3b1b-4ad6-a602-579d20b3ec43",
        "Name": "bd443d5d-655c-4f12-8d3c-736ff042e926",
        "Uri": null
    },
    {
        "Files": [
            {
                "ID": "33f6b4bf-c746-444d-93c3-19e146f99fca",
                "ParentID": "08dc2c38-fe7f-4808-8920-2f5643887ee5",
                "RelativeID": "779bc63d-b354-423c-a648-1a2346c6f82b",
                "Description": "FTP_site:Borland Delphi 7.msi",
                "Kind": 2,
                "Size": 5818880,
                "Revision": 0,
                "RevisionID": null,
                "FileName": "/Entities/779bc63d-b354-423c-a648-1a2346c6f82b/pcl/Borland Delphi 7.msi",
                "LinkID": "0a2e64f0-32e5-4341-b5bd-d20f11ba39c7",
                "OwnerID": "55014932-3b1b-4ad6-a602-579d20b3ec43",
                "Name": "33f6b4bf-c746-444d-93c3-19e146f99fca",
                "Uri": "/api/glx/documents/DownloadFile/33f6b4bf-c746-444d-93c3-19e146f99fca"
            }
        ],
        "ID": "08dc2c38-fe7f-4808-8920-2f5643887ee5",
        "ParentID": "f1d1638c-cdfe-47cc-a824-a14c53d0d1a6",
        "RelativeID": "779bc63d-b354-423c-a648-1a2346c6f82b",
        "Description": "FTP_site:pcl2",
        "Kind": 102,
        "Size": 0,
        "Revision": 0,
        "RevisionID": null,
        "FileName": "",
        "LinkID": "",
        "OwnerID": "55014932-3b1b-4ad6-a602-579d20b3ec43",
        "Name": "08dc2c38-fe7f-4808-8920-2f5643887ee5",
        "Uri": null
    },
    ...
]

Optional url query parameters

Use the provider parameter to get the entity’s documents from a specific provider. Valid values are (case insensitive): db, ftp, sshftp, sharepoint, azure and NetworkShare. The parameter will be ignored if its value is wrong. Additionally to the provider parameter you can use the setup parameter that defines the provider’s setup name so you can filter the documents to a specific provider setup. This parameter will be ignored if the provider parameter is not defined.

Download a file

To download a document file we need to do a GET request to file’s URI as it returned in the documents file list /api/{DomainName}/documents/{file ID}

Request:

/api/glx/documents/DownloadFile/0a2e64f0-32e5-4341-b5bd-d20f11ba39c7

Response: The raw data.

Extending the API

All the functionality we’ve seen up to this point originates directly from the components hosted under the SLNet host the API runs on.

However, we can extend the API in several ways:

  • We can create new aliases for entities
  • We can perform transformations on entity JSON payloads
  • We can define custom relations between entities
  • We can inject pre and post custom actions in our CRUD calls

The AliasMap configuration file

The AliasMap is an xml file, where we can configure extensions and/or customizations for our API. It follows a relatively simple structure that mirrors the structure of metadata the api maintains internally to map URLs to functionality.

Below we can examine the structure of an AliasMap file:

<?xml version="1.0" encoding="utf-8" ?>
<AliasMapConfig>

  <Alias Key="glx::customers"
         UriName="customers"
         ObjectName="gxTrader"
         EntityName="Galaxy:Trader"
         IsArray="false"
         IsService="false"
         CollectionType="Glx.Data.DataObjects.gxTraderCollectionFactory, Glx.Schema"
         CollectionItemType="Glx.Data.DataObjects.gxTraderDataObject, Glx.Schema">

    <JQOutgoing>.Customers[0] as $cust| $cust|with_entries(select(.key != "CompanySites"))|del(.Sites) + { Sites:$cust.Sites | map(select(.Description == "Primary"))}</JQOutgoing>
    <JQIncoming></JQIncoming>
  </Alias>

  <Alias Key="glx::commercialentry" >
    <OnBeforePost>
      <Actions>
        <InvokeAction ClassName="Glx.SFA.Custom.WebAPI.OnBeforeCommercialEntryPost" AssemblyName="Glx.SFA.Custom" >
          <Properties>
            <Set Prop="Flags" Value="1;2;3;4;5" />
            <Set Prop="Origin" Value="Sales" />
            <Set Prop="Source" Value="eOrder" />
          </Properties>
        </InvokeAction>
      </Actions>
    </OnBeforePost>
  </Alias>

  <Alias Key="glx::mediator">
    <Relations>
      <Relation Name="customers" ParentAlias="glx::mediator" RelationObject="Galaxy:MoRE" RelationOperation="GetCustomers" ResultObjectBaseURI="/api/glx/entities/customers" ResultIDField="TraderID">
        <Parameters>
          <Parameter Name="CompanyID" Type="Profile" />
          <Parameter Name="MediatorID" Type="RequestObject" Alias="ID" />
        </Parameters>
      </Relation>
    </Relations>
  </Alias>

</AliasMapConfig>

The AliasMap.xml file must be placed on the same directory the api DLLs are placed. This is typically under /Apps/WebServices/SLnet.WebExtApi.ServiceInterface in any Galaxy installation.

In the following sections, we will examine the AliasMap file in more detail, and see how we can setup each kind of API extensions.

Creating Aliases to entities

The api follows a standard URL pattern, that originates from the way SLnet itself maintains a registry of available components. This is typically /api/<domain alias>/entities/<entity alias> where <domain alias> and <entity alias> comes from the component itself.

For instance, under Galaxy, there is an Entity called Trader. The URL for the Traders collection would be /api/glx/entities/trader.

However, the Trader entity is in essence a “parent” entity, that is used to describe customers, suppliers, and a number of other entities that participate in transactions with a company.

But what if we wanted to promote customers as a first-class API entity, and give it its own URL at /api/glx/entities/customers ?

We could use the AliasMap, to provide another alias to the Trader entity, and expose it at an additional URL:

<Alias Key="glx::customers"
         UriName="customers"
         ObjectName="gxTrader"
         EntityName="Galaxy:Trader"
         IsArray="false"
         IsService="false"
         CollectionType="Glx.Data.DataObjects.gxTraderCollectionFactory, Glx.Schema"
         CollectionItemType="Glx.Data.DataObjects.gxTraderDataObject, Glx.Schema">
</Alias>

Note: It is apparent that configuring a new alias mapping for an entity requires some internal knowledge of the components themselves, like the implementing classes and existing SLNet registry name of our original entity.

Now, if we start up the application server, we will see that two URLs will give us the same list of Traders:

  • /api/glx/entities/trader
  • /api/glx/entities/customer

In essence, all we did was to provide the client with another URL where they can retrieve a list of traders.

However, this is of little value if we’re talking about the exact same list and payload. The Trader payload is a larga data structure, containing a multitude of information that superseeds the data we’d like to see for a customer.

In the next section we will see how we can define a transformation on the original payload that will return only the Customer’s information, rather than the full Trader payload.

Transforming JSON payloads

If we examine the Trader payload, there is a nested array under the “Customers” property, which contains a detail object describing our Customer data for that Trader.

Using the AliasMap, we can define a JSON trasformation using the JQ format and extract only the data we need from the original payload:

<Alias Key="glx::customers"
         UriName="customers"
         ObjectName="gxTrader"
         EntityName="Galaxy:Trader"
         IsArray="false"
         IsService="false"
         CollectionType="Glx.Data.DataObjects.gxTraderCollectionFactory, Glx.Schema"
         CollectionItemType="Glx.Data.DataObjects.gxTraderDataObject, Glx.Schema">

    <JQOutgoing>
        .Customers[0] as $cust| $cust|with_entries(select(.key != "CompanySites"))|del(.Sites) + { Sites:$cust.Sites | map(select(.Description == "Primary"))}
    </JQOutgoing>
  </Alias>

The result of this transformation is the data found in the original payload for the first customer in the nested list, with a single nested customer site, specifically the one whose description is “Primary”.

Using the <JQOutgoing> element we can instruct the api to execute this trasformation before it returns the data to the caller.

We can also define the reverse transformation, which will take place when a client posts JSON to the api and before any other processing occurs, using the <JQIncoming> element.

A full examination of the JQ format is past the scope of this document. An introduction to JQ can be found at GitHub, which is a good starting point for someone getting acquainted with JQ.

Entity Relations

Entity Pre / Post Actions for CRUD