curl --request PUT \
--url https://app.stockshift.io/api/v1/apps/push-token \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"token": "7d1c2a4f0b6e9c3d8a5f2e1b4c7d0a9e6f3b8c5d2a1e4f7b0c3d6e9a2f5b8c1d"
}
'import requests
url = "https://app.stockshift.io/api/v1/apps/push-token"
payload = { "token": "7d1c2a4f0b6e9c3d8a5f2e1b4c7d0a9e6f3b8c5d2a1e4f7b0c3d6e9a2f5b8c1d" }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.put(url, json=payload, headers=headers)
print(response.text)<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.stockshift.io/api/v1/apps/push-token",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_POSTFIELDS => json_encode([
'token' => '7d1c2a4f0b6e9c3d8a5f2e1b4c7d0a9e6f3b8c5d2a1e4f7b0c3d6e9a2f5b8c1d'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://app.stockshift.io/api/v1/apps/push-token"
payload := strings.NewReader("{\n \"token\": \"7d1c2a4f0b6e9c3d8a5f2e1b4c7d0a9e6f3b8c5d2a1e4f7b0c3d6e9a2f5b8c1d\"\n}")
req, _ := http.NewRequest("PUT", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"code": "<string>",
"detail": "<string>",
"errors": {},
"feature": "<string>",
"reason": "<string>",
"attempts_left": 123,
"rule_ids": [
"<string>"
],
"witness": "<string>",
"blind_recount": true,
"flags": [
{
"flag": "<string>",
"facts": {},
"choices": [
"<string>"
]
}
]
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"code": "<string>",
"detail": "<string>",
"errors": {},
"feature": "<string>",
"reason": "<string>",
"attempts_left": 123,
"rule_ids": [
"<string>"
],
"witness": "<string>",
"blind_recount": true,
"flags": [
{
"flag": "<string>",
"facts": {},
"choices": [
"<string>"
]
}
]
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"code": "<string>",
"detail": "<string>",
"errors": {},
"feature": "<string>",
"reason": "<string>",
"attempts_left": 123,
"rule_ids": [
"<string>"
],
"witness": "<string>",
"blind_recount": true,
"flags": [
{
"flag": "<string>",
"facts": {},
"choices": [
"<string>"
]
}
]
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"code": "<string>",
"detail": "<string>",
"errors": {},
"feature": "<string>",
"reason": "<string>",
"attempts_left": 123,
"rule_ids": [
"<string>"
],
"witness": "<string>",
"blind_recount": true,
"flags": [
{
"flag": "<string>",
"facts": {},
"choices": [
"<string>"
]
}
]
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"code": "<string>",
"detail": "<string>",
"errors": {},
"feature": "<string>",
"reason": "<string>",
"attempts_left": 123,
"rule_ids": [
"<string>"
],
"witness": "<string>",
"blind_recount": true,
"flags": [
{
"flag": "<string>",
"facts": {},
"choices": [
"<string>"
]
}
]
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"code": "<string>",
"detail": "<string>",
"errors": {},
"feature": "<string>",
"reason": "<string>",
"attempts_left": 123,
"rule_ids": [
"<string>"
],
"witness": "<string>",
"blind_recount": true,
"flags": [
{
"flag": "<string>",
"facts": {},
"choices": [
"<string>"
]
}
]
}Register the calling app's push token
Keeps the token APNs or FCM gave the app, so the server can tell the device that a feed changed or that a lock, wipe or sign-out waits. A push carries ids only; the app pulls the rest. The device holds one token: send a new one whenever the platform rotates it. Needs an access token issued to an app; any other is not found. Audited, without the token.
curl --request PUT \
--url https://app.stockshift.io/api/v1/apps/push-token \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"token": "7d1c2a4f0b6e9c3d8a5f2e1b4c7d0a9e6f3b8c5d2a1e4f7b0c3d6e9a2f5b8c1d"
}
'import requests
url = "https://app.stockshift.io/api/v1/apps/push-token"
payload = { "token": "7d1c2a4f0b6e9c3d8a5f2e1b4c7d0a9e6f3b8c5d2a1e4f7b0c3d6e9a2f5b8c1d" }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.put(url, json=payload, headers=headers)
print(response.text)<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.stockshift.io/api/v1/apps/push-token",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_POSTFIELDS => json_encode([
'token' => '7d1c2a4f0b6e9c3d8a5f2e1b4c7d0a9e6f3b8c5d2a1e4f7b0c3d6e9a2f5b8c1d'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://app.stockshift.io/api/v1/apps/push-token"
payload := strings.NewReader("{\n \"token\": \"7d1c2a4f0b6e9c3d8a5f2e1b4c7d0a9e6f3b8c5d2a1e4f7b0c3d6e9a2f5b8c1d\"\n}")
req, _ := http.NewRequest("PUT", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"code": "<string>",
"detail": "<string>",
"errors": {},
"feature": "<string>",
"reason": "<string>",
"attempts_left": 123,
"rule_ids": [
"<string>"
],
"witness": "<string>",
"blind_recount": true,
"flags": [
{
"flag": "<string>",
"facts": {},
"choices": [
"<string>"
]
}
]
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"code": "<string>",
"detail": "<string>",
"errors": {},
"feature": "<string>",
"reason": "<string>",
"attempts_left": 123,
"rule_ids": [
"<string>"
],
"witness": "<string>",
"blind_recount": true,
"flags": [
{
"flag": "<string>",
"facts": {},
"choices": [
"<string>"
]
}
]
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"code": "<string>",
"detail": "<string>",
"errors": {},
"feature": "<string>",
"reason": "<string>",
"attempts_left": 123,
"rule_ids": [
"<string>"
],
"witness": "<string>",
"blind_recount": true,
"flags": [
{
"flag": "<string>",
"facts": {},
"choices": [
"<string>"
]
}
]
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"code": "<string>",
"detail": "<string>",
"errors": {},
"feature": "<string>",
"reason": "<string>",
"attempts_left": 123,
"rule_ids": [
"<string>"
],
"witness": "<string>",
"blind_recount": true,
"flags": [
{
"flag": "<string>",
"facts": {},
"choices": [
"<string>"
]
}
]
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"code": "<string>",
"detail": "<string>",
"errors": {},
"feature": "<string>",
"reason": "<string>",
"attempts_left": 123,
"rule_ids": [
"<string>"
],
"witness": "<string>",
"blind_recount": true,
"flags": [
{
"flag": "<string>",
"facts": {},
"choices": [
"<string>"
]
}
]
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"code": "<string>",
"detail": "<string>",
"errors": {},
"feature": "<string>",
"reason": "<string>",
"attempts_left": 123,
"rule_ids": [
"<string>"
],
"witness": "<string>",
"blind_recount": true,
"flags": [
{
"flag": "<string>",
"facts": {},
"choices": [
"<string>"
]
}
]
}Authorizations
How the StockShift apps sign in. The app opens /apps/authorize in the system browser with a PKCE S256 challenge and the thumbprint of its device key. The member signs in there, with every check the web enforces. The browser returns a code to the app, which trades it at POST /apps/token with the PKCE verifier and a DPoP proof (RFC 9449) signed by the device key. The answer holds an access token of one hour, which is sent as a bearer token on every call, and a rotating refresh token of 30 days. A refresh needs a DPoP proof with the server's latest nonce, so a stolen refresh token is useless without the device key. A session ends 180 days after sign-in at most. Every app request carries StockShift-Client: / (), such as ios/1.4.2 (311). Below the minimum of GET /apps/version-policy a call is answered 426 client_upgrade_required with the minimum and the store link. Below the deprecated-below version it works and the response adds StockShift-Client-Deprecated: sunset=; minimum=. Signing in and out, the device status, wipe receipt and PIN lock calls and the version policy answer every version.
Body
The push token an app got from APNs or FCM.
The device token from APNs (hex) or the registration token from FCM, as the platform gave it.
4096^[A-Za-z0-9_:\-]+$"7d1c2a4f0b6e9c3d8a5f2e1b4c7d0a9e6f3b8c5d2a1e4f7b0c3d6e9a2f5b8c1d"
Which APNs servers know the token: sandbox for an iOS or macOS app built from Xcode, otherwise production, the default.
production, sandbox Response
No content
Was this page helpful?