NAV
Homepage Integrity Systems
GraphQL C#

Introduction

A wholly owned subsidiary of Meat & Livestock Australia, the Integrity Systems Company (ISC) is responsible for delivering the red meat industry’s on-farm assurance and through-chain traceability programs. These are the Livestock Production Assurance (LPA) program, and the National Livestock Identification System (NLIS) which together make up Australia’s red meat integrity system.

These programs are connected through LPA National Vendor Declarations (LPA NVDs), which link on-farm assurance to livestock traceability. This makes LPA NVDs the legal document guaranteeing the quality, safety and traceability of Australian red meat products.

The ISC is also responsible for the development and delivery of the Digital Value Chain Strategy which will ensure the Australian red meat industry can make better use of existing and new data. The strategy will deliver improved feedback systems for producers including Livestock Data Link (LDL), along with conducting research and development that seeks to find the best digital technology and database management systems that will strengthen our integrity systems over time.

ISC’s mission is to grow red meat value chain opportunities through integrity and information systems innovation. It is essential to enhance our systems and technologies to stay ahead of our global competitors, maintain our point of difference, and enable Australia’s red meat industry to capture price premiums from consumers and customers who are willing to pay more for higher levels of product assurance.

Our API’s are a combination of SOAP, REST and GraphQL. Our API has predictable, resource-oriented URLs, and uses HTTP response in combination with API Status codes to indicate API errors. We use built-in HTTP features, like HTTP verbs, which are understood by off-the-shelf HTTP clients. JSON and XML is returned in all API responses including errors.

For further information about ISCs APIs contact:

Peter Quigley

ISC Integrator Analyst

pquigley@integritysystems.com.au

02 9436 9246

Authentication

All our API’s require clients to use an API key and include an NLIS username and password.

Your API key is specific to you and your application. Take reasonable care in the use of your API Key and access details.

You can apply for a developer key by emailing pquigley@integritysystems.com.au

Single sign-on (SSO) provides a seamless way for your applications to authenticate/authorise by one username and password for key integrity and information systems, including:

Using this method, your application can obtain an access token scoped to your application. (NLIS, LPA, etc)

The ISC API uses this as a bearer token in the Authorization header to authenticate a call and give restricted access to its resources.

To request a token in production, call the following URI :
https://auth.integritysystems.com.au/connect/token

To request a token in UAT, call the following URI :
https://auth-uat.integritysystems.com.au/connect/token

HTTP Request

To get the token, use this code (note the different scopes for NLIS and LPA):

POST /connect/token HTTP/1.1
Host: auth-uat.integritysystems.com.au
Content-Type: application/x-www-form-urlencoded

client_id=[YOUR CLIENT ID]&client_secret=[YOUR SECRET]&grant_type=password&scope=nlis_scope&username=[NLIS username]&password=[NLIS password]

[Or]

client_id=[YOUR CLIENT ID]&client_secret=[YOUR SECRET]&grant_type=password&scope=lpa_scope&username=[LPA username]&password=[LPA password]

Note: client_id, client_secret, username and password need to be updated with your valid values.

var client = new RestClient("https://auth-uat.integritysystems.com.au/connect/token");
client.Timeout = -1;
var request = new RestRequest(Method.POST);
request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
var body = @"client_id=[YOUR CLIENT ID]&client_secret=[YOUR SECRET]&grant_type=password&scope=nlis_scope&username=[NLIS username]&password=[NLIS password]";
request.AddParameter("application/x-www-form-urlencoded", body,  ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);

Request Headers

HTTP HeaderDescriptionExample
Content-typeThe MIME type of the body of the request.Content-Type: application/x-www-form-urlencoded

Request Body

HTTP BodyDescriptionExample
Client IdClient identifier of your applicationclient_id=mySuperApp
Client SecretYour client secret assignedclient_secret=mysecret
Grant TypeWe use Password Grant Typegrant_type=password
ScopeAvailable values include lpa_scope, nlis_scope, mymla_scope dependent on your userscope=nlis_scope or scope=lpa_scope
UserNameWhen the scope is for NLIS users, the username is your nlis userID. When the scope is for LPA users, the username is a combination of PIC and LPA user ID.username=9PROD2G0 or username=Q1KK0786-1005672
PasswordThe account holders relevant password. Eg LPA, NLIS or myMLA passwordpassword=userpassword

