How to update quantityAvailable of a product in an inventory via API?

One workaround would be to use the Stock Transfer API to move items from the default internal location to a “hold” location that represents the “sold” goods. I’ll put together an example, if that’s something you’d be interested in.

Also, to clarify my point about the Stock Movement process, you could conceivably create an outbound request for a product through the API and then fulfill that request either manually through the Stock Movement feature or programmatically via the API. In either case, it’s just more complicated than what you are looking to do so I’d like to come up with a more streamlined approach as well.

1 Like

Disclaimer: Since I don’t have a ton of time to document the APIs I’m sort of hoping that we can use this forum as a way to identify and prioritize the API documentation needs as well as to figure out what APIs need a little love in terms of development.

All that is to say that this conversation is exactly what I want. So thank you for bringing this up.


As you have probably discovered, the main documentation for the API is here (and not very good).

We started to work on OpenAPI (Swagger) documentation, but it uses our Grails 3 migration branch so it might be outdated and potentially useless until the migration is done.

However, I can see whether we can get it updated to the latest version (0.8.21) just so all of the known endpoints are there.

A few of the product subresources are listed in the Swagger docs, but for other domains, you need to dig around the source code.

So for now, just ask for recommendations here and I’ll do my best to provide a solution. And eventually, those will get merged back into the docs. The cycle of life.

Lastly, the error I’m seeing on the generic Transaction API is actually a small bug which can be solved by adding a property editor for the Inventory domain.

Request

curl -b cookies.txt -X POST -H "Content-Type: application/json" \
-d '{"transactionDate": "03/10/2023 00:00:00", "transactionType": "9", "inventory": "1", "destination":"1"}' \ 
https://openboxes.ngrok.io/openboxes/api/generic/transaction

Response

{
	"errorCode": 400,
	"errorMessage": "Validation error. Cannot create product due to validation errors:\n- Field error in object 'org.pih.warehouse.inventory.Transaction' on field 'inventory': rejected value [1]; codes [typeMismatch.org.pih.warehouse.inventory.Transaction.inventory,typeMismatch.inventory,typeMismatch.org.pih.warehouse.inventory.Inventory,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [org.pih.warehouse.inventory.Transaction.inventory,inventory]; arguments []; default message [inventory]]; default message [Failed to convert property value of type 'java.lang.Integer' to required type 'org.pih.warehouse.inventory.Inventory' for property 'inventory'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.Integer] to required type [org.pih.warehouse.inventory.Inventory] for property 'inventory': no matching editors or conversion strategy found]\n",
	"errorMessages": ["Failed to convert property value of type java.lang.Integer to required type org.pih.warehouse.inventory.Inventory for property inventory; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.Integer] to required type [org.pih.warehouse.inventory.Inventory] for property inventory: no matching editors or conversion strategy found"]
}

So I can work on fixing that as well as any other issues that stand in the way

but a proper solution is also in order.

Here are a few suggestions.

Let me know how you were expecting to handle your “sales” use case. For example, did you have an API request in mind when you started the process? Would you prefer to handle this interaction using messaging (i.e. EDI message) or direct API requests?

I thought to make it quite straightforward, to be honest :slightly_smiling_face:
Without creating transaction records in openboxes but with funds transaction records in our app. For example, the customer adds some products to a cart, the merchant scans product QR codes (which are IDs of the inventory items) with our terminal app, the customer pays and after that I just call openboxes API with inventory item IDs to decrease their quantity and create corresponding funds transaction records in our system.

But I think merchants would like to see the history of their inventory items in openboxes too, so your suggestion about using Stock Transfer API or Transaction API sounds good to me. Now I think that just updating the inventory items quantity is not a really good idea.

after that I just call openboxes API with inventory item IDs to decrease their quantity

Ok thanks, that should be easy to implement.

Now I think that just updating the inventory items quantity is not a really good idea.

There’s no way to do this in OB without a transaction. But from your perspective, there shouldn’t be much of a difference. You can just pass us a list of sold items (i.e. inventory item, quantity, unit of measure) and we should be able to create a transaction for you.

For example, we could send us the following request

request

POST /api/transactions

payload

{
    transactionType: "SALES",
    transactionDate: "2022-03-10 21:49:00",
    items: [
        { inventoryItemId: "09f5e116819082200181979bdcf92b8c", quantity: 1, unitOfMeasure: "EA" },
        { inventoryItemId: "8a8a9e9666194c8901661d2e06340167", quantity: 10, unitOfMeasure: "EA" }]
}

