Build with SoWayJuli.
Everything you need to register an application, authorize users, and call the SoWayJuli APIs.
SoWayJuli is a product of JuliHyro Studio Workspace ↗.
Quickstart
Go from a new registration to your first authenticated API request.
- Create an application.
Open Developer Portal → Applications. Save the application and copy its tenant ID, client ID, and software ID.
- Register the callback.
Use the exact callback URI your client listens on. For local tests, use
http://localhost; production callbacks should use HTTPS. Scheme, host, port, path, and trailing slash must match exactly. - Choose scopes.
Request only what you need.
profileis required for basic profile/media access;emailandphonegrant those fields. See Available scopes. - Create state and PKCE values.
Generate a new unpredictable state and verifier for every authorization attempt. Store both server-side or in a short-lived protected client session; never accept a callback without comparing its state.
- Start authorization.
POST the app IDs, callback, requested scopes, state, and S256 challenge to
/api/v0/backend-api/graph/authorize. Check both the HTTP status anddata.transaction_status; opendata.login_urlin the user's browser when the transaction is pending. - Handle the callback.
Compare returned state with the value saved for this attempt. Only then read the authorization code. The generated example on the app Details page includes a local callback listener.
- Exchange and call.
POST the code, exact callback, client ID, and original verifier to
/api/v0/backend-api/token. Send the returned access token asAuthorization: Bearer .... Store the rotated refresh token securely.
Minimal profile request
curl "$BASE_URL/user/profile" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Accept: application/json"
Install the generated Python example dependency with python -m pip install requests. See the API reference for request bodies and response examples.
Use HTTPS, keep certificate verification enabled, validate state, and never commit access/refresh tokens or private credentials.
Register an application
Create a registration that identifies your integration to SoWayJuli.
Create your registration
- Open the Applications area in Developer Portal.
- Create a new application and provide its name and description.
- Add the callback URL your OAuth client will use.
- Select the minimum required scopes.
- Save the registration, then open its Details page to copy the client ID and review the generated Python example.
Callback URL requirements
The callback sent in the authorization and token-exchange flow must match a registered callback. For local development, use a local callback listener that matches the URI configured in the registration.
Choose scopes carefully
Scopes describe the access your app requests. Start with the smallest set that supports your feature and add scopes only when needed. The current registry is listed on Available scopes.
Manage applications
Review and maintain your existing registrations from Developer Portal.
Open app details
Go to Developer Portal → Applications, select the registration, and open its Details page. Use that page to review the client ID, callback URLs, allowed scopes, status, and app-specific code example.
When configuration changes
- Keep callback URLs aligned with the actual callback listener.
- Request only the scopes your application uses.
- Update your local configuration when the registration changes.
- Do not expose access tokens in logs, screenshots, or public repositories.
Application status
Control whether a registration is active for use.
Active
An active registration is enabled for its intended application flow.
Offline
An offline registration is disabled for starting new authorization flows. If an integration stops working after a status change, review the app status in its Details page before debugging callback or token handling.
Application status is separate from a user's account status and from access-token expiration.
Python: simple flow
The generated app-specific example is the runnable path; this page explains the security-critical values it creates.
Install Requests
python -m pip install requests
Generate PKCE values
import base64
import hashlib
import secrets
state = secrets.token_urlsafe(32)
verifier = secrets.token_urlsafe(64)
challenge = base64.urlsafe_b64encode(
hashlib.sha256(verifier.encode("ascii")).digest()
).rstrip(b"=").decode("ascii")
Keep state and verifier for this attempt only. Send challenge with code_challenge_method: "S256"; do not send the verifier in the authorize request.
Authorize request
response = requests.post(
BASE_URL + "/api/v0/backend-api/graph/authorize",
json={
"tenant_id": TENANT_ID,
"client_id": CLIENT_ID,
"software_id": SOFTWARE_ID,
"callback": REDIRECT_URI,
"scope": "profile email",
"state": state,
"code_challenge": challenge,
"code_challenge_method": "S256",
},
timeout=15,
)
response.raise_for_status()
body = response.json()
data = body["data"]
if data["transaction_status"] != "Pending":
raise RuntimeError("Authorization was rejected")
webbrowser.open(data["login_url"])
After the local callback receives a code, compare the callback's state with the saved state before exchanging it.
Exchange and call
token_response = requests.post(
BASE_URL + "/api/v0/backend-api/token",
json={
"grant_type": "authorization_code",
"code": authorization_code,
"callback": REDIRECT_URI,
"client_id": CLIENT_ID,
"code_verifier": verifier,
},
timeout=15,
)
token_response.raise_for_status()
access_token = token_response.json()["data"]["access_token"]
profile = requests.get(
BASE_URL + "/user/profile",
headers={"Authorization": "Bearer " + access_token},
timeout=15,
)
profile.raise_for_status()
For a full callback listener, media downloads, and safe state handling, use the generated example on the registration's Details page.
Python: advanced flow
Handle one-time codes, rotating refresh tokens, scopes, binary media, and transient failures safely.
Token lifecycle
- Authorization codes are short-lived and one-time use.
- Access tokens expire; use the returned
expires_invalue instead of assuming a fixed lifetime. - Refresh tokens rotate. Persist the newly returned access/refresh pair atomically and discard the old refresh token.
- Never log tokens, authorization codes, PKCE verifiers, or password fields.
Refresh example
response = requests.post(
BASE_URL + "/api/v0/backend-api/token",
json={
"grant_type": "refresh_token",
"client_id": CLIENT_ID,
"refresh_token": current_refresh_token,
},
timeout=15,
)
response.raise_for_status()
tokens = response.json()["data"]
save_token_pair(tokens["access_token"], tokens["refresh_token"])
Retries and scopes
- Retry network failures with bounded exponential backoff and a timeout; do not retry invalid credentials or invalid grants automatically.
- On
429, wait at least the number of seconds inRetry-After. - On
401, refresh once, replace the token pair, and retry the original request at most once. - On
403orNOT ALLOWED CURRENT SCOPE, request the missing scope and repeat user authorization; refreshing does not add scopes. - Use
response.raise_for_status()and retain the responserequestIdfor support diagnostics.
Profile and media
The profile scope grants basic profile fields and media, email grants email, and phone grants phone number. Avatar/banner endpoints return image bytes; write response.content and check the response content type rather than parsing JSON.
Errors & troubleshooting
Diagnose the response before retrying. HTTP status is authoritative; API errors also include a stable code and request ID.
AUTHORIZE_UNABLE_TO_CONNECT
Check that the tenant, client, and software IDs belong to the same active application. Compare the callback exactly and request only scopes enabled for that application. Correct the request and begin a new authorization attempt.
INVALID_PASSWORD_ENVELOPE
Reload Login/Register and retry. Confirm the page is served from HTTPS and the password encryption key request succeeds.
Callback URI mismatch
The callback used in authorize and token exchange must exactly match the app registration, including scheme, host, port, path, and trailing slash. For local development, use a loopback URL.
invalid_request
For an authorization-code grant include code, callback, and client ID. For refresh include refresh token and client ID. Ensure the JSON property names and grant_type match the examples.
invalid_grant
The code may be expired or already exchanged, the client/callback may differ, or PKCE verification failed. Send the original verifier for the S256 challenge. For refresh, use the newest rotated token. Restart authorization if a code was consumed.
401 Unauthorized
For profile/media, check the Bearer header and token expiry. Refresh once and replace both stored tokens. For Login, check the account credentials; do not repeatedly retry guesses.
403 or NOT ALLOWED CURRENT SCOPE
Check whether the application is active and whether the token has the required scope. Add the scope to the app registration, re-authorize the user, and use the new token; an existing token does not gain permissions.
429 RATE_LIMIT_EXCEEDED
Wait for the duration in Retry-After and retry with bounded backoff. Do not retry non-idempotent requests automatically.
413 UPLOAD_TOO_LARGE / INVALID_IMAGE
Upload one supported, decodable image smaller than 64 MiB. Check the multipart body, actual file size, and image dimensions; a file extension alone does not establish its image format.
500 or unexpected failure
Retry once after a short delay. If it continues, contact the service operator with the endpoint, approximate time, and requestId. Never include passwords, tokens, or authorization codes.