HTTP Response

Response Body Parameters

The above request returns JSON structured like this:

[{
  "access_token" : "eyJ0eXAiOiJKV1Q...",
  "expires_in" : "3600",
  "token_type" : "Bearer"
}]
ParameterDescriptionExample
access_tokenThe access token returned.“access_token” : “eyJ0eXAiOiJKV1Q…”
expires_inNumber of minutes the token will be valid for.“expires_in” : “3600”
token_typeThe type of token generated by the API.“token_type” : “Bearer”

For other response codes, check the message included in the body of the response object.

eNVD

About eNVD

The Livestock Production Assurance (LPA) National Vendor Declaration (NVD) communicates the food safety and treatment status of sheep, cattle or goats every time they move – between properties, to saleyards, or to processors.

NVDs are a legal document that are key to Australian red meat’s traceability and market access. It is crucial that the NVD is filled out accurately. When users tick a box or answer a question on the LPA NVD, they are declaring their on-farm practices meet LPA requirements, and ultimately customer expectations.

Their tick must be backed up by accurate farm records. This is a pledge that the meat from their farm has been produced safely, ethically and meets biosecurity requirements – it means they they stand by what they sell.

The LPA electronic National Vendor Declaration (eNVD) system is a faster, easier way to complete livestock consignments. The eNVD system is not just a place to fill out a NVD – it is a system for completing all consignment paperwork digitally including livestock assurance and health declarations. The system will be continuously improved to create better experiences for users and add value to our industry’s integrity. For more information, visit the ISC website here.

eNVD Authentication

All eNVD API’s require clients to use an API key and include an NLIS or LPA username and password.

The token returned will also include:

A user must be accredited for a program to be able to create that program’s form.

A token will expire 24 hours after creation.

Visit the authentication page for more information on how to use the auth service.

Supported programs

The eNVD API supports the creation and exchange of a number of quality assurance program related forms.

The programs supported by eNVD include:

Livestock Production Assurance

The Livestock Production Assurance (LPA) program is the Australian livestock industry’s on-farm assurance program covering food safety, animal welfare and biosecurity. It provides evidence of livestock history and on-farm practices when transferring livestock through the value chain.

Meat Standards Australia

Meat Standards Australia (MSA) is a national beef and sheepmeat eating quality program. It aims to accurately predict eating quality and ideal cooking methods for individual beef and sheepmeat cuts. It is a paddock-to-plate system ensuring that all critical control points that impact on eating quality along the supply chain are monitored and managed. MSA gives consumers the confidence to purchase beef and sheepmeat that will meet their expectations, every time.

National Feedlot Accreditation Scheme

The National Feedlot Accreditation Scheme (NFAS) is an independently audited quality assurance scheme that was initiated by ALFA and is managed by the Feedlot Industry Accreditation Committee to provide a Quality System for beef feedlots that will impact positively on product integrity, quality and acceptability and for which lot feeders maintain responsibility.

National Livestock Health Declarations

The Farm Biosecurity Program awareness campaign is a joint initiative of Animal Health Australia (AHA) and Plant Health Australia (PHA) on behalf of their members to help producers reduce the risks posed by diseases, pests and weeds to their crops and livestock through good practices.

Consignments

What is a consignment?

In eNVD, we call the collection of forms that accompany livestock in transit, a consignment. A consignment can contain one to four program forms and is issued a unique consignment ID by the eNVD system which can be used when updating or retrieving information related to the consignment.

A consignment can be associated with:

When entering the owner, origin, destination or consignee details, the user can also provide their Property Identification Code (PIC). When an eNVD user retrieves a consignment, they will be able to view any consignments associated with the PIC(s) linked to their account.

Consignment statuses

Each consignment created via the eNVD system will have a status that determines

DRAFT - an incomplete, unsent consignment. A draft consignment will only be visible to the user that created it and is fully editable. A draft consignment can be deleted by the user that created it.

