Guide
Serial Activation Quickstart
Implement serial activation for custom JUCE/C++ clients, including claim, email verification, claim-status resume, final activation, cache, and unlock behavior.
SoundSync
Prerequisites
Complete these before writing activation code.
| Prerequisite | Exact requirement |
|---|---|
| Product configured | Product ID and activation mode exist in SoundSync. |
| Product credential created | You have App ID, plugin key, plugin secret, environment, and API base URL. |
| Test Connection passed | POST /api/plugin-connections/verify succeeds for productId, appId, pluginKey, and pluginSecret. |
| Test serial available | Use a serial/license key assigned to the product and environment under test. |
| Machine ID strategy selected | Use a stable per-machine instance_id/machine_id. Do not regenerate on each launch. |
| Secure persistence selected | Choose encrypted per-product, per-environment, per-machine storage before first activation. |
SoundSync
State machine
Unlock only after the final activation response.
1. POST /api/license/v1/claim with license_key, instance_id, email.
2. Keep claim_id and pending state locally.
3. Customer verifies by email.
4. GET /api/license/v1/claim-status with license_key, instance_id, claim_id.
5. When status is verified, read claim_activation_token.
6. POST /api/license/v1/activate with license_key, instance_id, claim_activation_token.
7. Store validation_token, activationId, productId, status, grace fields, and support fields.
8. Unlock audio/access only after final activation succeeds.
SoundSync
Request sequence
These calls work together.
/api/license/v1/claim
Start a serial claim when customer identity or email verification is required.
- Auth
- x-ss-app-id + x-ss-plugin-key + x-ss-plugin-secret OR x-ss-app-id + x-ss-product-id + x-soundsync-public-client (matching generated native client)
- Scope
- Product, app, credential, environment, and machine scoped.
- Request fields
- instance_id: string
- machine_id: string
- product_id: integer
- plugin_version: string
- version: string
- license_key: string (required)
- email: string (required)
- Returns
- pending_email_verification with claim_id/claimId and emailSent when verification is required.
- Side effects
- Creates or restores a pending serial claim and can send the verification email.
- Notes
- Requires a serial/license_key, customer email and instance_id (machine_id is an alias). A pending response includes claim_id and emailSent:true; it does not activate the device. 503 VERIFICATION_EMAIL_FAILED with emailSent:false is not confirmed delivery or authorization: retry the claim deliberately after recovery. Owner mismatch/login-required errors do not transfer ownership. Keep the same serial and machine through claim-status and final activation. A compatibility CUSTOMER_ACCOUNT_EXISTS response directs account login/password recovery, not offline access.
Request, response and error reference
Request (application/json)
{
"type": "object",
"properties": {
"instance_id": {
"type": "string"
},
"machine_id": {
"type": "string"
},
"product_id": {
"type": "integer",
"minimum": 1
},
"plugin_version": {
"type": "string"
},
"version": {
"type": "string"
},
"license_key": {
"type": "string"
},
"email": {
"type": "string",
"format": "email"
}
},
"required": [
"license_key",
"email"
],
"additionalProperties": true,
"anyOf": [
{
"required": [
"instance_id"
]
},
{
"required": [
"machine_id"
]
}
]
}
HTTP 200 response (application/json)
{
"type": "object",
"properties": {
"ok": {
"type": "boolean"
},
"status": {
"type": "string"
},
"code": {
"type": "string"
},
"claim_id": {
"type": "string"
},
"claimId": {
"type": "string"
},
"emailSent": {
"type": "boolean"
},
"message": {
"type": "string"
},
"nextAction": {
"type": "string"
},
"passwordResetUrl": {
"type": "string"
},
"claimRequired": {
"type": "boolean"
},
"ownerVerificationRequired": {
"type": "boolean"
},
"requiredFields": {
"type": "array",
"items": {
"type": "string"
}
},
"claim_activation_token": {
"type": "string"
},
"claimActivationToken": {
"type": "string"
}
},
"additionalProperties": true
}
Documented failures: 400, 401, 403, 429, 500, 503.
Operation errors: IDENTITY_FIELDS_REQUIRED, INVALID_EMAIL, SERIAL_OWNER_EMAIL_MISMATCH, SERIAL_OWNER_LOGIN_REQUIRED, VERIFICATION_EMAIL_FAILED.
/api/license/v1/claim-status
Restore a pending serial claim and receive claim activation material after email verification.
- Auth
- x-ss-app-id + x-ss-plugin-key + x-ss-plugin-secret OR x-ss-app-id + x-ss-product-id + x-soundsync-public-client (matching generated native client)
- Scope
- Product, app, credential, environment, and machine scoped.
- Request fields
- query license_key: string (required)
- query instance_id: string (required)
- query claim_id: string
- Returns
- pending_email_verification, verified with claim_activation_token/claimActivationToken, or identity_required. Inspect status/code and ok; HTTP 200 alone is not activation.
- Side effects
- Does not activate the machine. The client must call /activate after verified status.
- Notes
- HTTP 200 may mean pending_email_verification, verified or identity_required; inspect status/code and ok. Only verified returns claim_activation_token for a separate final POST /activate on the same product/serial/machine. 400 VERIFICATION_RESTART_REQUIRED means the link expired or was not sent: submit email and serial to /claim again instead of polling forever. A confirmed owner/access denial is not offline grace. Pending polling/finalization can retry transient outages without discarding the saved claim. GET is a compatibility transport; prefer POST to keep serial/proof out of URLs and logs.
Request, response and error reference
Query and header parameters
[
{
"name": "license_key",
"in": "query",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "instance_id",
"in": "query",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "claim_id",
"in": "query",
"required": false,
"schema": {
"type": "string"
}
}
]
HTTP 200 response (application/json)
{
"type": "object",
"properties": {
"ok": {
"type": "boolean"
},
"status": {
"type": "string"
},
"code": {
"type": "string"
},
"claim_id": {
"type": "string"
},
"claimId": {
"type": "string"
},
"emailSent": {
"type": "boolean"
},
"message": {
"type": "string"
},
"nextAction": {
"type": "string"
},
"passwordResetUrl": {
"type": "string"
},
"claimRequired": {
"type": "boolean"
},
"ownerVerificationRequired": {
"type": "boolean"
},
"requiredFields": {
"type": "array",
"items": {
"type": "string"
}
},
"claim_activation_token": {
"type": "string"
},
"claimActivationToken": {
"type": "string"
}
},
"additionalProperties": true
}
Documented failures: 400, 401, 403, 429, 500, 503.
Operation errors: VERIFICATION_RESTART_REQUIRED.
/api/license/v1/claim-status
Restore a pending serial claim and receive claim activation material after email verification.
- Auth
- x-ss-app-id + x-ss-plugin-key + x-ss-plugin-secret OR x-ss-app-id + x-ss-product-id + x-soundsync-public-client (matching generated native client)
- Scope
- Product, app, credential, environment, and machine scoped.
- Request fields
- instance_id: string (required)
- machine_id: string
- product_id: integer
- plugin_version: string
- version: string
- license_key: string (required)
- claim_id: string
- Returns
- pending_email_verification, verified with claim_activation_token/claimActivationToken, or identity_required. Inspect status/code and ok; HTTP 200 alone is not activation.
- Side effects
- Does not activate the machine. The client must call /activate after verified status.
- Notes
- HTTP 200 may mean pending_email_verification, verified or identity_required; inspect status/code and ok. Only verified returns claim_activation_token for a separate final POST /activate on the same product/serial/machine. 400 VERIFICATION_RESTART_REQUIRED means the link expired or was not sent: submit email and serial to /claim again instead of polling forever. A confirmed owner/access denial is not offline grace. Pending polling/finalization can retry transient outages without discarding the saved claim. GET is a compatibility transport; prefer POST to keep serial/proof out of URLs and logs.
Request, response and error reference
Request (application/json)
{
"type": "object",
"properties": {
"instance_id": {
"type": "string"
},
"machine_id": {
"type": "string"
},
"product_id": {
"type": "integer",
"minimum": 1
},
"plugin_version": {
"type": "string"
},
"version": {
"type": "string"
},
"license_key": {
"type": "string"
},
"claim_id": {
"type": "string"
}
},
"required": [
"license_key",
"instance_id"
],
"additionalProperties": true
}
HTTP 200 response (application/json)
{
"type": "object",
"properties": {
"ok": {
"type": "boolean"
},
"status": {
"type": "string"
},
"code": {
"type": "string"
},
"claim_id": {
"type": "string"
},
"claimId": {
"type": "string"
},
"emailSent": {
"type": "boolean"
},
"message": {
"type": "string"
},
"nextAction": {
"type": "string"
},
"passwordResetUrl": {
"type": "string"
},
"claimRequired": {
"type": "boolean"
},
"ownerVerificationRequired": {
"type": "boolean"
},
"requiredFields": {
"type": "array",
"items": {
"type": "string"
}
},
"claim_activation_token": {
"type": "string"
},
"claimActivationToken": {
"type": "string"
}
},
"additionalProperties": true
}
Documented failures: 400, 401, 403, 429, 500, 503.
Operation errors: VERIFICATION_RESTART_REQUIRED.
/api/license/v1/activate
Activate the current machine after direct serial/customer proof or a verified serial claim.
- Auth
- x-ss-app-id + x-ss-plugin-key + x-ss-plugin-secret OR x-ss-app-id + x-ss-product-id + x-soundsync-public-client (matching generated native client)
- Scope
- Product, app, credential, environment, and machine scoped.
- Request fields
- instance_id: string
- machine_id: string
- product_id: integer
- plugin_version: string
- version: string
- license_key: string
- serial: string
- email: string
- validation_token: string
- token: string
- activationId: value
- licenseReference: string
- plugin_session_id: string
- device_credential: string. Private renewal proof returned by authorized enrollment; deviceCredential is an accepted body alias. Bind to the same app/product and instance_id. It cannot independently unlock offline audio.
- license_kind: serial | email_entitlement
- device_credential_supported: boolean. Send true to request native renewal-credential enrollment. deviceCredentialSupported is an accepted body alias.
- claim_id: string
- claim_activation_token: string
- Returns
- status activated, activationId, validation_token, entitlement_id, customer, max_activations, remaining, productId, grace_expires_at, offline_grace_days.
- Side effects
- Creates or reactivates a machine activation, ensures entitlement state, marks serial redeemed/billing eligible where applicable, records events, and issues a validation token.
- Notes
- Generated native clients request device_credential_supported:true and retain the returned device_credential, plugin_session_id and newest validation_token privately. Device proof can renew after a short-lived token/session expires, but never bypasses revoked access, product/device binding or entitlement checks. device_credential_status:pending is incomplete enrollment, not an offline grant. Only an authorized signed offline_lease can support JUCE offline access.
Request, response and error reference
Request (application/json)
{
"type": "object",
"properties": {
"instance_id": {
"type": "string"
},
"machine_id": {
"type": "string"
},
"product_id": {
"type": "integer",
"minimum": 1
},
"plugin_version": {
"type": "string"
},
"version": {
"type": "string"
},
"license_key": {
"type": "string"
},
"serial": {
"type": "string"
},
"email": {
"type": "string",
"format": "email"
},
"validation_token": {
"type": "string"
},
"token": {
"type": "string"
},
"activationId": {
"oneOf": [
{
"type": "integer",
"minimum": 1
},
{
"type": "string",
"pattern": "^[1-9][0-9]*$"
}
]
},
"licenseReference": {
"type": "string"
},
"plugin_session_id": {
"type": "string"
},
"device_credential": {
"type": "string",
"description": "Private renewal proof returned by authorized enrollment; deviceCredential is an accepted body alias. Bind to the same app/product and instance_id. It cannot independently unlock offline audio."
},
"license_kind": {
"type": "string",
"enum": [
"serial",
"email_entitlement"
]
},
"device_credential_supported": {
"type": "boolean",
"description": "Send true to request native renewal-credential enrollment. deviceCredentialSupported is an accepted body alias."
},
"claim_id": {
"type": "string"
},
"claim_activation_token": {
"type": "string"
}
},
"additionalProperties": true,
"anyOf": [
{
"required": [
"instance_id"
]
},
{
"required": [
"machine_id"
]
}
]
}
HTTP 200 response (application/json)
{
"type": "object",
"properties": {
"ok": {
"type": "boolean"
},
"status": {
"type": "string"
},
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"activationId": {
"type": "integer",
"minimum": 1
},
"validation_token": {
"type": "string"
},
"entitlement_id": {
"type": "integer",
"minimum": 1
},
"customer": {
"type": "object",
"properties": {},
"additionalProperties": true
},
"productId": {
"type": "integer",
"minimum": 1
},
"device_credential": {
"type": "string",
"description": "Private product/device-bound renewal proof. Returned when device_credential_supported is true and enrollment succeeds. Protect it in native secure storage; it is not a portal/library token or an offline grant."
},
"device_credential_status": {
"type": "string",
"enum": [
"ready",
"pending"
],
"description": "pending means renewal enrollment is incomplete, even when activation was otherwise authorized. Follow the maintained client retry and access decision; do not manufacture offline authorization."
},
"plugin_session_id": {
"type": "string",
"description": "Private short-lived plugin license session. Distinct from durable device_credential and customer library tokens."
},
"plugin_session_expires_at": {
"type": "string",
"format": "date-time",
"description": "Expiry of plugin_session_id, not the offline grace deadline."
},
"max_activations": {
"type": "integer",
"minimum": 0
},
"remaining": {
"type": "integer",
"minimum": 0
},
"grace_expires_at": {
"type": [
"string",
"null"
]
},
"offline_grace_days": {
"type": "integer",
"minimum": 0
},
"offline_grace": {
"type": "object",
"properties": {
"enabled": {
"type": "boolean"
},
"days": {
"type": "integer",
"minimum": 0
},
"expiresAt": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": true
},
"offline_lease": {
"type": "string",
"description": "Optional signed JSON envelope issued only by successful authorized activation/validation with a positive grace period and configured signer. Contains alg, kid, base64 payload and signature. Verify with trusted package public keys; decoding alone is not authorization. Never log customer grants."
}
},
"additionalProperties": true
}
Documented failures: 400, 401, 403, 500, 503.
SoundSync
Important answers
These are the behaviors custom clients most often get wrong.
| Question | Answer |
|---|---|
| Does /claim activate the machine? | No. It creates/restores a pending claim and may send verification email. |
| Does email verification activate the machine? | No. It verifies the claim so the plugin can restore claim status. |
| Does /claim-status activate the machine? | No. A verified claim-status returns claim_activation_token. |
| What is passed to /activate? | license_key, instance_id, claim_activation_token, and optionally claim_id plus version/product context. |
| What survives restart? | Pending claim fields such as license_key, instance_id, claim_id, email, productId, environment, and started-at timestamp; do not store customer password. |
| How often may polling run? | HISE runtime observes 2-second polling up to 120 attempts. Custom clients should not poll faster than that. |
| What if the claim expires? | Claim verification tokens are 48-hour email tokens; claim activation material is short-lived. Restart claim if eligible. |
Package-specific access policy
JUCE 0.7.3 supports signed offline access for the grace period selected in product Access settings, through 90 days. A prior successful activation or validation and the matching package verification keys are required. Startup uses a verified unexpired grant or online authorization. Zero, disabled, missing or expired grace requires online authorization; confirmed denial or deactivation locks access. Regenerate and rebuild older plugins to use this release.
