====== 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
Current List of Uplink API Endpoints (examples)¶
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:
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:
- 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"}
- 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
- 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"}
- 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"}, ... }
]
}
2) Link a parent to an instance (make {parent_id} the parent of {id})¶
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.
3) Unlink parent from an instance¶
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>"
4) Link a child to a parent (attach an existing instance as a child)¶
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.
5) Unlink a child from a parent¶
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.