NOTE: The transaction type (SALES) tell us whether we should add or subtract the quantities from the quantity on hand.

From what you said earlier, we can do this as a straight-up transaction (i.e. Debit 1 EA of Inventory Item 001 of Product ABC from Location A). We can always build in the additional layers later.

For example, we can get more complicated if you eventually want to track other data elements like customer info, sales reference number, sales interaction type (POS vs e-commerce), or if you need to track the fulfillment process (allocation, picking, packing, shipping) as disparate events within a workflow.

The transaction type (SALES) tell us whether we should add or subtract the quantities from the quantity on hand.

So in this case

{ inventoryItemId: "09f5e116819082200181979bdcf92b8c", quantity: 1, unitOfMeasure: "EA" }

this inventory item will decrease from 10 (for example) to 9, I mean we are decreasing its quantity by 1, not setting it to 1, right? And after that openboxes will automatically create a transaction?

But until this API is implemented I need to use Transaction API or Stock Transfer API to create transactions myself, correct?

this inventory item will decrease from 10 (for example) to 9, I mean we are decreasing its quantity by 1, not setting it to 1, right? And after that openboxes will automatically create a transaction?

Correct. But we can also create an API that will allow you to adjust the transaction by telling us what the current Quantity on Hand for an inventory item and we’ll create the adjustment transaction.

But until this API is implemented I need to use Transaction API or Stock Transfer API to create transactions myself, correct?

Correct.

Using the generic Transaction API would basically be the same as above but would require two API requests (one to create the transaction header and another to create the transaction entry for each item).
In other words, the only difference between the two is that the streamlined API above would basically merge those two requests into a single request.

Using the Stock Transfer API would be a hack that allows you to move quantity from available to unavailable like you can do using either of the transfer features available from the UI.

The end result would look something like this.

1 Like

The document above is about Transaction API or Stock Transfer API?

If not, then could you give me an example with both API?

No, Stock Movement API is the one that would require you to go through a multi-step fulfillment process (add items, revise quantities, allocate stock, pick stock, pack stock, ship stock). This API is used for our internal stock movement workflow. You need something very simple “debit quantity X from location A for inventory item 123 at time Y”.

I just remembered a third API that is closer to what you originally wanted. I’ll write up an example of the Transaction and Stock Adjustment APIs once I fix the bugs preventing their use and the Stock Transfer API once I run through a few examples in Postman.

In time meantime, this is a screenshot of a Stock Transfer API request I created sometime last year in Postman. I can’t say this will work but it’s what I plan to use as the starting point for an example.

So right now there is no way to use openboxes for what I need?

You can use the Stock Transfer API until we fix the bugs on the other two APIs. I just didn’t have time to test the Stock Transfer API yesterday. However, I was able to get it working locally tonight. I just need a bit more time to make sure this will work for your use case i.e.

  • origin bin location = null
  • destination bin location = Hold Bin

So I’ll look into that tomorrow and either provide you with a working example to get you started or link to yet another ticket that will need to be completed before you can continue. The good news is that now of the issues we’ve encountered so far will take more than a day or two to complete.

As I alluded to in a previous comment, we are building our APIs for our own internal use so while most of them work fine for us, we haven’t had much feedback from developers on how they would like to interact with the OpenBoxes API.

So your feedback and patience are very much appreciated.


Step 1. Create a stock transfer by specifying what items need to be transferred

POST https://openboxes.ngrok.io/openboxes/api/stockTransfers

Payload

{
    "stockTransferItems": [
        {
            "product.id": "09f5e1167e6ee8ec017e741ff8a637a5",
            "product.productCode": "24465",
            "inventoryItem.id": "09f5e1167f2c6949017f658d60953859",
            "location.id": "1",
            "lotNumber": "FMQWR77N00",
            "originBinLocation.id": "09f5e1168103c8ba0181496a1a416df2",
            "destinationBinLocation.id": "09f5e1167e6ee8ec017e7414e83e36f0",
            "quantity": 1
        }
    ]
}

This will create a PENDING stock transfer