SUBMITTED - a sent consignment. Once submitted, only information related to the description of the livestock (number of head, breed, brands earmarks) and the transporter information can be edited. Once submitted, the consignment is visible to all associated users. A submitted consignment can be deleted by the user that created it.

COMPLETED - a submitted, sent and locked consignment. This status is triggered automatically via the eNVD system 48 hours after the movement date. The window after movement date is to allow any head count discrepancies to be made one receipt of livestock at the destination. A completed consignment cannot be deleted.

Forms and Form IDs

Forms capture all the information related to the livestock within the consignment. The information captured on a form is determined by the program owner.

A eNVD user can only create a form for the programs they are accredited to (i.e. user must be both LPA and EU accredited to be able to create an EU vendor declaration).

All forms in the eNVD platform are identified with a unique Form ID. As new versions of forms are published, the form ID will also be updated to identify the change.

As of December 2021, the current form versions are:

Form IDProgramForm NameSpecies
LPAC1LPALPA National Vendor DeclarationCattle
EUC1LPALPA European Union National Vendor DeclarationCattle
LPABC1LPALPA National Vendor DeclarationBobby Calves
LPASL1LPALPA National Vendor DeclarationSheep
LPAG2LPALPA National Vendor DeclarationGoats
NFASC0NFASNFAS Form BCattle
NFASDDC1NFASNFAS Delivery DocketCattle
NFASEUC1NFASNFAS Delivery Docket European Union Grain Fed High Quality BeefCattle
MSAC1MSAMSA Cattle Vendor DeclarationCattle
HSSL1National Sheep Health DeclarationSheep
HSG0National Goat Health DeclarationGoats
HSC2National Cattle Health DeclarationCattle

Question IDs

Each form includes questions that help describe the livestock, their movement history, health status or any risks that may be relevant to the receiver of the livestock. Each form question is identified with a unique question ID.

             "id": "3",
             "order": 3,
             "text": "Number of head",

Where a consignment includes multiple forms, the same question may be asked multiple times (e.g Number of head). Rather than ask the user to provide the same answer for each form, the eNVD system links the question ID to multiple forms.

             "id": "3",
             "order": 3,
             "text": "Number of head",
             "forms": [
               "LPAC1", 
               "MSAC1",
               "NFASC0",
               "NFASDDC1",
               "NFASEUC1",
               "HSC2"

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.

GraphQL has three main characteristics:

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.

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.

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:

  1. A locally installed IDE like Postman and Insomnia
  2. Playground, a web based interface which is available by navigating in your browser to one of our GraphQL API endpoints

eNVD Request 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

Retrieving a consignment Sample

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
     # These are self-explantory meta fields that are pre-filled during creation/update
     submittedAt
     updatedAt
     updatedBy
     # The current status of the consignment
     status
     # The current species of the consignment
     species
     # These are the movement fields for all forms
     owner {
       address {
         line1
         postcode
         state
         town
       }
       name
       pic
     }
     destination {
       address {
         line1
         postcode
         state
         town
       }
       name
       pic
     }
     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
       # 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
         text
         help
         type
         acceptableAnswers {
           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
     }
   }
 }
// NOTE: The full code of this example can be found in the appendix. Only the relevant snippet
// is included here due to the verbosity

public async Task<IEnumerable<Consignment>> QueryConsignments(GraphQLHttpClient client)
{

    // Query copied from document
    var request = new GraphQLRequest
    {
        Query = @"
        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
                    # These are self-explantory meta fields that are pre-filled during creation/update
                    submittedAt
                    updatedAt
                    updatedBy
                    # The current status of the consignment
                    status
                    # The current species of the consignment
                    species
                    # These are the movement fields for all forms
                    owner {
                        address {
                            line1
                            postcode
                            state
                            town
                        }
                        name
                        pic
                    }
                    destination {
                        address {
                            line1
                            postcode
                            state
                            town
                        }
                        name
                        pic
                    }
                    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
                        # 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
                        # typically, that question will be the parent question which is containing this child question
                        childQuestions {
                            id
                            text
                            help
                            type
                            acceptableAnswers {
                                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
                    }
                }
            }
        }
        "
    };

    var response = await client.SendQueryAsync<ConsignmentListResponseType>(request);

    // Now you can use the data from the response 
    return response.Data.Consignments.Items;
}

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.

