Skip to main content
sLocker

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

  1. Create the task with the order's own idempotency key. The answer carries delivery_no, the driver's access_code and the recipient's pickup_code. A repeat with the same key and the same content answers the original task (replayed: true); the same key with different content is 409 IDEMPOTENCY_CONFLICT.
  2. At the cabinet, the driver types the code on the screen — or your app opens the doors remotely with openPartnerLockerDelivery, which answers a claim_token and the compartments that opened.
  3. Confirm with the claim token (confirm_all, or accept_partial when 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.
  4. 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-react and slocker-react-native), PyPI (superroute-slocker, the customer side as its extra [customer]), NuGet (Superroute.SLocker; Superroute.SLocker.Customer, .UI, .Wpf and .WinForms) and pub.dev (slocker; slocker_customer and slocker_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) with ca.superroute:slocker and slocker-android and the customer side's slocker-customer, slocker-customer-kotlin and slocker-ui-android, and the Go modules (go/, tag go/v<version>; go/customer, tag go/customer/v<version>);
  • to the public repository Goodneo-Solutions/slocker-customer-releases: the customer side for iOS and macOS (SLockerCustomer and SLockerUI as prebuilt XCFrameworks attached to that repository's GitHub Release of the version, depending on sapp-releases), so the carrier Swift package in slocker-releases depends on nothing;
  • to the public repository Goodneo-Solutions/slocker-php: the PHP package at its root, tag v<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)

Source distribution for superroute-slocker 0.2.0
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)

Table of built distributions (wheels) for superroute-slocker 0.2.0
File Interpreter ABI Platform Actions
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 log

Release 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 log

Security 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.

Select from 2 files

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 log

A 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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page