{
    "data": {
        "id": "ff80818186cd348e0186d943eb3f0019",
        "description": null,
        "stockTransferNumber": "NMR354",
        "status": "PENDING",
        "dateCreated": "March 12, 2023",
        "origin.id": "8a8a9e9665c4f59d0165c54ec6b10027",
        "origin.name": "Distribution Center",
        "destination.id": "8a8a9e9665c4f59d0165c54ec6b10027",
        "destination.name": "Distribution Center",
        "stockTransferItems": [
            {
                "id": "ff80818186cd348e0186d943eb3f001a",
                "productAvailabilityId": "6617209c-31e7-4fbc-a13d-b0becdd3f047",
                "product.id": "09f5e1167e6ee8ec017e741ff8a637a5",
                "product.productCode": "24465",
                "product.name": "Laptop, MacBook Pro, 14in",
                "product.translatedName": "Laptop, MacBook Pro, 14in",
                "product.handlingIcons": [],
                "inventoryItem.id": "09f5e1167f2c6949017f658d60953859",
                "lotNumber": "FMQWR77N00",
                "expirationDate": null,
                "recalled": false,
                "originBinLocation.id": "09f5e1168103c8ba0181496a1a416df2",
                "originBinLocation.name": "Speed Cart",
                "originZone": null,
                "onHold": false,
                "destinationBinLocation.id": "09f5e1167e6ee8ec017e7414e83e36f0",
                "destinationBinLocation.name": "Hold Bin",
                "destinationZone.id": null,
                "destinationZone.name": null,
                "quantity": 1,
                "quantityOnHand": 1,
                "quantityNotPicked": 1,
                "status": "PENDING",
                "recipient": null,
                "splitItems": [],
                "picklistItems": [],
                "sortOrder": 0
            }
        ],
        "orderedBy": "Justin Miranda",
        "type": "TRANSFER_ORDER",
        "dateShipped": "",
        "expectedDeliveryDate": "",
        "shipmentType": "",
        "trackingNumber": "",
        "driverName": "",
        "comments": "",
        "documents": ""
    }
}

Step 2. Post a status update on the newly create stock transfer

POST https://openboxes.ngrok.io/openboxes/api/stockTransfers/ff80818186cd348e0186d943eb3f0019

Payload

{
    "status": "COMPLETED",
}

And that will return the full stock transfer object again with the COMPLETED status.

{
    "data": {
        "id": "ff80818186cd348e0186d9345f720011",
        "description": "Stock Transfer Description (if desired)",
        "stockTransferNumber": "VST066",
        "status": "COMPLETED",
        "dateCreated": "March 12, 2023",
        "origin.id": "1",
        "origin.name": "Boston: Haiti Stock",
        "destination.id": "1",
        "destination.name": "Boston: Haiti Stock",
        "stockTransferItems": [
            {
                "id": "ff80818186cd348e0186d9345f720012",
                "productAvailabilityId": "ff80818186cd348e0186d9345f720012",
                "product.id": "09f5e1167e6ee8ec017e741ff8a637a5",
                "product.productCode": "24465",
                "product.name": "Laptop, MacBook Pro, 14in",
                "product.translatedName": "Laptop, MacBook Pro, 14in",
                "product.handlingIcons": [],
                "inventoryItem.id": "09f5e1167f2c6949017f658d449b3856",
                "lotNumber": null,
                "expirationDate": null,
                "recalled": false,
                "originBinLocation.id": "09f5e1168103c8ba0181496a1a416df2",
                "originBinLocation.name": "Speed Cart",
                "originZone": null,
                "onHold": false,
                "destinationBinLocation.id": "09f5e1167e6ee8ec017e7414e83e36f0",
                "destinationBinLocation.name": "Hold Bin",
                "destinationZone.id": null,
                "destinationZone.name": null,
                "quantity": 1,
                "quantityOnHand": 0,
                "quantityNotPicked": 0,
                "status": "COMPLETED",
                "recipient": null,
                "splitItems": [],
                "picklistItems": [],
                "sortOrder": 0
            }
        ],
        "orderedBy": "Justin Miranda",
        "type": "TRANSFER_ORDER",
        "dateShipped": "",
        "expectedDeliveryDate": "",
        "shipmentType": "",
        "trackingNumber": "",
        "driverName": "",
        "comments": "",
        "documents": ""
    }
}

Here’s what the operation looks like from the UI perspective.

Stock Card (Before)

Stock Card (After)

Stock History

1 Like

In Payload of Step 1 what is inventoryItem.id and where can I get it?