A single consignment request is similar, and you would list fields the same as those in the items field above.

query {
  consignment(id: "C-12341234") {
    ... same as above
  }
public async Task<Consignment> QueryConsignment(GraphQLHttpClient client, string number)
{
    // You can query for everything as per the above but for this example we only care about the number
    var request = new GraphQLRequest
    {
        Query = @"
        query QueryConsignment ($id: String!) {
            consignment(id: $id) {
                number
            }
        }
        ",
        OperationName = "QueryConsignment",
        Variables = new
        {
            id = number
        }
    };

    var response = await client.SendQueryAsync<ConsignmentResponseType>(request);
    return response.Data.Consignment;
}
Note
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.

Creating a consignment

At a minimum, each consingment should include an NVD. Other programs like MSA and NFAS may be included, provided the user has that program accreditation (program accreditation is provided as part of the authentication token).

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: "17", index: null, value: "Yes" }
      
      # This show an example of the `quantity` subform being answered a an array
      # as can be seen by the definition of the `index` parameter
      { questionId: "2", index: 1, value: "8" }
      { questionId: "2", index: 0, value: "4" }
      { questionId: "3", index: 1, value: "2" }
      { questionId: "3", index: 0, value: "2" }
      { questionId: "4", index: 0, value: "breed1" }
      { questionId: "4", index: 1, value: "breed2" }
      { questionId: "5", index: 1, value: "Heifer : F" }
      { questionId: "5", index: 0, value: "Bull : M" }
      { questionId: "8", index: 0, value: "Yes" }
      { questionId: "8", index: 1, value: "Yes" }
      { questionId: "9", index: 0, value: "https://www.mla.com.au/globalassets/mla-corporate/mla_logo_home.png" }
      { questionId: "9", index: 1, value: "https://www.mla.com.au/globalassets/mla-corporate/mla_logo_home.png" }
    ]
  }) {
    data {
      number
      forms {
        type
        serialNumber
      }
      pdfUrl
    }
  }
}
// NOTE: The full code of this example can be found in the appendix. Only the relevant snippet
// is included here due to the verbosity

public async Task<Consignment> CreateConsignment(GraphQLHttpClient client)
{
    var request = new GraphQLRequest
    {
        Query = @"
        mutation CreateConsignment($input: CreateOrSaveConsignmentInput!) {
            createOrSaveConsignment(input: $input) {
                data {
                    number
                    createdAt
                    forms {
                        type
                        serialNumber
                    }
                    pdfUrl
                    answers {
                        questionId
                        index
                        value
                    }
                }
            }
        }
        ",
        OperationName = "CreateConsignment",
        Variables = new
        {
            input = new
            {
                // The forms to attach to ths consignment
                forms = new[] { "LPAC1" },
                // The initial movement date estimated for this consignment
                // The transporter movement date will be applied in answers
                movementDate = "2020-10-15",
                destination = new
                {
                    name = "Joe Bloggs",
                    pic = "AAAAAAAA",
                },
                // The list of answers for any questions (partial or otherwise)
                answers = new[] {
                    // This shows an example of a SINGLE_CHOICE question being answered with Yes
                    new AnswerType { QuestionId = "17", Index = null, Value = "Yes" },

                    // This show an example of the `quantity` subform being answered a an array
                    // as can be seen by the definition of the `index` parameter
                    new AnswerType{ QuestionId = "2", Index = 1, Value = "8" },
                    new AnswerType{ QuestionId = "2", Index = 0, Value = "4" },
                    new AnswerType{ QuestionId = "3", Index = 1, Value = "2" },
                    new AnswerType{ QuestionId = "3", Index = 0, Value = "2" },
                    new AnswerType{ QuestionId = "4", Index = 0, Value = "breed1" },
                    new AnswerType{ QuestionId = "4", Index = 1, Value = "breed2" },
                    new AnswerType{ QuestionId = "5", Index = 1, Value = "Heifer : F" },
                    new AnswerType{ QuestionId = "5", Index = 0, Value = "Bull : M" },
                    new AnswerType{ QuestionId = "8", Index = 0, Value = "Yes" },
                    new AnswerType{ QuestionId = "8", Index = 1, Value = "Yes" },
                    new AnswerType{ QuestionId = "9", Index = 0, Value = "https://www.mla.com.au/globalassets/mla-corporate/mla_logo_home.png" },
                    new AnswerType{ QuestionId = "9", Index = 1, Value = "https://www.mla.com.au/globalassets/mla-corporate/mla_logo_home.png" },
                },
            }
        }
    };

    var response = await client.SendQueryAsync<CreateOrSaveConsignmentResponseType>(request);
    return response.Data.CreateOrSaveConsignment.Data;
}

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 in a sequence such as:

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.

