Skip to content

====== Uplink API ======

API root URL: https://uplink.sensational.systems/api/v1/

Authentication: Token-based (DRF TokenAuthentication). Include an Authorization: Token <token> header on requests.

IMPORTANT: All passwords and API tokens and test calls are saved in the Sensational passwords file (internal).

API Users

  • GPT

These are the main routes exposed by the DefaultRouter at the API root:

  • catalogue/products: https://uplink.sensational.systems/api/v1/catalogue/products/
  • catalogue/boxprofiles: https://uplink.sensational.systems/api/v1/catalogue/boxprofiles/
  • orders/packages: https://uplink.sensational.systems/api/v1/orders/packages/
  • orders/shipments: https://uplink.sensational.systems/api/v1/orders/shipments/
  • orders/check_ins: https://uplink.sensational.systems/api/v1/orders/check_ins/
  • orders: https://uplink.sensational.systems/api/v1/orders/
  • products: https://uplink.sensational.systems/api/v1/products/
  • product-instances: https://uplink.sensational.systems/api/v1/product-instances/
  • stock-locations: https://uplink.sensational.systems/api/v1/stock-locations/
  • contacts: https://uplink.sensational.systems/api/v1/contacts/
  • devices: https://uplink.sensational.systems/api/v1/devices/
  • health: https://uplink.sensational.systems/api/v1/health/
  • me: https://uplink.sensational.systems/api/v1/me/

API migration, user set-up and token generation

API Migration

Make sure DRF authtoken migrations are applied. Example:

python manage.py migrate authtoken

Example output (successful migrations):

Applying authtoken.0001_initial... OK
Applying authtoken.0002_auto_20160226_1747... OK
Applying authtoken.0003_tokenproxy... OK
Applying authtoken.0004_alter_tokenproxy_options... OK

API User Set-up

Create a user and give it permissions to use the API. Example using manage.py shell:

from django.contrib.auth.models import User
api_user = User.objects.create_user(username='api_user', password='your_strong_password')
api_user.is_staff = True
api_user.save()

from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from catalogue.models import ProductInstance
content_type = ContentType.objects.get_for_model(ProductInstance)
view_perm = Permission.objects.get(content_type=content_type, codename='view_productinstance')
change_perm = Permission.objects.get(content_type=content_type, codename='change_productinstance')
api_user.user_permissions.add(view_perm, change_perm)

for perm in api_user.get_user_permissions():
    print(perm)

# Example permission strings you might see:
# products.view_productinstance
# products.change_productinstance

Token Generation

After creating the user, generate a token in the Django shell:

from rest_framework.authtoken.models import Token
token, created = Token.objects.get_or_create(user=api_user)
print(token.key)

You can also obtain a token via the login endpoint (if enabled):

curl -X POST -d "username=api_user&password=your_password" https://uplink.sensational.systems/api-token-auth/
# returns: {"token": "abcd1234yourtoken"}

Product Instances

Common product-instance endpoints (examples):

Get a product instance by id

curl -X GET "https://uplink.sensational.systems/api/v1/product-instances/<id>/" \
  -H "Authorization: Token <YOUR_TOKEN>"

Get a product instance's AppKey

For security, you must add a 'true' parameter in the call when retrieving the AppKey of a product instance.

curl -X GET "https://uplink.sensational.systems/api/v1/product-instances/<id>/?keys=true/" \
  -H "Authorization: Token <YOUR_TOKEN>"

Get a product instance by devEUI

curl -X GET "https://uplink.sensational.systems/api/v1/product-instances/eui/<dev_eui>/" \
  -H "Authorization: Token <YOUR_TOKEN>"

Get a product instance by ICCID

curl -X GET "https://uplink.sensational.systems/api/v1/product-instances/iccid/<iccid>/" \
  -H "Authorization: Token <YOUR_TOKEN>"

Search for product instances by barcode/serial

curl -X GET "https://uplink.sensational.systems/api/v1/product-instances/search/<query>/" \
  -H "Authorization: Token <YOUR_TOKEN>"

Update a product instance (allowed fields: name, notes, expiry_date, extra_data)

curl -v -X PATCH https://uplink.sensational.systems/api/v1/product-instances/<id>/ \
  -H "Authorization: Token <YOUR_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"name": "Updated by script", "notes": "note text", "expiry_date": "2025-04-25", "extra_data": {"foo": "bar"}}'

extra_data field behavior

PATCH – add or update keys (shallow merge): - New keys are added to existing data - Existing keys are updated with new values (nested objects are replaced wholesale, not merged) - Null values are not allowed – use the DELETE endpoint below to remove keys

DELETE – remove keys (requires explicit confirmation): - Must include ?confirm_delete=true query parameter - Payload: {"keys": ["key1", "key2"]} - Only top-level keys can be deleted; nested content is removed by replacing the parent object via PATCH


Examples:

  1. Adding/updating specific keys (preserves other keys):
# Existing extra_data: {"key1": "value1", "key2": "value2"}
curl -X PATCH https://uplink.sensational.systems/api/v1/product-instances/123/ \
  -H "Authorization: Token <YOUR_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"extra_data": {"key1": "updated", "key3": "new"}}'
# Result: {"key1": "updated", "key2": "value2", "key3": "new"}
  1. Replacing a nested object wholesale (not merged – the whole nested object is overwritten):