I have created two Bin Locations - Store and Sold - under my location and after moving the product from Default location to Store location I tried to get inventoryItem.id from
GET /openboxes/api/products/2c96808386c648e70186c69efed0000b/availableItems
Response

{
    "data": [
        {
            "inventoryItem.id": "2c96808386c648e70186c6adf57c000c",
            "product.name": "test product 1",
            "product": {
                "id": "2c96808386c648e70186c69efed0000b",
                "productCode": "tp1",
                "name": "test product 1",
                "description": null,
                "category": "test category",
                "unitOfMeasure": null,
                "pricePerUnit": 1,
                "dateCreated": "2023-03-09T13:45:54Z",
                "lastUpdated": "Mar 10, 2023",
                "updatedBy": "Miss Administrator",
                "color": null,
                "handlingIcons": [],
                "lotAndExpiryControl": false,
                "active": true
            },
            "productCode": "tp1",
            "lotNumber": null,
            "expirationDate": null,
            "binLocation": {
                "id": "2c96808386da37c80186dc7cb9f90017",
                "name": "Store",
                "description": null,
                "locationNumber": null,
                "locationGroup": null,
                "parentLocation": {
                    "id": "2c96808386da37c80186dc7c32520015",
                    "name": "test merchant",
                    "description": null,
                    "locationNumber": null,
                    "locationGroup": null,
                    "parentLocation": null,
                    "locationType": {
                        "id": "2c96808386c648e70186c87661f1002d",
                        "name": "super",
                        "description": null,
                        "locationTypeCode": "DISTRIBUTOR"
                    },
                    "sortOrder": null,
                    "hasBinLocationSupport": true,
                    "hasPackingSupport": true,
                    "hasPartialReceivingSupport": true,
                    "hasCentralPurchasingEnabled": true,
                    "organizationName": "tst",
                    "organizationCode": "TST",
                    "backgroundColor": "FFFFFF",
                    "zoneName": null,
                    "zoneId": null,
                    "active": true,
                    "organization": {
                        "id": "2c96808386c648e70186c67a9c6b0004",
                        "name": "tst",
                        "description": null,
                        "code": "TST",
                        "dateCreated": "2023-03-09T13:06:10Z",
                        "lastUpdated": "2023-03-09T13:06:10Z",
                        "defaultLocation": null,
                        "partyType": {
                            "id": "1",
                            "name": "Organization",
                            "code": "ORG",
                            "partyTypeCode": "ORGANIZATION"
                        },
                        "roles": [
                            {
                                "id": "2c96808386c648e70186c67c40fa0006",
                                "roleType": "ROLE_ORGANIZATION",
                                "startDate": null,
                                "endDate": null
                            }
                        ],
                        "sequences": {}
                    },
                    "manager": {
                        "id": "1",
                        "name": "Miss Administrator",
                        "firstName": "Miss",
                        "lastName": "Administrator",
                        "email": "admin@openboxes.com",
                        "username": "admin"
                    },
                    "address": null,
                    "supportedActivities": [
                        "PARTIAL_RECEIVING",
                        "DYNAMIC_CREATION",
                        "EXTERNAL",
                        "PLACE_ORDER",
                        "ENABLE_NOTIFICATIONS",
                        "ADJUST_INVENTORY",
                        "HOLD_STOCK",
                        "SUBMIT_REQUEST",
                        "REQUIRE_ACCOUNTING",
                        "MANAGE_INVENTORY",
                        "APPROVE_ORDER",
                        "PICK_STOCK",
                        "FULFILL_REQUEST",
                        "PLACE_REQUEST",
                        "PUTAWAY_STOCK",
                        "SEND_STOCK",
                        "CROSS_DOCKING",
                        "ENABLE_CENTRAL_PURCHASING",
                        "CONSUME_STOCK",
                        "PACK_SHIPMENT",
                        "FULFILL_ORDER",
                        "RECEIVE_STOCK",
                        "APPROVE_REQUEST"
                    ]
                },
                "locationType": {
                    "id": "cab2b4f35ba2d867015ba2e17e390001",
                    "name": "Bin Location",
                    "description": "Default bin location type",
                    "locationTypeCode": "BIN_LOCATION"
                },
                "sortOrder": null,
                "hasBinLocationSupport": true,
                "hasPackingSupport": false,
                "hasPartialReceivingSupport": false,
                "hasCentralPurchasingEnabled": false,
                "organizationName": "tst",
                "organizationCode": "TST",
                "backgroundColor": "FFFFFF",
                "zoneName": null,
                "zoneId": null,
                "active": true,
                "organization": {
                    "id": "2c96808386c648e70186c67a9c6b0004",
                    "name": "tst",
                    "description": null,
                    "code": "TST",
                    "dateCreated": "2023-03-09T13:06:10Z",
                    "lastUpdated": "2023-03-09T13:06:10Z",
                    "defaultLocation": null,
                    "partyType": {
                        "id": "1",
                        "name": "Organization",
                        "code": "ORG",
                        "partyTypeCode": "ORGANIZATION"
                    },
                    "roles": [
                        {
                            "id": "2c96808386c648e70186c67c40fa0006",
                            "roleType": "ROLE_ORGANIZATION",
                            "startDate": null,
                            "endDate": null
                        }
                    ],
                    "sequences": {}
                },
                "manager": null,
                "address": null,
                "supportedActivities": [
                    "PICK_STOCK",
                    "PUTAWAY_STOCK"
                ]
            },
            "zone": null,
            "quantityAvailable": 10,
            "quantityOnHand": null,
            "status": "AVAILABLE",
            "pickedRequisitionNumbers": "",
            "binLocation.id": "2c96808386da37c80186dc7cb9f90017",
            "binLocation.name": "Store"
        }
    ]
}