Benefits of GraphQL

As can be seen in the above example, you won’t need to:

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

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
        questionId
        value
      }
    }
  }
}
// NOTE: The full code of this example can be found in the appendix. Only the relevant snippet
// is included here due to the verbosity

public async Task<Consignment> UpdateConsignment(GraphQLHttpClient client, string number)
{
    var request = new GraphQLRequest
    {
        Query = @"
        mutation UpdateConsignment($input: CreateOrSaveConsignmentInput!) {
            createOrSaveConsignment(input: $input) {
                data {
                    number
                    answers {
                        index
                        questionId
                        value
                    }
                }
            }
        }
        ",
        OperationName = "UpdateConsignment",
        Variables = new
        {
            input = new
            {
                number = number,
                // Since it is possible to delete at an index inside an array
                // All values for the array must be sent to cover against potential deletion of an index
                answers = new[] {

                    new AnswerType{ QuestionId = "2", Index = 1, Value = "8" },
                    new AnswerType{ QuestionId = "2", Index = 0, Value = "4" },
                    new AnswerType{ QuestionId = "3", Index = 1, Value = "2" },
                    new AnswerType{ QuestionId = "3", Index = 0, Value = "2" },
                    // This is the update
                    new AnswerType{ QuestionId = "4", Index = 0, Value = "Hereford" },
                    new AnswerType{ QuestionId = "4", Index = 1, Value = "breed2" },
                    new AnswerType{ QuestionId = "5", Index = 1, Value = "Heifer : F" },
                    new AnswerType{ QuestionId = "5", Index = 0, Value = "Bull : M" },
                    new AnswerType{ QuestionId = "8", Index = 0, Value = "Yes" },
                    new AnswerType{ QuestionId = "8", Index = 1, Value = "Yes" },
                    new AnswerType{ QuestionId = "9", Index = 0, Value = "https://www.mla.com.au/globalassets/mla-corporate/mla_logo_home.png" },
                    new AnswerType{ QuestionId = "9", Index = 1, Value = "https://www.mla.com.au/globalassets/mla-corporate/mla_logo_home.png" },
                }
            }
        }
    };

    var response = await client.SendQueryAsync<CreateOrSaveConsignmentResponseType>(request);
    // Here you'll see, '17' is still present as it is not an array field thus doesn't need to be handled differently
    return response.Data.CreateOrSaveConsignment.Data;
}

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.

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.

:page_facing_up:20210602_mapping.json

It contains an array of entries such as:

[{
  "Id": "1",
  "Text": "Please provide a description of the livestock moving",
  "Form": "LPAC1",
  "Field": "description.quantity"
}]

In 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:

As a further example / explanation of the “Field”, given the following excerpt from the MSA form model:

