S SoWayJuli
Documentation›API reference
REFERENCE

API reference

Request formats, response shapes, OAuth examples, and troubleshooting for tenant integrations with SoWayJuli.

Base URL and transport

Use your registered SoWayJuli HTTPS origin as BASE_URL. Never put authorization codes or tokens in a URL. Keep certificate validation enabled in API clients.

Response format

JSON endpoints return an envelope. Always check the HTTP status first, then inspect success and error.code.

{
	"success": true,
	"data": { "example": "value" },
	"error": null,
	"requestId": "request-correlation-id"
}
{
	"success": false,
	"data": null,
	"error": { "code": "INVALID_GRANT", "message": "invalid_grant" },
	"requestId": "request-correlation-id"
}

Website account APIs

Not for direct use by tenants or third-party services

These endpoints support SoWayJuli's own website and account-management flows. Tenant applications and third-party services must use the OAuth endpoints in this reference instead.

  • GET /api/v0/backend-api/PasswordEncryptionKey supports the website's password forms.
  • POST /api/v0/backend-api/Register creates a SoWayJuli user account when registration is enabled.
  • POST /api/v0/backend-api/Login establishes the website's browser session.
  • GET /api/v0/backend-api/Me returns the current website account.
  • POST /api/v0/backend-api/Logout ends the website's browser session.
  • POST /api/v0/backend-api/UpdateProfile, POST /api/v0/backend-api/ChangeEmail, and POST /api/v0/backend-api/ChangePassword manage the signed-in website account.

OAuth authorization

POST /api/v0/backend-api/graph/authorize

Begins an authorization transaction. The tenant_id, client_id, software_id, and callback must match an active application registration. scope is a space-separated list and defaults to profile. Generate a random state per attempt and validate it at the callback. Use PKCE with S256.

curl -X POST "$BASE_URL/api/v0/backend-api/graph/authorize" \
	-H "Content-Type: application/json" \
	-H "Accept: application/json" \
	-d '{
		"tenant_id":"TENANT_ID",
		"client_id":"CLIENT_ID",
		"software_id":"SOFTWARE_ID",
		"callback":"http://localhost:8080/callback",
		"scope":"profile email",
		"state":"RANDOM_STATE",
		"code_challenge":"BASE64URL_SHA256_OF_VERIFIER",
		"code_challenge_method":"S256"
	}'

On HTTP success, inspect data.transaction_status. A pending transaction returns a data.login_url; open it in the user's browser. A response can be HTTP 200 while the transaction status is Failed, so do not treat HTTP 200 alone as authorization success.

POST /api/v0/backend-api/token

Exchange an authorization code. Codes expire quickly and can only be used once. The callback and client ID must match the authorization request.

curl -X POST "$BASE_URL/api/v0/backend-api/token" \
	-H "Content-Type: application/json" \
	-d '{
		"grant_type":"authorization_code",
		"code":"CALLBACK_CODE",
		"callback":"http://localhost:8080/callback",
		"client_id":"CLIENT_ID",
		"code_verifier":"ORIGINAL_PKCE_VERIFIER"
	}'

The response data contains access_token, token_type (Bearer), expires_in in seconds, refresh_token, and granted scope.

Refresh a token

curl -X POST "$BASE_URL/api/v0/backend-api/token" \
	-H "Content-Type: application/json" \
	-d '{"grant_type":"refresh_token","client_id":"CLIENT_ID","refresh_token":"CURRENT_REFRESH_TOKEN"}'

Refresh tokens rotate: store the newly returned refresh token and discard the old one. Reusing the old token returns invalid_grant.

Profile and media

GET /user/profile

Requires an access token in the Authorization header. The profile scope grants identity/profile fields, email grants the email field, and phone grants the phone number.

curl "$BASE_URL/user/profile" \
	-H "Authorization: Bearer ACCESS_TOKEN" \
	-H "Accept: application/json"

Fields outside the granted scopes contain NOT ALLOWED CURRENT SCOPE; this is a scope result, not a missing database value.

  • GET /usercdn/user/profile/avatar and GET /usercdn/user/profile/banner return image bytes and require a bearer token with profile.
  • POST /api/v0/backend-api/UploadAvatar and POST /api/v0/backend-api/UploadBanner accept one multipart image file, require profile, and reject files at or above 64 MiB.

POST /user/logout

Tenant integrations may use this endpoint to revoke the bearer access token presented in the request. It does not sign out the user's SoWayJuli browser session or revoke other tokens.

curl -X POST "$BASE_URL/user/logout" \
	-H "Authorization: Bearer ACCESS_TOKEN" \
	-H "Accept: application/json"

A successful response returns data.logged_out: true and data.token_revoked: true. Missing, invalid, expired, or already revoked tokens return 401 Unauthorized.

Troubleshooting

AUTHORIZE_UNABLE_TO_CONNECT

Verify the tenant, client, and software IDs against the application registration. Check the callback character-for-character and request only scopes enabled for that application. Start a new authorization attempt after correcting the request.

invalid_request from token exchange

For authorization_code, include code, callback, and client_id. For refresh_token, include refresh_token and client_id.

invalid_grant

The code/token may be expired, already used/revoked, issued to a different client, or paired with a different callback. For PKCE, send the original verifier that matches the S256 challenge. Restart authorization for a fresh code.

401 Unauthorized on profile or media

Send Authorization: Bearer ACCESS_TOKEN. Access tokens expire; refresh the token and replace the stored access and refresh tokens with the newly issued pair.

403 INSUFFICIENT_SCOPE or NOT ALLOWED CURRENT SCOPE

Request the required scope in the app registration, authorize again, and use the newly issued token. Existing tokens do not gain newly configured scopes.

404 on a callback

Ensure the local callback listener is running, the callback path matches, and the full callback URI is registered exactly. Check the callback handler's path before parsing query values.

429 RATE_LIMIT_EXCEEDED

Wait for the duration in Retry-After before retrying. Use bounded backoff and avoid retrying non-idempotent requests automatically.

413 UPLOAD_TOO_LARGE or INVALID_IMAGE

Send one supported, decodable image smaller than 64 MiB. Check both the actual file bytes and the multipart field name expected by the client.

Unexpected 500

Retry once after a short delay. If the issue continues, contact the service operator with the endpoint, approximate time, and response requestId. Do not include passwords, tokens, or authorization codes.

Open the troubleshooting guide →