# Existing extra_data: {"config": {"setting1": "a", "setting2": "b"}, "other": "data"}
curl -X PATCH https://uplink.sensational.systems/api/v1/product-instances/123/ \
  -H "Authorization: Token <YOUR_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"extra_data": {"config": {"setting1": "updated"}}}'
# Result: {"config": {"setting1": "updated"}, "other": "data"}
# Note: setting2 is gone – the nested object was replaced, not merged
  1. Deleting specific top-level keys (must use DELETE + confirm_delete flag):
# Existing extra_data: {"key1": "value1", "key2": "value2"}
curl -X DELETE "https://uplink.sensational.systems/api/v1/product-instances/123/extra-data/?confirm_delete=true" \
  -H "Authorization: Token <YOUR_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"keys": ["key1"]}'
# Result: {"key2": "value2"}
  1. Deleting a nested object (delete its top-level key):
# Existing extra_data: {"config": {"setting1": "a"}, "other": "data"}
curl -X DELETE "https://uplink.sensational.systems/api/v1/product-instances/123/extra-data/?confirm_delete=true" \
  -H "Authorization: Token <YOUR_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"keys": ["config"]}'
# Result: {"other": "data"}

Testing locally

# Set your API token (use 'gpt' user token for testing)
TOKEN='2210301485473e0cc517ae6403edd96118a4e3f1'

# 1. Check current extra_data
curl -s http://localhost:8001/api/v1/product-instances/1/ \
  -H "Authorization: Token $TOKEN" | python3 -m json.tool

# 2. Add a new key (merge) - preserves existing data
curl -s -X PATCH http://localhost:8001/api/v1/product-instances/1/ \
  -H "Authorization: Token $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"extra_data": {"new_key": "new_value"}}' | python3 -m json.tool

# 3. Update an existing key
curl -s -X PATCH http://localhost:8001/api/v1/product-instances/1/ \
  -H "Authorization: Token $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"extra_data": {"new_key": "updated_value"}}' | python3 -m json.tool

# 4. Delete a key (requires ?confirm_delete=true)
curl -s -X DELETE "http://localhost:8001/api/v1/product-instances/1/extra-data/?confirm_delete=true" \
  -H "Authorization: Token $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"keys": ["new_key"]}' | python3 -m json.tool

# 5. Replace a nested object (wholesale replacement, not merge)
curl -s -X PATCH http://localhost:8001/api/v1/product-instances/1/ \
  -H "Authorization: Token $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"extra_data": {"config": {"setting1": "value1"}}}' | python3 -m json.tool

# 6. View only extra_data field
curl -s http://localhost:8001/api/v1/product-instances/1/ \
  -H "Authorization: Token $TOKEN" | python3 -c "import sys, json; print(json.dumps(json.load(sys.stdin).get('extra_data'), indent=2))"

Note: Use http:// (not https://) for local development. For production, use https://uplink.sensational.systems.

Relations endpoints (parents & children)

These endpoints allow listing parent/child relations and linking/unlinking instances.

1) Get all relations for an instance

GET /api/v1/product-instances/{id}/relations/

Example:

curl -X GET "https://uplink.sensational.systems/api/v1/product-instances/123/relations/" \
  -H "Authorization: Token <YOUR_TOKEN>"

Response (example):

{
  "parent": {
    "id": 77,
    "serial_number": "GW-12345",
    "dev_eui": "...",
    "product": { "sku": "GW-1", "name": "Gateway" },
    ...
  },
  "children": [
    { "id": 124, "iccid": "...", "product": {"sku": "SIM-1"}, ... },
    { "id": 125, "iccid": "...", "product": {"sku": "SIM-1"}, ... }
  ]
}

POST /api/v1/product-instances/{id}/link-parent/

Payload: { "parent_id": <int> }

curl -X POST "https://uplink.sensational.systems/api/v1/product-instances/124/link-parent/" \
  -H "Authorization: Token <YOUR_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"parent_id": 77}'

Notes: the parent instance must be allowed to have children (SIM instances cannot be parents). If the child already has a different parent the request will be rejected with HTTP 400.

POST /api/v1/product-instances/{id}/unlink-parent/

curl -X POST "https://uplink.sensational.systems/api/v1/product-instances/124/unlink-parent/" \
  -H "Authorization: Token <YOUR_TOKEN>"

POST /api/v1/product-instances/{parent_id}/link-child/

Payload: { "child_id": <int> }

curl -X POST "https://uplink.sensational.systems/api/v1/product-instances/77/link-child/" \
  -H "Authorization: Token <YOUR_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"child_id": 124}'

Notes: parent must be allowed to have children (not a SIM). If the child already has a different parent the request will be rejected.

POST /api/v1/product-instances/{parent_id}/unlink-child/

Payload: { "child_id": <int> } (required)

curl -X POST "https://uplink.sensational.systems/api/v1/product-instances/77/unlink-child/" \
  -H "Authorization: Token <YOUR_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"child_id": 124}'

Behavioral notes and constraints

  • A ProductInstance can have at most one parent. Attempts to set a different parent will return HTTP 400.
  • A parent instance must allow children. In this codebase SIM products are considered not allowed to be parents.
  • Many children can be attached to a parent (subject to product rules).
  • Linking/unlinking returns HTTP 200/201 with serialized instance data on success or appropriate 4xx responses on error.