[{
  "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:

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:

eNVD 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.

{
  ...
  "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, the result is greatly improved overall performance.

How can I request form data if we’re no longer submitting the data as forms?

The change to store and manage a consignment a set of questions and answers abstracted away from forms generally removes the need for querying the data in the structure of a particular form. Having said that, you can, if you wish, query for the data for a specific form and the API will provide that to you, mapping from the consignment and its questions and answers format to the form in question. From a consumer’s perspective this means that your queries and mutations in general are vastly simplified, and for the rare case when the data is required in a specific form’s shape, it is possible.

Do we need to use the questions, help, hint and other text you provide within our systems?

It is advised to use this information to provide users with a unified experience and to ensure that the wording surrounding a question stays aligned with how the question is used and what it means. Moving away from the usage of these fields puts the onus on API consumers to monitor them for updates and keep the wording in their implementation aligned.

When I’m making calls in Playground it is saying that I am not authenticated, how can I fix this?

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 will then succeed.

Your API key is specific to you and your application. Take reasonable care in the use of your API Key and access details.

Appendix

Full C# script example

NLIS

About NLIS

The National Livestock Identification System (NLIS) is Australia’s system for the identification and traceability of cattle, sheep and goats. NLIS reflects Australia’s commitment to biosecurity and food safety and provides a competitive advantage in a global market.

As animals are bought, sold and moved along the supply chain, they must be tagged with an NLIS accredited tag or device from their property (PIC) of birth. In most cases this tag will remain with the animal for their entire life and it is illegal to remove this tag.

If tags are lost or become defective then a new tag can be applied, however if the animal is no longer at its place of birth then a ‘post breeder’ tag must be used. This indicates that the animal no longer has ‘lifetime’ traceability.

All animals leaving a PIC must be identified with an NLIS accredited device before moving, unless a permit is obtained from the state or territory. Each movement they make to a location with a different PIC must be recorded centrally on the NLIS database by people with NLIS accounts. NLIS accounts are free to open and operate.

Using this information, the NLIS is able to provide a life history of an animal’s movements and discern if contact with other livestock occurred. The NLIS is required to facilitate the traceability of animals in accordance with the National Traceability and Performance Standards.

About the NLIS API

The purpose of the NLIS SOAP API is to define how third-party systems can communicate with the NLIS Database:

For the majority of users, the NLIS Database website (www.nlis.mla.com.au) will provide the primary mechanism for interacting with the database: for submitting updates to data, and for requesting data from NLIS. The NLIS SOAP interface specifications details those enquiry/update processes that can be initiated/integrated from outside that website. It provides all the information required for third-party software vendors/developers to integrate their systems with the NLIS Database.

For example:

All the processes detailed in the NLIS SOAP interface specifications are provided to approved users of the NLIS Database, via direct user interaction. This document simply details alternative, programmable methods for obtaining the same results.

NLIS API Documentation

  1. Interface Specification Part 1 - Specification
  2. Interface Specification Part 2 - XML Schema
  3. Interface Specification Part 3 - Example Files

NLIS PIC Register

State authorities maintain PIC registers containing details for livestock properties in their area. State PIC registers are uploaded to the NLIS Database to form a national PIC register. Disclosure of PIC register information is governed by the NLIS Terms of Use.

The NLIS PIC Register API allows you to search for the details related to a PIC, or the PIC associated with a trading name or location on the PIC Register.

The NLIS PIC Register API can allow producers, agents, saleyards, feedlot operators or abattoirs to find a PIC:

Sample PIC register requests

Wildcard search

https://<nlis environment>/PICRegister?SearchText=nsw&Pagesize=2&PageIndex=1

Field Search/ Advanced search

https://<nlis environment>/PICRegister?PIC=PICTEST1

https://<nlis environment>/PICRegister?BusinessName=Holding&FirstName=George&pageindex=1&pagesize=10

https://<nlis environment>/PICRegister?State=WA&Lastname=Basha&pageindex=1&pagesize=20

https://<nlis environment>/PICRegister?BusinessName=Holding&

https://<nlis environment>/PICRegister?PIC=PICTEST1&PropertyName=Holding&Brand=5040

https://<nlis environment>/PICRegister?PIC=PICTEST1&Brand=5040&pageindex=1&pagesize=20

https://<nlis environment>/PICRegister?Town=BROKEN HILL&State=NSW&Postcode=2880  

https://<nlis environment>/PICRegister?State=NSW&Postcode=2880&pageindex=1&pagesize=10  

https://<nlis environment>/PICRegister?Postcode=3310&pageindex=1&pagesize=10  

https://<nlis environment>/PICRegister?PICStatus=A&pageindex=1&pagesize=10

Note: PICStatus should be provided as short-codes. 
For example: Active, Inactive, Blocked, Disbanded, Blocked become A,I,D,B.

Query PICRegister API by PIC

https://<nlis environment>/PICRegister/(PIC=WZ100013)

Sample PIC register responses

[{
  "Value": {
    "Offset": 0,
    "Limit": 1,
    "Total": 1,
    "SubSet": [
      {
        "PIC": "PICTEST3",
        "PICName": "NLIS TestPIC",
        "PICRegisterId": 1,
        "BusinessName": "",
        "FirstName": "JOHN",
        "LastName": "DOE",
        "Town": "NORTH SYDNEY",
        "Species": [
          {
            "ShortCode": "C"
          },
          {
            "ShortCode": "S"
          }
        ],
        "PropertyTypes": [
          {
            "ShortCode": "PR"
          }
        ],
        "Brands": [
          {
            "ShortCode": "5UA0"                           
          }],
        "RegisteredState": "NSW",
        "AdminStatus": {
          "ShortCode": "A",
          "Description": "Active"
        },
        "ShireCode": "CT",
        "ContactDetails": [
          {
            "FirstName": "JOHN",
            "LastName": "DOE",
            "ContactTypeShortCode": "PICMANAGERCONTACT"
          }
        ]
      }
    ]
  }
}]

Search parameters

NameDescriptionMatch Types
SearchTextWords match any of the following fields: PIC, PICName, LastName, Town, State
PICExact Match
FirstName(of Manager)Fuzzy Match
LastName(of Manager)Fuzzy Match
BusinessName Fuzzy Match
PropertyNameFuzzy Match
BrandFuzzy Match
TownFuzzy Match
StateNSW, QLD, VIC, WA etcExact Match
PostCode2000, 2060, 4000 etcExact Match
PICStatusActive, Disbanded, Inactive, Amalgamated, BlockedExact Match
PICTypePR, FL, SA, SY etc (See all PIC Types)Exact Match

Other parameters

NameDescription
pageindexIdentify which page of results to return. Allows pagination through many records.
pagesizeIdentify the number of records to return. Allows pagination through many records.

ISC environments

ProductUATProduction
Authenticationhttps://auth-uat.integritysystems.com.auhttps://auth.integritysystems.com.au
eNVD APIhttps://api.uat.integritysystems.com.au/graphqlhttps://api.integritysystems.com.au/graphql
NLIS SOAP APIwww.uat.nlis.com.auwww.nlis.com.au
NLIS PIC Registerhttps://service.uat.nlis.com.au/api/v2/PICRegisterhttps://service.nlis.com.au/api/v2/PICRegister

Status Codes

Status CodeMeaning
200Ok – The default success code for requests that return a valid response in the response body, even if the response does not contain any rows or pagination. When POST-ing a new record a 200 indicates that while the request was correctly processed the record may not have been accepted, the body of the Response will indicate the reason for this error.
201Created – Success code for a POST request for a collection, indicating the record was successfully created. Note: While a 201 indicates the record was recorded, there may be Informational or Warning messages (in the Response body) that may require some corrective action to occur.
304Not Modified – The request content has not changed since a specified date and time (provided in an If-Modified-Since parameter in the request).
400Bad Request – The data passed in the request could not be understood, or translated to into an internal format. Typically, the passed message fails simple type validation rules (e.g. passing a string when a number is expected), or document structure rules (e.g. is a malformed XML or JSON document).
401Unauthorized – This error is returned when the OAuth token used has expired or is invalid; the user attempts to perform an action for which they are not authorized; or if a login attempt fails due to bad credentials.
403Forbidden – Authentication was provided, but the authenticated user is not permitted to perform the requested operation.
404Not Found – The requested resource could not be found. This error is generally returned when an entity with a specific ID is not found in our database. Check the URI for correct format, and that any IDs passed in the URI are valid.
500Internal Server Error – Any internal server error that was not explicitly trapped by the application. An error has occurred within the platform, or the system is not compatible with the NLIS API, so the request could not be completed. Contact the NLIS Development Team at technicalsupport@IntegritySystems.com.au for assistance with this error.
503Service Unavailable – We’re temporarially offline for maintanance. Please try again later.