Consuming the REST API
Conventions
The Jiwa REST API is based on the Data Transfer Object (DTO) pattern.
Every request accepts a DTO and almost every response is a DTO. You can, in some cases encode the request DTO as URL parameters instead, such as authentication and query requests.
The DTO classes can be obtained from the following routes:
Language | Route |
---|---|
C# | /types/csharp |
VB.NET | /types/vbnet |
F# | /types/fsharp |
Typescript | /types/typescript |
Java | /types/java |
Kotlin | /types/kotlin |
Swift | /types/swift |
Additionally, you can use your tool of choice to generate the DTO's from the Open API specification document located at /openapi
Content Negotiation
By default the Jiwa REST API is configured to accept json DTO's and return json DTO's. This default content type can be changed in the configuration, or the client can override the content type. If using the ServiceStack client, then there is no concern of the content type as it is automatically negotiated.
In addition to using the standard Accept
HTTP Header to retrieve the response a different format, you can also request an alternative Content-Type by appending ?format=ext to the query string, e.g:
- /Debtors/{DebtorID}?format=json
- /Debtors/{DebtorID}?format=xml
- /Debtors/{DebtorID}?format=csv
Or by appending the format .ext to the end of the route, e.g:
- /Debtors/{DebtorID}.json
- /Debtors/{DebtorID}.xml
- /Debtors/{DebtorID}.csv
Verbs
- GET operations are used to retrieve data
- POST operations are used to create data
- PATCH operations are used to update existing data
- DELETE operations are used to delete existing data
HTTP Response Codes
The response code returned will vary based on the type of operation and the result of the request.
200 OK
Will be returned for most GET operations - unless there was nothing to return, then a 204 No Content will be returned
201 Created
Will be returned for successful POST operations
204 No Content
Will be returned for successful DELETE operations
401 Not Authenticated
Will be returned if the operation requires the user to be authenticated, and they are not - or if they are authenticating and it failed due to bad credentials.
403 Forbidden
Will be returned if the user is authenticated, but do not have permission to perform the operation. See Setting Route Permissions for information on setting route permissions.
404 Not Found
Will be returned if the route is invalid, or the resource requested does not exist.
409 Conflict
Will be returned if the Jiwa business logic determines you can't perform the operation. This may occur with DELETE operations if the record cannot be deleted because it is referenced elsewhere (e.g: You can't delete a product because it is used on a sales order).
You may also get a 409 response due to a concurrency conflict. Jiwa uses optimistic concurrency control, and if the record changes because of another users or processes actions between a read and save, then this error will be returned.
Routing
ServiceStack clients don't care about routes
Note that if using a ServiceStack client you do not need to be concerned with routes. The route is worked out by the client based on the type of the DTO being sent.
The convention used for route structure is to use a plural noun of the resource followed by the primary key (usually an ID) of the resource identifier:
/SalesOrders/{InvoiceID}
The above URL route when used as a GET operation will retrieve the sales order with the InvoiceID provided:
/SalesOrders/5244dd5e199749f4b6fe
will retrieve sales order with InvoiceID of 5244dd5e199749f4b6fe
The same applies to PATCH and DELETE operations
For POST operations, you don't know and cannot supply the InvoiceID, as it is generated for you - so it is simply a POST to:
/SalesOrders
with the content body being the DTO.
Where there is relational data, such as sales orders have many possible related notes the URL appends to the sales order route URL the plural of the relational data (Notes) and when appropriate the primary key of the note (NoteID). A GET of the following route:
/SalesOrders/5244dd5e199749f4b6fe/Notes
will retrieve all notes for InvoiceID 5244dd5e199749f4b6fe
A POST to the same route will add a new note:
/SalesOrders/5244dd5e199749f4b6fe/Notes
A PATCH to the following route updates the note with NoteID 66581f3f-3efb-4cb5-878c-37f6a55405cf which is attached to sales order with InvoiceID 5244dd5e199749f4b6fe
/SalesOrders/5244dd5e199749f4b6fe/Notes/66581f3f-3efb-4cb5-878c-37f6a55405cf
A DELETE to the same route will remove the note from the sales order
/SalesOrders/5244dd5e199749f4b6fe/Notes/66581f3f-3efb-4cb5-878c-37f6a55405cf
An alternative method of POST or PATCH operations for relational data is not to use the explicit route, but provide the relational data in a POST or PATCH of the parent route.
An example below shows the addition of a new note, the addition of a new line, and the edit of an existing line to an existing sales order using a single PATCH operation to the sales order:
The response returned from the above request will be a json document representing the full sales order DTO model from the business logic - see the meta data page for the SalesOrderPATCHRequest for more detail.
Note(1): The DebtorID or DebtorAccountNo can be provided. If both are provided, then the DebtorID will be used to resolve the debtor.
Note(2): A sales order line can update an existing line by providing the InvoiceLineID property. New lines are appended by omitting InvoiceLineID.
Note(3): A new sales order line added can provide either the InventoryID, or the PartNo. If both are provided, then the InventoryID will be used to resolve the inventory item.
Note(4): When sales order lines are added, omitting the price will cause Jiwa to determine the price per normal pricing scheme logic.
Note(5): The last line added in the above example sets a custom line field value for a setting with ID "1ae102b94dc54dfc8a45"
Note(6): When adding notes if the NoteType is omitted, then the default note type configured in Jiwa is used.
Authenticating
API Key Authentication
Most routes will require authentication. There are 2 methods of authentication - user credential and API Key. API Key authentication was introduced in version 7.2 of Jiwa.
User Credentials Authentication
User credentials authentication is performed by invoking the /auth route and passing the username and password of a Jiwa user. A successful authentication will return an authentication response containing the SessionId. All subsequent requests will need to provide the SessionId as the value for the cookie "ss-id".
An alternative to using a cookie, is to send subsequent requests with the header "X-ss-id" set to the SessionId.
The response returned from the above request:
{"SessionId":"6w1nLX8r0sIrJHClX9Vj","UserName":"Admin","DisplayName":"","ResponseStatus":{}}
API Key Authentication
API Keys can be provided either as a HTTP Bearer token in the request header or as a URL or form parameter. There is no authentication step with API Keys, you simply provide the API Key with the request being made.
curl -H 'Authorization: Bearer znis9fHm0_D5EGRrGdWG7WA2EP7Hke_gloC6R76A2t0' -H 'Accept: application/json' -H 'Content-Type: application/json' -X GET https://api.jiwa.com.au/Debtors
curl -H 'Accept: application/json' -H 'Content-Type: application/json' -X GET https://api.jiwa.com.au/Debtors?apikey=znis9fHm0_D5EGRrGdWG7WA2EP7Hke_gloC6R76A2t0
As API Keys are associated with a Jiwa staff member, all permissions are that of the staff member - for all intents the permissions are the same as if the authentication was performed with username and password. API Keys can have an expiration date, and also can be revoked by marking the key as not Enabled. Requests using an expired or revoked key will fail with a 401 Not Authenticated error.
There are 2 types of API Keys in Jiwa 7.2 - Staff API Keys and Debtor API Keys.
Staff API Keys
Staff API Keys are simply an alternate way of a staff member or user authenticating to the username and password. A staff member can have an API key associated with them via the Staff Maintenance form in Jiwa:
Debtor API Keys
Debtor API keys are intended to be given to customers to interact with your Jiwa API to perform such functions as:
- Retrieve a filterable, paginated list of products
- Retrieve a product
- Retrieve a filterable, paginated list of sales orders
- Place sales orders
- Retrieve a sales order
- Add, edit or remove lines from a sales order
- Retrieve a filterable, paginated list of sales quotes
- Place a sales quote (request for quote)
- Retrieve a sales quote
- Add, edit or remove lines from a sales quote
- Retrieve a list of their transactions
- Retrieve their account record
Debtor API Keys are associated with a debtor via the API Keys tab of the Debtor Maintenance form:
Note that the API Key is linked to a staff member. When a Debtor API Key is used in a request the API will login into Jiwa as the associated staff member - so it is recommended to create a new user (staff member) with restricted permissions which you should link all your Debtor API Keys to.
Permissions
It is recommended to create a specific user which will be used for Debtor API Keys, and also a specific User Group to which the user belongs to. The REST API permissions for the User Group should be set as shown below:
Recommended Route Exposure
As the Debtor API key is expected to be given to a customer, care must be taken to only expose routes/services necessary and not to leak sensitive information. You must not trust any request using a Debtor API Key, and so validation and filtering must be performed within the REST API.
By default the REST API plugin will filter some requests and responses to prevent unwanted information disclosure. The following lists which routes currently filter the requests and responses:
Route | Summary | Verb | Request Filtering Performed | Response Filtering Performed |
---|---|---|---|---|
/Inventory/{InventoryID} | Retrieves an inventory item | GET | None |
|
/Queries/SalesOrderList | Retrieves a list of sales orders | GET | Filtered on DebtorID | None |
/SalesOrders | Creates a new sales order | POST | DebtorID replaced CreditNote flag removed InvoiceInitDate removed SystemSettings removed Payments Removed Removes the following from sales order lines:
| Removes the following from sales order lines:
|
/SalesOrders/{InvoiceID} | Retrieves a sales order | GET | Filtered on DebtorID | SystemSettings removed Removes the following from sales order lines:
|
/SalesOrders/{InvoiceID} | Updates a sales order | PATCH | DebtorID replaced CreditNote flag removed InvoiceInitDate removed SystemSettings removed Payments Removed Removes the following from sales order lines:
| SystemSettings removed Removes the following from sales order lines:
|
/SalesOrders/{InvoiceID}/Save | Saves a stateful sales order | GET | Filtered on DebtorID | Removes the following from sales order lines:
|
/SalesOrders/{InvoiceID}/Abandon | Abandons a stateful sales order | DELETE | Filtered on DebtorID | None |
/SalesOrders/{InvoiceID}/Historys/{InvoiceHistoryID}/Lines/{InvoiceLineID} | Retrieves a sales order line | GET | Filtered on DebtorID of Sales Order | Removes the following from sales order lines:
|
/SalesOrders/{InvoiceID}/Historys/{InvoiceHistoryID}/Lines | Appends a line to a sales order | POST | Filtered on DebtorID of Sales Order Removes the following from the sales order line:
| Removes the following from sales order lines:
|
/SalesOrders/{InvoiceID}/Historys/{InvoiceHistoryID}/Lines/{InvoiceLineID} | Updates a sales order line | PATCH | Filtered on DebtorID of Sales Order Removes the following from the sales order line:
| Removes the following from sales order lines:
|
/SalesOrders/{InvoiceID}/Historys/{InvoiceHistoryID}/Lines/{InvoiceLineID} | Deletes a sales order line | DELETE | Filtered on DebtorID of Sales Order | None |
/Queries/SalesQuoteList | Retrieves a list of sales quotes | GET | Filtered on DebtorID | None |
/SalesQuotes | Creates a new sales Quote | POST | DebtorID replaced | Removes the following from sales quote lines:
|
/SalesQuotes/{QuoteID} | Retrieves a sales Quote | GET | Filtered on DebtorID | Removes the following from sales quote lines:
|
/SalesQuotes/{QuoteID} | Updates a sales Quote | PATCH | Filtered on DebtorID Removes the following from sales quote lines:
| Removes the following from sales quote lines:
|
/SalesQuotes/{QuoteID}/Save | Saves a stateful sales Quote | GET | Filtered on DebtorID of Sales Quote | Removes the following from sales quote lines:
|
/SalesQuotes/{QuoteID}/Abandon | Abandons a stateful sales Quote | DELETE | Filtered on DebtorID of Sales Quote | None |
/SalesQuotes/{QuoteID}/Historys/{QuoteHistoryID}/Lines/{QuoteLineID} | Retrieves a sales Quote line | GET | Filtered on DebtorID of Sales Quote | Removes the following from sales quote lines:
|
/SalesQuotes/{QuoteID}/Historys/{QuoteHistoryID}/Lines | Appends a line to a sales Quote | POST | Filtered on DebtorID of Sales Quote Removes the following from the sales quote line:
| Removes the following from sales quote lines:
|
/SalesQuotes/{QuoteID}/Historys/{QuoteHistoryID}/Lines/{QuoteLineID} | Updates a sales Quote line | PATCH | Filtered on DebtorID of Sales Quote Removes the following from the sales quote line:
| Removes the following from sales quote lines:
|
/SalesQuotes/{QuoteID}/Historys/{QuoteHistoryID}/Lines/{QuoteLineID} | Deletes a sales Quote line | DELETE | Filtered on DebtorID of Sales Quote | Removes the following from sales quote lines:
|
/Queries/DebtorTransactionList | Retrieves a list of debtor transactions | GET | Filtered on DebtorID | None |
/Debtors | Retrieves a debtor for a API Key authenticated customer | GET | None - DebtorID implied | Removes the following:
|
/KeepAlive | Extends an authenticated users session | GET | None | None |
Example of calling a route
This example shows how to retrieve a debtor record for a known DebtorID.
The response returned from the above request will be a json document representing the full debtor DTO model from the business logic - see the meta data page for the DebtorGETRequest for more detail.
Stateful Interaction
While typically interactions with a REST API would be stateless, it is possible to interact in a stateful way by passing the Request header "jiwa-stateful" with the value of "true".
When stateful requests are received, the server caches the appropriate business logic and subsequent requests will interact with that in-memory object. This allows the consumer to perform actions like building a sales order without it being saved to the database until it is ready to save it.
Stateful requests will be committed to the database when a SAVE Request is received. Pending changes can also be discarded with an ABANDON Request.
Below is an example of a stateful interaction with the Debtors - the object is statefully retrieved, and updated until a SAVE Request is sent.
var client = new ServiceStack.JsonServiceClient("http://localhost"); var authResponse = client.Send<ServiceStack.AuthenticateResponse>(new ServiceStack.Authenticate() { provider = "credentials", UserName = "api", Password = "password", RememberMe = true }); // Read a debtor client.Headers.Add("jiwa-stateful", "true"); var debtorGETResponse = client.Get(new DebtorGETRequest { DebtorID = "0000000061000000001V" }); // Update debtor var debtorPATCHResponse = client.Patch(new DebtorPATCHRequest() { DebtorID = "0000000061000000001V", Name = "My new name", CreditLimit = 1000 }); // Update some more fields debtorPATCHResponse = client.Patch(new DebtorPATCHRequest() { DebtorID = "0000000061000000001V", Address1 = "SE2L10 100 Walker Street" }); // Save the changes var debtorSAVEResponse = client.Get(new DebtorSAVERequest() { DebtorID = "0000000061000000001V" }); }
When the server creates the business logic object, it is stored in a collection associated with the users session (this is actually a property of the Manager class - the ObjectDictionary). Subsequent stateful requests for the same type (eg: Debtor Maintenance operations) will retrieve any existing business logic for the same record, otherwise a new business logic instance is created.
This means two subsequent stateful operations for different debtors will result in two business logic objects created by the server, and they will remain independent of each other.
Conversely, two subsequent stateful operations for the same debtor will result only one business logic object created by the server, and the second operation will be working on the same business logic instance as the first operation.