...
The eNVD API is available to integrators that deliver livestock assurance and movement related solutions to the Australian red meat industry. Licensed integrators are transitioning from REST to GraphQL with further details below.
...
About GraphQL
GraphQL is a syntax that describes how to ask for or manipulate data, and is generally used to load data from a server (eNVD) to a client (you), or for you to create or request changes to data.
...
It lets the client specify exactly what data it needs.
It makes it easier to aggregate data from multiple sources.
It uses a type system to describe data.
With GraphQL, the client is able to make a single call to fetch the required information rather than to construct several REST requests to fetch the same. The client is also able to easily understand what is available in the API and how to request it as the endpoint itself provides such documentation via an introspection query.
eNVD uses GraphQL as it offers more flexibility for developers. The option to precisely create or retrieve a consignment (or multiple consignments) including the information that a user wants is a great advantage over sending multiple REST calls to achieve the same.
Visit the GraphQL website to learn more about how GraphQL works.
Authentication
eNVD require requires clients to use an API key which will allow application users to login.
You can then login users from 2 different systems.
...
You can apply for a API key by emailing technicalsupport@integritysystems technicalsupport@IntegritySystems.com.au.
The current eNVD API uses JSON web tokens. When an HTTP POST request is sent to the Auth Server, a valid token is returned or an invalid request. The returned token must then be passed in as a request header with all future requests.
To request a token in production, call the following URL URI : auth.integritysystems.com.au/connect/token
To request a token in UAT, call the following URL URI : auth-uat.integritysystems.com.au/connect/token
Request headers
HTTP Header | Description | Example |
---|---|---|
Content-type | The MIME type of the body of the request | Content-Type: application/x-www-form-urlencoded |
...
HTTP Body | Description | Example |
---|---|---|
Client ID | Client identifier of your application | client_id=mySuperApp |
Client Secret | Your assigned client secret | client_secret=mysecret |
Grant Type | We use Password Grant Type | grant_type=password |
Scope | Available values include lpa_scope or nlis_scope, dependent on your user | scope=lpa_scope |
UserName | When the scope is for LPA users, the username is the PIC-userid (note the - between PIC and userid) | username=Q1KK0786-1005672 |
Password | The account holders holder's relevant password | password=userpassword |
...
Code Block |
---|
{ "access_token" : "eyJ0eXAiOiJKV1Q...", "expires_in" : "3600", "token_type" : "Bearer" } |
Response codes
If a response code of 200 is returned, it means you have successfully authenticated and can access your token.
...
For other response codes, check the message included in the body of the response object.
Current REST APIs
Creating a consignment using the eNVD REST APIs may have looked something like:
Code Block |
---|
POST /Auth: LPA oauth2 token request
POST /Consignments: Create consignment
PUT /Forms: Create Data for Consignment form {LPA.C.1}
POST /Subforms: Create consignment Subform values for Program LPA.C.1 {Quantity}
PUT /Subforms: Update the consignment Subform values for Program LPA.C.1 {Quantity}
POST /Subforms: Create consignment Subform values for Program LPA.C.1 {PartB}
PUT /Subforms: Update the consignment Subform values for Program LPA.C.1 {PartB}
PUT /Consignments: Update completed status to submitted
GET /Print: Print Consignment |
Using GraphQL for eNVD
Now that you’ve been given a GraphQL primer, let’s explore some examples of how to use it for eNVD.
Tooling
There are many tools that can be used to play with GraphQL. Two that we recommend are:
Insomnia (https://insomnia.rest/)
GraphQL Playground (https://www.apollographql.com/docs/apollo-server/testing/graphql-playground/)
Consignment retrieval
Using GraphQL this process can be simplified to look more like (with inline comments to help explain what each field is):
...
language | graphql |
---|
...
Program accreditation
To be able to create an NVD via the eNVD system, a user must be LPA accredited. The table below outlines the program accreditation required for each type of form supported by eNVD.
Forms | Program Accreditation | ||||
---|---|---|---|---|---|
| LPA | EU | MSA | NFAS | |
NVD | ✓ |
|
|
| |
NVD EU | ✓ | ✓ |
|
| |
MSA | ✓ |
| ✓ |
| |
NFAS | ✓ |
|
| ✓ | |
NFAS EU | ✓ | ✓ |
| ✓ | |
Health Dec |
|
|
|
|
Using the GraphQL API
Must like a REST API, interacting with a GraphQL API involving making calls to the endpoint in way that follows GraphQL rules and the types of the API. For initial learning and experimentation we recommend two use either:
Playground, a web based interface which is available by navigating in your browser to one of our GraphQL API endpoints
Please review the documentation for each tool to learn how to use it to interact with a GraphQL API.
Examples
The following examples provide some eNVD specific context to how interactions with the GraphQL API work. Please bare in mind that these are examples only and queries should be modified to suit your needs, such as reducing the fields in the requests to only ask for the data you need.
Retrieving a consignment
In the existing REST API, retrieving a consignment and its forms and subforms would require multiple requests. Using GraphQL this process can be simplified to look more like the below, asking for all information in a single call. Note that there are inline comments to help explain what each field is:
Code Block | ||
---|---|---|
| ||
query { consignments { totalCount items { # The consignment number number # The forms attached to this consignment forms { # Program name e.g. LPAC1 type # The form serial number serialNumber } # The url for the printed form pdfUrl # ProgramThese name e.g. LPAC1 typeare self-explantory meta fields that are pre-filled during creation/update submittedAt # The form serial number updatedAt serialNumber updatedBy } # The urlcurrent status forof the consignment printed form pdfUrlstatus # These are# self-explantoryThe metacurrent fieldsspecies thatof arethe pre-filledconsignment during creation/update submittedAtspecies submittedBy # These are updatedAtthe movement fields for all updatedByforms # The currentowner status{ of the consignment status address { # The current species of the consignment line1 species # These are thepostcode movement fields for all forms owner {state address { town line1 } postcode name state pic town } } destination { name address { pic } line1 destination { address { postcode line1 state postcode town state } town name } name pic pic } } consignee { consignee { address { line1 postcode state town } name pic } origin { address { line1 postcode state town } name pic } # A global declaration across all forms in the consignment declaration { accept address { line1 postcode state town } certificateNumber date email fullName phone signature } # The list of questions for the consignment based on the forms attached (this will be dynamic due to this) questions { # The question id, you will need ths in order to answer it id # The question text, i.e. the actual question itself text # The question help, a long text field in markdown to explain the question help # The type of the question. This will help in choosing how to diplay the question, # The type can be SINGLE_CHOICE, MULTIPLE_CHOICE, STRING, NUMBER etc type # Whether this question is deprecated i.e. has been removed from rotation deprecated # If this question has a limited field of answers, this will contain how to display the # answer and what the value to send for it is # If this contains nothing, then the user can answer it with anything they want acceptableAnswers { displayName value } # This is a list of questions that are related to this one, can be n-levels deep # When there is no `trigger` defined, this means that the child question is always visible # When there is a `trigger` defined, this means that the child question is only visible when the condition passes # e.g. if the `trigger` is: `{ id: '1', value: 'Yes }`, this means that the question with id "1" must have a value of "Yes" for this to be visible # typically, that question will be the parent question which is containing this child question childQuestions { id ... (same as questions) text trigger { help type id acceptableAnswers { value displayName value } triggers { questionId value } } } # These are the answers to the questions presented above. The `questionId` will allow you to figure out what to insert as the current answer answers { # The question id questionId # The value of the answer value # An index if this is part of an array to indicate position, otherwise null index } } } } } } |
A single consignment request is similar, and you would list fields the same as those in the items field above.
Code Block |
---|
# Or request a single consignment query { consignment(id: "C-12341234") { ... same as above } } |
Notice now that there is only one (1) endpoint at /graphql
which you can query for data from within Forms, Subforms and the URL for the printed Consignment + Forms is already available all within the one call.
...
Create a consignment
Previously, in the V3 API, there were multiple REST calls required in order to create a consignment and attach forms and subforms to it. Each of them had to be done individually .
With GraphQL, you now only need to do one call as shown below. You also have the opportunity to define the information that you want to receive in return for the successful call, which helps enable verifying changes are as expected and keeping in sync, all within a single call.
...
language | graphql |
---|
...
in a sequence such as:
Code Block |
---|
POST /Auth: LPA oauth2 token request
POST /Consignments: Create consignment
PUT /Forms: Create Data for Consignment form {LPA.C.1}
POST /Subforms: Create consignment Subform values for Program LPA.C.1 {Quantity}
PUT /Subforms: Update the consignment Subform values for Program LPA.C.1 {Quantity}
POST /Subforms: Create consignment Subform values for Program LPA.C.1 {PartB}
PUT /Subforms: Update the consignment Subform values for Program LPA.C.1 {PartB}
PUT /Consignments: Update completed status to submitted
GET /Print: Print Consignment |
With the GraphQL API you now only need to make one call (called a mutation) as shown below. Note that questions and answers are now centralised, with the answers automatically mapped out to all relevant forms. You also have the opportunity to define the information that you want to receive in return for the successful call, which helps enable verifying changes are as expected and keeping in sync.
Code Block | ||
---|---|---|
| ||
mutation { createOrSaveConsignment(input: { # The forms to attach to ths consignment forms: [LPAC1] # The initial movement date estimated for this consignment # The transporter movement date will be applied in answers movementDate: "2020-10-15" destination: { name: "Joe Bloggs" pic: "AAAAAAAA" } # The list of answers for any questions (partial or otherwise) answers: [ # This shows an example of a SINGLE_CHOICE question being answered with Yes { questionId: "951", index: 1null, value: "breed2Yes" } # This show an example of {the questionId: "96", index: 1, value: "Heifer : F" }`quantity` subform being answered a an array # as {can questionId: "96", index: 0, value: "Bull : M" }be seen by the definition of the `index` parameter { questionId: "992", index: 01, value: "Yes8" } { questionId: "992", index: 10, value: "Yes4" } ] }) { questionId: "3", index: 1, data { value: "2" } forms { questionId: "3", index: 0, value: "2" } type { questionId: "4", index: serialNumber0, value: "breed1" } } { questionId: "4", pdfUrlindex: 1, value: "breed2" } } { } } |
Benefits
As can be seen in the above example, you won’t need to:
Create each form individually
Create each subform individually
Duplicate answers across forms and subforms
Use multiple endpoints to achieve this, only 1 call is required!
This helps reduce the number of calls which reduces network traffic and the time to process a query or change. It also ensures alignment in the data across all forms.
Consignment updates
An update looks very similar to a create, where you provide what is essentially a patch of the information you wish to change. With partial updates you just provide the information you want to change, not a complete picture of how the data must be after the change.
Code Block | ||
---|---|---|
| ||
mutation { createOrSaveConsignment(input: { number: "C-23452345" answers: [ questionId: "5", index: 1, value: "Heifer : F" } { questionId: "5", index: 0, value: "Bull : M" } { questionId: "8", index: 0, value: "Yes" } # Here we are updating the values for the `quantity subform` { { questionId: "938", index: 1, value: "10Yes" } { questionId: "939", index: 0, value: "2https://www.mla.com.au/globalassets/mla-corporate/mla_logo_home.png" } { questionId: "949", index: 1, value: "1" } { questionId: "94", index: 0, value: "1" } https://www.mla.com.au/globalassets/mla-corporate/mla_logo_home.png" } ] }) { data { questionId: "95", index: 0, value: "breed1"number } forms { questionId: "95", index: 1, value: "breed2" } type { questionId: "96", index: 1, value: "Heifer : F" serialNumber } {pdfUrl questionId: "96", index: 0, value:} "Bull : M" } { questionId: "99", index: 0, value: "Yes" } { questionId: "99", index: 1, value: "Yes" } ] }) { data { ... (same as create) } } } |
Consignment printing
You do not need to do a special call to print. The payload will contain a pdfUrl
if requested which is the printed consignment, which can then be used separately to execute the print.
Mapping from Forms to Questions
A part of the move to using the GraphQL API is understanding how the new format of questions and answers maps back to forms, subforms and the data they contain. The attachment below helps to explain this by mapping the IDs of the questions in the GraphQL API to the form and subform elements using their paths. As example, given the following excerpt from the MSA form:
Code Block |
---|
{ "version": "0.1", "showTitle": false, "type": "object", "format": "IsValidDateOfDispatch,IsValidDeclarationDate", "properties": { "owner": { "type": "object", "properties": { "msa-reg-num": { "title": "MSA Registration no. of owner", "type": "string", } } |
Benefits
As can be seen in the above example, you won’t need to:
Create each form individually
Create each subform individually
Duplicate answers across forms and subforms
Use multiple endpoints to achieve this, only 1 call is required!
This helps reduce the number of calls which reduces network traffic and the time to process a query or change. Importantly, it also ensures alignment in the data across all forms.
Update a consignment
An update looks very similar to a create, where you provide what is essentially a patch of the information you wish to change again using a mutation. With partial updates you just provide the information you want to change, not a complete picture of how the data must be after the change.
Code Block | ||
---|---|---|
| ||
mutation { createOrSaveConsignment(input: { number: "C-12345678" answers: [ # Here we are updating the values for the `quantity subform` { questionId: "4", index: 0, value: "Hereford" } ] }) { # Same as create data { answers { index "maxLength":questionId 4 value }, } "pic": { } "title": "Property Identification Code (PIC) of owner", "dictionaryKey": "owner", "type": "string", "maxLength": 8, } } |
Printing a consignment
In a consignment query or mutation there is a field pdfUrl
which, if requested, contains a URL which can be used to print the consignment.
Mapping from Forms to Questions
As seen in the above examples, part of the move from the REST API to the GraphQL API is understanding how the new format of questions and answers maps back to forms and subforms. To help navigate this mapping, the attached json file contains mappings between the two.
View file | ||
---|---|---|
|
It contains an array of entries such as:
Code Block |
---|
{ "Id": "1", "Text": "Please provide a description of the livestock moving", "formatForm": "PIC_IsValidLPAC1", } "Field": "description.quantity" }, |
the MSA Registration no. of owner would be identified under the MSA form column heading as:
Code Block |
---|
owner.msa-reg-num |
View file | ||
---|---|---|
|
FAQ
Are there limits as to how much I can request in one call?
Yes there are limits but not many. We trust that consumers will operate a fair use manner, only requesting the data they require at the rate which they require it.
An example of this would be in a scenario where you need to know the most recently modified consignments. For such a scenario it is generally reasonable to request perhaps the last 20 records and for each record just the fields that are required to make the decision of which consignment to dive deeper into. Then, upon perhaps user selection of a consignment, you may wish to make a second query to request all the information from that consignment. While in GraphQL this is possible to do in the first query, requesting all the information for each consignment in the first call, this is beyond what the use case calls for and is advised against.
This leads into where there are limits.
In queries that return a list of records such as a search for consignments there will be limits. These are documented in the API and if you breach the limit then you will be informed as such. As per the approach of fair use we try to keep these limits high so they do not hamper reasonable normal operation.
A second scenario where you will find limits is in the query depth that is allowed. This essentially describes the level of nested information that you can reach in a single call. The schema of the data does not require much depth to gain all information for a consignment, however where references to other records such as one consignment linking to another, limits will apply. This is best explained visually by the scenario below where you can view a consignment and the consignment from which it was copied. You could then potentially go further requesting the consignment from which it was copied and so on, but limits apply to keep this reasonable.
Code Block |
---|
query {
getConsignment(number: "C-100000001") {
answers [...]
copiedFrom { // The consignment that this one was copied from
answers [...]
copiedFrom { // The same link again, reaching further into the chain of consignments
answers [...]
copiedFrom {
... // This could go as deep as the links allow, so a hard limit of depth is applied
}
}
}
}
} |
How are errors handled in the API?
The way that errors are handled within a GraphQL API is fundamentally different to a REST API in some important ways. When you query a GraphQL API each field can and may resolve independently, meaning each field’s resolution may result in success or failure. This can result in your request returning an HTTP status code that indicates success, however you need to look at the data and errors returned to see if the fields resolved successfully. See the examples for the structure that you can expect in responses to help detect and process errors. It is then the job of the API consumer to decide how to manage that error, such as creating and executing a new partial query to retrieve the data that was not successful in the first queryIn this entry there are 3 pieces of information to help you find the form and subform based piece of information, such as how it would referred to using the REST API:
“Text” - The question text that is presented to the user. This helps to double check that the question is the right one, and provides text they can choose to use in their system.
“Form”- The form that the question links to. There are separate entries in the JSON for each form, so if a question is used for multiple forms (which is often the case) then it will appear multiple times, once for each form. Note that form names have had their periods removed, so LPA.C.1 will be LPAC1 in the JSON.
“Field” - This points to the JSON schema field in the form model, and is the way that integrators can map between the old model using this field and ‘Form’, and the new model which uses ‘ID’
As a further example / explanation of the “Field”, given the following excerpt from the MSA form model:
Code Block |
---|
{
"version": "0.1",
"showTitle": false,
"type": "object",
"format": "IsValidDateOfDispatch,IsValidDeclarationDate",
"properties": {
"owner": {
"type": "object",
"properties": {
"msa-reg-num": {
"title": "MSA Registration no. of owner",
"type": "string",
"maxLength": 4
},
"pic": {
"title": "Property Identification Code (PIC) of owner",
"dictionaryKey": "owner",
"type": "string",
"maxLength": 8,
"format": "PIC_IsValid"
}
}, |
the MSA Registration no. of owner would be identified as:
Code Block |
---|
owner.msa-reg-num |
The final piece of information is to help you map this to the new question and answer format used in the GraphQL API:
“ID” - This is the ID of the question in its new structure, and will become the new way that integrators refer to a question instead of their json path
FAQ
Are there limits as to how much I can request in one call?
We trust that consumers will operate a fair use manner, only requesting the data they require at the rate which they require it. An example of this would be a query for the 20 most recently modified consignments for a list. For such a scenario it is generally reasonable to request just the high level information for each consignment, leaving the detail for future calls.
How are errors handled?
A successful request returns a status of 200, however unlike a REST API call, that does not mean your query or mutation was successful. Instead you need to look in the body of the response and check if any errors were returned.
Code Block |
---|
{
...
"errors": [
{
"message": "",
"path": ""
}
]
} |
See https://graphql.org/learn/serving-over-http/#response for more detail.
How is the performance compared to the REST API?
The performance is excellent, however it does depend on what is requested in some ways that can be appreciated. If you are requesting a large amount of data and the resolution takes a while then that single GraphQL query may take some time to return. Given however that this is achievable in a single call with generally no further calls required as you have all the information you need, it generally results in , the result is greatly improved performance over what may have taken multiple REST calls to achieveoverall performance.
How can I request form data if we’re no longer submitting the data as forms?
...
Playground provides an interface to make queries to the GraphQL API, much like tools such as Insomnia and Postman. To make calls to an authenticated endpoint you must provide authentication headers much as you would for a call using any other tool. Within Playground there is a section where you can enter headers in JSON format, located in the version at the time of writing at the bottom of the screen. It is here that you need to correctly construct the Authorization header that is required along with a valid token, and Playground will pass this along with your calls which should will then succeed.
[Internal] To be completed:
What would be the impact on performance if the api user would use it inconsiderately?