Skip to main content

Quickstart

In this guide you log in, make your first calls with curl, and create an API key. It takes about ten minutes.

You need:

  • a running app-server (local development uses http://localhost:8081),
  • curl,
  • a user name and password.

On a development database made by wms-dev-db, the seeded users are demo1 (role ADMIN), sup1 (SUPERVISOR) and op1 (OPERATOR). Their password is welcome1. See crates/tools/wms-dev-db/README.md. Do not use these users anywhere else.

Not confirmed by code: the exact response bodies below. They are built from the code, not copied from a live server. Field values (ids, names) will differ on your system. To verify: run each step with curl -i against a development server and compare the HTTP status and the field names.

Step 1. Set your variables​

export BASE_URL="http://localhost:8081"

Step 2. Log in​

curl -s -i -c cookies.txt -X POST "$BASE_URL/api/login" \
-H "Content-Type: application/json" \
-d '{"username": "demo1", "pwd": "welcome1"}'

The server answers:

HTTP/1.1 200 OK
set-cookie: auth-token=<token>; HttpOnly; Path=/
content-type: application/json

{"result":{"success":true}}
  • -c cookies.txt saves the cookie in a file. The cookie is named auth-token.
  • Use -b cookies.txt on the next calls to send it back.
  • The token lasts for SERVICE_TOKEN_DURATION_SEC seconds (1800 in the development config). Every call you make with the cookie renews it.

If the user name or password is wrong, you get HTTP 403 and this body. The body is the same for an unknown user and for a wrong password, on purpose.

{
"id": null,
"error": {
"message": "LOGIN_FAIL",
"data": { "req_uuid": "0b6c0c3e-9d0b-4b43-8d1a-5b5f0f7d1c11", "detail": null }
}
}

After 10 failed logins in 60 seconds for the same user name, the server refuses further tries with the same LOGIN_FAIL answer until the window passes. See 06-rate-limits-and-limits.md.

Step 3. Ask who you are​

me needs a login but no warehouse and no special permission.

curl -s -b cookies.txt -X POST "$BASE_URL/api/rpc" \
-H "Content-Type: application/json" \
-d '{"jsonrpc": "2.0", "id": 1, "method": "me", "params": {}}'

Expected shape (values will differ):

{
"jsonrpc": "2.0",
"id": 1,
"result": {
"data": {
"id": 1000,
"username": "demo1",
"typ": "User",
"locale": "en",
"timezone": "Europe/Sofia",
"date_format": "dd.MM.yyyy HH:mm:ss",
"mode": "dark",
"status": "Active",
"api_access": true,
"rf_login": true
}
}
}

The real answer has more fields. See reference/enterprise.md.

Step 4. Find your warehouses​

Most methods work inside one warehouse. Ask which warehouses you may use.

curl -s -b cookies.txt -X POST "$BASE_URL/api/rpc" \
-H "Content-Type: application/json" \
-d '{"jsonrpc": "2.0", "id": 2, "method": "list_user_warehouses", "params": {"data": {}}}'

Expected shape:

{
"jsonrpc": "2.0",
"id": 2,
"result": {
"data": [
{
"user_warehouse_id": 1,
"warehouse_id": 1000,
"warehouse_code": "WH1",
"warehouse_name": "Main Warehouse",
"schema_name": "wh1",
"role_id": 1000,
"role_code": "ADMIN",
"is_default": true
}
]
}
}

Use warehouse_id as the value of the X-Warehouse-Id header.

export WAREHOUSE_ID=1000

If you leave the header out, the server uses your default warehouse (the one with is_default set). If you have no default, or more than one, the call fails with WAREHOUSE_REQUIRED.

Step 5. Read data​

list_owner lists owners. It has no required parameters. list_options limits the answer.

curl -s -b cookies.txt -X POST "$BASE_URL/api/rpc" \
-H "X-Warehouse-Id: $WAREHOUSE_ID" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 3,
"method": "list_owner",
"params": { "list_options": { "limit": 5, "offset": 0 } }
}'

Expected shape:

{
"jsonrpc": "2.0",
"id": 3,
"result": {
"data": [
{ "id": 1000, "code": "OWNER1", "company": "Example Co", "owner_type": "Own" }
]
}
}

The real rows have more fields. To find one row by a field, add filters:

curl -s -b cookies.txt -X POST "$BASE_URL/api/rpc" \
-H "X-Warehouse-Id: $WAREHOUSE_ID" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 4,
"method": "list_owner",
"params": { "filters": [ { "code": "OWNER1" } ] }
}'

Step 6. Run a command​

A command changes data. generate_lpn makes the next pallet code for an owner. Use an owner_id from step 5.

curl -s -b cookies.txt -X POST "$BASE_URL/api/rpc" \
-H "X-Warehouse-Id: $WAREHOUSE_ID" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 5,
"method": "generate_lpn",
"params": { "data": { "owner_id": 1000 } }
}'

Expected shape:

{
"jsonrpc": "2.0",
"id": 5,
"result": {
"meta": { "idempotency_replayed": false },
"data": { "code": "LPN0000001" }
}
}

Note the difference from step 5. A command answers with meta and data. meta.idempotency_replayed tells you if the answer is a stored copy of an earlier call.

Most commands need an idempotency key, so you can repeat them safely. generate_lpn does not. For a command that does, see 04-command-types-and-concurrency.md and 08-workflows-by-example.md.

Step 7. Make an API key​

Scripts and other programs should use an API key, not a cookie. Only an ADMIN can make one (the default roles of generate_user_api_key are ADMIN).

curl -s -b cookies.txt -X POST "$BASE_URL/api/rpc" \
-H "X-Warehouse-Id: $WAREHOUSE_ID" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 6,
"method": "generate_user_api_key",
"params": {
"data": {
"user_id": 1000,
"name": "quickstart key",
"expires_at": "2027-01-01T00:00:00Z"
}
}
}'

Expected shape:

{
"jsonrpc": "2.0",
"id": 6,
"result": {
"data": {
"id": 1,
"plaintext_key": "wms_live_sk_<32 random characters>",
"key_prefix": "wms_live_sk_abcd1234",
"expires_at": "2027-01-01T00:00:00Z"
}
}
}

Copy plaintext_key now. The server stores only a hash. It never shows the key again.

The key belongs to the user in user_id. Calls with the key run with that user's role. The user must have api_access set to true.

export WMS_API_KEY="wms_live_sk_..."

Step 8. Call with the API key​

curl -s -X POST "$BASE_URL/api/rpc" \
-H "Authorization: Bearer $WMS_API_KEY" \
-H "X-Warehouse-Id: $WAREHOUSE_ID" \
-H "Content-Type: application/json" \
-d '{"jsonrpc": "2.0", "id": 7, "method": "me", "params": {}}'

The answer is the same as in step 3. You no longer need the cookie.

Step 9. Read a failure​

First, leave out a required field. generate_lpn needs data.owner_id:

curl -s -i -X POST "$BASE_URL/api/rpc" \
-H "Authorization: Bearer $WMS_API_KEY" \
-H "X-Warehouse-Id: $WAREHOUSE_ID" \
-H "Content-Type: application/json" \
-d '{"jsonrpc": "2.0", "id": 8, "method": "generate_lpn", "params": {"data": {}}}'

Expected: HTTP 400 and this shape. The text in detail comes from the JSON parser and may differ.

{
"id": 8,
"error": {
"message": "RPC_PARAMS_INVALID",
"data": {
"req_uuid": "6f3d2a7e-0f44-4a43-bb51-0a9c3c8a5d11",
"detail": "missing field `owner_id`"
}
}
}

Second, call a method your role may not call (or a method that does not exist). The permission check runs before the method lookup. A user with no permission row for the name gets HTTP 403:

{
"id": null,
"error": {
"message": "RBAC_FORBIDDEN",
"data": { "req_uuid": "1c0e8c1a-77f4-47f8-8a52-2d1c5c7f3b90", "detail": "no_such_method" }
}
}

id is null here because the server refused the call before it ran the method. Only a superuser (typ of Admin or Sys) skips the permission check. For a superuser, an unknown method gives HTTP 400 and RPC_REQUEST_METHOD_UNKNOWN.

Error answers are not standard JSON-RPC. There is no jsonrpc key and no numeric code. Read error.message and error.data.detail. Save error.data.req_uuid: it is the id to give to support, because it is in the server log. See 05-errors.md.

Where to go next​