and use it in payload for
POST /openboxes/api/stockTransfers
Payload

{
    "stockTransferItems": [
        {
            "product.id": "2c96808386c648e70186c69efed0000b",
            "product.productCode": "tp1",
            "inventoryItem.id": "2c96808386c648e70186c6adf57c000c",
            "location.id": "2c96808386da37c80186dc7c32520015",
            "lotNumber": "Default",
            "originBinLocation.id": "2c96808386da37c80186dc7cb9f90017",
            "destinationBinLocation.id": "2c96808386da37c80186dc7ccfd70019",
            "quantity": 1
        }
    ]
}

It seems that it correctly creates stock transfer in PENDING status and I can see it in UI
Response

{
    "data": {
        "id": "2c96808386da37c80186dc84235a0022",
        "description": null,
        "stockTransferNumber": "827MHK",
        "status": "PENDING",
        "dateCreated": "March 13, 2023",
        "origin.id": "2c96808386da37c80186dc7c32520015",
        "origin.name": "test merchant",
        "destination.id": "2c96808386da37c80186dc7c32520015",
        "destination.name": "test merchant",
        "stockTransferItems": [
            {
                "id": "2c96808386da37c80186dc84235a0023",
                "productAvailabilityId": "2c96808386da37c80186dc84235a0023",
                "product.id": "2c96808386c648e70186c69efed0000b",
                "product.productCode": "tp1",
                "product.name": "test product 1",
                "product.handlingIcons": [],
                "inventoryItem.id": "2c96808386da37c80186dc73fb6c000e",
                "lotNumber": "Default",
                "expirationDate": null,
                "recalled": false,
                "originBinLocation.id": "2c96808386da37c80186dc7cb9f90017",
                "originBinLocation.name": "Store",
                "originZone": null,
                "onHold": false,
                "destinationBinLocation.id": "2c96808386da37c80186dc7ccfd70019",
                "destinationBinLocation.name": "Sold",
                "destinationZone.id": null,
                "destinationZone.name": null,
                "quantity": 1,
                "quantityOnHand": 0,
                "quantityNotPicked": 0,
                "status": "PENDING",
                "recipient": null,
                "splitItems": [],
                "picklistItems": [],
                "sortOrder": 0
            }
        ],
        "orderedBy": "Miss Administrator",
        "type": "TRANSFER_ORDER",
        "dateShipped": "",
        "expectedDeliveryDate": "",
        "shipmentType": "",
        "trackingNumber": "",
        "driverName": "",
        "comments": "",
        "documents": ""
    }
}

but when I try to update this transfer with ID I get from the previous step
POST /openboxes/api/stockTransfers/2c96808386da37c80186dc84235a0022
Payload

{
    "status": "COMPLETED"
}

I always get a response with an empty stockTransferItems array
Response

