sLocker
Deliver to Superroute smart lockers. sLocker is the SDK for carriers and delivery partners that put parcels into Superroute smart lockers: create a delivery task, open the doors remotely for the driver, confirm the deposit, follow the pickup, and receive and verify the webhooks — with a mock cabinet to rehearse all of it. For apps made for a service provider's customers, its customer side brings the parcels waiting in lockers, locker locations, locker storage, the locker shop and locker resale, with a pickup list and a locker map for every UI kit. Superroute SDK License — see LICENSE and open.superroute.ca/developer/license.
| Package | Platform | Install |
|---|---|---|
superroute-slocker (PyPI) |
Python 3.9+ | pip install superroute-slocker |
superroute/slocker (Packagist) |
PHP 8.1+ | composer require superroute/slocker |
ca.superroute:slocker (Maven) |
Java 11+ and Android | see Java and Kotlin |
ca.superroute:slocker-android (Maven) |
Kotlin coroutines and OkHttp, for Android | see Java and Kotlin |
github.com/Goodneo-Solutions/slocker-releases/go |
Go 1.21+ | go get github.com/Goodneo-Solutions/slocker-releases/go |
Superroute.SLocker (NuGet) |
.NET 8 | dotnet add package Superroute.SLocker |
@superrouteca/slocker (npm) |
Node 20+, browsers, Deno, Bun; TypeScript types | npm install @superrouteca/slocker |
SLocker (Swift package) |
iOS 15+, macOS 12+ | https://github.com/Goodneo-Solutions/slocker-releases |
slocker (pub.dev) |
Dart 3.5+ and Flutter | dart pub add slocker |
The customer side (built on sApp, from sApp 0.2.2):
| Package | Platform | Install |
|---|---|---|
@superrouteca/slocker-customer (npm) |
JavaScript and TypeScript: web, Node 20+, React Native | npm install @superrouteca/slocker-customer |
@superrouteca/slocker-ui (npm) |
Web UI kit: sr-pickup-list, sr-locker-map |
npm install @superrouteca/slocker-ui |
@superrouteca/slocker-react / @superrouteca/slocker-react-native (npm) |
React and React Native UI kits | npm install @superrouteca/slocker-react |
slocker_customer / slocker_ui (pub.dev) |
Dart; Flutter UI kit | dart pub add slocker_customer, flutter pub add slocker_ui |
SLockerCustomer / SLockerUI (Swift package) |
iOS 15+, macOS 12+; SwiftUI kit | https://github.com/Goodneo-Solutions/slocker-customer-releases |
ca.superroute:slocker-customer-kotlin / slocker-ui-android (Maven) |
Kotlin on the JVM and Android; Jetpack Compose kit | see Java and Kotlin |
ca.superroute:slocker-customer (Maven) |
Java 17+ | see Java and Kotlin |
Superroute.SLocker.Customer / .UI / .Wpf / .WinForms (NuGet) |
.NET 8; WPF and WinForms kits | dotnet add package Superroute.SLocker.Customer |
superroute-slocker[customer] (PyPI) |
Python 3.9+, import slocker.customer |
pip install "superroute-slocker[customer]" |
superroute/slocker with superroute/sapp (Packagist) |
PHP 8.1+, Superroute\SLocker\Customer |
composer require superroute/slocker superroute/sapp |
github.com/Goodneo-Solutions/slocker-releases/go/customer |
Go 1.21+ | go get github.com/Goodneo-Solutions/slocker-releases/go/customer |
Every package of a release carries the same version and covers the same operations. The carrier packages do not depend on sApp; only the customer side does.
What it covers
The partner locker delivery API of Superroute (/api/v1/third-party-delivery/…), 40 operations, each a method:
- Delivery tasks — create (idempotent), list, read, cancel, rotate a code.
- At the cabinet — open the doors remotely with the delivery code, confirm (all or a partial delivery), abort, reassign a package to another compartment, cancel the placement, reopen within the 60-second correction window, open for pickup.
- Scopes and locations — the grants and sub-scopes, create / update / disable a sub-scope, the locations of a scope, the locations near an address or a point.
- Usage — summary, daily, by location.
- Carriers — the carrier profile, intake locations and receiving warehouses, pre-advising packages into a warehouse, the carrier's packages, cancelling, rerouting, and resetting a pickup code.
- Mock cabinet — create, list and delete mock lockers, force doors open or closed, take a cabinet offline, change its features and compartment sizes, seed parcels, clear them, or declare them taken.
The operations are generated from Superroute's published OpenAPI document (specs/partner-locker-api.json). The same
operations exist in Superroute's GraphQL API (specs/partner-locker-graphql.json lists them); sLocker calls REST.
Signing in and choosing the provider
The API takes the bearer token of a Superroute API account (POST /api/v1/user/login; a token lasts a week). Pass a
token you already have, or sign in with the client, which keeps the token for the following calls:
import { SLockerClient } from '@superrouteca/slocker';
const sl = new SLockerClient({ providerId: 4611 }); // baseUrl defaults to https://api.superroute.ca
await sl.login({ email: 'api-partner@example.com', password: process.env.SR_PASSWORD });
// later: await sl.refreshToken(); await sl.rotateToken({ gracePeriodMinutes: 60 }); await sl.logout();
providerId is your third-party delivery provider (sent as X-Third-Party-Provider-Id). It may be left out when the
account acts for exactly one provider; any call can name another one (providerId in its parameters).
A delivery, end to end
- Create the task with the order's own idempotency key. The answer carries
delivery_no, the driver'saccess_codeand the recipient'spickup_code. A repeat with the same key and the same content answers the original task (replayed: true); the same key with different content is409 IDEMPOTENCY_CONFLICT. - At the cabinet, the driver types the code on the screen — or your app opens the doors remotely with
openPartnerLockerDelivery, which answers aclaim_tokenand the compartments that opened. - Confirm with the claim token (
confirm_all, oraccept_partialwhen some packages did not fit). For a remote open the cabinet settles the deposit by itself once every opened door reports closed; an explicit confirm afterwards answers the same result. - Webhooks tell you the rest:
partner_locker.delivery.doors_opened,.delivered(with the pickup code and the compartments),partner_locker.pickup.completed,.failed,.expired,.cancelled,.correction_reopened.
const created = await sl.api.createPartnerLockerDelivery({
idempotencyKey: 'FOOD-20260714-1001-v1', // your own, stable for this order
body: {
external_order_id: 'FOOD-20260714-1001',
platform_scope_group_id: 12,
recipient: { phone: '+14165550123' },
package_count: 2,
},
});
const { delivery_no, access_code } = created.data;
const opened = await sl.api.openPartnerLockerDelivery({ body: { location_id: 101, access_code } });
const { claim_token, attempt } = opened.data;
if (attempt.can_confirm) {
await sl.api.confirmPartnerLockerDelivery({ body: { claim_token, decision: 'confirm_all' } });
}
const task = await sl.api.partnerLockerDelivery({ deliveryNo: delivery_no });
The same in Python:
from slocker import SLockerClient, SLockerError
sl = SLockerClient(provider_id=4611)
sl.login('api-partner@example.com', password)
created = sl.create_partner_locker_delivery(
idempotency_key='FOOD-20260714-1001-v1',
body={'external_order_id': 'FOOD-20260714-1001', 'platform_scope_group_id': 12,
'recipient': {'phone': '+14165550123'}, 'package_count': 2})
try:
opened = sl.open_partner_locker_delivery(body={'location_id': 101, 'access_code': created['data']['access_code']})
sl.confirm_partner_locker_delivery(body={'claim_token': opened['data']['claim_token'], 'decision': 'confirm_all'})
except SLockerError as e:
if e.code == 'LOCKER_DELIVERY_IN_USE':
... # another attempt is in progress: wait, or use its claim token
raise
The sandbox: a mock cabinet
A mock cabinet runs the same pipeline as a real one without hardware; a task created with mock: true only uses mock
cabinets, and its webhooks carry livemode: false, an event_id starting PLE-MOCK- and the header X-Webhook-Test: 1 (sent to your sandbox URL when one is set). Up to 5 mock cabinets per provider, 1–60 compartments each.
locker = sl.create_partner_locker_mock_locker(body={'location_id': 101, 'name': 'Dev cabinet', 'compartments': 8})
device_id = locker['data']['locker']['device_id']
task = sl.create_partner_locker_delivery(idempotency_key='TEST-1', body={
'external_order_id': 'TEST-1', 'platform_scope_group_id': 12, 'recipient': {'email': 'qa@example.com'}, 'mock': True})
opened = sl.open_partner_locker_delivery(body={'location_id': 101, 'access_code': task['data']['access_code']})
sl.confirm_partner_locker_delivery(body={'claim_token': opened['data']['claim_token'], 'decision': 'confirm_all'})
# → webhook partner_locker.delivery.delivered, livemode false
sl.mark_partner_locker_mock_parcels_taken(device_id, body={'compartments': [1]})
# → webhook partner_locker.pickup.completed (a full pickup settlement)
sl.clear_partner_locker_mock_parcels(device_id) # empties the cabinet without a pickup settlement
sl.set_partner_locker_mock_door(device_id, body={'compartment': 2, 'action': 'open'}) # a door left open
sl.set_partner_locker_mock_compartments(device_id, body={'compartments': [1, 2], 'grid_length_cm': 20,
'grid_width_cm': 20, 'grid_height_cm': 20, 'weight_limit_kg': 5}) # rehearse "does not fit"
sl.delete_partner_locker_mock_locker(device_id)
Use the location list in the same mode as the task (mock: true on partnerLockerScopeLocations /
partnerLockerNearbyLocations): a live location for a mock task answers 409 LOCKER_NO_CABINET_FOR_MODE.
Webhooks
Verify every webhook against the raw body exactly as received, before parsing it. sLocker checks the timestamp
(±5 minutes by default), the X-Webhook-Content-Digest and the X-Webhook-Signature
(v1= base64 HMAC-SHA256 of {timestamp}\n{X-Webhook-Delivery-Id}\n{body} with your endpoint's signing secret), and
answers the event. During a key rotation the previous secret stays valid for 24 hours: pass both. An endpoint switched
to Standard Webhooks (webhook-id / webhook-timestamp / webhook-signature) or to CloudEvents structured bodies is
verified too. The same verification covers the carrier channel's carrier_package.distribution.* events.
Deduplicate on eventId (stable across retries; deliveryId changes on every attempt), answer 2xx within 10 seconds,
and keep sandbox events (event.test) apart from production.
import { verifyWebhook, WebhookVerificationError } from '@superrouteca/slocker';
app.post('/webhooks/slocker', express.raw({ type: '*/*' }), async (req, res) => {
try {
const event = await verifyWebhook(req.body, req.headers, [process.env.SLOCKER_SECRET, process.env.SLOCKER_OLD_SECRET]);
if (!event.test) await queue.add(event.eventId, event);
res.sendStatus(204);
} catch (e) {
if (e instanceof WebhookVerificationError) return res.status(400).send(e.reason);
throw e;
}
});
| Language | Verify | Sign a test request |
|---|---|---|
| Python | slocker.verify(body, headers, secrets) |
slocker.sign(secret, body) |
| PHP | Webhooks::verify(file_get_contents('php://input'), getallheaders(), $secrets) |
Webhooks::sign($secret, $body) |
| Java | Webhooks.verify(body, headers, secrets) |
Webhooks.sign(...) |
| Go | slocker.Verify(body, slocker.HeaderMap(r.Header), secrets, nil) |
slocker.Sign(secret, body, slocker.SignOptions{}) |
| C# | Webhooks.Verify(body, headers, secrets) |
Webhooks.Sign(secret, body) |
| JavaScript | await verifyWebhook(body, headers, secrets) |
await signWebhook(secret, body) |
| Swift | try Webhooks.verify(body:headers:secrets:) |
Webhooks.sign(secret:body:) |
| Dart | verifyWebhook(bodyBytes, headers, secrets) |
signWebhook(secret, bodyBytes) |
A refused webhook raises an error whose reason is one of MISSING_HEADER, INVALID_TIMESTAMP,
TIMESTAMP_OUT_OF_TOLERANCE, DIGEST_MISMATCH, SIGNATURE_MISMATCH or INVALID_PAYLOAD.
Errors and retries
Every failed call raises one error type with the HTTP status, the server's stable code, its message, the field
errors and retryAfter (seconds). Act on the status and the code, never on the message. Codes the SDK adds:
UNAUTHENTICATED (401 without a code: sign in again), HTTP_<status> (an answer without a code), NETWORK_ERROR
(no answer; status 0) and INVALID_ARGUMENT (a missing path parameter or an unknown operation; nothing was sent).
Only repeatable calls are retried: reads, deletes, and the two creates that carry an idempotency key
(createPartnerLockerDelivery, submitCarrierPackages). They are repeated after a timeout, a lost connection, 408,
425, 429 or 5xx — at most maxRetries times (2 by default), waiting what Retry-After asks (at most 60 seconds) or
an exponential backoff with jitter. Opening doors, confirming, aborting, reassigning and every other change are never
repeated by the SDK: after a timeout, read the task (partnerLockerDelivery) before deciding.
When the SDK makes the idempotency key itself it is kept across its own retries only; give your own key (one per order, unchanged on every retry of yours) so that a retry after a crash finds the original task.
Each language
Python
from slocker import SLockerClient
sl = SLockerClient(token, provider_id=4611) # sync; urllib from the standard library
sl.partner_locker_deliveries(status='delivered', per_page=50)
sl.call('partnerLockerDeliveries', {'status': 'delivered'}) # any operation by name
PHP
use Superroute\SLocker\Client;
$sl = new Client($token, providerId: 4611); // curl
$sl->partnerLockerDeliveries(status: 'delivered', perPage: 50);
Java and Kotlin
Add the Maven repository https://goodneo-solutions.github.io/slocker-releases/maven, then
implementation("ca.superroute:slocker:0.2.0") — or, in an Android app, ca.superroute:slocker-android:0.2.0.
Client sl = Client.builder().token(token).providerId(4611L).build(); // java.net.http, or HttpURLConnection on Android
Object page = sl.partnerLockerDeliveries(Params.create().put("status", "delivered").put("per_page", 50));
val sl = SLockerAsync(Client.builder().token(token).providerId(4611L).transport(OkHttpTransport()).build())
val page = sl.api.partnerLockerDeliveries(status = "delivered", perPage = 50) // suspend, on Dispatchers.IO
Answers are plain JSON values (Map, List, String, Long, Double, Boolean, null).
Go
sl := slocker.New(slocker.Config{Token: token, ProviderID: 4611})
page, err := sl.PartnerLockerDeliveries(ctx, slocker.Params{"status": "delivered", "per_page": 50})
var e *slocker.Error
if errors.As(err, &e) && e.Code == "LOCKER_RATE_LIMITED" { /* … */ }
C#
var sl = new SLockerClient(new SLockerOptions { Token = token, ProviderId = 4611 });
var page = await sl.PartnerLockerDeliveriesAsync(status: "delivered", perPage: 50);
Swift
let sl = SLockerClient(token: token, providerId: 4611)
let page = try await sl.partnerLockerDeliveries(status: "delivered", perPage: 50)
Dart and Flutter
final sl = SLockerClient(token: token, providerId: 4611);
final page = await sl.partnerLockerDeliveries(status: 'delivered', perPage: 50);
Every client takes its own transport (for tests, proxies or an HTTP stack the app already has), a base URL (white-label API hosts and test environments), the retry count and the wait function.
Customer side
Apps made for a service provider's customers — on the Open Platform's partner app surface
(open.superroute.ca/app/v1) — use the customer side: 38 operations, each a method.
- Pickups — the person's parcels in lockers with their pickup codes, opening the door to collect, handing a pickup over to someone else and taking it back.
- Locker locations — lockers near the person, one location, its indoor guide and feedback on it, the person's preferred locker.
- Locker storage — claiming a rental, keeping it, the return time, sending it on (onward quote, delivery, shipment), pickup sites, resending the code.
- Locker shop — the catalogue of a location, items, orders, opening the door of an order, refund requests, access codes.
- Locker resale — the terms, the person's deals, creating a deal, releasing or revoking the buyer's code, aborting, cancelling or reporting a deal.
The customer side is built on the sApp core: sign-in, token refresh, the app key for calls before sign-in, idempotency
keys, retries, extra query and errors (SuperrouteError / SuperrouteException, a result: false answer included)
are sApp's own. What a caller may do is decided by the server.
Moving from sApp
From sApp 0.2.2 the smart-locker methods are in sLocker, under the same names and with the same arguments as in sApp
0.2.1: api.platformAppPickups() on an sApp client becomes api.platformAppPickups() on an sLocker customer. The
pickup list and the locker map of the UI kits move the same way. A path belongs to sLocker when its first segment after
the client scope is pickups, locker-locations, locker-storage, locker-shop or locker-resale
(tools/locker_paths.json, the same list as sApp's). Store orders stay in sApp, their locker open and refund included.
| sApp 0.2.1 method | Request |
|---|---|
platformAppPickups, platformAppPickup |
GET /app/v1/pickups, GET /app/v1/pickups/{deliveryId} |
platformAppCollectPickup |
POST /app/v1/pickups/{deliveryId}/collect |
platformAppHandOverPickup, platformAppTakeBackPickup |
POST /app/v1/pickups/{deliveryId}/handoff, …/handoff/revoke |
platformAppLockerLocations, platformAppLockerLocation |
GET /app/v1/locker-locations, …/{locationId} |
platformAppLockerLocationGuide, platformAppLockerLocationGuideFeedback |
GET …/{locationId}/guide, POST …/guide/feedback |
platformAppUpdateLockerPreference |
PUT /app/v1/locker-locations/{locationId}/preference |
platformAppLockerShopCatalog |
GET /app/v1/locker-locations/{locationId}/shop |
platformAppLockerShopItems, platformAppLockerShopOrders, platformAppLockerShopOrder |
GET /app/v1/locker-shop/items, …/orders, …/orders/{id} |
platformAppLockerShopCreateOrder, platformAppLockerShopOpen, platformAppLockerShopRefundRequest |
POST /app/v1/locker-shop/orders, …/{id}/open, …/{id}/refund-request |
platformAppLockerShopIssueAccessCode |
POST /app/v1/locker-shop/access-codes |
platformAppLockerStorageRentals, platformAppLockerStorageRental |
GET /app/v1/locker-storage/rentals, …/{id} |
platformAppLockerStorageClaim |
POST /app/v1/locker-storage/rentals/claim |
platformAppLockerStorageKeep, …ReturnTime, …ResendCode, …OnwardQuote, …Delivery, …Shipment, …PickupSite, …PickupSiteCancel |
POST /app/v1/locker-storage/rentals/{id}/keep, return-time, resend-code, onward-quote, delivery, shipment, pickup-site, pickup-site/cancel |
New in sLocker: lockerResaleTerms, lockerResaleDeals, lockerResaleDeal, createLockerResaleDeal,
releaseLockerResaleCode, revokeLockerResaleCode, abortLockerResaleDeal, cancelLockerResaleDeal,
reportLockerResaleDeal (/app/v1/locker-resale/…).
sLocker on its own
Create a customer with your partner app's code, the service provider's client code, the app key and a token store; sLocker makes the sApp connection inside:
import { SLocker } from '@superrouteca/slocker-customer';
const locker = SLocker.customer({
appCode: 'p_acme_parcels',
clientCode: 'k3m9q2', // the service provider's public client code
appKey: 'sp_appkey_…', // restricted key for calls before sign-in
tokenStore, // your secure storage: { get, set, clear }
locale: 'en',
});
await locker.auth.signInWithIdToken(googleIdToken); // sApp's sign-in
const pickups = await locker.api.platformAppPickups({ status: 'ready' });
await locker.api.platformAppCollectPickup({ deliveryId: 42, body: { user_confirmed: true } });
const deals = await locker.api.lockerResaleDeals({ open: true });
| Language | sLocker on its own | Beside an sApp client |
|---|---|---|
| JavaScript | SLocker.customer({ appCode, clientCode, appKey, tokenStore }) |
SLocker.customer({ sapp: sr }) |
| Dart | SLocker.customer(appCode: …, clientCode: …, appKey: …, tokenStore: …) |
SLocker.customerFor(sr) |
| Swift | SLocker.customer(appCode:clientCode:appKey:tokenStore:) |
SLocker.customer(sapp: sr) |
| Kotlin | SLocker.customer(appCode = …, clientCode = …, appKey = …, tokenStore = …) |
SLocker.customer(sapp = sr) |
| Java | SLocker.customer(appCode, clientCode, appKey, tokenStore) or SLocker.customer(PartnerClient.builder(…)) |
SLocker.customer(sr) |
| C# | SLocker.Customer("p_acme_parcels", clientCode: …, appKey: …, tokenStore: …) |
SLocker.Customer(sr) |
| Python | customer('p_acme_parcels', client_code=…, app_key=…, token_store=…) |
customer(sapp=sr) |
| PHP | SLocker::customer('p_acme_parcels', clientCode: …, appKey: …) |
SLocker::customer(sapp: $sr) |
| Go | customer.New(customer.Options{AppCode: …, ClientCode: …, AppKey: …}) |
customer.Share(sr) |
Each answers a customer (SLockerCustomerClient in Swift) with api (one method per operation; API in Go, Api in
C#), the sApp client underneath
(sapp; SApp in Go and C#) for sign-in and every other partner operation, and callOperation for an operation
described by hand.
Beside an sApp client
An app that already uses sApp passes its client, so both share one sign-in and one token store:
import 'package:sapp/sapp.dart';
import 'package:slocker_customer/slocker_customer.dart';
final sr = PartnerClient(appCode: 'p_acme_parcels', clientCode: 'k3m9q2', appKey: 'sp_appkey_…', tokenStore: store);
final locker = SLocker.customerFor(sr);
final notifications = await sr.api.platformAppNotifications(); // sApp
final pickups = await locker.api.platformAppPickups(page: 1); // sLocker
from sapp import PartnerClient
from slocker.customer import customer
sr = PartnerClient('p_acme_parcels', client_code='k3m9q2', app_key='sp_appkey_…')
locker = customer(sapp=sr)
locker.api.platform_app_pickups(status='ready')
// Package.swift: .package(url: "https://github.com/Goodneo-Solutions/slocker-customer-releases", from: "0.2.0")
// in the target: .product(name: "SLockerCustomer", package: "slocker-customer-releases"), and "SLockerUI" for the kit;
// sApp (sapp-releases) comes with it.
import SApp
import SLockerCustomer
let locker = SLocker.customer(sapp: sr)
let pickups = try await locker.api.platformAppPickups(status: "ready")
UI kits: the pickup list and the locker map
The pickup list (parcels in lockers, their pickup codes, opening the door after confirming) and the locker map (lockers near the person with distance, status and free compartments, search and filters, and the same lockers on the app's own sMap map) behave exactly as they did in the sApp UI kits. They sit inside the sApp kit's scope or provider, take its client, theme and strings in 14 languages, raise events and never navigate.
| Kit | Pickup list | Locker map |
|---|---|---|
Flutter (slocker_ui) |
SrPickupList |
SrLockerMap with SrSMapAdapter, SrMapAdapter, SrMapPoint |
Web (@superrouteca/slocker-ui) |
<sr-pickup-list> (SrPickupList) |
<sr-locker-map> (SrLockerMap, smapAdapter) |
| React / React Native | PickupList |
LockerMap (smapAdapter) |
SwiftUI (SLockerUI) |
PickupList |
LockerMap with SRSMapAdapter, SRMapAdapter, SRMapPoint |
Jetpack Compose (slocker-ui-android) |
PickupList |
LockerMap with SrSMapAdapter, SrMapAdapter, SrMapPoint |
WPF / WinForms (Superroute.SLocker.Wpf / .WinForms) |
PickupList (PickupListViewModel) |
LockerMap (LockerMapViewModel, SMapAdapter, IMapAdapter, MapPoint) |
SuperrouteScope(
client: sr,
theme: ThemeTokens.fromBranding(branding),
onSignInRequired: () => router.go('/sign-in'),
child: SrPickupList(onShowCode: (qr) => showQr(qr)),
)
<script type="module">
import { configure } from '@superrouteca/sapp-ui';
import '@superrouteca/slocker-ui';
configure({ client: sr, theme: themeFromBranding(branding), locale: 'en' });
</script>
<sr-pickup-list></sr-pickup-list>
<sr-locker-map></sr-locker-map>
Development
specs/ the partner locker slice of Superroute's REST and GraphQL documents, and operations.json
tools/sync_specs.py copies the slice from a Superroute checkout (and checks no route lacks its Swagger entry)
tools/generate.py writes every language's operation table and typed methods from the spec
tools/make_client_cases.py writes tests/cases/client.json: requests, answers, retries, sign-in
tools/make_webhook_vectors.py writes tests/cases/webhooks.json, signed exactly as Superroute signs
tests/cases/ the cases every language runs
python3 -I tools/sync_specs.py ../Superroute # then commit specs/ and the generated files
cd python && python3 -m unittest discover -s tests
cd js && npm test
cd php && php tests/run.php
cd go && go test ./...
cd java && ./gradlew :slocker:test :slocker-android:test
dotnet test dotnet/tests/Superroute.SLocker.Tests
cd swift && swift test
cd dart && dart pub get && dart test
Never edit generated files (generated/, Generated/, _operations.py, operations_gen.go, SLockerOperations.kt):
change Superroute's spec, sync, and generate again.
The customer side has its own spec and generator, and is built against the published sApp packages (0.2.2); the generator and the locker path check read the sApp source:
specs/customer-api.json the smart-locker operations of the partner app surface
tools/locker_paths.json the one list of locker paths, the same file as sApp's (tools/check_locker_paths.py)
tools/customer_operations.json the 38 operations the customer side must have
tools/sync_customer_specs.py copies the locker operations from a Superroute checkout, then generates
tools/generate_customer.py writes LockerApi in every language with sApp's own generator
tools/fetch-sapp.sh, sapp-ref the sApp source at .sapp (git-ignored), at its release tag
SAPP_DIR=../sApp tools/fetch-sapp.sh # or tools/fetch-sapp.sh to clone it (needs read access)
python3 -I tools/sync_customer_specs.py ../Superroute
npm install && npm test # js-customer, web-ui, react, react-native
cd dart-customer && dart pub get && dart test
cd flutter-ui && flutter test # with a pubspec_overrides.yaml naming ../dart-customer
cd swift-customer && swift test
cd java-customer && ./gradlew test
cd kotlin-customer && ./gradlew :slocker-customer-kotlin:test :slocker-ui-android:testDebugUnitTest
dotnet test dotnet/tests/Superroute.SLocker.Customer.Tests && dotnet test dotnet/tests/Superroute.SLocker.UI.Tests
pip install superroute-sapp && python3 -m unittest discover -s python/tests
php php/tests/customer.php # the sApp PHP source at .sapp/php
(cd go/customer && go test ./...)
In CI the customer workflows (customer-*.yml) use the published sApp packages. Only the internal profile and the
locker path check read the private sApp repository, with the secret SAPP_DEPLOY_KEY (a read-only deploy key of the
sApp repository), and are skipped without it.
Releases
sLocker started at 0.1.0; each release adds 0.0.1 to the last one, skipping every version that contains the digit 3
or 4 (0.1.0, 0.1.1, 0.1.2, 0.1.5, 0.1.6, …, 0.1.9, 0.2.0, …). Tagging a version (0.1.1, no v) runs
.github/workflows/release.yml, which tests every language and then publishes:
- npm (
@superrouteca/slocker; the customer side's@superrouteca/slocker-customer,slocker-ui,slocker-reactandslocker-react-native), PyPI (superroute-slocker, the customer side as its extra[customer]), NuGet (Superroute.SLocker;Superroute.SLocker.Customer,.UI,.Wpfand.WinForms) and pub.dev (slocker;slocker_customerandslocker_ui) directly, by trusted publishing; - to the public repository
Goodneo-Solutions/slocker-releases: the Swift package (a prebuilt XCFramework attached to that repository's GitHub Release of the version), the Maven repository (maven/, served by GitHub Pages) withca.superroute:slockerandslocker-androidand the customer side'sslocker-customer,slocker-customer-kotlinandslocker-ui-android, and the Go modules (go/, taggo/v<version>;go/customer, taggo/customer/v<version>); - to the public repository
Goodneo-Solutions/slocker-customer-releases: the customer side for iOS and macOS (SLockerCustomerandSLockerUIas prebuilt XCFrameworks attached to that repository's GitHub Release of the version, depending onsapp-releases), so the carrier Swift package inslocker-releasesdepends on nothing; - to the public repository
Goodneo-Solutions/slocker-php: the PHP package at its root, tagv<version>, which Packagist reads.
This source repository stays private; the packages are public under the Superroute SDK License (see LICENSE).
Metadata
Release files for superroute-slocker 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| superroute_slocker-0.2.0.tar.gz | 49.1 kB | With provenance | Details for superroute_slocker-0.2.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| superroute_slocker-0.2.0-py3-none-any.whl | Python 3 | none | any | Details for superroute_slocker-0.2.0-py3-none-any.whl |
Total release size: 77.3 kB
Release files / superroute_slocker-0.2.0.tar.gz
| Download URL | superroute_slocker-0.2.0.tar.gz |
|---|---|
| Size | 49.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5af40e939a8f4fcda51dc9f61329fbd106860af38300496eccb17f4bf9a092e3
|
|
BLAKE2b-256 checksum How to use checksums |
8e85d3391105742186dc07c9a410c82798b49813f5d067211fc0ef9906c3eebd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 31, 2026.
Transparency logRelease files / superroute_slocker-0.2.0-py3-none-any.whl
| Download URL | superroute_slocker-0.2.0-py3-none-any.whl |
|---|---|
| Size | 28.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
59ea5e1aab3307ed2e5da55500e660316d2d650f4457237594b155eea6162f42
|
|
BLAKE2b-256 checksum How to use checksums |
aff9bca9e0c58ecfe5c134489b8339a0dca85346817b3662d495274896294c50
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 31, 2026.
Transparency logSecurity for superroute-slocker 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for superroute_slocker-0.2.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 31, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.