{
    "data": {
        "id": "2c96808386da37c80186dc8454940024",
        "description": null,
        "stockTransferNumber": "355YBV",
        "status": "COMPLETED",
        "dateCreated": "March 13, 2023",
        "origin.id": "2c96808386da37c80186dc7c32520015",
        "origin.name": "test merchant",
        "destination.id": "2c96808386da37c80186dc7c32520015",
        "destination.name": "test merchant",
        "stockTransferItems": [],
        "orderedBy": "Miss Administrator",
        "type": "TRANSFER_ORDER",
        "dateShipped": "",
        "expectedDeliveryDate": "",
        "shipmentType": "",
        "trackingNumber": "",
        "driverName": "",
        "comments": "",
        "documents": ""
    }
}

And in UI I see a new completed transfer with 0 items in it.

Can you double check the status update request and make sure you’re posting to the right stock transfer?

In your previous message you state that you are posting a COMPLETED status to stock transfer with ID 2c96808386da37c80186dc84235a0022 (which is the PENDING one with stock transfer number 827MHK).

POST /openboxes/api/stockTransfers/2c96808386da37c80186dc84235a0022
{
    "status": "COMPLETED"
}

But the response shows a different stock transfer with a different ID and stock transfer number.

{
    "data": {
        "id": "2c96808386da37c80186dc8454940024",
        "description": null,
        "stockTransferNumber": "355YBV",
        ...

Never mind, I see what you’re saying. The API seems to be ignoring the ID in the URL for the /status request and creates a new (blank) stock transfer with status = COMPLETED and no stock transfer items instead. That’s bizarre.

So my hunch is that the API uses the ID from the payload instead of the one from the URL (request parameters) to locate the stock transfer. You probably still need to include the ID in the URL, but you may also need to pass the ID in the payload.

POST https://openboxes.ngrok.io/openboxes/api/stockTransfers/ff80818186de43cc0186de45904f0001
{
    "id": "ff80818186de43cc0186de45904f0001", 
    "status": "COMPLETED"
}

But something doesn’t seem right, so I’m going to batman-signal the developer who wrote that API to see if he has any thoughts.

@Artur does this seem right? Can you see something else going on the Stock Transfer API that might explain this behavior?


So as for the answer to your question from the other day

So right now there is no way to use openboxes for what I need?

To be completely honest, it seems like a bad time to consume our APIs. I didn’t realize they were this unusable from the outside. There are other major issues as well namely, the lack of a coherent authentication and authorization mechanism i.e. we currently rely on cookie-based authentication and we allow API users to manhandle any location they want.

And while I’m planning to prioritize the API over the next few weeks, I can’t say I would recommend using them until we fix all of these bugs as well as whatever else we uncover.

1 Like

POST https://openboxes.ngrok.io/openboxes/api/stockTransfers/ff80818186de43cc0186de45904f0001
{
“id”: “ff80818186de43cc0186de45904f0001”,
“status”: “COMPLETED”
}

This payload has correctly updated the status of this transfer, but… now its stockTransferItems is an empty array and it doesn’t have items in UI :slightly_smiling_face:

It looks like I need to pass all the data from the previous step in payload.

So weird. I’m investigating that, but in the meantime here’s a video to show you what I did the other night to figure out what data I needed to pass to the Stock Transfer API Screencastify

Disregard. Rollback appears to only be available for return orders.

One other trick you might not have encountered …

If you are looking to use the same Stock Transfer over and over while testing you can POST to the rollback subresource to rollback the state from COMPLETED.

POST https://openboxes.ngrok.io/openboxes/api/stockTransfers/:id/rollback

That will allow you to reuse the same stock transfer while testing.

So I’ve made some progress on this but probably not enough to make this production ready before I leave for vacation this weekend.

Progress / work-to-be-done

a) I’ve added a new transaction API that will handle CRUD for transactions using a single request.

b) I’ve also added basic auth so we don’t have to authenticate before sending API requests. The reason we haven’t implemented basic auth or API keys / tokens is that our app is our only API client at the moment so it was unnecessary. I was hoping to wait until we migrated to the latest version of Grails (i.e. Spring Security), but I think it should be fine to add a temporary solution for now.

c) The last part will be adding authorization so that we require the API user to have write access to the location for which they are creating transactions.

If you’re still interested, I could potentially push my feature branch to github if you have means to build and deploy the code. I could also supply you with a WAR file or deploy the branch to one of my test servers if you want to test it out.

If not, I’ll continue the work when I get back in April.

Justin

We went with our own implementation that should meet our needs for now, so take your time and enjoy your vacation :slightly_smiling_face: