MENU navbar-image

Introduction

This documentation aims to provide all the information you need to work with our API.

Authenticating requests

To authenticate requests, include an Authorization header with the value "Bearer {ACCESS_TOKEN}".

All authenticated endpoints are marked with a requires authentication badge in the documentation below.

Token based authentication

You can retrieve your access token by calling api/login endpoint.

Access tokens are valid 7 days, then expire. You can obtain new access token with expiring access token by calling /api/token/refresh. You can only refresh still valid tokens, otherwise new request to api/login will be required.

Cookie based authentication

You can also authenticate using cookie-based sessions. Before calling any other endpoints you have to obtain XSRF cookie by hitting /sanctum/csrf-cookie endpoint. In return you receive XSRF-TOKEN cookie, which content you have to attach to each request as X-XSRF-TOKEN header.

Then call /login endpoint. When your authenticated session is created, call any endpoint using X-XSRF-TOKEN header.

Multi-Factor Authorization (MFA)

User can protect its account setting up the MFA as email or one-time passwords (OTP). If mfa_enabled is set to 1 and mfa_method is set to any of email or otp values, user will have to use preferred method as second factor on login.

If mfa_method is email, API will automatically send MFA code to user's email inbox.

Most of API endpoints are secured with additional layer and cannot be properly called without this second-factor authorization done. Instead of endpoint response API will reply with message "MFA is required for this request." and HTTP code 401 Unauthorized. That means user did not successfully authorized itself with second-factor.

Multi-Factor Authorization (MFA) - Remember Session

The user has the option to remember their device, which means they won't have to enter the MFA code for 30 days. If using a Token based authentication, you need to add a header named X-MFA-Session-Token with the value {mfa_token} obtained from the /verify endpoint.

Appendix A. Laravel validation rules

List of validation codes returned for Laravel's built-in validation rules:

Code
Description
MUST_BE_ACCEPTED
The field must be accepted. Value must be one of: 1, true, "yes" or "on".
MUST_BE_ACCEPTED_IF:other:value
The field must be accepted if other field equals to value. Accepted values are the same as in MUST_BE_ACCEPTED.
MUST_BE_ACTIVE_URL
The field must be an active URL address with a valid A or AAAA record according to the dns_get_record PHP function.
MUST_BE_AFTER:date
The field must be a date that is after a specified date.
MUST_BE_AFTER_OR_EQUAL:date
The field must be a date that is after or equal to a specified date.
MUST_CONTAIN_ONLY_LETTERS
The field must contain only letters.
MUST_CONTAIN_ONLY_LETTERS_NUMBERS_DASHES_UNDERSCORES
The field must contain only letters, numbers, dashes and underscores.
MUST_CONTAIN_ONLY_LETTERS_NUMBERS
The field must contain only letters and numbers.
MUST_BE_ARRAY
The field must be an array.
MUST_BE_BEFORE:date
The field must be a date that is before a specified date.
MUST_BE_BEFORE_OR_EQUAL:date
The field must be a date that is before or equal to a specified date.
BETWEEN:NUMBERS:min:max
The field must be a number between min and max.
BETWEEN:FILE_KB:min:max
The field must be a file with a size between min and max KB.
BETWEEN:STRING_LENGTH:min:max
The field must be a string with a length between min and max.
BETWEEN:ARRAY_COUNT:min:max
The field must be an array with between min and max elements.
MUST_BE_BOOLEAN
The field must be a boolean. Accepted values include: true, false, 1, 0, "1" and "0".
MUST_BE_CONFIRMED
The field must have a matching confirmation field named {field}_confirmation.
MUST_BE_CURRENT_PASSWORD
The field must match the authenticated user's current password.
MUST_BE_DATE
The field must be a valid date according to the strtotime PHP function.
MUST_EQUAL_DATE:date
The field must be a date equal to the specified date.
MUST_BE_DATE_FORMAT:format
The field must match the given date format.
MUST_BE_DIFFERENT:other
The field must have a different value than the field named other.
MUST_HAVE_DIGITS:digits
The field must be numeric and contain exactly digits digits.
MUST_HAVE_DIGITS_BETWEEN:min:max
The field must be numeric and contain between min and max digits.
INVALID_IMAGE_DIMENSIONS
The uploaded image has invalid dimensions.
MUST_BE_DISTINCT
The field's values must not contain any duplicates.
MUST_BE_EMAIL
The field must be a valid email address.
MUST_END_WITH:value
The field must end with value.
MUST_EXIST:table.column
The field's value must already exist in the specified database table and column.
MUST_BE_FILE
The field must be a file.
MUST_HAVE_A_VALUE
The field must have a non-empty value.
GREATER_THAN:NUMBER:value
The field must be numeric and greater than value.
GREATER_THAN:FILE_KB:value
The field must be a file with a size greater than value KB.
GREATER_THAN:STRING_LENGTH:value
The field must be a string with a length greater than value.
GREATER_THAN:ARRAY_COUNT:value
The field must be an array with have more than value elements.
GREATER_THAN_OR_EQUALS:NUMBER:value
The field must be numeric and greater than or equal to value.
GREATER_THAN_OR_EQUALS:FILE_KB:value
The field must be a file with a size greater than or equal to value KB.
GREATER_THAN_OR_EQUALS:STRING_LENGTH:value
The field must be a string with a length greater than or equal to value.
GREATER_THAN_OR_EQUALS:ARRAY_COUNT:value
The field must be an array with at least value elements.
MUST_BE_IMAGE
The field must be an image.
MUST_BE_IN:values
The field value must be on of the values list.
MUST_BE_IN_ARRAY:other
The field's value must exist in the other field values.
MUST_BE_INTEGER
The field must be an integer.
MUST_BE_IP_ADDRESS
The field must be a valid IP address.
MUST_BE_IPV4_ADDRESS
The field must be a valid IPv4 address.
MUST_BE_IPV6_ADDRESS
The field must be a valid IPv6 address.
MUST_BE_JSON
The field must contain valid JSON.
LESS_THAN:NUMBER:value
The field must be numeric and less than value.
LESS_THAN:FILE_KB:value
The field must be a file with a size less than value KB.
LESS_THAN:STRING_LENGTH:value
The field must be a string with a length less than value.
LESS_THAN:ARRAY_COUNT:value
The field must be an array with fewer than value elements.
LESS_THAN_OR_EQUALS:NUMBER:value
The field must be numeric and less than or equal to value.
LESS_THAN_OR_EQUALS:FILE_KB:value
The field must be a file with a size less than or equal to value KB.
LESS_THAN_OR_EQUALS:STRING_LENGTH:value
The field must be a string with a length less than or equal to value.
LESS_THAN_OR_EQUALS:ARRAY_COUNT:value
The field must be an array with no more than value elements.
MAXIMUM:NUMBER:value
The field must be numeric and must not exceed the value.
MAXIMUM:FILE_KB:value
The field must be a file with a size no greater than value KB.
MAXIMUM:STRING_LENGTH:value
The field must be a string with a maximum length equal to value.
MAXIMUM:ARRAY_COUNT:value
The field must be an array with no more than value elements.
MUST_BE_MIME_OF:values
The field must be a file with one of the given file extensions, for example: pdf, png, mpeg.
Note that the rule uses file extensions, but Laravel internally verifies the actual MIME type using the file's contents.
MUST_BE_MIMETYPE_OF:values
The field must be a file matching one of the specified MIME types, such as: application/pdf, image/png, video/mpeg.
MINIMUM:NUMBER:min
The field must be numeric and at least value.
MINIMUM:FILE_KB:min
The field must be a file with a size of at least value KB.
MINIMUM:STRING_LENGTH:min
The field must be a string with a minimum length equal to value.
MINIMUM:ARRAY_COUNT:min
The field must be an array with at least value elements.
MUST_BE_MULTIPLE_OF:value
The field must be multiple of value.
MUST_NOT_BE_IN:values
The field's value must not be on of the values list. This is the opposite of MUST_BE_IN rule.
INVALID_FORMAT
The field does not match the given regular expression.
MUST_BE_NUMERIC
The field must be numeric.
MUST_BE_PRESENT
The field must be present, but can be empty.
REQUIRED
The field must be present and not empty.
Field is considered empty if its value is null, an empty string, an empty array or a file input with no file uploaded.
REQUIRED_IF:other:value
The field must be present and not empty if other field is equal to value.
REQUIRED_UNLESS:other:values
The field must be present and not empty unless other field is equal to value.
REQUIRED_WITH:values
The field must be present and not empty if any of the fields in values are present and not empty.
REQUIRED_WITH_ALL:values
The field must be present and not empty if all of the fields in values are present and not empty.
REQUIRED_WITHOUT:values
The field must be present and not empty if any of the fields in values are not present or empty.
REQUIRED_WITHOUT_ALL:values
The field must be present and not empty if all of the fields in values are not present or empty.
PROHIBITED
The field must be empty or not present in the request.
PROHIBITED_IF:other:value
The field must be empty or not present if other field is equal to value.
PROHIBITED_UNLESS:other:value
The field must be empty or not present unless other field is equal to value.
PROHIBITS:other
If the field is present, the other field can't be present at the same time.
MUST_BE_SAME:other
The field must have the same value as the other field.
SIZE:NUMBER:size
The field must be numeric and equal to size.
SIZE:FILE_KB:size
The field must be a file with a size of size KB.
SIZE:STRING_LENGTH:size
The field must be a string with a length equal to size.
SIZE:ARRAY_COUNT:size
The field must be an array with exactly size elements.
MUST_START_WITH:value
The field must start with value.
MUST_BE_STRING
The field must be a string.
MUST_BE_TIMEZONE
The field must be a valid timezone according to the timezone_identifiers_list PHP function.
MUST_BE_UNIQUE:table.column
The field's value must not already exist in the specified database table and column.
FAILED_TO_UPLOAD
File upload failed.
MUST_BE_URL
The field must be a valid URL address.
MUST_BE_UUID
The field must be a valid UUID.

More detailed information about validation rules can be found in Laravel documentation.

Appendix B. Custom validation rules

List of custom validation rules messages:

Code
Description
RULES:CONFIG_MODES_LIMIT_RULE:EXCEEDED:max
Checks whether the device's config modes limit has been reached. Fails when the number of config modes assigned to the device already equals to max.
RULES:FILE_UPLOADS_MAX_TOTAL_SIZE_RULE:EXCEEDED:max_size
Checks whether the total size of all uploaded files in the request exceeds the specified limit. Fails if the total size is greater than max_size KB.
RULES:IS_TIMESTAMP_RULE:INVALID_VALUE
Checks whether the value is a valid timestamp. Fails if it's not.
RULES:IS_TIMEZONE_RULE:INVALID_VALUE
Checks whether the value is a valid timezone. Fails if it's not. List of valid timezones is available at the /api/timezones endpoint.
RULES:NOT_EXIST_IN_OTHER_REGIONS_RULE:DEVICE_IDENTIFIER_IN_USE
Checks whether the device identifier (serial or Bluetooth ID) is already in use in another region. Fails if it is.
RULES:NOT_EXIST_IN_OTHER_REGIONS_RULE:USER_EMAIL_IN_USE
Checks whether the user's email is already in use in another region. Fails if it is.
RULES:NOT_INFECTED_RULE:FILE_INFECTED_OR_UPLOAD_ISSUE
Checks whether the uploaded file is infected. Fails if the upload fails or AV software reports an issue.
RULES:PATIENT_DEVICE_RULE:NOT_ASSIGNED
Checks the patient-device assignment. Fails if the device is not assigned to the user.
RULES:PORTRAIT_VIDEO_RULE:UNREADABLE_VIDEO
RULES:PORTRAIT_VIDEO_RULE:NOT_PORTRAIT
Checks whether the uploaded video is portrait-oriented (height greater than width), accounting for rotation metadata. Returns UNREADABLE_VIDEO if the video file could not be read, or NOT_PORTRAIT if it is landscape or square.
RULES:P2P_SESSION_MEMBER_RULE:NOT_A_MEMBER
Checks whether the current user is the member of the P2P session they are trying to access. Fails if the user is not a session's patient or clinician.
RULES:URL_EXISTS_RULE:EXPECTED_CONTENT_TYPE:content_type
RULES:URL_EXISTS_RULE:NOT_FOUND
Checks whether the specified URL exists and returns an HTTP 200 response.
If content_type was specified, the rule also verifies the returned Content-Type header. If it doesn't match, the error code will include EXPECTED_CONTENT_TYPE:content_type.
Otherwise, it only checks the status code and returns NOT_FOUND if it's not 200.
Fails if HTTP code is not 200 or the Content-Type doesn't match the expected value (if specified).
PASSWORD:MUST_CONTAIN_AT_LEAST_ONE_LOWERCASE
Checks whether the password contains at least one lowercase letter.
PASSWORD:MUST_CONTAIN_AT_LEAST_ONE_UPPERCASE
Checks whether the password contains at least one uppercase letter.
PASSWORD:MUST_CONTAIN_AT_LEAST_ONE_NUMBER
Checks whether the password contains at least one number.
PASSWORD:MUST_CONTAIN_AT_LEAST_ONE_SYMBOL
Checks whether the password contains at least one symbol.

Ably

API endpoints for Ably realtime authentication

Get Ably token

requires authentication

Returns Ably JWT scoped to the channels available to the logged-in user. For ClinicAdmin, Clinician and ClinicianSupport, passing patient returns a token scoped only to the channels of the given patient.

Example request:
curl --request GET \
    --get "http://localhost:8000/api/ably/token?patient=1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/ably/token"
);

const params = {
    "patient": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6ImFwcElkLmtleUlkIn0.eyJpYXQiOjE3NjEyMDY0MDAsImV4cCI6MTc2MTIxMDAwMCwieC1hYmx5LWNsaWVudElkIjoiMSIsIngtYWJseS1jYXBhYmlsaXR5Ijoie1wibWVldGluZzoxXCI6W1wic3Vic2NyaWJlXCIsXCJwdWJsaXNoXCJdfSJ9.c2lnbmF0dXJl"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to get Ably token",
    "code": "ABLY:TOKEN:INSUFFICIENT_PERMISSION"
}
 

Example response (404, User not found):


{
    "message": "User not found",
    "code": "ABLY:TOKEN:USER_NOT_FOUND"
}
 

Example response (500, Server error):


{
    "message": "Server error: Ably token not generated",
    "code": "ABLY:TOKEN:SERVER_ERROR"
}
 

Request   

GET api/ably/token

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

patient   integer  optional  

Patient ID. Used only for ClinicAdmin, Clinician and ClinicianSupport, ignored for other roles. Example: 1

Acadle

Endpoints related to Acadle integration

Acadle SSO

requires authentication

Creates the SSO authorization link. The link expires after 60 seconds, but can be re-generated without any limits.

Example request:
curl --request POST \
    "http://localhost:8000/api/acadle/sso" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/acadle/sso"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "sso_url": "https://adp.acadle.com/sso/authenticate/callback?ssoToken=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE3MzczNzU3OTksImV4cCI6MTczNzM3NTg1OSwiZmlyc3RuYW1lIjoiVG9tIiwibGFzdG5hbWUiOiJTbWl0aCIsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsInVzZXJuYW1lIjpudWxsLCJ0aW1lem9uZSI6IlVUQyJ9.VzE2q6V53bdYJM7aB6CWWxDqcxF4gpdiEF80dD9j-5k",
    "sso_expires": 1737375859
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to use Acadle",
    "code": "ACADLE:SSO:INSUFFICIENT_PERMISSION"
}
 

Example response (403, Survey required):


{
    "message": "Completing the survey is required to proceed to Acadle",
    "code": "ACADLE:SSO:SURVEY_REQUIRED"
}
 

Request   

POST api/acadle/sso

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Check Acadle survey status

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/acadle/survey/status" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/acadle/survey/status"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "survey_sent": false,
    "survey_id": null
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to use Acadle",
    "code": "ACADLE:SURVEY_STATUS:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/acadle/survey/status

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Get Acadle survey data

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/acadle/survey" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/acadle/survey"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (201):


{
    "id": 1,
    "user_id": 301,
    "cpo_number": "628190",
    "country": "Croatia",
    "medical_training": "biomedical_engineer",
    "myo_exp_zeus": "0",
    "has_demo_hand_access": 0,
    "wants_demo_hand": 0,
    "myo_exp_covvi": 1,
    "myo_exp_fillauer": 0,
    "myo_exp_ossur": 1,
    "myo_exp_ottobock": 1,
    "myo_exp_steeper": 0,
    "myo_exp_taska": 0,
    "myo_exp_vincent": 0,
    "myo_exp_pattern_recognition": 1,
    "myo_exp_other": "Qui voluptas est soluta omnis dignissimos et.",
    "partial_hand_protheses": "3",
    "below_elbow_protheses_single_action": "4",
    "below_elbow_protheses_multi_action": "2",
    "above_elbow_protheses": "5",
    "shoulder_protheses": "2",
    "zeus_components_description": "A voluptatibus doloribus non et.",
    "contact_name": "Narciso",
    "contact_surname": "Runolfsson",
    "company_name": "Hammes, Romaguera and Mosciski",
    "street": "824 Roberts Green",
    "city": "South Gus",
    "postal_code": "64342",
    "email": "marco.wisoky@hammes.com",
    "phone": "+1-947-700-3697",
    "phone_country": "py",
    "device_side": "R",
    "created_at": "2026-11-03T17:26:42.000000Z",
    "updated_at": "2026-11-03T17:26:42.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to use Acadle",
    "code": "ACADLE:SURVEY_RESULTS:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Survey not found):


{
    "message": "Survey not found",
    "code": "ACADLE:SURVEY_RESULTS:SURVEY_NOT_FOUND"
}
 

Request   

GET api/acadle/survey

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Response

Response Fields

id   integer   

Survey ID.

user_id   integer   

Associated user ID.

cpo_number   string   

CPO number.

country   string   

Country code.

medical_training   string   

Medical training level.

myo_exp_zeus   string   

Zeus myo experience.

myo_exp_covvi   string   

Covvi myo experience.

myo_exp_fillauer   string   

Fillauer myo experience.

myo_exp_ossur   string   

Ossur myo experience.

myo_exp_ottobock   string   

Ottobock myo experience.

myo_exp_steeper   string   

Steeper myo experience.

myo_exp_taska   string   

Taska myo experience.

myo_exp_vincent   string   

Vincent myo experience.

myo_exp_pattern_recognition   string   

Pattern recognition myo experience.

myo_exp_other   string   

Other myo experience.

has_demo_hand_access   boolean   

Whether the user has demo hand access.

wants_demo_hand   boolean   

Whether the user wants a demo hand.

partial_hand_protheses   string   

Partial hand prostheses experience.

below_elbow_protheses_single_action   string   

Below elbow single action prostheses experience.

below_elbow_protheses_multi_action   string   

Below elbow multi-action prostheses experience.

above_elbow_protheses   string   

Above elbow prostheses experience.

shoulder_protheses   string   

Shoulder prostheses experience.

zeus_components_description   string   

Zeus components description.

contact_name   string   

Contact first name.

contact_surname   string   

Contact last name.

company_name   string   

Company name.

street   string   

Street address.

city   string   

City.

postal_code   string   

Postal code.

email   string   

Contact email.

phone   string   

Contact phone.

phone_country   string   

Phone country code.

device_side   string   

Device side.

Must be one of:
  • L
  • R
created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Get Acadle survey data (SuperAdmin)

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/acadle/survey/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/acadle/survey/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (201):


{
    "id": 2,
    "user_id": 302,
    "cpo_number": "725356340",
    "country": "Uganda",
    "medical_training": "clinician",
    "myo_exp_zeus": "1",
    "has_demo_hand_access": 1,
    "wants_demo_hand": 1,
    "myo_exp_covvi": 0,
    "myo_exp_fillauer": 0,
    "myo_exp_ossur": 0,
    "myo_exp_ottobock": 1,
    "myo_exp_steeper": 1,
    "myo_exp_taska": 0,
    "myo_exp_vincent": 1,
    "myo_exp_pattern_recognition": 0,
    "myo_exp_other": "Ducimus fugit incidunt minus nihil.",
    "partial_hand_protheses": "5",
    "below_elbow_protheses_single_action": "2",
    "below_elbow_protheses_multi_action": "2",
    "above_elbow_protheses": "1",
    "shoulder_protheses": "2",
    "zeus_components_description": "Sunt quidem numquam qui explicabo eveniet qui.",
    "contact_name": "Maverick",
    "contact_surname": "Daniel",
    "company_name": "Bergstrom, Kertzmann and Schroeder",
    "street": "389 Weimann Camp Suite 012",
    "city": "South Arnold",
    "postal_code": "27553",
    "email": "brannon.wisoky@prohaska.com",
    "phone": "865-585-4461",
    "phone_country": "sc",
    "device_side": "L",
    "created_at": "2026-11-03T17:26:43.000000Z",
    "updated_at": "2026-11-03T17:26:43.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to use Acadle",
    "code": "ACADLE:SURVEY_RESULTS_ADMIN:INSUFFICIENT_PERMISSION"
}
 

Example response (404, User not found):


{
    "message": "User not found",
    "code": "ACADLE:SURVEY_RESULTS_ADMIN:USER_NOT_FOUND"
}
 

Example response (404, Survey not found):


{
    "message": "Survey not found",
    "code": "ACADLE:SURVEY_RESULTS_ADMIN:SURVEY_NOT_FOUND"
}
 

Request   

GET api/acadle/survey/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

User ID. Example: 1

Response

Response Fields

id   integer   

Survey ID.

user_id   integer   

Associated user ID.

cpo_number   string   

CPO number.

country   string   

Country code.

medical_training   string   

Medical training level.

myo_exp_zeus   string   

Zeus myo experience.

myo_exp_covvi   string   

Covvi myo experience.

myo_exp_fillauer   string   

Fillauer myo experience.

myo_exp_ossur   string   

Ossur myo experience.

myo_exp_ottobock   string   

Ottobock myo experience.

myo_exp_steeper   string   

Steeper myo experience.

myo_exp_taska   string   

Taska myo experience.

myo_exp_vincent   string   

Vincent myo experience.

myo_exp_pattern_recognition   string   

Pattern recognition myo experience.

myo_exp_other   string   

Other myo experience.

has_demo_hand_access   boolean   

Whether the user has demo hand access.

wants_demo_hand   boolean   

Whether the user wants a demo hand.

partial_hand_protheses   string   

Partial hand prostheses experience.

below_elbow_protheses_single_action   string   

Below elbow single action prostheses experience.

below_elbow_protheses_multi_action   string   

Below elbow multi-action prostheses experience.

above_elbow_protheses   string   

Above elbow prostheses experience.

shoulder_protheses   string   

Shoulder prostheses experience.

zeus_components_description   string   

Zeus components description.

contact_name   string   

Contact first name.

contact_surname   string   

Contact last name.

company_name   string   

Company name.

street   string   

Street address.

city   string   

City.

postal_code   string   

Postal code.

email   string   

Contact email.

phone   string   

Contact phone.

phone_country   string   

Phone country code.

device_side   string   

Device side.

Must be one of:
  • L
  • R
created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

List all Acadle surveys (SuperAdmin)

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/acadle/surveys?search=john" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/acadle/surveys"
);

const params = {
    "search": "john",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 3,
            "user_id": 303,
            "cpo_number": "37",
            "country": "Nepal",
            "medical_training": "physiotherapist",
            "myo_exp_zeus": "1",
            "has_demo_hand_access": 1,
            "wants_demo_hand": 0,
            "myo_exp_covvi": 1,
            "myo_exp_fillauer": 1,
            "myo_exp_ossur": 0,
            "myo_exp_ottobock": 0,
            "myo_exp_steeper": 1,
            "myo_exp_taska": 1,
            "myo_exp_vincent": 1,
            "myo_exp_pattern_recognition": 0,
            "myo_exp_other": "Iste amet dicta possimus voluptatem soluta aliquam architecto eum.",
            "partial_hand_protheses": "2",
            "below_elbow_protheses_single_action": "5",
            "below_elbow_protheses_multi_action": "0",
            "above_elbow_protheses": "0",
            "shoulder_protheses": "4",
            "zeus_components_description": "Et rerum maxime tempore optio ea nam voluptatem.",
            "contact_name": "Jaime",
            "contact_surname": "Russel",
            "company_name": "Maggio-Friesen",
            "street": "327 Mitchell Knoll",
            "city": "Giuseppeborough",
            "postal_code": "24708-8772",
            "email": "joe.bahringer@carroll.com",
            "phone": "+1 (254) 685-6857",
            "phone_country": "gp",
            "device_side": "R",
            "created_at": "2026-11-03T17:26:45.000000Z",
            "updated_at": "2026-11-03T17:26:45.000000Z"
        },
        {
            "id": 4,
            "user_id": 304,
            "cpo_number": "630447577",
            "country": "Palestinian Territories",
            "medical_training": "physiotherapist",
            "myo_exp_zeus": "0",
            "has_demo_hand_access": 0,
            "wants_demo_hand": 0,
            "myo_exp_covvi": 1,
            "myo_exp_fillauer": 1,
            "myo_exp_ossur": 0,
            "myo_exp_ottobock": 0,
            "myo_exp_steeper": 0,
            "myo_exp_taska": 1,
            "myo_exp_vincent": 1,
            "myo_exp_pattern_recognition": 1,
            "myo_exp_other": "Est ratione autem numquam quam ipsa doloribus.",
            "partial_hand_protheses": "1",
            "below_elbow_protheses_single_action": "2",
            "below_elbow_protheses_multi_action": "1",
            "above_elbow_protheses": "0",
            "shoulder_protheses": "1",
            "zeus_components_description": "Magnam ut nobis tenetur porro ut libero exercitationem.",
            "contact_name": "Sonny",
            "contact_surname": "Kautzer",
            "company_name": "Gorczany-Strosin",
            "street": "671 Ullrich Plain",
            "city": "Elifort",
            "postal_code": "26660-7459",
            "email": "hirthe.antonetta@kessler.net",
            "phone": "862-637-2964",
            "phone_country": "sa",
            "device_side": "R",
            "created_at": "2026-11-03T17:26:46.000000Z",
            "updated_at": "2026-11-03T17:26:46.000000Z"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to use Acadle",
    "code": "ACADLE:SURVEYS:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/acadle/surveys

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

search   string  optional  

Filter surveys by searching in: user name, user email, contact name, contact surname, contact email, CPO number. Example: john

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

extend   string  optional  

Comma-separated list of relation extensions (available: user).

sortby   string  optional  

Sort by field (available: date, user_name, user_email, contact_name, contact_surname, country, medical_training, cpo_number). Default: date, desc.

sortdir   string  optional  

Sort direction (available: asc, desc).

Response

Response Fields

items   object   
id   integer   

Survey ID.

user_id   integer   

Associated user ID.

cpo_number   string   

CPO number.

country   string   

Country code.

medical_training   string   

Medical training level.

myo_exp_zeus   string   

Zeus myo experience.

myo_exp_covvi   string   

Covvi myo experience.

myo_exp_fillauer   string   

Fillauer myo experience.

myo_exp_ossur   string   

Ossur myo experience.

myo_exp_ottobock   string   

Ottobock myo experience.

myo_exp_steeper   string   

Steeper myo experience.

myo_exp_taska   string   

Taska myo experience.

myo_exp_vincent   string   

Vincent myo experience.

myo_exp_pattern_recognition   string   

Pattern recognition myo experience.

myo_exp_other   string   

Other myo experience.

has_demo_hand_access   boolean   

Whether the user has demo hand access.

wants_demo_hand   boolean   

Whether the user wants a demo hand.

partial_hand_protheses   string   

Partial hand prostheses experience.

below_elbow_protheses_single_action   string   

Below elbow single action prostheses experience.

below_elbow_protheses_multi_action   string   

Below elbow multi-action prostheses experience.

above_elbow_protheses   string   

Above elbow prostheses experience.

shoulder_protheses   string   

Shoulder prostheses experience.

zeus_components_description   string   

Zeus components description.

contact_name   string   

Contact first name.

contact_surname   string   

Contact last name.

company_name   string   

Company name.

street   string   

Street address.

city   string   

City.

postal_code   string   

Postal code.

email   string   

Contact email.

phone   string   

Contact phone.

phone_country   string   

Phone country code.

device_side   string   

Device side.

Must be one of:
  • L
  • R
created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

Send Acadle survey

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/acadle/survey" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"cpo_number\": 4,
    \"country\": \"Angola\",
    \"medical_training\": \"physiotherapist\",
    \"myo_exp_zeus\": false,
    \"myo_exp_covvi\": false,
    \"myo_exp_fillauer\": false,
    \"myo_exp_ossur\": false,
    \"myo_exp_ottobock\": false,
    \"myo_exp_steeper\": false,
    \"myo_exp_taska\": false,
    \"myo_exp_vincent\": false,
    \"myo_exp_pattern_recognition\": false,
    \"myo_exp_other\": \"Ea est at et commodi dolor sint.\",
    \"has_demo_hand_access\": false,
    \"wants_demo_hand\": false,
    \"partial_hand_protheses\": \"1-5\",
    \"below_elbow_protheses_single_action\": \"1-5\",
    \"below_elbow_protheses_multi_action\": \"5-10\",
    \"above_elbow_protheses\": \"50+\",
    \"shoulder_protheses\": \"1-5\",
    \"zeus_components_description\": \"Omnis explicabo accusantium impedit.\",
    \"contact_name\": \"Otho\",
    \"company_name\": \"Becker, Gibson and Kertzmann\",
    \"street\": \"172 Durgan Course Apt. 280\",
    \"city\": \"Hayesmouth\",
    \"postal_code\": \"60829\",
    \"email\": \"kautzer.wilber@smitham.com\",
    \"phone\": \"+1-251-242-3240\",
    \"phone_country\": \"AQ\",
    \"device_side\": \"L\"
}"
const url = new URL(
    "http://localhost:8000/api/acadle/survey"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "cpo_number": 4,
    "country": "Angola",
    "medical_training": "physiotherapist",
    "myo_exp_zeus": false,
    "myo_exp_covvi": false,
    "myo_exp_fillauer": false,
    "myo_exp_ossur": false,
    "myo_exp_ottobock": false,
    "myo_exp_steeper": false,
    "myo_exp_taska": false,
    "myo_exp_vincent": false,
    "myo_exp_pattern_recognition": false,
    "myo_exp_other": "Ea est at et commodi dolor sint.",
    "has_demo_hand_access": false,
    "wants_demo_hand": false,
    "partial_hand_protheses": "1-5",
    "below_elbow_protheses_single_action": "1-5",
    "below_elbow_protheses_multi_action": "5-10",
    "above_elbow_protheses": "50+",
    "shoulder_protheses": "1-5",
    "zeus_components_description": "Omnis explicabo accusantium impedit.",
    "contact_name": "Otho",
    "company_name": "Becker, Gibson and Kertzmann",
    "street": "172 Durgan Course Apt. 280",
    "city": "Hayesmouth",
    "postal_code": "60829",
    "email": "kautzer.wilber@smitham.com",
    "phone": "+1-251-242-3240",
    "phone_country": "AQ",
    "device_side": "L"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 5,
    "user_id": 305,
    "cpo_number": "9502",
    "country": "Greenland",
    "medical_training": "biomedical_engineer",
    "myo_exp_zeus": "0",
    "has_demo_hand_access": 0,
    "wants_demo_hand": 0,
    "myo_exp_covvi": 1,
    "myo_exp_fillauer": 0,
    "myo_exp_ossur": 1,
    "myo_exp_ottobock": 1,
    "myo_exp_steeper": 1,
    "myo_exp_taska": 0,
    "myo_exp_vincent": 1,
    "myo_exp_pattern_recognition": 0,
    "myo_exp_other": "Architecto omnis aut expedita hic.",
    "partial_hand_protheses": "1",
    "below_elbow_protheses_single_action": "1",
    "below_elbow_protheses_multi_action": "1",
    "above_elbow_protheses": "0",
    "shoulder_protheses": "2",
    "zeus_components_description": "Iure aut consequuntur similique placeat occaecati voluptatibus odio fuga.",
    "contact_name": "Athena",
    "contact_surname": "Lesch",
    "company_name": "Kertzmann-DuBuque",
    "street": "144 Cole Plaza",
    "city": "New Nina",
    "postal_code": "65784",
    "email": "jlowe@carroll.org",
    "phone": "+1-928-533-9959",
    "phone_country": "gd",
    "device_side": "R",
    "created_at": "2026-11-03T17:26:47.000000Z",
    "updated_at": "2026-11-03T17:26:47.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to use Acadle",
    "code": "ACADLE:SURVEY_SEND:INSUFFICIENT_PERMISSION"
}
 

Example response (403, Survey already sent):


{
    "message": "Survey already sent",
    "code": "ACADLE:SURVEY_SEND:SURVEY_ALREADY_SENT"
}
 

Request   

POST api/acadle/survey

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

cpo_number   string  optional  

CPO Number. Example: 4

country   string   

Country name. Example: Angola

medical_training   string   

Clinician's medical/clinical training. Example: physiotherapist

Must be one of:
  • clinician
  • physiotherapist
  • physician
  • biomedical_engineer
  • other
  • prosthetist
myo_exp_zeus   boolean   

Does clinician have an experience working with Zeus hands. Example: false

myo_exp_covvi   boolean   

Does clinician have an experience working with Covvi hands. Example: false

myo_exp_fillauer   boolean   

Does clinician have an experience working with Fillauer hands. Example: false

myo_exp_ossur   boolean   

Does clinician have an experience working with Ossur hands. Example: false

myo_exp_ottobock   boolean   

Does clinician have an experience working with Ottobock hands. Example: false

myo_exp_steeper   boolean   

Does clinician have an experience working with Steeper hands. Example: false

myo_exp_taska   boolean   

Does clinician have an experience working with Taska hands. Example: false

myo_exp_vincent   boolean   

Does clinician have an experience working with Vincent hands. Example: false

myo_exp_pattern_recognition   boolean   

Does clinician have an experience working with Pattern Recognition. Example: false

myo_exp_other   string  optional  

Does clinician have an experience working with any other hands. MINIMUM:STRING_LENGTH:3. Example: Ea est at et commodi dolor sint.

has_demo_hand_access   boolean   

Does clinician have access to a demo hand. Example: false

wants_demo_hand   boolean   

Does clinician want a demo hand. Example: false

partial_hand_protheses   string  optional  

How many prostheses of type partial hand does clinician make per year. Example: 1-5

below_elbow_protheses_single_action   string  optional  

How many prostheses of type below elbow (single-action) does clinician make per year. Example: 1-5

below_elbow_protheses_multi_action   string  optional  

How many prostheses of type below elbow (multi-action) does clinician make per year. Example: 5-10

above_elbow_protheses   string  optional  

How many prostheses of type above elbow does clinician make per year. Example: 50+

shoulder_protheses   string  optional  

How many prostheses of type shoulder does clinician make per year. Example: 1-5

zeus_components_description   string  optional  

Description which components would clinician like to use with a Zeus hand. MINIMUM:STRING_LENGTH:10. Example: Omnis explicabo accusantium impedit.

contact_name   string  optional  

Contact information: full name. This field is required when wants_demo_hand is true. Example: Otho

company_name   string  optional  

Contact information: company name. This field is required when wants_demo_hand is true. Example: Becker, Gibson and Kertzmann

street   string  optional  

Contact information: street. This field is required when wants_demo_hand is true. Example: 172 Durgan Course Apt. 280

city   string  optional  

Contact information: city/state. This field is required when wants_demo_hand is true. Example: Hayesmouth

postal_code   string  optional  

Contact information: postal code. This field is required when wants_demo_hand is true. Example: 60829

email   string  optional  

Contact information: email. This field is required when wants_demo_hand is true. MUST_BE_EMAIL. Example: kautzer.wilber@smitham.com

phone   string  optional  

Contact information: phone. This field is required when wants_demo_hand is true. Example: +1-251-242-3240

phone_country   string  optional  

Contact information: phone country. This field is required when wants_demo_hand is true. SIZE:STRING_LENGTH:2. Example: AQ

device_side   string  optional  

The side of the hand. This field is required when wants_demo_hand is true. Example: L

Must be one of:
  • L
  • R

Response

Response Fields

id   integer   

Survey ID.

user_id   integer   

Associated user ID.

cpo_number   string   

CPO number.

country   string   

Country code.

medical_training   string   

Medical training level.

myo_exp_zeus   string   

Zeus myo experience.

myo_exp_covvi   string   

Covvi myo experience.

myo_exp_fillauer   string   

Fillauer myo experience.

myo_exp_ossur   string   

Ossur myo experience.

myo_exp_ottobock   string   

Ottobock myo experience.

myo_exp_steeper   string   

Steeper myo experience.

myo_exp_taska   string   

Taska myo experience.

myo_exp_vincent   string   

Vincent myo experience.

myo_exp_pattern_recognition   string   

Pattern recognition myo experience.

myo_exp_other   string   

Other myo experience.

has_demo_hand_access   boolean   

Whether the user has demo hand access.

wants_demo_hand   boolean   

Whether the user wants a demo hand.

partial_hand_protheses   string   

Partial hand prostheses experience.

below_elbow_protheses_single_action   string   

Below elbow single action prostheses experience.

below_elbow_protheses_multi_action   string   

Below elbow multi-action prostheses experience.

above_elbow_protheses   string   

Above elbow prostheses experience.

shoulder_protheses   string   

Shoulder prostheses experience.

zeus_components_description   string   

Zeus components description.

contact_name   string   

Contact first name.

contact_surname   string   

Contact last name.

company_name   string   

Company name.

street   string   

Street address.

city   string   

City.

postal_code   string   

Postal code.

email   string   

Contact email.

phone   string   

Contact phone.

phone_country   string   

Phone country code.

device_side   string   

Device side.

Must be one of:
  • L
  • R
created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Activation codes

API endpoints for activation codes

Get activation codes

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/activation-codes" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/activation-codes"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 1,
            "code": "65YFH4",
            "created_by": 41,
            "used_by": 42,
            "used_at": null,
            "active": 0,
            "created_at": "2026-11-03T17:24:21.000000Z",
            "updated_at": "2026-11-03T17:24:21.000000Z"
        },
        {
            "id": 2,
            "code": "XNV86Z",
            "created_by": 43,
            "used_by": 44,
            "used_at": null,
            "active": 1,
            "created_at": "2026-11-03T17:24:22.000000Z",
            "updated_at": "2026-11-03T17:24:22.000000Z"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage activation codes",
    "code": "ACTIVATION_CODE:LIST:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/activation-codes

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

sortby   string  optional  

Sort by field (available: date). Default: date, desc.

sortdir   string  optional  

Sort direction (available: asc, desc).

Response

Response Fields

items   object   
id   integer   

Activation code ID.

code   string   

Activation code value.

created_by   integer   

ID of the user who created this code.

used_by   integer   

ID of the user who used this code.

used_at   string   

Timestamp when the code was used.

active   boolean   

Whether the code is active.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

creator   object   

User who created this code.

user   object   

User who used this code.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

Get active activation codes

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/activation-codes/active" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/activation-codes/active"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 3,
            "code": "N27H5J",
            "created_by": 45,
            "used_by": 46,
            "used_at": null,
            "active": 1,
            "created_at": "2026-11-03T17:24:23.000000Z",
            "updated_at": "2026-11-03T17:24:23.000000Z"
        },
        {
            "id": 4,
            "code": "7RYTSW",
            "created_by": 47,
            "used_by": 48,
            "used_at": null,
            "active": 0,
            "created_at": "2026-11-03T17:24:24.000000Z",
            "updated_at": "2026-11-03T17:24:24.000000Z"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage activation codes",
    "code": "ACTIVATION_CODE:ACTIVE:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/activation-codes/active

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

Response

Response Fields

items   object   
id   integer   

Activation code ID.

code   string   

Activation code value.

created_by   integer   

ID of the user who created this code.

used_by   integer   

ID of the user who used this code.

used_at   string   

Timestamp when the code was used.

active   boolean   

Whether the code is active.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

creator   object   

User who created this code.

user   object   

User who used this code.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

Find activation code

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/activation-codes/find/vitae" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/activation-codes/find/vitae"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "id": 5,
    "code": "CYQ8V7",
    "created_by": null,
    "used_by": null,
    "used_at": null,
    "active": 1,
    "created_at": "2026-11-03T17:24:24.000000Z",
    "updated_at": "2026-11-03T17:24:24.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage activation codes",
    "code": "ACTIVATION_CODE:FIND:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Activation code not found):


{
    "message": "Activation code not found",
    "code": "ACTIVATION_CODE:FIND:CODE_NOT_FOUND"
}
 

Request   

GET api/activation-codes/find/{code}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

code   string   

Activation code (not ID). Example: vitae

Response

Response Fields

id   integer   

Activation code ID.

code   string   

Activation code value.

created_by   integer   

ID of the user who created this code.

used_by   integer   

ID of the user who used this code.

used_at   string   

Timestamp when the code was used.

active   boolean   

Whether the code is active.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

creator   object   

User who created this code.

user   object   

User who used this code.

Create activation code

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/activation-codes" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/activation-codes"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (201):


{
    "id": 6,
    "code": "WPTBK6",
    "created_by": null,
    "used_by": null,
    "used_at": null,
    "active": 0,
    "created_at": "2026-11-03T17:24:24.000000Z",
    "updated_at": "2026-11-03T17:24:24.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage activation codes",
    "code": "ACTIVATION_CODE:CREATE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Server error):


{
    "message": "Server error: activation code not created",
    "code": "ACTIVATION_CODE:CREATE:SERVER_ERROR"
}
 

Request   

POST api/activation-codes

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Response

Response Fields

id   integer   

Activation code ID.

code   string   

Activation code value.

created_by   integer   

ID of the user who created this code.

used_by   integer   

ID of the user who used this code.

used_at   string   

Timestamp when the code was used.

active   boolean   

Whether the code is active.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

creator   object   

User who created this code.

user   object   

User who used this code.

Create activation code (multi-region)

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/activation-codes/16" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/activation-codes/16"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (201):


{
    "id": 7,
    "code": "9P6GTQ",
    "created_by": null,
    "used_by": null,
    "used_at": null,
    "active": 0,
    "created_at": "2026-11-03T17:24:24.000000Z",
    "updated_at": "2026-11-03T17:24:24.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage activation codes",
    "code": "ACTIVATION_CODE:CREATE:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/activation-codes/{code}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

code   integer   

Example: 16

Response

Response Fields

id   integer   

Activation code ID.

code   string   

Activation code value.

created_by   integer   

ID of the user who created this code.

used_by   integer   

ID of the user who used this code.

used_at   string   

Timestamp when the code was used.

active   boolean   

Whether the code is active.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

creator   object   

User who created this code.

user   object   

User who used this code.

Deactivate activation code

requires authentication

Example request:
curl --request DELETE \
    "http://localhost:8000/api/activation-codes/14" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/activation-codes/14"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (202):


{
    "id": 8,
    "code": "4QZGU9",
    "created_by": null,
    "used_by": null,
    "used_at": null,
    "active": 1,
    "created_at": "2026-11-03T17:24:24.000000Z",
    "updated_at": "2026-11-03T17:24:24.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage activation codes",
    "code": "ACTIVATION_CODE:DEACTIVATE:INSUFFICIENT_PERMISSION"
}
 

Example response (403, Activation code is not active):


{
    "message": "Cannot deactivate: code is not active",
    "code": "ACTIVATION_CODE:DEACTIVATE:CODE_NOT_ACTIVE"
}
 

Example response (404, Activation code not found):


{
    "message": "Activation code not found",
    "code": "ACTIVATION_CODE:DEACTIVATE:CODE_NOT_FOUND"
}
 

Example response (500, Server error):


{
    "message": "Server error: activation code not deactivated",
    "code": "ACTIVATION_CODE:DEACTIVATE:SERVER_ERROR"
}
 

Request   

DELETE api/activation-codes/{codeId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

codeId   integer   

Activation code ID. Example: 14

Response

Response Fields

id   integer   

Activation code ID.

code   string   

Activation code value.

created_by   integer   

ID of the user who created this code.

used_by   integer   

ID of the user who used this code.

used_at   string   

Timestamp when the code was used.

active   boolean   

Whether the code is active.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

creator   object   

User who created this code.

user   object   

User who used this code.

Analytics consent

API endpoints for analytics consent state and reporting

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/analytics-consent" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/analytics-consent"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, Current consent state):


{
    "decision": "granted",
    "decided_at": "2026-08-06T09:15:00.000000Z",
    "consent_text_version": "clinician-2026-08-v1",
    "current_version": "clinician-2026-08-v1",
    "should_ask": false,
    "visitor_id": "e3b0c44298fc1c14"
}
 

requires authentication

Example request:
curl --request PUT \
    "http://localhost:8000/api/analytics-consent" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"decision\": \"granted\",
    \"consent_text_version\": \"clinician-2026-08-v1\",
    \"surface\": \"onboarding_modal\"
}"
const url = new URL(
    "http://localhost:8000/api/analytics-consent"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "decision": "granted",
    "consent_text_version": "clinician-2026-08-v1",
    "surface": "onboarding_modal"
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, Consent state after the decision):


{
    "decision": "granted",
    "decided_at": "2026-08-06T09:15:00.000000Z",
    "consent_text_version": "clinician-2026-08-v1",
    "current_version": "clinician-2026-08-v1",
    "should_ask": false,
    "visitor_id": "e3b0c44298fc1c14"
}
 

Example response (403, Account has no consent audience):


{
    "message": "Analytics consent is not applicable to this account",
    "code": "ANALYTICS_CONSENT:SET:NO_AUDIENCE"
}
 

Example response (422, Submitted consent text version is not current):


{
    "message": "The submitted consent text version is no longer current",
    "code": "ANALYTICS_CONSENT:SET:STALE_VERSION"
}
 

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/analytics-consent/metrics?from=2026-08-01&to=2026-08-31&interval=day&version=clinician-2026-08-v1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/analytics-consent/metrics"
);

const params = {
    "from": "2026-08-01",
    "to": "2026-08-31",
    "interval": "day",
    "version": "clinician-2026-08-v1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, Consent metrics per audience):


{
    "clinician": {
        "series": [
            {
                "period": "2026-08-01",
                "grants": 12,
                "declines": 3,
                "withdrawals": 1
            }
        ],
        "current": {
            "granted": 128,
            "denied": 14,
            "never_asked": 37
        }
    },
    "patient": {
        "series": [
            {
                "period": "2026-08-01",
                "grants": 41,
                "declines": 9,
                "withdrawals": 2
            }
        ],
        "current": {
            "granted": 512,
            "denied": 63,
            "never_asked": 210
        }
    }
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view analytics consent data",
    "code": "ANALYTICS_CONSENT:METRICS:INSUFFICIENT_PERMISSION"
}
 

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/analytics-consent/patient-funnel?from=2026-08-01&to=2026-08-31" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/analytics-consent/patient-funnel"
);

const params = {
    "from": "2026-08-01",
    "to": "2026-08-31",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, Patient creation funnel per clinician):


{
    "clinicians": [
        {
            "clinician_id": 17,
            "created": 8,
            "failed": 2
        },
        {
            "clinician_id": 42,
            "created": 3,
            "failed": 0
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view analytics consent data",
    "code": "ANALYTICS_CONSENT:FUNNEL:INSUFFICIENT_PERMISSION"
}
 

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/user/1/analytics-consent" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/user/1/analytics-consent"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, Patient consent decision):


{
    "decision": "granted"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view analytics consent data",
    "code": "ANALYTICS_CONSENT:PATIENT_STATUS:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Patient not found):


{
    "message": "User not found",
    "code": "ANALYTICS_CONSENT:PATIENT_STATUS:USER_NOT_FOUND"
}
 

Authentication

API endpoints for managing authentication

Login user

Example request:
curl --request POST \
    "http://localhost:8000/api/login" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"email\": \"test@example.com\",
    \"password\": \"secretpassword\"
}"
const url = new URL(
    "http://localhost:8000/api/login"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "email": "test@example.com",
    "password": "secretpassword"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, OK):


{
    "access_token": "7|x7de9EgE0xiBNLgHU91DHvhj85HVgTG1bekCssIA",
    "expires": "2021-10-25 17:05:25"
}
 

Example response (403, Too many attempts):


{
    "message": "Login: too many attempts",
    "code": "GENERAL:TOO_MANY_ATTEMPTS"
}
 

Example response (422, Invalid credentials):


{
    "message": "The given data was invalid.",
    "errors": {
        "email": [
            "Given credentials not found"
        ]
    },
    "code": "AUTH:LOGIN:INVALID_CREDENTIALS"
}
 

Request   

POST api/login

Headers

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

email   string   

User email. MUST_BE_EMAIL. Example: test@example.com

password   string   

User password. Example: secretpassword

Register user

Example request:
curl --request POST \
    "http://localhost:8000/api/register" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"region\": \"us\",
    \"name\": \"Tom Smith\",
    \"email\": \"name@domain.com\",
    \"password\": \"Test123!\",
    \"phone\": \"810.749.8286\",
    \"phone_country\": \"US\",
    \"language\": \"en\",
    \"clinic_name\": \"Rice, Conn and Nienow\",
    \"clinic_location\": \"North Josefaview\",
    \"address1\": \"38104 Cleo Grove Apt. 202\",
    \"address2\": \"Lake Luz, UT 11385-1186\",
    \"mfa_enabled\": true,
    \"mfa_method\": \"email\",
    \"activation_code\": \"1A2B3C\"
}"
const url = new URL(
    "http://localhost:8000/api/register"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "region": "us",
    "name": "Tom Smith",
    "email": "name@domain.com",
    "password": "Test123!",
    "phone": "810.749.8286",
    "phone_country": "US",
    "language": "en",
    "clinic_name": "Rice, Conn and Nienow",
    "clinic_location": "North Josefaview",
    "address1": "38104 Cleo Grove Apt. 202",
    "address2": "Lake Luz, UT 11385-1186",
    "mfa_enabled": true,
    "mfa_method": "email",
    "activation_code": "1A2B3C"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 2,
    "mrn": "TVS6B8ES1793726635",
    "name": "Ned Renner",
    "email": "1793726635lemke.keven@example.com",
    "language": "en",
    "phone": "+1 (347) 757-4011",
    "phone_country": "BS",
    "phone_verified_at": null,
    "address1": "7851 Matilda Common Apt. 084",
    "address2": "North Lucianoview, NC 27975",
    "postal_code": "54954",
    "city": "Spencer Inc",
    "country": "RO",
    "clinic_name": "North Rosalia",
    "clinic_location": "5196 Jazlyn Oval\nShawnaview, OK 97686",
    "image": null,
    "public_image": null,
    "mfa_enabled": 0,
    "mfa_method": null,
    "mfa_verified_to": null,
    "location_id": null,
    "created_by": null,
    "active": 1,
    "is_ambassador": 0,
    "test_account": false,
    "notifications_timezone": null,
    "notifications_at": null,
    "created_at": "2026-11-03T17:23:55.000000Z",
    "updated_at": "2026-11-03T17:23:55.000000Z",
    "invitation_status": "accepted",
    "acadle_invitation_status": null,
    "roles": [
        {
            "id": 3,
            "name": "ClinicAdmin"
        }
    ]
}
 

Example response (403, Too many attempts):


{
    "message": "Register: too many attempts",
    "code": "GENERAL:TOO_MANY_ATTEMPTS"
}
 

Example response (403, E-mail in use (in another region)):


{
    "message": "E-mail address already in use (in another region)",
    "code": "AUTH:REGISTER:EMAIL_IN_USE"
}
 

Example response (403, Activation code is incorrect):


{
    "message": "Activation code is incorrect",
    "code": "AUTH:REGISTER:INCORRECT_CODE"
}
 

Example response (500, Server error):


{
    "message": "Server error: user not created",
    "code": "AUTH:REGISTER:SERVER_ERROR"
}
 

Request   

POST api/register

Headers

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

region   string   

User region. Example: us

name   string   

User name. Example: Tom Smith

email   string   

User e-mail address. MUST_BE_EMAIL. Example: name@domain.com

password   string   

User password. Example: Test123!

phone   string   

User phone number. Example: 810.749.8286

phone_country   string   

Phone number's country (2 characters). SIZE:STRING_LENGTH:2. Example: US

language   string   

User language. Example: en

clinic_name   string  optional  

Clinic name. Example: Rice, Conn and Nienow

clinic_location   string  optional  

Clinic location. Example: North Josefaview

address1   string  optional  

Address line 1. Example: 38104 Cleo Grove Apt. 202

address2   string  optional  

Address line 2. Example: Lake Luz, UT 11385-1186

mfa_enabled   boolean  optional  

MFA enabled. Example: true

mfa_method   string  optional  

MFA method. Example: email

Must be one of:
  • email
  • sms
activation_code   string   

Activation code. SIZE:STRING_LENGTH:6. Example: 1A2B3C

Response

Response Fields

id   integer   

User ID.

mrn   string   

Medical Record Number.

name   string   

User full name.

email   string   

User email address.

language   string   

User preferred language.

phone   string   

User phone number.

phone_country   string   

Phone country code.

phone_verified_at   string   

Phone verification date.

address1   string   

Address line 1.

address2   string   

Address line 2.

postal_code   string   

Postal code.

city   string   

City.

country   string   

Country code (ISO 3166 Alpha-2).

clinic_name   string   

Clinic name.

clinic_location   string   

Clinic location.

image   string   

User profile image URL.

mfa_enabled   boolean   

Whether MFA is enabled.

mfa_method   string   

Preferred MFA method.

mfa_verified_to   string   

MFA session verified until.

location_id   integer   

Location ID.

created_by   integer   

ID of the user who created this account.

active   boolean   

Whether the user account is active.

is_internal   boolean   

Super Admin only: Whether the user is an internal user (QA/ADP team).

notifications_timezone   string   

Timezone for notifications.

notifications_at   string   

Preferred notification time.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

invitation_status   string   

User invitation status.

acadle_invitation_status   string   

Acadle invitation status.

roles   object[]   

User roles.

id   integer   

Role ID.

name   string   

Role name.

permissions   object[]   

User permissions.

clinicians   object[]   

Assigned clinicians.

devices   object[]   

Assigned devices.

patients   object[]   

Assigned patients.

invitations   object[]   

User invitations.

Register mobile user

Example request:
curl --request POST \
    "http://localhost:8000/api/mobile/register" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"region\": \"us\",
    \"name\": \"Tom Smith\",
    \"email\": \"name@domain.com\",
    \"password\": \"Test123!\",
    \"language\": \"en\",
    \"terms_accepted\": true
}"
const url = new URL(
    "http://localhost:8000/api/mobile/register"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "region": "us",
    "name": "Tom Smith",
    "email": "name@domain.com",
    "password": "Test123!",
    "language": "en",
    "terms_accepted": true
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 3,
    "mrn": "FTAPJ2X21793726636",
    "name": "Marcelina Walker",
    "email": "1793726636dino80@example.net",
    "language": "en",
    "phone": "+1-228-345-1410",
    "phone_country": "KG",
    "phone_verified_at": null,
    "address1": "56003 Anna Lodge Apt. 504",
    "address2": "West Keara, KS 53195",
    "postal_code": "46879-7735",
    "city": "O'Conner Inc",
    "country": "BG",
    "clinic_name": "Devanstad",
    "clinic_location": "56451 Cruz Summit Suite 443\nBeattyfort, NJ 02242-6821",
    "image": null,
    "public_image": null,
    "mfa_enabled": 0,
    "mfa_method": null,
    "mfa_verified_to": null,
    "location_id": null,
    "created_by": null,
    "active": 1,
    "is_ambassador": 0,
    "test_account": false,
    "notifications_timezone": null,
    "notifications_at": null,
    "created_at": "2026-11-03T17:23:56.000000Z",
    "updated_at": "2026-11-03T17:23:56.000000Z",
    "invitation_status": "accepted",
    "acadle_invitation_status": null,
    "roles": [
        {
            "id": 3,
            "name": "ClinicAdmin"
        }
    ]
}
 

Example response (403, Too many attempts):


{
    "message": "Register: too many attempts",
    "code": "GENERAL:TOO_MANY_ATTEMPTS"
}
 

Example response (403, E-mail in use (in another region)):


{
    "message": "E-mail address already in use (in another region)",
    "code": "AUTH:MOBILE_REGISTER:EMAIL_IN_USE"
}
 

Example response (500, Server error):


{
    "message": "Server error: user not created",
    "code": "AUTH:MOBILE_REGISTER:SERVER_ERROR"
}
 

Request   

POST api/mobile/register

Headers

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

region   string   

User region. Example: us

name   string   

User name. Example: Tom Smith

email   string   

User e-mail address. MUST_BE_EMAIL. Example: name@domain.com

password   string   

User password. Example: Test123!

language   string  optional  

User language. Example: en

terms_accepted   boolean   

User accepted terms. Must be accepted. Example: true

Response

Response Fields

id   integer   

User ID.

mrn   string   

Medical Record Number.

name   string   

User full name.

email   string   

User email address.

language   string   

User preferred language.

phone   string   

User phone number.

phone_country   string   

Phone country code.

phone_verified_at   string   

Phone verification date.

address1   string   

Address line 1.

address2   string   

Address line 2.

postal_code   string   

Postal code.

city   string   

City.

country   string   

Country code (ISO 3166 Alpha-2).

clinic_name   string   

Clinic name.

clinic_location   string   

Clinic location.

image   string   

User profile image URL.

mfa_enabled   boolean   

Whether MFA is enabled.

mfa_method   string   

Preferred MFA method.

mfa_verified_to   string   

MFA session verified until.

location_id   integer   

Location ID.

created_by   integer   

ID of the user who created this account.

active   boolean   

Whether the user account is active.

is_internal   boolean   

Super Admin only: Whether the user is an internal user (QA/ADP team).

notifications_timezone   string   

Timezone for notifications.

notifications_at   string   

Preferred notification time.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

invitation_status   string   

User invitation status.

acadle_invitation_status   string   

Acadle invitation status.

roles   object[]   

User roles.

id   integer   

Role ID.

name   string   

Role name.

permissions   object[]   

User permissions.

clinicians   object[]   

Assigned clinicians.

devices   object[]   

Assigned devices.

patients   object[]   

Assigned patients.

invitations   object[]   

User invitations.

Request password reset

Request sending password reset email message with token that allows to change the password

Example request:
curl --request POST \
    "http://localhost:8000/api/password/reset" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"email\": \"test@example.com\"
}"
const url = new URL(
    "http://localhost:8000/api/password/reset"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "email": "test@example.com"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, OK):


{
    "code": "passwords.sent",
    "message": "Reset password link successfully sent"
}
 

Example response (400, Throttled request):


{
    "code": "passwords.throttled",
    "message": "You have requested password reset recently"
}
 

Request   

POST api/password/reset

Headers

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

email   string   

User email. MUST_BE_EMAIL. Example: test@example.com

Verify password reset token

Check if token is valid before using it to reset password

Example request:
curl --request POST \
    "http://localhost:8000/api/password/reset/verify" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"token\": \"158bed12188492617e43ecfcca43f5990b3f5f0383b5083247389482b70af019\",
    \"email\": \"test@example.com\"
}"
const url = new URL(
    "http://localhost:8000/api/password/reset/verify"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "token": "158bed12188492617e43ecfcca43f5990b3f5f0383b5083247389482b70af019",
    "email": "test@example.com"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, OK):


{
    "message": "Valid token",
    "code": "AUTH:PASSWORD_RESET_VERIFY:VALID_TOKEN"
}
 

Example response (400, Invalid token):


{
    "message": "Invalid token",
    "code": "AUTH:PASSWORD_RESET_VERIFY:INVALID_TOKEN"
}
 

Request   

POST api/password/reset/verify

Headers

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

token   string   

Password reset token. Example: 158bed12188492617e43ecfcca43f5990b3f5f0383b5083247389482b70af019

email   string   

User email. MUST_BE_EMAIL. Example: test@example.com

Change password with token

Change user password using password reset token sent to email address

Example request:
curl --request POST \
    "http://localhost:8000/api/password/reset/change" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"token\": \"158bed12188492617e43ecfcca43f5990b3f5f0383b5083247389482b70af019\",
    \"email\": \"test@example.com\",
    \"password\": \"secretpassword\"
}"
const url = new URL(
    "http://localhost:8000/api/password/reset/change"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "token": "158bed12188492617e43ecfcca43f5990b3f5f0383b5083247389482b70af019",
    "email": "test@example.com",
    "password": "secretpassword"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, OK):


{
    "code": "passwords.reset",
    "message": "Password changed successfully"
}
 

Example response (400, Invalid token):


{
    "code": "passwords.token",
    "message": "Invalid token"
}
 

Example response (404, User not found):


{
    "code": "passwords.user",
    "message": "User not found"
}
 

Request   

POST api/password/reset/change

Headers

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

token   string   

Password reset token. Example: 158bed12188492617e43ecfcca43f5990b3f5f0383b5083247389482b70af019

email   string   

User email. MUST_BE_EMAIL. Example: test@example.com

password   string   

User new password. Example: secretpassword

Logout current device

requires authentication

Logout and delete current access token

Example request:
curl --request POST \
    "http://localhost:8000/api/logout" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/logout"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (202):


[]
 

Request   

POST api/logout

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Logout everywhere

requires authentication

Logout and delete all access tokens owned by account

Example request:
curl --request POST \
    "http://localhost:8000/api/logout/everywhere" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/logout/everywhere"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (202):


[]
 

Request   

POST api/logout/everywhere

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Refresh access token

requires authentication

Refresh the new access token from the expiring token

Example request:
curl --request POST \
    "http://localhost:8000/api/token/refresh" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/token/refresh"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "access_token": "7|x7de9EgE0xiBNLgHU91DHvhj85HVgTG1bekCssIA",
    "expires": "2021-10-25 17:05:25"
}
 

Request   

POST api/token/refresh

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Check MFA status

requires authentication

Check any of available MFA methods. Supported methods: email, sms, otp.

Example request:
curl --request GET \
    --get "http://localhost:8000/api/mfa/status" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/mfa/status"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "enabled": 1,
    "method": "email",
    "phone": 1,
    "otp": 1
}
 

Request   

GET api/mfa/status

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Response

Response Fields

enabled   string   

Determines if user enabled MFA

method   string   

Preferred MFA method

phone   string   

Determines if user has verified phone number and sms channel could be used

otp   string   

Determines if user has setup OTP with authenticator application

Use recovery code

requires authentication

Use generated recovery code in order to access account in case when other MFA methods couldn't be used. This method only checks if code is valid, implement account access scenario on your own. Sent code is removed and couldn't be used anymore. If remaining_codes counter equals zero, generate a new set.

Example request:
curl --request POST \
    "http://localhost:8000/api/mfa/recovery-code" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"code\": \"ZZASRM6S\"
}"
const url = new URL(
    "http://localhost:8000/api/mfa/recovery-code"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "code": "ZZASRM6S"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, OK):


{
    "access_token": "7|x7de9EgE0xiBNLgHU91DHvhj85HVgTG1bekCssIA",
    "expires": "2021-10-25 17:05:25",
    "remaining_codes": 9
}
 

Example response (401, Invalid code):


{
    "message": "Invalid code",
    "code": "AUTH:USE_RECOVERY_CODE:INVALID_CODE"
}
 

Request   

POST api/mfa/recovery-code

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

code   string   

Recovery code. Example: ZZASRM6S

Send MFA code

requires authentication

Send multi-factor authentication code via selected channel. Code is valid for 15 minutes.

Example request:
curl --request POST \
    "http://localhost:8000/api/mfa/send" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"channel\": \"email\"
}"
const url = new URL(
    "http://localhost:8000/api/mfa/send"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "channel": "email"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, OK):


{
    "message": "Code sent",
    "code": "AUTH:SEND_MFA:SENT"
}
 

Example response (400, Channel SMS, phone number not verified):


{
    "message": "Phone number is not verified",
    "code": "AUTH:SEND_MFA:PHONE_NUMBER_NOT_VERIFIED"
}
 

Example response (500, Channel SMS, provider problem):


{
    "message": "Code sending failed",
    "code": "AUTH:SEND_MFA:SERVER_ERROR"
}
 

Request   

POST api/mfa/send

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

channel   string   

Authentication channel. Example: email

Must be one of:
  • email
  • sms

Verify MFA code

requires authentication

Verify multi-factor code obtained from selected channel.

Example request:
curl --request POST \
    "http://localhost:8000/api/mfa/verify" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"channel\": \"email\",
    \"remember_mfa_session\": true,
    \"code\": \"445566\",
    \"machine_key\": \"35282880-244a-4328-9435-2aaf432f3619\"
}"
const url = new URL(
    "http://localhost:8000/api/mfa/verify"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "channel": "email",
    "remember_mfa_session": true,
    "code": "445566",
    "machine_key": "35282880-244a-4328-9435-2aaf432f3619"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, OK, token auth):


{
    "access_token": "7|x7de9EgE0xiBNLgHU91DHvhj85HVgTG1bekCssIA",
    "expires": "2021-10-25 17:05:25",
    "mfa_token": "fd63e55c-2a67-44b2-95b9-a771778e9971",
    "mfa_expires": "2023-04-25 21:00:00"
}
 

Example response (200, OK, cookie auth):


{
    "message": "OK",
    "mfa_token": "fd63e55c-2a67-44b2-95b9-a771778e9971",
    "mfa_expires": "2023-04-25 21:00:00"
}
 

Example response (401, Invalid code):


{
    "message": "Invalid code",
    "code": "AUTH:VERIFY_MFA:INVALID_CODE"
}
 

Request   

POST api/mfa/verify

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

channel   string   

Authentication channel. Example: email

Must be one of:
  • email
  • sms
remember_mfa_session   boolean  optional  

Do not require MFA code. By default, for 30 days. Example: true

code   string   

Authentication code. Example: 445566

machine_key   string  optional  

Unique machine identifier. Example: 35282880-244a-4328-9435-2aaf432f3619

Verify phone number

requires authentication

Verify phone number with text message

Example request:
curl --request POST \
    "http://localhost:8000/api/mfa/phone/verify" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"code\": \"445566\"
}"
const url = new URL(
    "http://localhost:8000/api/mfa/phone/verify"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "code": "445566"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, OK):


{
    "message": "Phone number verified",
    "code": "AUTH:VERIFY_PHONE:VERIFIED"
}
 

Example response (401, Invalid code):


{
    "message": "Invalid code",
    "code": "AUTH:VERIFY_PHONE:INVALID_CODE"
}
 

Request   

POST api/mfa/phone/verify

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

code   string   

Verification code. Example: 445566

Verify MFA OTP

requires authentication

Verify one-time password (OTP). If verification is successful, new access token with additional permissions will be generated.

Example request:
curl --request POST \
    "http://localhost:8000/api/mfa/otp/verify" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"code\": \"445566\"
}"
const url = new URL(
    "http://localhost:8000/api/mfa/otp/verify"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "code": "445566"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, OK, token auth):


{
    "access_token": "7|x7de9EgE0xiBNLgHU91DHvhj85HVgTG1bekCssIA",
    "expires": "2021-10-25 17:05:25"
}
 

Example response (200, OK, cookie auth):


{
    "message": "OK"
}
 

Example response (401, Invalid code):


{
    "message": "Invalid code",
    "code": "AUTH:VERIFY_MFA_OTP:INVALID_CODE"
}
 

Request   

POST api/mfa/otp/verify

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

code   string   

One-time password from app. Example: 445566

Change password

requires authentication

Change authenticated user password

Example request:
curl --request POST \
    "http://localhost:8000/api/password/change" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"old_password\": \"oldpassword\",
    \"new_password\": \"newpassword\"
}"
const url = new URL(
    "http://localhost:8000/api/password/change"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "old_password": "oldpassword",
    "new_password": "newpassword"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, OK):


{
    "message": "Password changed successfully",
    "code": "AUTH:PASSWORD_CHANGE:CHANGED"
}
 

Example response (422, Invalid old password):


{
    "message": "The given data was invalid.",
    "errors": {
        "old_password": [
            "Old password is incorrect"
        ]
    },
    "code": "AUTH:PASSWORD_CHANGE:INVALID_OLD_PASSWORD"
}
 

Request   

POST api/password/change

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

old_password   string   

User old password. Example: oldpassword

new_password   string   

User new password. Example: newpassword

Set MFA status

requires authentication

Set MFA status and preferred method. Supported methods: email, sms, otp.

Example request:
curl --request POST \
    "http://localhost:8000/api/mfa/status" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"enabled\": true,
    \"method\": \"email\"
}"
const url = new URL(
    "http://localhost:8000/api/mfa/status"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "enabled": true,
    "method": "email"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, OK):


{
    "enabled": 1,
    "method": "email",
    "phone": 1,
    "otp": 1
}
 

Request   

POST api/mfa/status

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

enabled   boolean  optional  

Use MFA after login. Example: true

method   string  optional  

Preferred MFA method. Example: email

Must be one of:
  • email
  • sms
  • otp

Generate recovery codes

requires authentication

Generate recovery codes for authenticated user and revoke old ones. User could use these codes in case when couldn't use any of MFA methods (e.g. lost device with OTP app or device is not accessible right now).

Example request:
curl --request GET \
    --get "http://localhost:8000/api/mfa/recovery-codes" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/mfa/recovery-codes"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "recovery_codes": [
        "A3H8PF8P",
        "IZ8CGK2H",
        "DTYENLLT",
        "0RKEZFST",
        "9MPW91BS",
        "S38Z6HS6",
        "UF5ATOKP",
        "HSZXP8EL",
        "ZZASRM6S",
        "07GR4CD1"
    ]
}
 

Request   

GET api/mfa/recovery-codes

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Setup MFA OTP

requires authentication

Setup multi-factor authentication with one-time passwords (OTP). Use secret on your own or generate QR code with given url. Then user could scan QR code with authentication app (e.g. Microsoft Authenticator, Authy). If secret has been already generated, new secret will override existing one and revoke previous setup.

Example request:
curl --request POST \
    "http://localhost:8000/api/mfa/otp/setup" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/mfa/otp/setup"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "secret": "VXGJ6JMIAWWDFXYDLKO3VG3RSGTS34BGMVTGQIEHMVVMJ2JBGCSNPQZDV4B6OMDIGI4UKCVCVKVMA7EASLHZEJWW4ZNKLAUTSZYN7EA",
    "url": "otpauth://totp/AetherDigitalTherapy?issuer=AetherDigitalTherapy&secret=VXGJ6JMIAWWDFXYDLKO3VG3RSGTS34BGMVTGQIEHMVVMJ2JBGCSNPQZDV4B6OMDIGI4UKCVCVKVMA7EASLHZEJWW4ZNKLAUTSZYN7EA",
    "recovery_codes": [
        "A3H8PF8P",
        "IZ8CGK2H",
        "DTYENLLT",
        "0RKEZFST",
        "9MPW91BS",
        "S38Z6HS6",
        "UF5ATOKP",
        "HSZXP8EL",
        "ZZASRM6S",
        "07GR4CD1"
    ]
}
 

Request   

POST api/mfa/otp/setup

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Login user (SPA)

Authorize user and create cookie-based session. Hit GET /sanctum/csrf-cookie endpoint to retrieve XSRF-TOKEN cookie. Then attach X-XSRF-TOKEN HTTP header to any request to authorize. See more: Laravel Sanctum documentation

Example request:
curl --request POST \
    "http://localhost:8000/login" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"email\": \"test@example.com\",
    \"password\": \"secretpassword\"
}"
const url = new URL(
    "http://localhost:8000/login"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "email": "test@example.com",
    "password": "secretpassword"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "id": 332,
    "mrn": "QR6WY9TD1793726823",
    "name": "Mr. Adelbert Kassulke II",
    "email": "1793726823cvon@example.net",
    "language": "en",
    "phone": "(929) 352-4012",
    "phone_country": "FK",
    "phone_verified_at": null,
    "address1": "80162 Kristofer Radial Suite 135",
    "address2": "West Nicoburgh, IL 42036",
    "postal_code": "13072-3690",
    "city": "Douglas Ltd",
    "country": "EE",
    "clinic_name": "West Trent",
    "clinic_location": "2870 Hickle Crest Apt. 308\nEast Stefanfurt, IL 08167-8674",
    "image": null,
    "public_image": null,
    "mfa_enabled": 0,
    "mfa_method": null,
    "mfa_verified_to": null,
    "location_id": null,
    "created_by": null,
    "active": 1,
    "is_ambassador": 0,
    "test_account": false,
    "notifications_timezone": null,
    "notifications_at": null,
    "created_at": "2026-11-03T17:27:04.000000Z",
    "updated_at": "2026-11-03T17:27:04.000000Z",
    "invitation_status": null,
    "acadle_invitation_status": null,
    "roles": []
}
 

Example response (403, Too many attempts):


{
    "message": "Login: too many attempts",
    "code": "GENERAL:TOO_MANY_ATTEMPTS"
}
 

Example response (422, Invalid credentials):


{
    "message": "The given data was invalid.",
    "errors": {
        "email": [
            "Given credentials not found"
        ]
    },
    "code": "AUTH:LOGIN_COOKIE:INVALID_CREDENTIALS"
}
 

Request   

POST login

Headers

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

email   string   

User email. MUST_BE_EMAIL. Example: test@example.com

password   string   

User password. Example: secretpassword

Response

Response Fields

id   integer   

User ID.

mrn   string   

Medical Record Number.

name   string   

User full name.

email   string   

User email address.

language   string   

User preferred language.

phone   string   

User phone number.

phone_country   string   

Phone country code.

phone_verified_at   string   

Phone verification date.

address1   string   

Address line 1.

address2   string   

Address line 2.

postal_code   string   

Postal code.

city   string   

City.

country   string   

Country code (ISO 3166 Alpha-2).

clinic_name   string   

Clinic name.

clinic_location   string   

Clinic location.

image   string   

User profile image URL.

mfa_enabled   boolean   

Whether MFA is enabled.

mfa_method   string   

Preferred MFA method.

mfa_verified_to   string   

MFA session verified until.

location_id   integer   

Location ID.

created_by   integer   

ID of the user who created this account.

active   boolean   

Whether the user account is active.

is_internal   boolean   

Super Admin only: Whether the user is an internal user (QA/ADP team).

notifications_timezone   string   

Timezone for notifications.

notifications_at   string   

Preferred notification time.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

invitation_status   string   

User invitation status.

acadle_invitation_status   string   

Acadle invitation status.

roles   object[]   

User roles.

id   integer   

Role ID.

name   string   

Role name.

permissions   object[]   

User permissions.

clinicians   object[]   

Assigned clinicians.

devices   object[]   

Assigned devices.

patients   object[]   

Assigned patients.

invitations   object[]   

User invitations.

Chat

API endpoints for chat management

Authorize a user

requires authentication

This method authorizes a user using Ably service. Endpoint used only by Ably SDK

Check more details on https://ably.com/docs/auth/token

Example request:
curl --request GET \
    --get "http://localhost:8000/api/chat/authorize-user" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/chat/authorize-user"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
 

{
    "message": "api.responses.general.unauthenticated",
    "code": "GENERAL:UNAUTHENTICATED"
}
 

Request   

GET api/chat/authorize-user

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

List all chat rooms

requires authentication

This method retrieves all chat rooms. Possible extend options:

Example request:
curl --request GET \
    --get "http://localhost:8000/api/chat/rooms" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/chat/rooms"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 100,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 1,
            "owner": null,
            "patient_id": null,
            "encryption_key": "thNW+AMBja+XzTn/BDNTVaY8T7imNS+9mentIyyJ+Q8=",
            "name": "at",
            "friendly_name": "at",
            "created_at": "2026-11-03T17:26:37.000000Z",
            "deleted_at": null,
            "updated_at": "2026-11-03T17:26:37.000000Z"
        },
        {
            "id": 2,
            "owner": null,
            "patient_id": null,
            "encryption_key": "xy5FIam3dLF+yaH9fMedCqvjaXa0pMhljldHoIS/bkA=",
            "name": "nihil",
            "friendly_name": "nihil",
            "created_at": "2026-11-03T17:26:37.000000Z",
            "deleted_at": null,
            "updated_at": "2026-11-03T17:26:37.000000Z"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to list chat room",
    "code": "CHAT:LIST_ROOMS:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/chat/rooms

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

perpage   integer  optional  

Elements per page (Default: 100).

page   integer  optional  

Page number (Default: 1).

Response

Response Fields

items   object   
id   integer   

Chat room ID.

name   string   

Chat room name (channel SID).

friendly_name   string   

Human-readable chat room name.

owner   integer   

User ID of the room owner.

patient_id   integer   

Associated patient user ID.

encryption_key   string   

Encryption key for the chat room.

deleted_at   string   

Soft delete timestamp.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

messages   object[]   

Chat room messages.

last_message   object   

Last message in the chat room.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

Retrieve a chat room

requires authentication

This method retrieves a single chat room identified by roomId. Possible extend options:

Example request:
curl --request GET \
    --get "http://localhost:8000/api/chat/room/at" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/chat/room/at"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "id": 3,
    "owner": null,
    "patient_id": null,
    "encryption_key": "ftKnHg/Y9wE0ipGrE/HRO7y4kwC6/kmH2cWtQF3AKqA=",
    "name": "aliquid",
    "friendly_name": "aliquid",
    "created_at": "2026-11-03T17:26:37.000000Z",
    "deleted_at": null,
    "updated_at": "2026-11-03T17:26:37.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view chat room",
    "code": "CHAT:GET_ROOM:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Chat room not found):


{
    "message": "Chat room not found",
    "code": "CHAT:GET_ROOM:ROOM_NOT_FOUND"
}
 

Request   

GET api/chat/room/{roomName}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

roomName   string   

Example: at

Response

Response Fields

id   integer   

Chat room ID.

name   string   

Chat room name (channel SID).

friendly_name   string   

Human-readable chat room name.

owner   integer   

User ID of the room owner.

patient_id   integer   

Associated patient user ID.

encryption_key   string   

Encryption key for the chat room.

deleted_at   string   

Soft delete timestamp.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

messages   object[]   

Chat room messages.

last_message   object   

Last message in the chat room.

Create a new chat room

requires authentication

This method creates a new chat room using the authenticated user's ID, a name for the room, and a list of participants.

    The list of participants should contain the IDs of the users who will be participants in the room.
Example request:
curl --request POST \
    "http://localhost:8000/api/chat/room" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"owner\": 1,
    \"name\": \"my-chat\",
    \"patient_id\": 1,
    \"participants\": [
        \"1\"
    ]
}"
const url = new URL(
    "http://localhost:8000/api/chat/room"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "owner": 1,
    "name": "my-chat",
    "patient_id": 1,
    "participants": [
        "1"
    ]
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 4,
    "owner": null,
    "patient_id": null,
    "encryption_key": "7JDQ2Qtz2GsEYxE9j41qYKsT8oTZAwt31caCPWuDLKo=",
    "name": "nemo",
    "friendly_name": "nemo",
    "created_at": "2026-11-03T17:26:37.000000Z",
    "deleted_at": null,
    "updated_at": "2026-11-03T17:26:37.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view chat room",
    "code": "CHAT:CREATE:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/chat/room

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

owner   string   

The id of an existing record in the App\Models\User table. Example: 1

name   string   

Name of chat room. MAXIMUM:STRING_LENGTH:255. Example: my-chat

patient_id   string   

The id of an existing record in the App\Models\User table. Example: 1

participants   string[]  optional  

Chat room participant. The id of an existing record in the App\Models\User table.

Response

Response Fields

id   integer   

Chat room ID.

name   string   

Chat room name (channel SID).

friendly_name   string   

Human-readable chat room name.

owner   integer   

User ID of the room owner.

patient_id   integer   

Associated patient user ID.

encryption_key   string   

Encryption key for the chat room.

deleted_at   string   

Soft delete timestamp.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

messages   object[]   

Chat room messages.

last_message   object   

Last message in the chat room.

Update an existing chat room

requires authentication

This method updates an existing chat room using the authenticated user's ID, a new name for the room, and a list of participants.

    The list of participants should contain the IDs of the users who will be participants in the room.
Example request:
curl --request PUT \
    "http://localhost:8000/api/chat/room/accusamus" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"owner\": 1,
    \"name\": \"my-chat\",
    \"participants\": [
        \"1\"
    ],
    \"participants_del\": [
        \"1\"
    ]
}"
const url = new URL(
    "http://localhost:8000/api/chat/room/accusamus"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "owner": 1,
    "name": "my-chat",
    "participants": [
        "1"
    ],
    "participants_del": [
        "1"
    ]
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (202):


{
    "id": 5,
    "owner": null,
    "patient_id": null,
    "encryption_key": "2VwPD0guJ3vT4VUhpzT+Mnmu+Te1IQqiMc4ZB7Y6cF4=",
    "name": "iusto",
    "friendly_name": "iusto",
    "created_at": "2026-11-03T17:26:37.000000Z",
    "deleted_at": null,
    "updated_at": "2026-11-03T17:26:37.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to update chat room",
    "code": "CHAT:UPDATE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Chat room not found):


{
    "message": "Chat room not found",
    "code": "CHAT:UPDATE:ROOM_NOT_FOUND"
}
 

Request   

PUT api/chat/room/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   string   

The ID of the room. Example: accusamus

Body Parameters

owner   string  optional  

The id of an existing record in the App\Models\User table. Example: 1

name   string  optional  

Name of chat room. MAXIMUM:STRING_LENGTH:255. Example: my-chat

participants   string[]  optional  

Chat room participant. The id of an existing record in the App\Models\User table.

participants_del   string[]  optional  

Chat room participant to be deleted. The id of an existing record in the App\Models\User table.

Response

Response Fields

id   integer   

Chat room ID.

name   string   

Chat room name (channel SID).

friendly_name   string   

Human-readable chat room name.

owner   integer   

User ID of the room owner.

patient_id   integer   

Associated patient user ID.

encryption_key   string   

Encryption key for the chat room.

deleted_at   string   

Soft delete timestamp.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

messages   object[]   

Chat room messages.

last_message   object   

Last message in the chat room.

Chat room archives

requires authentication

Get archived messages for room Possible extend options:

Example request:
curl --request GET \
    --get "http://localhost:8000/api/chat/room/aut/archive" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/chat/room/aut/archive"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "paginator": {
        "total": 1,
        "count": 1,
        "perpage": 5,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": "651a8e4868d5dc27c0000cc2",
            "channel": "chat.messages.room.44.56bf7d37-4ed6-4db4-947b-d68a1066677c.communication-channel",
            "clientId": "95",
            "msgId": "b8673175-01e6-4b6d-9032-226d3df20637",
            "data": "{\"encryptedMessage\":{\"message\":\"z6z1zNihCjlEePltz+BG8g==\",\"initialVector\":\"7376fcbf0b32fbdcd5c5b62c087b7600\"},\"user\":{\"id\":95,\"name\":\"Bartosz Druga firmaa\",\"email\":\"bartosz+drugafirma@refericon.pl\",\"image\":\"https://aether-dev-bucket.s3.amazonaws.com/users/7T6im01PAj4cahksWHllrL7se2SQ9buquIjGGFtp.jpg\",\"permissions\":[],\"roles\":[{\"id\":2,\"name\":\"Clinician\"}]},\"msgId\":\"b8673175-01e6-4b6d-9032-226d3df20637\",\"recipients\":[{\"delivered\":true,\"msgId\":\"b8673175-01e6-4b6d-9032-226d3df20637\",\"seen\":false,\"clientId\":\"95\"},{\"delivered\":true,\"msgId\":\"b8673175-01e6-4b6d-9032-226d3df20637\",\"seen\":false,\"clientId\":\"44\"},{\"delivered\":true,\"msgId\":\"b8673175-01e6-4b6d-9032-226d3df20637\",\"seen\":false,\"clientId\":\"1250\"},{\"delivered\":true,\"msgId\":\"b8673175-01e6-4b6d-9032-226d3df20637\",\"seen\":false,\"clientId\":\"3067\"}]}",
            "name": "message",
            "recipients": [
                {
                    "delivered": true,
                    "msgId": "b8673175-01e6-4b6d-9032-226d3df20637",
                    "seen": true,
                    "clientId": "95"
                },
                {
                    "delivered": true,
                    "msgId": "b8673175-01e6-4b6d-9032-226d3df20637",
                    "seen": false,
                    "clientId": "44"
                },
                {
                    "delivered": true,
                    "msgId": "b8673175-01e6-4b6d-9032-226d3df20637",
                    "seen": false,
                    "clientId": "1250"
                },
                {
                    "delivered": true,
                    "msgId": "b8673175-01e6-4b6d-9032-226d3df20637",
                    "seen": false,
                    "clientId": "3067"
                }
            ],
            "timestamp": 1696239176715,
            "created_at": "2023-10-02 09:32:56"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view archived messages",
    "code": "CHAT:GET_ARCHIVES:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Chat room not found):


{
    "message": "Chat room not found",
    "code": "CHAT:GET_ARCHIVES:ROOM_NOT_FOUND"
}
 

Request   

GET api/chat/room/{id}/archive

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   string   

The ID of the room. Example: aut

Query Parameters

perpage   integer  optional  

Elements per page (Default: 100).

page   integer  optional  

Page number (Default: 1).

extend   string  optional  

Comma-separated list of relation extensions (available: author).

sortby   string  optional  

Sort by field (available: timestamp). Default: timestamp, desc.

sortdir   string  optional  

Sort direction (available: asc, desc).

Unread messages

requires authentication

Get unread messaged for chat room. Possible extend options:

Example request:
curl --request GET \
    --get "http://localhost:8000/api/chat/messages/unread?room=2" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/chat/messages/unread"
);

const params = {
    "room": "2",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "paginator": {
        "total": 1,
        "count": 1,
        "perpage": 5,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": "651a8e4868d5dc27c0000cc2",
            "channel": "chat.messages.room.44.56bf7d37-4ed6-4db4-947b-d68a1066677c.communication-channel",
            "clientId": "95",
            "msgId": "b8673175-01e6-4b6d-9032-226d3df20637",
            "data": "{\"encryptedMessage\":{\"message\":\"z6z1zNihCjlEePltz+BG8g==\",\"initialVector\":\"7376fcbf0b32fbdcd5c5b62c087b7600\"},\"user\":{\"id\":95,\"name\":\"Bartosz Druga firmaa\",\"email\":\"bartosz+drugafirma@refericon.pl\",\"image\":\"https://aether-dev-bucket.s3.amazonaws.com/users/7T6im01PAj4cahksWHllrL7se2SQ9buquIjGGFtp.jpg\",\"permissions\":[],\"roles\":[{\"id\":2,\"name\":\"Clinician\"}]},\"msgId\":\"b8673175-01e6-4b6d-9032-226d3df20637\",\"recipients\":[{\"delivered\":true,\"msgId\":\"b8673175-01e6-4b6d-9032-226d3df20637\",\"seen\":false,\"clientId\":\"95\"},{\"delivered\":true,\"msgId\":\"b8673175-01e6-4b6d-9032-226d3df20637\",\"seen\":false,\"clientId\":\"44\"},{\"delivered\":true,\"msgId\":\"b8673175-01e6-4b6d-9032-226d3df20637\",\"seen\":false,\"clientId\":\"1250\"},{\"delivered\":true,\"msgId\":\"b8673175-01e6-4b6d-9032-226d3df20637\",\"seen\":false,\"clientId\":\"3067\"}]}",
            "name": "message",
            "recipients": [
                {
                    "delivered": true,
                    "msgId": "b8673175-01e6-4b6d-9032-226d3df20637",
                    "seen": true,
                    "clientId": "95"
                },
                {
                    "delivered": true,
                    "msgId": "b8673175-01e6-4b6d-9032-226d3df20637",
                    "seen": false,
                    "clientId": "44"
                },
                {
                    "delivered": true,
                    "msgId": "b8673175-01e6-4b6d-9032-226d3df20637",
                    "seen": false,
                    "clientId": "1250"
                },
                {
                    "delivered": true,
                    "msgId": "b8673175-01e6-4b6d-9032-226d3df20637",
                    "seen": false,
                    "clientId": "3067"
                }
            ],
            "timestamp": 1696239176715,
            "created_at": "2023-10-02 09:32:56"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view unread messages list",
    "code": "CHAT:UNREAD_MESSAGES:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/chat/messages/unread

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

room   integer  optional  

Filter unread messages by room. Provide single ID (room=1), array of IDs (room[]=1&room[]=2) or comma-separated list of IDs (room=1,2). Example: 2

perpage   integer  optional  

Elements per page (Default: 100).

page   integer  optional  

Page number (Default: 1).

Delete chat message

requires authentication

Example request:
curl --request DELETE \
    "http://localhost:8000/api/chat/messages/9?msgId=illum" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/chat/messages/9"
);

const params = {
    "msgId": "illum",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (202, OK):


{
    "paginator": {
        "total": 1,
        "count": 1,
        "perpage": 5,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": "651a8e4868d5dc27c0000cc2",
            "channel": "chat.messages.room.44.56bf7d37-4ed6-4db4-947b-d68a1066677c.communication-channel",
            "clientId": "95",
            "msgId": "b8673175-01e6-4b6d-9032-226d3df20637",
            "data": "{\"encryptedMessage\":{\"message\":\"z6z1zNihCjlEePltz+BG8g==\",\"initialVector\":\"7376fcbf0b32fbdcd5c5b62c087b7600\"},\"user\":{\"id\":95,\"name\":\"Bartosz Druga firmaa\",\"email\":\"bartosz+drugafirma@refericon.pl\",\"image\":\"https://aether-dev-bucket.s3.amazonaws.com/users/7T6im01PAj4cahksWHllrL7se2SQ9buquIjGGFtp.jpg\",\"permissions\":[],\"roles\":[{\"id\":2,\"name\":\"Clinician\"}]},\"msgId\":\"b8673175-01e6-4b6d-9032-226d3df20637\",\"recipients\":[{\"delivered\":true,\"msgId\":\"b8673175-01e6-4b6d-9032-226d3df20637\",\"seen\":false,\"clientId\":\"95\"},{\"delivered\":true,\"msgId\":\"b8673175-01e6-4b6d-9032-226d3df20637\",\"seen\":false,\"clientId\":\"44\"},{\"delivered\":true,\"msgId\":\"b8673175-01e6-4b6d-9032-226d3df20637\",\"seen\":false,\"clientId\":\"1250\"},{\"delivered\":true,\"msgId\":\"b8673175-01e6-4b6d-9032-226d3df20637\",\"seen\":false,\"clientId\":\"3067\"}]}",
            "name": "message",
            "recipients": [
                {
                    "delivered": true,
                    "msgId": "b8673175-01e6-4b6d-9032-226d3df20637",
                    "seen": true,
                    "clientId": "95"
                },
                {
                    "delivered": true,
                    "msgId": "b8673175-01e6-4b6d-9032-226d3df20637",
                    "seen": false,
                    "clientId": "44"
                },
                {
                    "delivered": true,
                    "msgId": "b8673175-01e6-4b6d-9032-226d3df20637",
                    "seen": false,
                    "clientId": "1250"
                },
                {
                    "delivered": true,
                    "msgId": "b8673175-01e6-4b6d-9032-226d3df20637",
                    "seen": false,
                    "clientId": "3067"
                }
            ],
            "timestamp": 1696239176715,
            "created_at": "2023-10-02 09:32:56"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to delete message",
    "code": "CHAT:DELETE_MESSAGE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Message not found):


{
    "message": "Chat message not found",
    "code": "CHAT:DELETE_MESSAGE:MESSAGE_NOT_FOUND"
}
 

Request   

DELETE api/chat/messages/{msgId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

msgId   integer   

Example: 9

Query Parameters

msgId   string   

Message ID. Example: illum

Get tickets list for chat room

requires authentication

Possible extend options:

Example request:
curl --request GET \
    --get "http://localhost:8000/api/chat/tickets/sit?status=culpa&sender=15&recipient=7" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/chat/tickets/sit"
);

const params = {
    "status": "culpa",
    "sender": "15",
    "recipient": "7",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 87,
            "sender_id": 293,
            "recipient_id": 294,
            "device_id": null,
            "meeting_date": "2026-11-03 17:26:38",
            "meeting_type": "online_meeting",
            "contact_email": "danika46@heathcote.com",
            "status": "new",
            "created_at": "2026-11-03T17:26:38.000000Z",
            "updated_at": "2026-11-03T17:26:38.000000Z",
            "sender": {
                "id": 293,
                "mrn": "6SB6T9B31793726797",
                "name": "Ms. Rebekah Hermiston",
                "email": "1793726797vena58@example.net",
                "language": "en",
                "phone": "(743) 815-4499",
                "phone_country": "MF",
                "phone_verified_at": null,
                "address1": "347 Nolan Lodge",
                "address2": "Port Columbus, MO 24803",
                "postal_code": "00949",
                "city": "Baumbach, Schneider and Jenkins",
                "country": "ES",
                "clinic_name": "Efrentown",
                "clinic_location": "675 Klein Cove Apt. 067\nNew Hassanstad, DC 84953-4768",
                "image": null,
                "public_image": null,
                "mfa_enabled": 0,
                "mfa_method": null,
                "mfa_verified_to": null,
                "location_id": null,
                "created_by": null,
                "active": 1,
                "is_ambassador": 0,
                "test_account": false,
                "notifications_timezone": null,
                "notifications_at": null,
                "created_at": "2026-11-03T17:26:37.000000Z",
                "updated_at": "2026-11-03T17:26:37.000000Z",
                "invitation_status": null,
                "acadle_invitation_status": null,
                "roles": []
            },
            "recipient": {
                "id": 294,
                "mrn": "XK2RVV7G1793726798",
                "name": "Cassie Effertz",
                "email": "1793726798graham.myra@example.org",
                "language": "en",
                "phone": "+1-330-750-8168",
                "phone_country": "GL",
                "phone_verified_at": null,
                "address1": "988 Christa Pike Suite 147",
                "address2": "Wilfredville, DC 27061",
                "postal_code": "96119-3038",
                "city": "Deckow Inc",
                "country": "PT",
                "clinic_name": "Melvinchester",
                "clinic_location": "6513 Nettie Knolls Suite 095\nWest Lamontfort, AR 99312",
                "image": null,
                "public_image": null,
                "mfa_enabled": 0,
                "mfa_method": null,
                "mfa_verified_to": null,
                "location_id": null,
                "created_by": null,
                "active": 1,
                "is_ambassador": 0,
                "test_account": false,
                "notifications_timezone": null,
                "notifications_at": null,
                "created_at": "2026-11-03T17:26:38.000000Z",
                "updated_at": "2026-11-03T17:26:38.000000Z",
                "invitation_status": null,
                "acadle_invitation_status": null,
                "roles": []
            },
            "device": null,
            "messages": []
        },
        {
            "id": 88,
            "sender_id": 295,
            "recipient_id": 296,
            "device_id": null,
            "meeting_date": "2026-11-03 17:26:39",
            "meeting_type": "online_meeting",
            "contact_email": "stefan.simonis@yahoo.com",
            "status": "new",
            "created_at": "2026-11-03T17:26:39.000000Z",
            "updated_at": "2026-11-03T17:26:39.000000Z",
            "sender": {
                "id": 295,
                "mrn": "A9WAVHEE1793726798",
                "name": "Magnus Howell",
                "email": "1793726798stefan.doyle@example.com",
                "language": "en",
                "phone": "1-559-874-0944",
                "phone_country": "GY",
                "phone_verified_at": null,
                "address1": "3429 Ola Cliffs",
                "address2": "Maximoshire, DC 51228",
                "postal_code": "44438-2825",
                "city": "Kovacek, Quitzon and Corwin",
                "country": "GB",
                "clinic_name": "Weimannview",
                "clinic_location": "3744 Simonis Divide Apt. 106\nPort Bertramton, AR 02438",
                "image": null,
                "public_image": null,
                "mfa_enabled": 0,
                "mfa_method": null,
                "mfa_verified_to": null,
                "location_id": null,
                "created_by": null,
                "active": 1,
                "is_ambassador": 0,
                "test_account": false,
                "notifications_timezone": null,
                "notifications_at": null,
                "created_at": "2026-11-03T17:26:38.000000Z",
                "updated_at": "2026-11-03T17:26:38.000000Z",
                "invitation_status": null,
                "acadle_invitation_status": null,
                "roles": []
            },
            "recipient": {
                "id": 296,
                "mrn": "CMMKSHN51793726799",
                "name": "Brycen Bednar",
                "email": "1793726799travon.powlowski@example.org",
                "language": "en",
                "phone": "(860) 216-7646",
                "phone_country": "GQ",
                "phone_verified_at": null,
                "address1": "6609 Wilderman Terrace",
                "address2": "Johnathonburgh, WA 00088",
                "postal_code": "95150",
                "city": "Hammes, Welch and Herzog",
                "country": "UA",
                "clinic_name": "East Effie",
                "clinic_location": "620 Kub Ports Apt. 993\nEast Clementinemouth, KY 03143-7123",
                "image": null,
                "public_image": null,
                "mfa_enabled": 0,
                "mfa_method": null,
                "mfa_verified_to": null,
                "location_id": null,
                "created_by": null,
                "active": 1,
                "is_ambassador": 0,
                "test_account": false,
                "notifications_timezone": null,
                "notifications_at": null,
                "created_at": "2026-11-03T17:26:39.000000Z",
                "updated_at": "2026-11-03T17:26:39.000000Z",
                "invitation_status": null,
                "acadle_invitation_status": null,
                "roles": []
            },
            "device": null,
            "messages": []
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view chat room tickets",
    "code": "CHAT:LIST_TICKETS:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Chat room not found):


{
    "message": "Chat room not found",
    "code": "CHAT:LIST_TICKETS:ROOM_NOT_FOUND"
}
 

Request   

GET api/chat/tickets/{roomId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

roomId   string   

Example: sit

Query Parameters

status   string  optional  

Filter tickets by status (available: new,in_progress,closed,reopened. Example: culpa

sender   integer  optional  

Filter tickets by sender. Example: 15

recipient   integer  optional  

Filter tickets by recipient. Example: 7

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

extend   string  optional  

Comma-separated list of relation extensions (available: sender, recipient, messages, messages.attachments, messages.sender).

sortby   string  optional  

Sort by field (available: date). Default: date, desc.

sortdir   string  optional  

Sort direction (available: asc, desc).

Response

Response Fields

items   object   
id   integer   

Support ticket ID.

sender_id   integer   

ID of the user who created the ticket.

recipient_id   integer   

ID of the recipient user.

device_id   integer   

Associated device ID.

meeting_date   string   

Scheduled meeting date.

meeting_type   string   

Meeting type.

Must be one of:
  • online
  • in-person
contact_email   string   

Contact email address.

status   string   

Ticket status.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

sender   object   

User who created the ticket.

recipient   object   

Recipient user.

device   object   

Associated device.

messages   object[]   

Ticket messages.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

List of available patients for chat

requires authentication

Possible extend options:

Example request:
curl --request GET \
    --get "http://localhost:8000/api/chat/available-patients" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/chat/available-patients"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 297,
            "mrn": "6KQMYZB61793726799",
            "name": "Dr. Clement Crooks",
            "email": "1793726799kreiger.abdullah@example.org",
            "language": "en",
            "phone": "+1-831-407-7394",
            "phone_country": "MX",
            "phone_verified_at": null,
            "address1": "16058 Renee Rapid Suite 896",
            "address2": "Rainatown, NY 82283",
            "postal_code": "82991",
            "city": "Bins-Harris",
            "country": "IE",
            "clinic_name": "West Kara",
            "clinic_location": "8097 Jonas Tunnel\nMcGlynnville, SC 95043",
            "image": null,
            "public_image": null,
            "mfa_enabled": 0,
            "mfa_method": null,
            "mfa_verified_to": null,
            "location_id": null,
            "created_by": null,
            "active": 1,
            "is_ambassador": 0,
            "test_account": false,
            "notifications_timezone": null,
            "notifications_at": null,
            "created_at": "2026-11-03T17:26:39.000000Z",
            "updated_at": "2026-11-03T17:26:39.000000Z",
            "invitation_status": "accepted",
            "acadle_invitation_status": null,
            "devices": [
                {
                    "id": 154,
                    "serial": "b55307cf-b819-3cd7-a3d1-12843f5a392d",
                    "bluetooth_id": "89ab80c3-d56f-3dda-a907-9f01c427e239",
                    "company_id": null,
                    "model_id": null,
                    "amputee_id": 297,
                    "clinician_id": null,
                    "firmware_version_id": null,
                    "pcb_version_id": null,
                    "reverse_magnets": 0,
                    "is_electrode": 0,
                    "active": 1,
                    "last_activity_at": "0000-00-00 00:00:00",
                    "first_connected_at": null,
                    "measurements": null,
                    "created_at": "2026-11-03T17:26:40.000000Z",
                    "updated_at": "2026-11-03T17:26:40.000000Z",
                    "first_config_change_at": null
                }
            ],
            "roles": [
                {
                    "id": 4,
                    "name": "Clinician"
                }
            ]
        },
        {
            "id": 298,
            "mrn": "4NTZSB7L1793726800",
            "name": "Camren Auer",
            "email": "1793726800parker.orion@example.com",
            "language": "en",
            "phone": "934-721-7261",
            "phone_country": "GD",
            "phone_verified_at": null,
            "address1": "3381 Lebsack Turnpike",
            "address2": "North Jalon, NH 45208",
            "postal_code": "81448-2837",
            "city": "Ledner PLC",
            "country": "BE",
            "clinic_name": "Greysonview",
            "clinic_location": "39907 Annabell Islands Apt. 989\nWest Madisynbury, MD 69789-5842",
            "image": null,
            "public_image": null,
            "mfa_enabled": 0,
            "mfa_method": null,
            "mfa_verified_to": null,
            "location_id": null,
            "created_by": null,
            "active": 1,
            "is_ambassador": 0,
            "test_account": false,
            "notifications_timezone": null,
            "notifications_at": null,
            "created_at": "2026-11-03T17:26:40.000000Z",
            "updated_at": "2026-11-03T17:26:40.000000Z",
            "invitation_status": "accepted",
            "acadle_invitation_status": null,
            "devices": [
                {
                    "id": 155,
                    "serial": "dc634340-01a1-34ac-acda-c6bc73ee6117",
                    "bluetooth_id": "25fc58ce-d73d-33ac-953e-5e8b0fe768b7",
                    "company_id": null,
                    "model_id": null,
                    "amputee_id": 298,
                    "clinician_id": null,
                    "firmware_version_id": null,
                    "pcb_version_id": null,
                    "reverse_magnets": 0,
                    "is_electrode": 0,
                    "active": 1,
                    "last_activity_at": "0000-00-00 00:00:00",
                    "first_connected_at": null,
                    "measurements": null,
                    "created_at": "2026-11-03T17:26:41.000000Z",
                    "updated_at": "2026-11-03T17:26:41.000000Z",
                    "first_config_change_at": null
                }
            ],
            "roles": [
                {
                    "id": 4,
                    "name": "Clinician"
                }
            ]
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to list available patients",
    "code": "CHAT:LIST_PATIENTS:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/chat/available-patients

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

Response

Response Fields

items   object   
id   integer   

User ID.

mrn   string   

Medical Record Number.

name   string   

User full name.

email   string   

User email address.

language   string   

User preferred language.

phone   string   

User phone number.

phone_country   string   

Phone country code.

phone_verified_at   string   

Phone verification date.

address1   string   

Address line 1.

address2   string   

Address line 2.

postal_code   string   

Postal code.

city   string   

City.

country   string   

Country code (ISO 3166 Alpha-2).

clinic_name   string   

Clinic name.

clinic_location   string   

Clinic location.

image   string   

User profile image URL.

mfa_enabled   boolean   

Whether MFA is enabled.

mfa_method   string   

Preferred MFA method.

mfa_verified_to   string   

MFA session verified until.

location_id   integer   

Location ID.

created_by   integer   

ID of the user who created this account.

active   boolean   

Whether the user account is active.

is_internal   boolean   

Super Admin only: Whether the user is an internal user (QA/ADP team).

notifications_timezone   string   

Timezone for notifications.

notifications_at   string   

Preferred notification time.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

invitation_status   string   

User invitation status.

acadle_invitation_status   string   

Acadle invitation status.

roles   object[]   

User roles.

id   integer   

Role ID.

name   string   

Role name.

permissions   object[]   

User permissions.

clinicians   object[]   

Assigned clinicians.

devices   object[]   

Assigned devices.

patients   object[]   

Assigned patients.

invitations   object[]   

User invitations.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

List of available participants for chat

requires authentication

Possible extend options:

Example request:
curl --request GET \
    --get "http://localhost:8000/api/chat/room/1/available-participants" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/chat/room/1/available-participants"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 299,
            "mrn": "8X62N2GQ1793726801",
            "name": "Aileen Gislason DVM",
            "email": "1793726801beer.marcia@example.net",
            "language": "en",
            "phone": "+15414484495",
            "phone_country": "MO",
            "phone_verified_at": null,
            "address1": "468 VonRueden Isle Suite 476",
            "address2": "Port Cindyport, SD 24636-5681",
            "postal_code": "51561",
            "city": "Morar Group",
            "country": "HU",
            "clinic_name": "Littelville",
            "clinic_location": "8918 Hermann Freeway Suite 478\nLake Lutherberg, WY 62835-6397",
            "image": null,
            "public_image": null,
            "mfa_enabled": 0,
            "mfa_method": null,
            "mfa_verified_to": null,
            "location_id": null,
            "created_by": null,
            "active": 1,
            "is_ambassador": 0,
            "test_account": false,
            "notifications_timezone": null,
            "notifications_at": null,
            "created_at": "2026-11-03T17:26:41.000000Z",
            "updated_at": "2026-11-03T17:26:41.000000Z",
            "invitation_status": null,
            "acadle_invitation_status": "accepted",
            "roles": [
                {
                    "id": 7,
                    "name": "AcadleUser"
                }
            ]
        },
        {
            "id": 300,
            "mrn": "F48NFL6V1793726801",
            "name": "Antone Ullrich II",
            "email": "1793726801tchamplin@example.net",
            "language": "en",
            "phone": "1-562-934-0712",
            "phone_country": "CI",
            "phone_verified_at": null,
            "address1": "4580 Brenda Rue",
            "address2": "West Vivianne, KS 49227",
            "postal_code": "40582-0509",
            "city": "Gerhold, Kessler and Zieme",
            "country": "RO",
            "clinic_name": "Bogisichtown",
            "clinic_location": "60434 Josh Knolls Apt. 185\nEast Coty, UT 72742",
            "image": null,
            "public_image": null,
            "mfa_enabled": 0,
            "mfa_method": null,
            "mfa_verified_to": null,
            "location_id": null,
            "created_by": null,
            "active": 1,
            "is_ambassador": 0,
            "test_account": false,
            "notifications_timezone": null,
            "notifications_at": null,
            "created_at": "2026-11-03T17:26:41.000000Z",
            "updated_at": "2026-11-03T17:26:41.000000Z",
            "invitation_status": null,
            "acadle_invitation_status": "accepted",
            "roles": [
                {
                    "id": 7,
                    "name": "AcadleUser"
                }
            ]
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to list available participants",
    "code": "CHAT:LIST_PARTICIPANTS:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Chat room not found):


{
    "message": "Chat room not found",
    "code": "CHAT:LIST_PARTICIPANTS:ROOM_NOT_FOUND"
}
 

Request   

GET api/chat/room/{id}/available-participants

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Chat room ID. Example: 1

Query Parameters

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

extend   string  optional  

Comma-separated list of relation extensions (available: roles, permissions).

Response

Response Fields

items   object   
id   integer   

User ID.

mrn   string   

Medical Record Number.

name   string   

User full name.

email   string   

User email address.

language   string   

User preferred language.

phone   string   

User phone number.

phone_country   string   

Phone country code.

phone_verified_at   string   

Phone verification date.

address1   string   

Address line 1.

address2   string   

Address line 2.

postal_code   string   

Postal code.

city   string   

City.

country   string   

Country code (ISO 3166 Alpha-2).

clinic_name   string   

Clinic name.

clinic_location   string   

Clinic location.

image   string   

User profile image URL.

mfa_enabled   boolean   

Whether MFA is enabled.

mfa_method   string   

Preferred MFA method.

mfa_verified_to   string   

MFA session verified until.

location_id   integer   

Location ID.

created_by   integer   

ID of the user who created this account.

active   boolean   

Whether the user account is active.

is_internal   boolean   

Super Admin only: Whether the user is an internal user (QA/ADP team).

notifications_timezone   string   

Timezone for notifications.

notifications_at   string   

Preferred notification time.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

invitation_status   string   

User invitation status.

acadle_invitation_status   string   

Acadle invitation status.

roles   object[]   

User roles.

id   integer   

Role ID.

name   string   

Role name.

permissions   object[]   

User permissions.

clinicians   object[]   

Assigned clinicians.

devices   object[]   

Assigned devices.

patients   object[]   

Assigned patients.

invitations   object[]   

User invitations.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

Community

Endpoints related to community videos

List community videos

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/community/videos?include_unpublished=" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/community/videos"
);

const params = {
    "include_unpublished": "0",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 1,
        "count": 1,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 1,
            "origin_region": "us",
            "request_id": "us-4428bf54-08a7-43ec-b312-54874c2ef5d8",
            "user_id": 1,
            "author_id": 2,
            "author_name": "Tom S.",
            "author_image": "https://example-bucket.s3.us-east-2.amazonaws.com/ambassadors/example.png",
            "video_url": "/videos/1215397088",
            "player_embed_url": "https://player.vimeo.com/video/1215397088?h=3bb25b045c",
            "thumbnail_url": "https://i.vimeocdn.com/video/2186556744-c0e3e0986946410a9a851a8b8acbc3be6c2b0e3b68ee0db18db107d0a1dd3204-d?region=us",
            "description": "community_videos.production.1",
            "translation_status": "SUCCESS",
            "processing_status": "ready",
            "published_at": "2026-07-31T12:59:20.000000Z",
            "created_at": "2026-07-31T12:59:20.000000Z",
            "updated_at": "2026-07-31T12:59:20.000000Z",
            "status": "published"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view community videos",
    "code": "COMMUNITY_VIDEOS:LIST:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/community/videos

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

include_unpublished   boolean  optional  

SuperAdmin/CommunityAdmin only: Include videos regardless of publish status (draft/uploading/processing/scheduled/unpublished/failed). Example: false

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

extend   string  optional  

Comma-separated list of relation extensions (available: categories, comments, reactions).

Get community video

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/community/videos/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/community/videos/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "id": 1,
    "origin_region": "us",
    "request_id": "us-4428bf54-08a7-43ec-b312-54874c2ef5d8",
    "user_id": 1,
    "author_id": 2,
    "author_name": "Tom S.",
    "author_image": "https://example-bucket.s3.us-east-2.amazonaws.com/ambassadors/example.png",
    "video_url": "/videos/1215397088",
    "player_embed_url": "https://player.vimeo.com/video/1215397088?h=3bb25b045c",
    "thumbnail_url": "https://i.vimeocdn.com/video/2186556744-c0e3e0986946410a9a851a8b8acbc3be6c2b0e3b68ee0db18db107d0a1dd3204-d?region=us",
    "description": "community_videos.production.1",
    "translation_status": "SUCCESS",
    "processing_status": "ready",
    "published_at": "2026-07-31T12:59:20.000000Z",
    "created_at": "2026-07-31T12:59:20.000000Z",
    "updated_at": "2026-07-31T12:59:20.000000Z",
    "status": "published"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view community videos",
    "code": "COMMUNITY_VIDEOS:GET:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Video not found):


{
    "message": "Community video not found",
    "code": "COMMUNITY_VIDEOS:GET:VIDEO_NOT_FOUND"
}
 

Request   

GET api/community/videos/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Community video ID. Example: 1

Query Parameters

extend   string  optional  

Comma-separated list of relation extensions (available: categories, comments, reactions).

Upload community video

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/community/videos" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: multipart/form-data" \
    --header "Accept: application/json" \
    --form "author_id=1"\
    --form "description=A short clip about adapting to a new prosthesis."\
    --form "video=@/tmp/phpJB2l4O" 
const url = new URL(
    "http://localhost:8000/api/community/videos"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "multipart/form-data",
    "Accept": "application/json",
};

const body = new FormData();
body.append('author_id', '1');
body.append('description', 'A short clip about adapting to a new prosthesis.');
body.append('video', document.querySelector('input[name="video"]').files[0]);

fetch(url, {
    method: "POST",
    headers,
    body,
}).then(response => response.json());

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage community videos",
    "code": "COMMUNITY_VIDEOS:UPLOAD:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/community/videos

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: multipart/form-data

Accept      

Example: application/json

Body Parameters

video   file   

Video file. Any format accepted by Vimeo, max 500MB. Must be a file. MAXIMUM:FILE_KB:512000. Example: /tmp/phpJB2l4O

author_id   integer   

ID of the community ambassador shown as the video author. This is the id field returned by GET /community/ambassadors (a shared, cross-region ambassador ID) — not a regional user ID. Example: 1

description   string  optional  

Video description (source text sent to SimpleLocalize for translation). Example: A short clip about adapting to a new prosthesis.

Update community video

requires authentication

Example request:
curl --request PUT \
    "http://localhost:8000/api/community/videos/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"author_id\": 1,
    \"description\": \"A short clip about adapting to a new prosthesis.\"
}"
const url = new URL(
    "http://localhost:8000/api/community/videos/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "author_id": 1,
    "description": "A short clip about adapting to a new prosthesis."
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (202):


{
    "id": 1,
    "origin_region": "us",
    "request_id": "us-4428bf54-08a7-43ec-b312-54874c2ef5d8",
    "user_id": 1,
    "author_id": 2,
    "author_name": "Tom S.",
    "author_image": "https://example-bucket.s3.us-east-2.amazonaws.com/ambassadors/example.png",
    "video_url": "/videos/1215397088",
    "player_embed_url": "https://player.vimeo.com/video/1215397088?h=3bb25b045c",
    "thumbnail_url": "https://i.vimeocdn.com/video/2186556744-c0e3e0986946410a9a851a8b8acbc3be6c2b0e3b68ee0db18db107d0a1dd3204-d?region=us",
    "description": "community_videos.production.us.1",
    "translation_status": "STARTED",
    "processing_status": "ready",
    "published_at": "2026-07-31T12:59:20.000000Z",
    "created_at": "2026-07-31T12:59:20.000000Z",
    "updated_at": "2026-07-31T12:59:20.000000Z",
    "status": "published"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage community videos",
    "code": "COMMUNITY_VIDEOS:UPDATE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Video not found):


{
    "message": "Community video not found",
    "code": "COMMUNITY_VIDEOS:UPDATE:VIDEO_NOT_FOUND"
}
 

Request   

PUT api/community/videos/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Community video ID. Example: 1

Body Parameters

author_id   integer  optional  

ID of the community ambassador shown as the video author. This is the id field returned by GET /community/ambassadors (a shared, cross-region ambassador ID) — not a regional user ID. Example: 1

description   string  optional  

Video description. Example: A short clip about adapting to a new prosthesis.

Publish community video

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/community/videos/1/publish" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"published_at\": \"2026-08-25 12:00:00\"
}"
const url = new URL(
    "http://localhost:8000/api/community/videos/1/publish"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "published_at": "2026-08-25 12:00:00"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "id": 1,
    "origin_region": "us",
    "request_id": "us-4428bf54-08a7-43ec-b312-54874c2ef5d8",
    "user_id": 1,
    "author_id": 2,
    "author_name": "Tom S.",
    "author_image": "https://example-bucket.s3.us-east-2.amazonaws.com/ambassadors/example.png",
    "video_url": "/videos/1215397088",
    "player_embed_url": "https://player.vimeo.com/video/1215397088?h=3bb25b045c",
    "thumbnail_url": "https://i.vimeocdn.com/video/2186556744-c0e3e0986946410a9a851a8b8acbc3be6c2b0e3b68ee0db18db107d0a1dd3204-d?region=us",
    "description": "community_videos.production.us.1",
    "translation_status": "SUCCESS",
    "processing_status": "ready",
    "published_at": "2026-07-31T12:59:20.000000Z",
    "created_at": "2026-07-31T12:59:20.000000Z",
    "updated_at": "2026-07-31T12:59:20.000000Z",
    "status": "published"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage community videos",
    "code": "COMMUNITY_VIDEOS:PUBLISH:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Video not found):


{
    "message": "Community video not found",
    "code": "COMMUNITY_VIDEOS:PUBLISH:VIDEO_NOT_FOUND"
}
 

Request   

POST api/community/videos/{id}/publish

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Community video ID. Example: 1

Body Parameters

published_at   string  optional  

Date and time to publish the video at. A future date schedules the video. If not present, the video is published immediately. MUST_BE_DATE. Example: 2026-08-25 12:00:00

Unpublish community video

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/community/videos/1/unpublish" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/community/videos/1/unpublish"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200):


{
    "id": 1,
    "origin_region": "us",
    "request_id": "us-4428bf54-08a7-43ec-b312-54874c2ef5d8",
    "user_id": 1,
    "author_id": 2,
    "author_name": "Tom S.",
    "author_image": "https://example-bucket.s3.us-east-2.amazonaws.com/ambassadors/example.png",
    "video_url": "/videos/1215397088",
    "player_embed_url": "https://player.vimeo.com/video/1215397088?h=3bb25b045c",
    "thumbnail_url": "https://i.vimeocdn.com/video/2186556744-c0e3e0986946410a9a851a8b8acbc3be6c2b0e3b68ee0db18db107d0a1dd3204-d?region=us",
    "description": "community_videos.production.us.1",
    "translation_status": "SUCCESS",
    "processing_status": "ready",
    "published_at": "9999-12-31T23:59:59.000000Z",
    "created_at": "2026-07-31T12:59:20.000000Z",
    "updated_at": "2026-07-31T12:59:20.000000Z",
    "status": "unpublished"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage community videos",
    "code": "COMMUNITY_VIDEOS:UNPUBLISH:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Video not found):


{
    "message": "Community video not found",
    "code": "COMMUNITY_VIDEOS:UNPUBLISH:VIDEO_NOT_FOUND"
}
 

Request   

POST api/community/videos/{id}/unpublish

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Community video ID. Example: 1

React to community video

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/community/videos/1/react" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/community/videos/1/react"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200, Current reaction state):


{
    "status": true
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to react to community videos",
    "code": "COMMUNITY_VIDEOS:REACT:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Video not found):


{
    "message": "Community video not found",
    "code": "COMMUNITY_VIDEOS:REACT:VIDEO_NOT_FOUND"
}
 

Request   

POST api/community/videos/{id}/react

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Community video ID. Example: 1

Update community video categories

requires authentication

Example request:
curl --request PUT \
    "http://localhost:8000/api/community/videos/1/categories" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"categories\": [
        1,
        2
    ]
}"
const url = new URL(
    "http://localhost:8000/api/community/videos/1/categories"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "categories": [
        1,
        2
    ]
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "id": 1,
    "origin_region": "us",
    "request_id": "us-4428bf54-08a7-43ec-b312-54874c2ef5d8",
    "user_id": 1,
    "author_id": 2,
    "author_name": "Tom S.",
    "author_image": "https://example-bucket.s3.us-east-2.amazonaws.com/ambassadors/example.png",
    "video_url": "/videos/1215397088",
    "player_embed_url": "https://player.vimeo.com/video/1215397088?h=3bb25b045c",
    "thumbnail_url": "https://i.vimeocdn.com/video/2186556744-c0e3e0986946410a9a851a8b8acbc3be6c2b0e3b68ee0db18db107d0a1dd3204-d?region=us",
    "description": "community_videos.production.1",
    "translation_status": "SUCCESS",
    "processing_status": "ready",
    "published_at": "2026-07-31T12:59:20.000000Z",
    "created_at": "2026-07-31T12:59:20.000000Z",
    "updated_at": "2026-07-31T12:59:20.000000Z",
    "status": "published",
    "categories": [
        {
            "id": 1,
            "name": "community_categories.production.1",
            "is_archived": false,
            "translation_status": "SUCCESS",
            "created_at": "2026-07-31T12:59:20.000000Z",
            "updated_at": "2026-07-31T12:59:20.000000Z",
            "thumbnail_url": "https://i.vimeocdn.com/video/2186556744-c0e3e0986946410a9a851a8b8acbc3be6c2b0e3b68ee0db18db107d0a1dd3204-d?region=us"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage community videos",
    "code": "COMMUNITY_VIDEOS:UPDATE_CATEGORIES:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Video not found):


{
    "message": "Community video not found",
    "code": "COMMUNITY_VIDEOS:UPDATE_CATEGORIES:VIDEO_NOT_FOUND"
}
 

Request   

PUT api/community/videos/{id}/categories

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Community video ID. Example: 1

Body Parameters

categories   integer[]   

Full list of category IDs to assign to the video (max 2). Replaces the existing set entirely — send the complete desired list, not just additions. An empty array removes all categories from the video.

List community categories

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/community/categories" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/community/categories"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 1,
        "count": 1,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 1,
            "name": "community_categories.production.us.1",
            "is_archived": false,
            "translation_status": "SUCCESS",
            "created_at": "2026-07-31T12:59:20.000000Z",
            "updated_at": "2026-07-31T12:59:20.000000Z",
            "thumbnail_url": "https://i.vimeocdn.com/video/2186556744-c0e3e0986946410a9a851a8b8acbc3be6c2b0e3b68ee0db18db107d0a1dd3204-d_540x960?region=us"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view community videos",
    "code": "COMMUNITY_CATEGORIES:LIST:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/community/categories

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

Create community category

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/community/categories" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Sports\"
}"
const url = new URL(
    "http://localhost:8000/api/community/categories"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Sports"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 1,
    "name": "community_categories.production.us.1",
    "is_archived": false,
    "translation_status": "STARTED",
    "created_at": "2026-07-31T12:59:20.000000Z",
    "updated_at": "2026-07-31T12:59:20.000000Z",
    "thumbnail_url": null
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage community videos",
    "code": "COMMUNITY_CATEGORIES:CREATE:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/community/categories

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

name   string   

Category name. Example: Sports

Update community category

requires authentication

Example request:
curl --request PUT \
    "http://localhost:8000/api/community/categories/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Sports\"
}"
const url = new URL(
    "http://localhost:8000/api/community/categories/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Sports"
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (202):


{
    "id": 1,
    "name": "community_categories.production.us.1",
    "is_archived": false,
    "translation_status": "STARTED",
    "created_at": "2026-07-31T12:59:20.000000Z",
    "updated_at": "2026-08-14T09:00:00.000000Z",
    "thumbnail_url": "https://i.vimeocdn.com/video/2186556744-c0e3e0986946410a9a851a8b8acbc3be6c2b0e3b68ee0db18db107d0a1dd3204-d_540x960?region=us"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage community videos",
    "code": "COMMUNITY_CATEGORIES:UPDATE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Category not found):


{
    "message": "Community category not found",
    "code": "COMMUNITY_CATEGORIES:UPDATE:CATEGORY_NOT_FOUND"
}
 

Request   

PUT api/community/categories/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Community category ID. Example: 1

Body Parameters

name   string  optional  

Category name. Example: Sports

Archive community category

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/community/categories/1/archive" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/community/categories/1/archive"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200):


{
    "id": 1,
    "name": "community_categories.production.us.1",
    "is_archived": true,
    "translation_status": "SUCCESS",
    "created_at": "2026-07-31T12:59:20.000000Z",
    "updated_at": "2026-08-14T09:00:00.000000Z",
    "thumbnail_url": "https://i.vimeocdn.com/video/2186556744-c0e3e0986946410a9a851a8b8acbc3be6c2b0e3b68ee0db18db107d0a1dd3204-d_540x960?region=us"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage community videos",
    "code": "COMMUNITY_CATEGORIES:ARCHIVE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Category not found):


{
    "message": "Community category not found",
    "code": "COMMUNITY_CATEGORIES:ARCHIVE:CATEGORY_NOT_FOUND"
}
 

Request   

POST api/community/categories/{id}/archive

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Community category ID. Example: 1

Unarchive community category

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/community/categories/1/unarchive" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/community/categories/1/unarchive"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200):


{
    "id": 1,
    "name": "community_categories.production.us.1",
    "is_archived": false,
    "translation_status": "SUCCESS",
    "created_at": "2026-07-31T12:59:20.000000Z",
    "updated_at": "2026-08-14T09:00:00.000000Z",
    "thumbnail_url": "https://i.vimeocdn.com/video/2186556744-c0e3e0986946410a9a851a8b8acbc3be6c2b0e3b68ee0db18db107d0a1dd3204-d_540x960?region=us"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage community videos",
    "code": "COMMUNITY_CATEGORIES:UNARCHIVE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Category not found):


{
    "message": "Community category not found",
    "code": "COMMUNITY_CATEGORIES:UNARCHIVE:CATEGORY_NOT_FOUND"
}
 

Request   

POST api/community/categories/{id}/unarchive

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Community category ID. Example: 1

List community category suggestions

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/community/categories/suggestions" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/community/categories/suggestions"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 1,
        "count": 1,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 1,
            "origin_region": "us",
            "request_id": "us-4428bf54-08a7-43ec-b312-54874c2ef5d8",
            "user_id": 1,
            "search_term": "Swimming",
            "created_at": "2026-08-14T09:00:00.000000Z",
            "updated_at": "2026-08-14T09:00:00.000000Z"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage community videos",
    "code": "COMMUNITY_CATEGORY_SUGGESTIONS:LIST:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/community/categories/suggestions

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

Create community category suggestion

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/community/categories/suggestions" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"search_term\": \"Swimming\"
}"
const url = new URL(
    "http://localhost:8000/api/community/categories/suggestions"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "search_term": "Swimming"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 1,
    "origin_region": "us",
    "request_id": "us-4428bf54-08a7-43ec-b312-54874c2ef5d8",
    "user_id": 1,
    "search_term": "Swimming",
    "created_at": "2026-08-14T09:00:00.000000Z",
    "updated_at": "2026-08-14T09:00:00.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to suggest community categories",
    "code": "COMMUNITY_CATEGORY_SUGGESTIONS:CREATE:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/community/categories/suggestions

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

search_term   string   

Category name the patient searched for. Example: Swimming

List all community comments

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/community/comments?status[]=pending&status[]=rejected" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/community/comments"
);

const params = {
    "status[0]": "pending",
    "status[1]": "rejected",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 3,
        "count": 3,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 1,
            "video_id": 1,
            "parent_comment_id": null,
            "display_name": "John D.",
            "is_ambassador": false,
            "content": "Great video, thank you!",
            "status": "approved",
            "rejection_reason": null,
            "created_at": "2026-07-31T12:59:20.000000Z",
            "updated_at": "2026-07-31T12:59:20.000000Z"
        },
        {
            "id": 2,
            "video_id": 2,
            "parent_comment_id": null,
            "display_name": "Anna K.",
            "is_ambassador": false,
            "content": "How long did it take you to get used to the prosthesis?",
            "status": "pending",
            "rejection_reason": null,
            "created_at": "2026-08-05T09:12:00.000000Z",
            "updated_at": "2026-08-05T09:12:00.000000Z"
        },
        {
            "id": 5,
            "video_id": 1,
            "parent_comment_id": 1,
            "display_name": "John D.",
            "is_ambassador": false,
            "content": "Great video, thank you so much!",
            "status": "pending",
            "rejection_reason": null,
            "created_at": "2026-09-10T14:30:00.000000Z",
            "updated_at": "2026-09-10T14:30:00.000000Z"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage community videos",
    "code": "COMMUNITY_COMMENTS:LIST_ALL:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/community/comments

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

status   string[]  optional  

Filter comments by status. Provide array of values (status[]=pending&status[]=rejected). Omit entirely to return comments with all statuses (pending, approved, rejected).

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

sortby   string  optional  

Sort by field (available: date). Default: date, asc.

sortdir   string  optional  

Sort direction (available: asc, desc).

List community comments

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/community/videos/1/comments" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/community/videos/1/comments"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 1,
        "count": 1,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 1,
            "video_id": 1,
            "parent_comment_id": null,
            "display_name": "John D.",
            "is_ambassador": false,
            "content": "Great video, thank you!",
            "status": "approved",
            "rejection_reason": null,
            "created_at": "2026-07-31T12:59:20.000000Z",
            "updated_at": "2026-07-31T12:59:20.000000Z"
        }
    ],
    "items_pending": [],
    "items_rejected": []
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to comment on community videos",
    "code": "COMMUNITY_COMMENTS:LIST:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/community/videos/{id}/comments

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Community video ID. Example: 1

Query Parameters

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

sortby   string  optional  

Sort by field (available: date). Default: date, asc.

sortdir   string  optional  

Sort direction (available: asc, desc).

Create community comment

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/community/videos/1/comments" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"content\": \"Great video, thank you!\"
}"
const url = new URL(
    "http://localhost:8000/api/community/videos/1/comments"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "content": "Great video, thank you!"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 1,
    "video_id": 1,
    "parent_comment_id": null,
    "display_name": "John D.",
    "is_ambassador": false,
    "content": "Great video, thank you!",
    "status": "pending",
    "rejection_reason": null,
    "created_at": "2026-07-31T12:59:20.000000Z",
    "updated_at": "2026-07-31T12:59:20.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to comment on community videos",
    "code": "COMMUNITY_COMMENTS:CREATE:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/community/videos/{id}/comments

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Community video ID. Example: 1

Body Parameters

content   string   

Comment content. Example: Great video, thank you!

Update community comment

requires authentication

Example request:
curl --request PUT \
    "http://localhost:8000/api/community/videos/1/comments/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"content\": \"Great video, thank you!\"
}"
const url = new URL(
    "http://localhost:8000/api/community/videos/1/comments/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "content": "Great video, thank you!"
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (202):


{
    "id": 2,
    "video_id": 1,
    "parent_comment_id": 1,
    "display_name": "John D.",
    "is_ambassador": false,
    "content": "Great video, thank you! (edited)",
    "status": "pending",
    "rejection_reason": null,
    "created_at": "2026-07-31T12:59:20.000000Z",
    "updated_at": "2026-07-31T12:59:20.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to comment on community videos",
    "code": "COMMUNITY_COMMENTS:UPDATE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Comment not found):


{
    "message": "Community comment not found",
    "code": "COMMUNITY_COMMENTS:UPDATE:COMMENT_NOT_FOUND"
}
 

Request   

PUT api/community/videos/{id}/comments/{commentId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Community video ID. Example: 1

commentId   integer   

Community comment ID. Example: 1

Body Parameters

content   string   

Comment content. Example: Great video, thank you!

Delete community comment

requires authentication

Example request:
curl --request DELETE \
    "http://localhost:8000/api/community/videos/1/comments/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/community/videos/1/comments/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (200):


{
    "message": "Community comment deleted"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to comment on community videos",
    "code": "COMMUNITY_COMMENTS:DELETE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Comment not found):


{
    "message": "Community comment not found",
    "code": "COMMUNITY_COMMENTS:DELETE:COMMENT_NOT_FOUND"
}
 

Request   

DELETE api/community/videos/{id}/comments/{commentId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Community video ID. Example: 1

commentId   integer   

Community comment ID. Example: 1

Approve community comment

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/community/videos/1/comments/1/approve" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/community/videos/1/comments/1/approve"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200):


{
    "id": 1,
    "video_id": 1,
    "parent_comment_id": null,
    "display_name": "John D.",
    "is_ambassador": false,
    "content": "Great video, thank you!",
    "status": "approved",
    "rejection_reason": null,
    "created_at": "2026-07-31T12:59:20.000000Z",
    "updated_at": "2026-07-31T12:59:20.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage community videos",
    "code": "COMMUNITY_COMMENTS:APPROVE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Comment not found):


{
    "message": "Community comment not found",
    "code": "COMMUNITY_COMMENTS:APPROVE:COMMENT_NOT_FOUND"
}
 

Request   

POST api/community/videos/{id}/comments/{commentId}/approve

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Community video ID. Example: 1

commentId   integer   

Community comment ID. Example: 1

Reject community comment

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/community/videos/1/comments/1/reject" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"reason\": \"Contains inappropriate language.\"
}"
const url = new URL(
    "http://localhost:8000/api/community/videos/1/comments/1/reject"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "reason": "Contains inappropriate language."
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "id": 1,
    "video_id": 1,
    "parent_comment_id": null,
    "display_name": "John D.",
    "is_ambassador": false,
    "content": "Great video, thank you!",
    "status": "rejected",
    "rejection_reason": "Contains inappropriate language.",
    "created_at": "2026-07-31T12:59:20.000000Z",
    "updated_at": "2026-07-31T12:59:20.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage community videos",
    "code": "COMMUNITY_COMMENTS:REJECT:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Comment not found):


{
    "message": "Community comment not found",
    "code": "COMMUNITY_COMMENTS:REJECT:COMMENT_NOT_FOUND"
}
 

Request   

POST api/community/videos/{id}/comments/{commentId}/reject

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Community video ID. Example: 1

commentId   integer   

Community comment ID. Example: 1

Body Parameters

reason   string   

Reason for rejecting the comment. Example: Contains inappropriate language.

Get community statistics

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/community/statistics?date_from=2026-08-10&date_to=2026-08-14" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/community/statistics"
);

const params = {
    "date_from": "2026-08-10",
    "date_to": "2026-08-14",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "date_from": "2026-08-10",
    "date_to": "2026-08-14",
    "engaged_users_count": 42,
    "content_resonance_percent": 63.5,
    "push_uploads_percent": 27.3
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage community videos",
    "code": "COMMUNITY_STATISTICS:GET:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/community/statistics

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

date_from   string  optional  

Start date (Y-m-d). Defaults to the start of the current ISO week (UTC). Example: 2026-08-10

date_to   string  optional  

End date (Y-m-d). Defaults to now. Example: 2026-08-14

Record community tab open

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/community/statistics/tab-open" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/community/statistics/tab-open"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200):


{
    "message": "Community statistic recorded",
    "code": "COMMUNITY_STATISTICS:RECORD:SUCCESS"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view community videos",
    "code": "COMMUNITY_STATISTICS:TAB_OPEN:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/community/statistics/tab-open

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

List community ambassadors

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/community/ambassadors" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/community/ambassadors"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 1,
        "count": 1,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 1,
            "origin_region": "us",
            "user_id": 1,
            "name": "Tom S.",
            "public_image": "https://example-bucket.s3.us-east-2.amazonaws.com/ambassadors/example.png"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage community videos",
    "code": "COMMUNITY_AMBASSADORS:LIST:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/community/ambassadors

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

Upload ambassador public image

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/community/ambassadors/1/public-image" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: multipart/form-data" \
    --header "Accept: application/json" \
    --form "public_image=@/tmp/phpWAy6Rz" 
const url = new URL(
    "http://localhost:8000/api/community/ambassadors/1/public-image"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "multipart/form-data",
    "Accept": "application/json",
};

const body = new FormData();
body.append('public_image', document.querySelector('input[name="public_image"]').files[0]);

fetch(url, {
    method: "POST",
    headers,
    body,
}).then(response => response.json());

Example response (200):


{
    "id": 331,
    "name": "Aniyah Brakus",
    "public_image": null
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage community videos",
    "code": "COMMUNITY_AMBASSADORS:UPLOAD_PUBLIC_IMAGE:INSUFFICIENT_PERMISSION"
}
 

Example response (403, User is not an ambassador):


{
    "message": "This user is not a community ambassador",
    "code": "COMMUNITY_AMBASSADORS:UPLOAD_PUBLIC_IMAGE:NOT_AN_AMBASSADOR"
}
 

Example response (404, User not found):


{
    "message": "User not found",
    "code": "COMMUNITY_AMBASSADORS:UPLOAD_PUBLIC_IMAGE:USER_NOT_FOUND"
}
 

Request   

POST api/community/ambassadors/{id}/public-image

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: multipart/form-data

Accept      

Example: application/json

URL Parameters

id   integer   

Ambassador's regional user ID (only works for ambassadors native to this region — not the shared ambassador id used as a video's author_id). Example: 1

Body Parameters

public_image   file   

Public avatar image file. MUST_BE_IMAGE MAXIMUM:FILE_KB:5120. Example: /tmp/phpWAy6Rz

Response

Response Fields

id   integer   

User ID.

mrn   string   

Medical Record Number.

name   string   

User full name.

email   string   

User email address.

language   string   

User preferred language.

phone   string   

User phone number.

phone_country   string   

Phone country code.

phone_verified_at   string   

Phone verification date.

address1   string   

Address line 1.

address2   string   

Address line 2.

postal_code   string   

Postal code.

city   string   

City.

country   string   

Country code (ISO 3166 Alpha-2).

clinic_name   string   

Clinic name.

clinic_location   string   

Clinic location.

image   string   

User profile image URL.

mfa_enabled   boolean   

Whether MFA is enabled.

mfa_method   string   

Preferred MFA method.

mfa_verified_to   string   

MFA session verified until.

location_id   integer   

Location ID.

created_by   integer   

ID of the user who created this account.

active   boolean   

Whether the user account is active.

is_internal   boolean   

Super Admin only: Whether the user is an internal user (QA/ADP team).

notifications_timezone   string   

Timezone for notifications.

notifications_at   string   

Preferred notification time.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

invitation_status   string   

User invitation status.

acadle_invitation_status   string   

Acadle invitation status.

roles   object[]   

User roles.

id   integer   

Role ID.

name   string   

Role name.

permissions   object[]   

User permissions.

clinicians   object[]   

Assigned clinicians.

devices   object[]   

Assigned devices.

patients   object[]   

Assigned patients.

invitations   object[]   

User invitations.

Config

API endpoints for device config management

Get device config

requires authentication

Definitions:

Example request:
curl --request GET \
    --get "http://localhost:8000/api/device/1/config?_format=et" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/device/1/config"
);

const params = {
    "_format": "et",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, Normal/compact response):


{
    "common": {
        "gripPairsConfig": [
            1,
            4,
            2,
            3,
            6,
            7,
            9,
            8
        ],
        "controlConfig": [
            0,
            1,
            0,
            0,
            0
        ],
        "emgThresholds": [
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0
        ],
        "interval": [
            100
        ],
        "gripSequentialConfig": [
            1,
            2,
            4,
            3,
            0,
            255,
            6,
            7,
            9,
            8,
            255,
            255
        ]
    },
    "modes": [
        {
            "id": 100,
            "name": "Mode 1",
            "slot": 0,
            "config": {
                "interval": [
                    300
                ],
                "fingerStrength": [
                    1,
                    100
                ],
                "autoGrasp": [
                    0,
                    100
                ],
                "emgSpike": [
                    0,
                    300
                ]
            }
        },
        {
            "id": 101,
            "name": "Mode 2",
            "slot": 1,
            "config": {
                "interval": [
                    400
                ],
                "fingerStrength": [
                    1,
                    100
                ],
                "autoGrasp": [
                    0,
                    100
                ],
                "emgSpike": [
                    0,
                    300
                ]
            }
        },
        {
            "id": 102,
            "name": "Mode 3",
            "slot": 2,
            "config": {
                "interval": [
                    500
                ],
                "fingerStrength": [
                    1,
                    100
                ],
                "autoGrasp": [
                    0,
                    100
                ],
                "emgSpike": [
                    0,
                    300
                ]
            }
        }
    ]
}
 

Example response (200):


[
    {
        "id": 1,
        "device_id": 22,
        "mode_id": 1,
        "key": "commodi",
        "value": "dolor",
        "created_at": "2026-11-03T17:24:25.000000Z",
        "updated_at": "2026-11-03T17:24:25.000000Z",
        "mode": {
            "id": 1,
            "device_id": 23,
            "slot": null,
            "name": "Quia quia totam repellendus occaecati qui nulla et.",
            "active": 1,
            "created_at": "2026-11-03T17:24:25.000000Z",
            "updated_at": "2026-11-03T17:24:25.000000Z"
        }
    },
    {
        "id": 2,
        "device_id": 24,
        "mode_id": 2,
        "key": "esse",
        "value": "aut",
        "created_at": "2026-11-03T17:24:25.000000Z",
        "updated_at": "2026-11-03T17:24:25.000000Z",
        "mode": {
            "id": 2,
            "device_id": 25,
            "slot": null,
            "name": "Voluptatum est aut iste laudantium vel.",
            "active": 1,
            "created_at": "2026-11-03T17:24:25.000000Z",
            "updated_at": "2026-11-03T17:24:25.000000Z"
        }
    }
]
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view device config",
    "code": "CONFIG:GET:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "CONFIG:GET:DEVICE_NOT_FOUND"
}
 

Request   

GET api/device/{deviceId}/config

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

deviceId   integer   

Device ID. Example: 1

Query Parameters

_format   string  optional  

Config format. Pass collection to receive config as resource collection. Example: et

Response

Response Fields

id   integer   

Config entry ID.

device_id   integer   

Associated device ID.

mode_id   integer   

Associated config mode ID.

key   string   

Config key.

value   string   

Config value.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

mode   object   

Associated config mode.

Update device config

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/device/1/config" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Remote session 2022-05-30\",
    \"common\": \"{\\\"gripPairsConfig\\\": [1, 4, 2, 3, 6, 7, 9, 8], \\\"controlConfig\\\": [0, 1, 0, 0, 0], \\\"gripSequentialConfig\\\": [1, 2, 4, 3, 0, 255, 6, 7, 9, 8, 255, 255]\",
    \"updateNote\": true,
    \"source\": \"guide\",
    \"modes\": [
        {
            \"id\": 1,
            \"config\": \"{\\\"gripPairsConfig\\\": [1, 4, 2, 3, 6, 7, 9, 8], \\\"controlConfig\\\": [0, 1, 0, 0, 0], \\\"gripSequentialConfig\\\": [1, 2, 4, 3, 0, 255, 6, 7, 9, 8, 255, 255]\"
        }
    ]
}"
const url = new URL(
    "http://localhost:8000/api/device/1/config"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Remote session 2022-05-30",
    "common": "{\"gripPairsConfig\": [1, 4, 2, 3, 6, 7, 9, 8], \"controlConfig\": [0, 1, 0, 0, 0], \"gripSequentialConfig\": [1, 2, 4, 3, 0, 255, 6, 7, 9, 8, 255, 255]",
    "updateNote": true,
    "source": "guide",
    "modes": [
        {
            "id": 1,
            "config": "{\"gripPairsConfig\": [1, 4, 2, 3, 6, 7, 9, 8], \"controlConfig\": [0, 1, 0, 0, 0], \"gripSequentialConfig\": [1, 2, 4, 3, 0, 255, 6, 7, 9, 8, 255, 255]"
        }
    ]
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, OK):


{
    "common": {
        "gripPairsConfig": [
            1,
            4,
            2,
            3,
            6,
            7,
            9,
            8
        ],
        "controlConfig": [
            0,
            1,
            0,
            0,
            0
        ],
        "emgThresholds": [
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0
        ],
        "interval": [
            100
        ],
        "gripSequentialConfig": [
            1,
            2,
            4,
            3,
            0,
            255,
            6,
            7,
            9,
            8,
            255,
            255
        ]
    },
    "modes": [
        {
            "id": 100,
            "name": "Mode 1",
            "slot": 0,
            "config": {
                "interval": [
                    300
                ],
                "fingerStrength": [
                    1,
                    100
                ],
                "autoGrasp": [
                    0,
                    100
                ],
                "emgSpike": [
                    0,
                    300
                ]
            }
        },
        {
            "id": 101,
            "name": "Mode 2",
            "slot": 1,
            "config": {
                "interval": [
                    400
                ],
                "fingerStrength": [
                    1,
                    100
                ],
                "autoGrasp": [
                    0,
                    100
                ],
                "emgSpike": [
                    0,
                    300
                ]
            }
        },
        {
            "id": 102,
            "name": "Mode 3",
            "slot": 2,
            "config": {
                "interval": [
                    500
                ],
                "fingerStrength": [
                    1,
                    100
                ],
                "autoGrasp": [
                    0,
                    100
                ],
                "emgSpike": [
                    0,
                    300
                ]
            }
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to update device config",
    "code": "CONFIG:UPDATE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "CONFIG:UPDATE:DEVICE_NOT_FOUND"
}
 

Example response (422, Invalid config):


{
    "message": "Config has some problems and cannot be saved.",
    "errors": {
        "modes": {
            "mode_3": "Config mode 3 does not belong to device 12."
        },
        "values": {
            "common.inputSite": "Invalid value [\"11\"] for key inputSite - contains string values.",
            "common.gripsPositions.1.initial": "Invalid value [200,\"100\",\"100\",\"100\",\"100\"] for key gripsPositions.1.initial - contains string values.",
            "mode_1.inputSite": "Invalid value [\"11\"] for key inputSite - contains string values.",
            "mode_1.gripsPositions.0.initial": "Invalid value [\"200\",\"100\",\"100\",\"100\",\"100\"] for key gripsPositions.1.initial - contains string values."
        }
    },
    "code": "CONFIG:UPDATE:INVALID_CONFIG"
}
 

Request   

POST api/device/{deviceId}/config

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

deviceId   integer   

Device ID. Example: 1

Body Parameters

name   string  optional  

Config history entry name (session name). Example: Remote session 2022-05-30

common   string  optional  

Common config as JSON string. MUST_BE_JSON. Example: {"gripPairsConfig": [1, 4, 2, 3, 6, 7, 9, 8], "controlConfig": [0, 1, 0, 0, 0], "gripSequentialConfig": [1, 2, 4, 3, 0, 255, 6, 7, 9, 8, 255, 255]

updateNote   boolean  optional  

Add config history note with content equal to name parameter. Example: true

source   string  optional  

Where this config save was initiated from. Defaults to configurator when not present. Example: guide

Must be one of:
  • configurator
  • guide
  • mobile
modes   object[]  optional  
id   string  optional  

Config mode ID. The id of an existing record in the App\Models\ConfigMode table. Example: 1

config   string  optional  

Config specific for mode as JSON string. MUST_BE_JSON. Example: {"gripPairsConfig": [1, 4, 2, 3, 6, 7, 9, 8], "controlConfig": [0, 1, 0, 0, 0], "gripSequentialConfig": [1, 2, 4, 3, 0, 255, 6, 7, 9, 8, 255, 255]

Get device config history

requires authentication

For amputees only restore points are returned.

Example request:
curl --request GET \
    --get "http://localhost:8000/api/device/1/config/history?restore_point=1&factory_reset_point=1&date_from=1642003200&date_to=1642003200" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"restore_point\": true,
    \"factory_reset_point\": true
}"
const url = new URL(
    "http://localhost:8000/api/device/1/config/history"
);

const params = {
    "restore_point": "1",
    "factory_reset_point": "1",
    "date_from": "1642003200",
    "date_to": "1642003200",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "restore_point": true,
    "factory_reset_point": true
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, Normal/compact response):


{
    "common": {
        "gripPairsConfig": [
            1,
            4,
            2,
            3,
            6,
            7,
            9,
            8
        ],
        "controlConfig": [
            0,
            1,
            0,
            0,
            0
        ],
        "emgThresholds": [
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0
        ],
        "interval": [
            100
        ],
        "gripSequentialConfig": [
            1,
            2,
            4,
            3,
            0,
            255,
            6,
            7,
            9,
            8,
            255,
            255
        ]
    },
    "modes": [
        {
            "id": 100,
            "name": "Mode 1",
            "slot": 0,
            "config": {
                "interval": [
                    300
                ],
                "fingerStrength": [
                    1,
                    100
                ],
                "autoGrasp": [
                    0,
                    100
                ],
                "emgSpike": [
                    0,
                    300
                ]
            }
        },
        {
            "id": 101,
            "name": "Mode 2",
            "slot": 1,
            "config": {
                "interval": [
                    400
                ],
                "fingerStrength": [
                    1,
                    100
                ],
                "autoGrasp": [
                    0,
                    100
                ],
                "emgSpike": [
                    0,
                    300
                ]
            }
        },
        {
            "id": 102,
            "name": "Mode 3",
            "slot": 2,
            "config": {
                "interval": [
                    500
                ],
                "fingerStrength": [
                    1,
                    100
                ],
                "autoGrasp": [
                    0,
                    100
                ],
                "emgSpike": [
                    0,
                    300
                ]
            }
        }
    ]
}
 

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 1,
            "user_id": null,
            "device_id": 26,
            "index": null,
            "name": "Ut sequi voluptas pariatur sit ratione qui odit.",
            "config": "{\"common\":{\"fingerStrength\":[1,100],\"gripPositions\":{\"_\":0,\"0\":{\"initial\":[54,18,34,42,15],\"limit\":[66,46,83,80,76]},\"1\":{\"initial\":[67,31,22,11,49],\"limit\":[73,48,45,54,56]},\"2\":{\"initial\":[35,31,2,33,6],\"limit\":[81,54,52,37,13]},\"3\":{\"initial\":[27,22,63,17,12],\"limit\":[45,87,77,67,52]},\"4\":{\"initial\":[26,41,39,50,5],\"limit\":[33,74,43,92,73]},\"5\":{\"initial\":[67,42,7,32,15],\"limit\":[95,48,21,58,88]},\"6\":{\"initial\":[89,27,64,14,21],\"limit\":[94,74,91,44,44]},\"7\":{\"initial\":[10,18,9,45,38],\"limit\":[81,44,39,61,71]},\"8\":{\"initial\":[41,25,38,77,2],\"limit\":[75,75,71,81,31]},\"9\":{\"initial\":[2,30,26,78,32],\"limit\":[41,41,89,87,65]},\"10\":{\"initial\":[13,8,14,45,40],\"limit\":[47,76,40,54,86]},\"11\":{\"initial\":[87,1,18,32,32],\"limit\":[88,80,47,77,47]},\"12\":{\"initial\":[38,24,30,47,78],\"limit\":[53,45,59,61,78]},\"13\":{\"initial\":[79,46,12,39,27],\"limit\":[88,68,31,59,81]}},\"inputSite\":[1]},\"modes\":[{\"id\":3,\"name\":\"Vel vel rerum qui in ut.\",\"slot\":0,\"config\":{\"autoGrasp\":[0,100],\"coContractionTimings\":[400,100],\"controlMode\":[0],\"emgGains\":[100,100],\"emgSpike\":[1,300],\"emgThresholds\":[0,70,60,10,40,0,50,20,90,40],\"gripPairsConfig\":[5,3,11,12,13,8,4,7],\"gripSequentialConfig\":[4,3,6,10,2,5,1,13,11,12,9,255],\"gripSwitchingMode\":[2],\"holdOpen\":[1500,2000],\"pulseTimings\":[150,690,790,510],\"softGrip\":[0],\"speedControlStrategy\":[1]}},{\"id\":4,\"name\":\"Et quas praesentium quis.\",\"slot\":1,\"config\":{\"autoGrasp\":[0,100],\"coContractionTimings\":[400,300],\"controlMode\":[1],\"emgGains\":[100,100],\"emgSpike\":[0,300],\"emgThresholds\":[90,80,70,30,60,50,70,80,70,80],\"gripPairsConfig\":[10,3,2,11,7,12,13,1],\"gripSequentialConfig\":[11,1,4,10,255,9,255,255,255,13,255,255],\"gripSwitchingMode\":[2],\"holdOpen\":[1500,2500],\"pulseTimings\":[80,450,700,560],\"softGrip\":[1],\"speedControlStrategy\":[1]}},{\"id\":5,\"name\":\"Quis provident itaque debitis perferendis est.\",\"slot\":2,\"config\":{\"autoGrasp\":[0,100],\"coContractionTimings\":[400,300],\"controlMode\":[0],\"emgGains\":[100,100],\"emgSpike\":[1,300],\"emgThresholds\":[80,10,0,70,40,60,70,80,50,70],\"gripPairsConfig\":[5,10,6,3,8,12,11,4],\"gripSequentialConfig\":[4,12,13,255,255,1,255,8,6,255,255,9],\"gripSwitchingMode\":[3],\"holdOpen\":[1500,2500],\"pulseTimings\":[850,530,120,360],\"softGrip\":[1],\"speedControlStrategy\":[0]}}]}",
            "restore_point": 0,
            "factory_reset_point": 0,
            "source": "configurator",
            "changed_by": 50,
            "firmware_version_id": null,
            "created_at": "2026-11-03T17:24:26.000000Z",
            "updated_at": "2026-11-03T17:24:26.000000Z",
            "author": {
                "id": 50,
                "mrn": "M9GQ5W861793726665",
                "name": "Art Wisoky",
                "email": "1793726665luz66@example.net",
                "language": "en",
                "phone": "(859) 333-1644",
                "phone_country": "BW",
                "phone_verified_at": null,
                "address1": "981 Telly Estates",
                "address2": "Ruthport, TX 74271-8363",
                "postal_code": "59442",
                "city": "Ratke Ltd",
                "country": "EE",
                "clinic_name": "Gloverborough",
                "clinic_location": "180 Felicity Path Suite 707\nEast Lincolnfort, GA 09304",
                "image": null,
                "public_image": null,
                "mfa_enabled": 0,
                "mfa_method": null,
                "mfa_verified_to": null,
                "location_id": null,
                "created_by": null,
                "active": 1,
                "is_ambassador": 0,
                "test_account": false,
                "notifications_timezone": null,
                "notifications_at": null,
                "created_at": "2026-11-03T17:24:25.000000Z",
                "updated_at": "2026-11-03T17:24:25.000000Z",
                "invitation_status": null,
                "acadle_invitation_status": null,
                "roles": []
            },
            "entries": [
                {
                    "id": 1,
                    "config_history_id": 1,
                    "config_id": 3,
                    "old_value": "voluptatibus",
                    "new_value": "non",
                    "created_at": "2026-11-03T17:24:26.000000Z",
                    "updated_at": "2026-11-03T17:24:26.000000Z",
                    "config_entry": {
                        "id": 3,
                        "device_id": 34,
                        "mode_id": null,
                        "key": "quis",
                        "value": "ratione",
                        "created_at": "2026-11-03T17:24:26.000000Z",
                        "updated_at": "2026-11-03T17:24:26.000000Z"
                    }
                }
            ]
        },
        {
            "id": 3,
            "user_id": null,
            "device_id": 35,
            "index": null,
            "name": "Sed doloremque ut in laudantium nisi.",
            "config": "{\"common\":{\"fingerStrength\":[1,300],\"gripPositions\":{\"_\":0,\"0\":{\"initial\":[17,18,64,49,16],\"limit\":[30,74,93,79,53]},\"1\":{\"initial\":[47,26,24,30,42],\"limit\":[91,55,27,36,83]},\"2\":{\"initial\":[34,34,64,20,81],\"limit\":[66,68,69,40,91]},\"3\":{\"initial\":[3,28,12,9,32],\"limit\":[44,69,93,28,55]},\"4\":{\"initial\":[49,67,7,17,26],\"limit\":[74,75,14,55,28]},\"5\":{\"initial\":[13,3,11,11,6],\"limit\":[35,82,18,28,7]},\"6\":{\"initial\":[75,21,7,6,57],\"limit\":[78,53,32,63,72]},\"7\":{\"initial\":[18,19,34,11,1],\"limit\":[55,84,41,74,24]},\"8\":{\"initial\":[1,78,74,57,40],\"limit\":[24,83,83,85,93]},\"9\":{\"initial\":[29,33,40,27,60],\"limit\":[80,54,58,30,65]},\"10\":{\"initial\":[47,13,42,21,46],\"limit\":[83,61,77,71,86]},\"11\":{\"initial\":[5,21,35,32,77],\"limit\":[44,36,53,40,92]},\"12\":{\"initial\":[43,30,16,25,28],\"limit\":[66,89,69,55,36]},\"13\":{\"initial\":[32,23,20,4,36],\"limit\":[89,70,83,95,81]}},\"inputSite\":[0]},\"modes\":[{\"id\":9,\"name\":\"Officia modi quas aperiam.\",\"slot\":0,\"config\":{\"autoGrasp\":[1,100],\"coContractionTimings\":[300,100],\"controlMode\":[0],\"emgGains\":[100,100],\"emgSpike\":[1,300],\"emgThresholds\":[20,0,50,50,80,40,0,60,90,60],\"gripPairsConfig\":[6,11,3,10,5,7,13,4],\"gripSequentialConfig\":[2,255,255,5,11,13,9,10,4,8,3,6],\"gripSwitchingMode\":[1],\"holdOpen\":[2000,2000],\"pulseTimings\":[720,770,420,950],\"softGrip\":[1],\"speedControlStrategy\":[0]}},{\"id\":10,\"name\":\"Consequatur et officiis recusandae ut.\",\"slot\":1,\"config\":{\"autoGrasp\":[1,0],\"coContractionTimings\":[500,200],\"controlMode\":[1],\"emgGains\":[100,100],\"emgSpike\":[0,300],\"emgThresholds\":[30,100,0,60,80,100,70,100,10,70],\"gripPairsConfig\":[2,7,6,13,4,9,11,3],\"gripSequentialConfig\":[6,255,255,3,10,4,9,1,7,255,5,13],\"gripSwitchingMode\":[3],\"holdOpen\":[1500,2000],\"pulseTimings\":[60,1000,710,820],\"softGrip\":[1],\"speedControlStrategy\":[1]}},{\"id\":11,\"name\":\"Esse temporibus consequatur maxime.\",\"slot\":2,\"config\":{\"autoGrasp\":[1,0],\"coContractionTimings\":[100,100],\"controlMode\":[0],\"emgGains\":[100,100],\"emgSpike\":[1,300],\"emgThresholds\":[100,40,60,80,90,90,80,10,20,60],\"gripPairsConfig\":[4,6,9,8,2,10,11,1],\"gripSequentialConfig\":[255,255,13,10,2,255,4,255,1,255,255,7],\"gripSwitchingMode\":[1],\"holdOpen\":[1500,2000],\"pulseTimings\":[330,830,690,490],\"softGrip\":[1],\"speedControlStrategy\":[1]}}]}",
            "restore_point": 0,
            "factory_reset_point": 0,
            "source": "configurator",
            "changed_by": 53,
            "firmware_version_id": null,
            "created_at": "2026-11-03T17:24:27.000000Z",
            "updated_at": "2026-11-03T17:24:27.000000Z",
            "author": {
                "id": 53,
                "mrn": "YJRS4TJR1793726667",
                "name": "Kara Sauer",
                "email": "1793726667crist.ernestine@example.org",
                "language": "en",
                "phone": "1-931-765-9614",
                "phone_country": "MV",
                "phone_verified_at": null,
                "address1": "9291 Franecki Greens",
                "address2": "East Clemmie, AR 59111",
                "postal_code": "76868-0006",
                "city": "Fahey-Mayert",
                "country": "CZ",
                "clinic_name": "South Adelechester",
                "clinic_location": "964 Koss Hollow Suite 766\nZiemannmouth, CA 82883",
                "image": null,
                "public_image": null,
                "mfa_enabled": 0,
                "mfa_method": null,
                "mfa_verified_to": null,
                "location_id": null,
                "created_by": null,
                "active": 1,
                "is_ambassador": 0,
                "test_account": false,
                "notifications_timezone": null,
                "notifications_at": null,
                "created_at": "2026-11-03T17:24:27.000000Z",
                "updated_at": "2026-11-03T17:24:27.000000Z",
                "invitation_status": null,
                "acadle_invitation_status": null,
                "roles": []
            },
            "entries": [
                {
                    "id": 2,
                    "config_history_id": 3,
                    "config_id": 4,
                    "old_value": "laborum",
                    "new_value": "sint",
                    "created_at": "2026-11-03T17:24:28.000000Z",
                    "updated_at": "2026-11-03T17:24:28.000000Z",
                    "config_entry": {
                        "id": 4,
                        "device_id": 43,
                        "mode_id": null,
                        "key": "sed",
                        "value": "voluptatem",
                        "created_at": "2026-11-03T17:24:28.000000Z",
                        "updated_at": "2026-11-03T17:24:28.000000Z"
                    }
                }
            ]
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view device config",
    "code": "CONFIG:HISTORY:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "CONFIG:HISTORY:DEVICE_NOT_FOUND"
}
 

Request   

GET api/device/{deviceId}/config/history

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

deviceId   integer   

Device ID. Example: 1

Query Parameters

restore_point   boolean  optional  

Filter config entries by restore point status. Example: 1

factory_reset_point   boolean  optional  

Filter config entries by factory reset point status. Example: 1

date_from   integer  optional  

Filter config entries from date (timestamp). Example: 1642003200

date_to   integer  optional  

Filter config entries to date (timestamp). Example: 1642003200

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

extend   string  optional  

Comma-separated list of relation extensions (available: author, entries).

sortby   string  optional  

Sort by field (available: date). Default: date, desc.

sortdir   string  optional  

Sort direction (available: asc, desc).

Body Parameters

restore_point   boolean  optional  

Example: true

factory_reset_point   boolean  optional  

Example: true

date_from   string  optional  
date_to   string  optional  

Response

Response Fields

items   object   
id   integer   

Config history entry ID.

device_id   integer   

Associated device ID.

index   integer   

Config history index.

name   string   

Config snapshot name.

config   string   

Serialized config data.

restore_point   boolean   

Whether this entry is a restore point.

factory_reset_point   boolean   

Whether this entry is a factory reset point.

changed_by   integer   

ID of the user who made the change.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

author   object   

User who made the change.

entries   object[]   

Individual config history entries.

notes   object[]   

Notes attached to this history entry.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

Get device config history entry

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/device/1/config/history/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/device/1/config/history/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "id": 5,
    "user_id": null,
    "device_id": 44,
    "index": null,
    "name": "Sit blanditiis doloremque aut dolorem dolorem recusandae.",
    "config": "{\"common\":{\"fingerStrength\":[1,100],\"gripPositions\":{\"_\":0,\"0\":{\"initial\":[11,34,9,40,37],\"limit\":[70,80,84,87,93]},\"1\":{\"initial\":[13,27,77,75,39],\"limit\":[56,91,83,92,64]},\"2\":{\"initial\":[25,25,22,6,18],\"limit\":[85,66,53,43,62]},\"3\":{\"initial\":[73,68,36,8,68],\"limit\":[73,72,36,62,86]},\"4\":{\"initial\":[2,3,18,18,7],\"limit\":[46,57,84,68,9]},\"5\":{\"initial\":[7,6,73,22,43],\"limit\":[78,54,90,75,95]},\"6\":{\"initial\":[26,57,8,14,47],\"limit\":[42,79,40,25,60]},\"7\":{\"initial\":[34,22,53,17,36],\"limit\":[86,34,81,23,80]},\"8\":{\"initial\":[17,9,2,41,45],\"limit\":[69,40,91,57,55]},\"9\":{\"initial\":[62,1,24,54,26],\"limit\":[82,39,88,94,62]},\"10\":{\"initial\":[15,18,3,21,17],\"limit\":[90,21,71,92,24]},\"11\":{\"initial\":[41,6,50,67,7],\"limit\":[58,23,88,81,34]},\"12\":{\"initial\":[44,9,15,63,82],\"limit\":[72,25,73,85,84]},\"13\":{\"initial\":[19,28,1,1,48],\"limit\":[91,62,44,42,55]}},\"inputSite\":[1]},\"modes\":[{\"id\":15,\"name\":\"Assumenda voluptates est repudiandae error.\",\"slot\":0,\"config\":{\"autoGrasp\":[0,100],\"coContractionTimings\":[400,200],\"controlMode\":[1],\"emgGains\":[100,100],\"emgSpike\":[0,300],\"emgThresholds\":[100,60,30,80,10,50,80,40,20,90],\"gripPairsConfig\":[2,4,3,12,13,1,6,11],\"gripSequentialConfig\":[1,255,11,2,9,7,13,6,8,255,255,3],\"gripSwitchingMode\":[1],\"holdOpen\":[1500,2000],\"pulseTimings\":[150,510,1000,620],\"softGrip\":[1],\"speedControlStrategy\":[0]}},{\"id\":16,\"name\":\"Expedita excepturi ea illum et.\",\"slot\":1,\"config\":{\"autoGrasp\":[1,0],\"coContractionTimings\":[400,200],\"controlMode\":[0],\"emgGains\":[100,100],\"emgSpike\":[1,300],\"emgThresholds\":[50,100,100,50,100,30,80,50,30,10],\"gripPairsConfig\":[3,10,6,7,11,12,1,13],\"gripSequentialConfig\":[255,11,255,1,255,8,255,9,255,7,6,5],\"gripSwitchingMode\":[3],\"holdOpen\":[2500,2500],\"pulseTimings\":[690,70,630,50],\"softGrip\":[1],\"speedControlStrategy\":[0]}},{\"id\":17,\"name\":\"Velit aperiam saepe non facilis eos ut reiciendis.\",\"slot\":2,\"config\":{\"autoGrasp\":[1,0],\"coContractionTimings\":[200,100],\"controlMode\":[1],\"emgGains\":[100,100],\"emgSpike\":[0,300],\"emgThresholds\":[10,90,100,80,10,0,90,10,0,30],\"gripPairsConfig\":[4,2,8,10,9,11,6,13],\"gripSequentialConfig\":[6,255,11,10,8,3,9,255,1,12,4,2],\"gripSwitchingMode\":[3],\"holdOpen\":[1500,2000],\"pulseTimings\":[270,980,210,940],\"softGrip\":[0],\"speedControlStrategy\":[1]}}]}",
    "restore_point": 1,
    "factory_reset_point": 0,
    "source": "configurator",
    "changed_by": 55,
    "firmware_version_id": null,
    "created_at": "2026-11-03T17:24:29.000000Z",
    "updated_at": "2026-11-03T17:24:29.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view device config",
    "code": "CONFIG:HISTORY_ENTRY:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "CONFIG:HISTORY_ENTRY:DEVICE_NOT_FOUND"
}
 

Example response (404, Config history entry not found):


{
    "message": "Config history entry not found",
    "code": "CONFIG:HISTORY_ENTRY:HISTORY_ENTRY_NOT_FOUND"
}
 

Request   

GET api/device/{deviceId}/config/history/{configId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

deviceId   integer   

Device ID. Example: 1

configId   integer   

Config history entry ID. Example: 1

Query Parameters

extend   string  optional  

Comma-separated list of relation extensions (available: author, entries).

Response

Response Fields

id   integer   

Config history entry ID.

device_id   integer   

Associated device ID.

index   integer   

Config history index.

name   string   

Config snapshot name.

config   string   

Serialized config data.

restore_point   boolean   

Whether this entry is a restore point.

factory_reset_point   boolean   

Whether this entry is a factory reset point.

changed_by   integer   

ID of the user who made the change.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

author   object   

User who made the change.

entries   object[]   

Individual config history entries.

notes   object[]   

Notes attached to this history entry.

Update config history

requires authentication

Returns updated config history in response.

Example request:
curl --request POST \
    "http://localhost:8000/api/device/1/config/history/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Remote session 2022-05-30\",
    \"restore_point\": true,
    \"factory_reset_point\": false
}"
const url = new URL(
    "http://localhost:8000/api/device/1/config/history/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Remote session 2022-05-30",
    "restore_point": true,
    "factory_reset_point": false
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (202):


{
    "id": 6,
    "user_id": null,
    "device_id": 48,
    "index": null,
    "name": "Minus et assumenda maxime enim assumenda eaque dolor.",
    "config": "{\"common\":{\"fingerStrength\":[1,100],\"gripPositions\":{\"_\":0,\"0\":{\"initial\":[66,15,57,7,44],\"limit\":[67,23,73,40,61]},\"1\":{\"initial\":[34,10,49,22,35],\"limit\":[68,22,62,52,78]},\"2\":{\"initial\":[31,12,21,3,9],\"limit\":[34,20,47,10,68]},\"3\":{\"initial\":[8,53,48,26,79],\"limit\":[30,82,57,63,83]},\"4\":{\"initial\":[30,17,45,84,47],\"limit\":[35,76,72,87,54]},\"5\":{\"initial\":[11,3,15,84,72],\"limit\":[22,8,46,89,94]},\"6\":{\"initial\":[81,31,8,20,42],\"limit\":[94,85,39,25,53]},\"7\":{\"initial\":[21,62,6,92,44],\"limit\":[90,63,10,95,55]},\"8\":{\"initial\":[26,36,17,3,28],\"limit\":[83,49,61,46,72]},\"9\":{\"initial\":[34,27,45,59,58],\"limit\":[41,82,71,70,93]},\"10\":{\"initial\":[1,11,28,41,34],\"limit\":[41,67,49,82,63]},\"11\":{\"initial\":[28,19,20,3,8],\"limit\":[70,55,23,48,28]},\"12\":{\"initial\":[6,31,93,23,11],\"limit\":[34,74,95,53,73]},\"13\":{\"initial\":[32,3,20,32,20],\"limit\":[88,17,88,63,36]}},\"inputSite\":[1]},\"modes\":[{\"id\":18,\"name\":\"Praesentium nemo praesentium doloremque et sequi eum fugit sit.\",\"slot\":0,\"config\":{\"autoGrasp\":[0,100],\"coContractionTimings\":[300,100],\"controlMode\":[0],\"emgGains\":[100,100],\"emgSpike\":[0,300],\"emgThresholds\":[10,90,0,40,90,20,70,80,10,70],\"gripPairsConfig\":[5,2,4,6,10,7,13,11],\"gripSequentialConfig\":[13,12,9,255,11,5,3,4,7,10,255,255],\"gripSwitchingMode\":[3],\"holdOpen\":[2000,2000],\"pulseTimings\":[480,470,210,850],\"softGrip\":[0],\"speedControlStrategy\":[1]}},{\"id\":19,\"name\":\"Ea aperiam minus enim consequatur voluptatum aliquid.\",\"slot\":1,\"config\":{\"autoGrasp\":[1,0],\"coContractionTimings\":[200,200],\"controlMode\":[0],\"emgGains\":[100,100],\"emgSpike\":[1,300],\"emgThresholds\":[100,70,70,0,100,100,10,70,0,40],\"gripPairsConfig\":[3,8,7,9,2,10,1,13],\"gripSequentialConfig\":[1,4,10,255,255,5,3,2,255,255,255,255],\"gripSwitchingMode\":[3],\"holdOpen\":[2000,2500],\"pulseTimings\":[760,20,640,700],\"softGrip\":[1],\"speedControlStrategy\":[1]}},{\"id\":20,\"name\":\"Recusandae minima et atque labore provident.\",\"slot\":2,\"config\":{\"autoGrasp\":[1,0],\"coContractionTimings\":[100,100],\"controlMode\":[0],\"emgGains\":[100,100],\"emgSpike\":[1,300],\"emgThresholds\":[10,0,20,90,20,0,60,40,10,50],\"gripPairsConfig\":[9,11,10,13,6,12,1,5],\"gripSequentialConfig\":[6,255,5,255,255,4,255,1,13,8,3,12],\"gripSwitchingMode\":[3],\"holdOpen\":[1500,2000],\"pulseTimings\":[350,580,530,370],\"softGrip\":[0],\"speedControlStrategy\":[0]}}]}",
    "restore_point": 0,
    "factory_reset_point": 0,
    "source": "configurator",
    "changed_by": 56,
    "firmware_version_id": null,
    "created_at": "2026-11-03T17:24:29.000000Z",
    "updated_at": "2026-11-03T17:24:29.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to update device config",
    "code": "CONFIG:HISTORY_UPDATE:INSUFFICIENT_PERMISSION"
}
 

Example response (403, Factory reset point already exists):


{
    "message": "Factory reset point does not exist",
    "code": "CONFIG:HISTORY_UPDATE:FACTORY_RESET_POINT_ALREADY_EXISTS"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "CONFIG:HISTORY_UPDATE:DEVICE_NOT_FOUND"
}
 

Example response (404, Config history entry not found):


{
    "message": "Config history entry not found",
    "code": "CONFIG:HISTORY_UPDATE:HISTORY_ENTRY_NOT_FOUND"
}
 

Request   

POST api/device/{deviceId}/config/history/{configId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

deviceId   integer   

Device ID. Example: 1

configId   integer   

Config history entry ID. Example: 1

Body Parameters

name   string  optional  

Config history entry name. Example: Remote session 2022-05-30

restore_point   boolean  optional  

Restore point status. Example: true

factory_reset_point   boolean  optional  

Point of factory reset. Example: false

Response

Response Fields

id   integer   

Config history entry ID.

device_id   integer   

Associated device ID.

index   integer   

Config history index.

name   string   

Config snapshot name.

config   string   

Serialized config data.

restore_point   boolean   

Whether this entry is a restore point.

factory_reset_point   boolean   

Whether this entry is a factory reset point.

changed_by   integer   

ID of the user who made the change.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

author   object   

User who made the change.

entries   object[]   

Individual config history entries.

notes   object[]   

Notes attached to this history entry.

Undo single config history change

requires authentication

Returns updated config in response.

Example request:
curl --request DELETE \
    "http://localhost:8000/api/device/1/config/history/undo/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/device/1/config/history/undo/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (202, Normal/compact response):


{
    "common": {
        "gripPairsConfig": [
            1,
            4,
            2,
            3,
            6,
            7,
            9,
            8
        ],
        "controlConfig": [
            0,
            1,
            0,
            0,
            0
        ],
        "emgThresholds": [
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0
        ],
        "interval": [
            100
        ],
        "gripSequentialConfig": [
            1,
            2,
            4,
            3,
            0,
            255,
            6,
            7,
            9,
            8,
            255,
            255
        ]
    },
    "modes": [
        {
            "id": 100,
            "name": "Mode 1",
            "slot": 0,
            "config": {
                "interval": [
                    300
                ],
                "fingerStrength": [
                    1,
                    100
                ],
                "autoGrasp": [
                    0,
                    100
                ],
                "emgSpike": [
                    0,
                    300
                ]
            }
        },
        {
            "id": 101,
            "name": "Mode 2",
            "slot": 1,
            "config": {
                "interval": [
                    400
                ],
                "fingerStrength": [
                    1,
                    100
                ],
                "autoGrasp": [
                    0,
                    100
                ],
                "emgSpike": [
                    0,
                    300
                ]
            }
        },
        {
            "id": 102,
            "name": "Mode 3",
            "slot": 2,
            "config": {
                "interval": [
                    500
                ],
                "fingerStrength": [
                    1,
                    100
                ],
                "autoGrasp": [
                    0,
                    100
                ],
                "emgSpike": [
                    0,
                    300
                ]
            }
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to update device config",
    "code": "CONFIG:UNDO:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "CONFIG:UNDO:DEVICE_NOT_FOUND"
}
 

Example response (404, Config history entry not found):


{
    "message": "Config history entry not found",
    "code": "CONFIG:UNDO:HISTORY_ENTRY_NOT_FOUND"
}
 

Request   

DELETE api/device/{deviceId}/config/history/undo/{configId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

deviceId   integer   

Device ID. Example: 1

configId   integer   

Config history entry ID. Example: 1

Restore config history entry

requires authentication

Restores config from given config history entry (all changes). Sends support ticket if patient is assigned to device, returns config instead.

Example request:
curl --request POST \
    "http://localhost:8000/api/device/1/config/restore/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/device/1/config/restore/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200, Patient not assigned, returns config):


{
    "common": {
        "gripPairsConfig": [
            1,
            4,
            2,
            3,
            6,
            7,
            9,
            8
        ],
        "controlConfig": [
            0,
            1,
            0,
            0,
            0
        ],
        "emgThresholds": [
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0
        ],
        "interval": [
            100
        ],
        "gripSequentialConfig": [
            1,
            2,
            4,
            3,
            0,
            255,
            6,
            7,
            9,
            8,
            255,
            255
        ]
    },
    "modes": [
        {
            "id": 100,
            "name": "Mode 1",
            "slot": 0,
            "config": {
                "interval": [
                    300
                ],
                "fingerStrength": [
                    1,
                    100
                ],
                "autoGrasp": [
                    0,
                    100
                ],
                "emgSpike": [
                    0,
                    300
                ]
            }
        },
        {
            "id": 101,
            "name": "Mode 2",
            "slot": 1,
            "config": {
                "interval": [
                    400
                ],
                "fingerStrength": [
                    1,
                    100
                ],
                "autoGrasp": [
                    0,
                    100
                ],
                "emgSpike": [
                    0,
                    300
                ]
            }
        },
        {
            "id": 102,
            "name": "Mode 3",
            "slot": 2,
            "config": {
                "interval": [
                    500
                ],
                "fingerStrength": [
                    1,
                    100
                ],
                "autoGrasp": [
                    0,
                    100
                ],
                "emgSpike": [
                    0,
                    300
                ]
            }
        }
    ]
}
 

Example response (202):


{
    "id": 1,
    "sender_id": 57,
    "recipient_id": 58,
    "device_id": null,
    "meeting_date": "2026-11-03 17:24:30",
    "meeting_type": "online_meeting",
    "contact_email": "elisha74@hotmail.com",
    "status": "new",
    "created_at": "2026-11-03T17:24:30.000000Z",
    "updated_at": "2026-11-03T17:24:30.000000Z",
    "sender": {
        "id": 57,
        "mrn": "3Y78YVC21793726669",
        "name": "Kian Denesik",
        "email": "1793726669smarquardt@example.net",
        "language": "en",
        "phone": "(636) 849-4896",
        "phone_country": "VE",
        "phone_verified_at": null,
        "address1": "4720 Emard Islands Suite 316",
        "address2": "Elysemouth, NY 62745-8369",
        "postal_code": "51244",
        "city": "Shields-Hickle",
        "country": "EE",
        "clinic_name": "Nolaborough",
        "clinic_location": "907 Nicklaus Points Suite 611\nLake Kristamouth, DE 85817-9435",
        "image": null,
        "public_image": null,
        "mfa_enabled": 0,
        "mfa_method": null,
        "mfa_verified_to": null,
        "location_id": null,
        "created_by": null,
        "active": 1,
        "is_ambassador": 0,
        "test_account": false,
        "notifications_timezone": null,
        "notifications_at": null,
        "created_at": "2026-11-03T17:24:29.000000Z",
        "updated_at": "2026-11-03T17:24:29.000000Z",
        "invitation_status": null,
        "acadle_invitation_status": null,
        "roles": []
    },
    "recipient": {
        "id": 58,
        "mrn": "RMWKPWRK1793726670",
        "name": "Mateo Cummerata",
        "email": "1793726670gisselle.hansen@example.org",
        "language": "en",
        "phone": "662-647-9078",
        "phone_country": "SR",
        "phone_verified_at": null,
        "address1": "32639 Darrion Street",
        "address2": "Alexandrinestad, KY 04065-8427",
        "postal_code": "76883-8944",
        "city": "Jaskolski-Douglas",
        "country": "HU",
        "clinic_name": "Dustyland",
        "clinic_location": "33319 Arely Wall Suite 612\nNew Einarside, MO 91369",
        "image": null,
        "public_image": null,
        "mfa_enabled": 0,
        "mfa_method": null,
        "mfa_verified_to": null,
        "location_id": null,
        "created_by": null,
        "active": 1,
        "is_ambassador": 0,
        "test_account": false,
        "notifications_timezone": null,
        "notifications_at": null,
        "created_at": "2026-11-03T17:24:30.000000Z",
        "updated_at": "2026-11-03T17:24:30.000000Z",
        "invitation_status": null,
        "acadle_invitation_status": null,
        "roles": []
    },
    "device": null,
    "messages": [
        {
            "id": 1,
            "ticket_id": 1,
            "sender_id": 59,
            "title": "Miss",
            "content": "Autem laboriosam error reiciendis dolores cumque veritatis.",
            "is_read": false,
            "created_at": "2026-11-03T17:24:32.000000Z",
            "updated_at": "2026-11-03T17:24:32.000000Z"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to update device config",
    "code": "CONFIG:RESTORE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "CONFIG:RESTORE:DEVICE_NOT_FOUND"
}
 

Example response (404, Config history entry not found):


{
    "message": "Config history entry not found",
    "code": "CONFIG:RESTORE:HISTORY_ENTRY_NOT_FOUND"
}
 

Request   

POST api/device/{deviceId}/config/restore/{configId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

deviceId   integer   

Device ID. Example: 1

configId   integer   

Config history entry ID. Example: 1

Response

Response Fields

id   integer   

Support ticket ID.

sender_id   integer   

ID of the user who created the ticket.

recipient_id   integer   

ID of the recipient user.

device_id   integer   

Associated device ID.

meeting_date   string   

Scheduled meeting date.

meeting_type   string   

Meeting type.

Must be one of:
  • online
  • in-person
contact_email   string   

Contact email address.

status   string   

Ticket status.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

sender   object   

User who created the ticket.

recipient   object   

Recipient user.

device   object   

Associated device.

messages   object[]   

Ticket messages.

Restore to factory reset point

requires authentication

Restores config from the device's factory reset point. Sends a support ticket if the patient is assigned to the device. Returns the array that contains:

Example request:
curl --request POST \
    "http://localhost:8000/api/device/1/config/restore-factory-reset" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/device/1/config/restore-factory-reset"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "config": {
        "common": {
            "inputSite": [
                1
            ],
            "generalHandSettings": false,
            "batteryBeep": [
                1,
                0
            ],
            "emgGains": false,
            "autoGrasp": false
        },
        "modes": [
            {
                "id": 1,
                "name": "Mode 0",
                "slot": 0,
                "config": {
                    "gripPairsConfig": false,
                    "emgThresholds": false,
                    "emgSpike": false,
                    "controlMode": false
                }
            },
            {
                "id": 1530,
                "name": "Mode 1",
                "slot": 1,
                "config": {
                    "gripPairsConfig": false,
                    "emgThresholds": false,
                    "emgSpike": false,
                    "controlMode": false
                }
            },
            {
                "id": 1531,
                "name": "Mode 2",
                "slot": 2,
                "config": {
                    "gripPairsConfig": false,
                    "emgThresholds": false,
                    "emgSpike": false,
                    "controlMode": false
                }
            }
        ]
    },
    "not_modified": {
        "common": {
            "inputSite": [
                1
            ],
            "batteryBeep": [
                1,
                0
            ],
            "generalHandSettings": [
                1,
                2,
                3,
                4
            ]
        },
        "modes": [
            {
                "userFeedbackType": false,
                "buzzingVolumeSettings": false
            },
            {
                "userFeedbackType": false,
                "buzzingVolumeSettings": false
            },
            {
                "userFeedbackType": false,
                "buzzingVolumeSettings": false
            }
        ]
    },
    "ticket": {
        "id": 1,
        "sender_id": 1,
        "recipient_id": 2,
        "device_id": 1,
        "meeting_date": "2025-07-22T15:00:00.000000Z",
        "meeting_type": "none",
        "contact_email": null,
        "status": "new",
        "created_at": "2025-07-22T15:00:00.000000Z",
        "updated_at": "2025-07-22T15:00:00.000000Z",
        "messages": [
            {
                "id": 1,
                "ticket_id": 1,
                "sender_id": 1,
                "title": "New config update",
                "content": "",
                "is_read": false,
                "created_at": "2025-07-22T15:00:00.000000Z",
                "updated_at": "2025-07-22T15:00:00.000000Z",
                "attachments": [
                    {
                        "id": 6629,
                        "ticket_id": 12973,
                        "ticket_message_id": 6517,
                        "type": "json",
                        "title": "Current config",
                        "attachment": "{\"common\":{},\"modes\":[{\"id\":1,\"name\":\"Mode 0\",\"slot\":0,\"config\":{}},{\"id\":2,\"name\":\"Mode 1\",\"slot\":1,\"config\":{}},{\"id\":3,\"name\":\"Mode 2\",\"slot\":2,\"config\":{}}]}",
                        "created_at": "2025-07-22T15:00:00.000000Z",
                        "updated_at": "2025-07-22T15:00:00.000000Z"
                    },
                    {
                        "id": 6630,
                        "ticket_id": 12973,
                        "ticket_message_id": 6517,
                        "type": "json",
                        "title": "New config",
                        "attachment": "{\"common\":{},\"modes\":[{\"id\":1,\"name\":\"Mode 0\",\"slot\":0,\"config\":{}},{\"id\":2,\"name\":\"Mode 1\",\"slot\":1,\"config\":{}},{\"id\":3,\"name\":\"Mode 2\",\"slot\":2,\"config\":{}}]}",
                        "created_at": "2025-07-22T15:00:00.000000Z",
                        "updated_at": "2025-07-22T15:00:00.000000Z"
                    }
                ]
            }
        ]
    }
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to update device config",
    "code": "CONFIG:RESTORE_FACTORY_RESET:INSUFFICIENT_PERMISSION"
}
 

Example response (403, Config history entry not found):


{
    "message": "Config history entry not found",
    "code": "CONFIG:RESTORE_FACTORY_RESET:NO_RESTORE_POINT"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "CONFIG:RESTORE_FACTORY_RESET:DEVICE_NOT_FOUND"
}
 

Request   

POST api/device/{deviceId}/config/restore-factory-reset

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

deviceId   integer   

Device ID. Example: 1

Send test config

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/device/1/config/send" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"description\": \"Fixed problem with grips.\",
    \"p2p_session\": 1,
    \"updateConfig\": false,
    \"common\": \"{\\\"gripPairsConfig\\\": [1, 4, 2, 3, 6, 7, 9, 8], \\\"controlConfig\\\": [0, 1, 0, 0, 0], \\\"gripSequentialConfig\\\": [1, 2, 4, 3, 0, 255, 6, 7, 9, 8, 255, 255]\",
    \"source\": \"guide\",
    \"modes\": [
        {
            \"id\": 1,
            \"config\": \"{\\\"gripPairsConfig\\\": [1, 4, 2, 3, 6, 7, 9, 8], \\\"controlConfig\\\": [0, 1, 0, 0, 0], \\\"gripSequentialConfig\\\": [1, 2, 4, 3, 0, 255, 6, 7, 9, 8, 255, 255]\"
        }
    ]
}"
const url = new URL(
    "http://localhost:8000/api/device/1/config/send"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "description": "Fixed problem with grips.",
    "p2p_session": 1,
    "updateConfig": false,
    "common": "{\"gripPairsConfig\": [1, 4, 2, 3, 6, 7, 9, 8], \"controlConfig\": [0, 1, 0, 0, 0], \"gripSequentialConfig\": [1, 2, 4, 3, 0, 255, 6, 7, 9, 8, 255, 255]",
    "source": "guide",
    "modes": [
        {
            "id": 1,
            "config": "{\"gripPairsConfig\": [1, 4, 2, 3, 6, 7, 9, 8], \"controlConfig\": [0, 1, 0, 0, 0], \"gripSequentialConfig\": [1, 2, 4, 3, 0, 255, 6, 7, 9, 8, 255, 255]"
        }
    ]
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 7,
    "sender_id": 68,
    "recipient_id": 69,
    "device_id": null,
    "meeting_date": "2026-11-03 17:24:35",
    "meeting_type": "online_meeting",
    "contact_email": "qharvey@muller.com",
    "status": "new",
    "created_at": "2026-11-03T17:24:36.000000Z",
    "updated_at": "2026-11-03T17:24:36.000000Z",
    "sender": {
        "id": 68,
        "mrn": "TH7E3RZB1793726675",
        "name": "Ole O'Keefe",
        "email": "1793726675quinten.ortiz@example.net",
        "language": "en",
        "phone": "+1-347-939-2759",
        "phone_country": "MW",
        "phone_verified_at": null,
        "address1": "461 Genesis Meadows Apt. 306",
        "address2": "North Shania, CA 97583-1720",
        "postal_code": "84944-1657",
        "city": "Volkman, Vandervort and Bailey",
        "country": "ES",
        "clinic_name": "East Ali",
        "clinic_location": "95389 Irma Skyway Suite 717\nLincolnfurt, NE 65223",
        "image": null,
        "public_image": null,
        "mfa_enabled": 0,
        "mfa_method": null,
        "mfa_verified_to": null,
        "location_id": null,
        "created_by": null,
        "active": 1,
        "is_ambassador": 0,
        "test_account": false,
        "notifications_timezone": null,
        "notifications_at": null,
        "created_at": "2026-11-03T17:24:35.000000Z",
        "updated_at": "2026-11-03T17:24:35.000000Z",
        "invitation_status": null,
        "acadle_invitation_status": null,
        "roles": []
    },
    "recipient": {
        "id": 69,
        "mrn": "6869NQHU1793726675",
        "name": "Miss Princess Armstrong MD",
        "email": "1793726675sglover@example.net",
        "language": "en",
        "phone": "+1.762.913.1522",
        "phone_country": "UY",
        "phone_verified_at": null,
        "address1": "21493 Turcotte Street Suite 840",
        "address2": "North Gerardfort, MS 30475",
        "postal_code": "51946",
        "city": "Schinner, Donnelly and Bergstrom",
        "country": "AT",
        "clinic_name": "North Shirley",
        "clinic_location": "6921 Sporer Hollow Suite 072\nBreitenbergview, KS 18927-6555",
        "image": null,
        "public_image": null,
        "mfa_enabled": 0,
        "mfa_method": null,
        "mfa_verified_to": null,
        "location_id": null,
        "created_by": null,
        "active": 1,
        "is_ambassador": 0,
        "test_account": false,
        "notifications_timezone": null,
        "notifications_at": null,
        "created_at": "2026-11-03T17:24:35.000000Z",
        "updated_at": "2026-11-03T17:24:35.000000Z",
        "invitation_status": null,
        "acadle_invitation_status": null,
        "roles": []
    },
    "device": null,
    "messages": [
        {
            "id": 4,
            "ticket_id": 7,
            "sender_id": 70,
            "title": "Dr.",
            "content": "Et dolores rerum fugiat dolorem beatae maxime.",
            "is_read": false,
            "created_at": "2026-11-03T17:24:37.000000Z",
            "updated_at": "2026-11-03T17:24:37.000000Z"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to update device config",
    "code": "CONFIG:SEND:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "CONFIG:SEND:DEVICE_NOT_FOUND"
}
 

Example response (422, Device does not have an amputee assigned):


{
    "message": "Device does not have an amputee assigned",
    "code": "CONFIG:SEND:NO_PATIENT"
}
 

Example response (422, Invalid P2P session):


{
    "message": "Invalid P2P session",
    "code": "CONFIG:SEND:INVALID_P2P_SESSION"
}
 

Example response (422, Invalid config):


{
    "message": "Config has some problems and cannot be saved.",
    "errors": {
        "modes": {
            "mode_3": "Config mode 3 does not belong to device 12."
        },
        "values": {
            "common.inputSite": "Invalid value [\"11\"] for key inputSite - contains string values.",
            "common.gripsPositions.1.initial": "Invalid value [200,\"100\",\"100\",\"100\",\"100\"] for key gripsPositions.1.initial - contains string values.",
            "mode_1.inputSite": "Invalid value [\"11\"] for key inputSite - contains string values.",
            "mode_1.gripsPositions.0.initial": "Invalid value [\"200\",\"100\",\"100\",\"100\",\"100\"] for key gripsPositions.1.initial - contains string values."
        }
    },
    "code": "CONFIG:SEND:INVALID_CONFIG"
}
 

Request   

POST api/device/{deviceId}/config/send

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

deviceId   integer   

Device ID. Example: 1

Body Parameters

description   string  optional  

Config description to add in message notification. Example: Fixed problem with grips.

p2p_session   integer  optional  

P2P Session ID. If config was prepared during P2P session, pass its ID, if not, pass null. The id of an existing record in the App\Models\P2PSession table. Example: 1

updateConfig   boolean  optional  

Determines if device config should be updated. Example: false

common   string  optional  

Common config as JSON string. MUST_BE_JSON. Example: {"gripPairsConfig": [1, 4, 2, 3, 6, 7, 9, 8], "controlConfig": [0, 1, 0, 0, 0], "gripSequentialConfig": [1, 2, 4, 3, 0, 255, 6, 7, 9, 8, 255, 255]

source   string  optional  

Where this config save was initiated from. Defaults to configurator when not present. Example: guide

Must be one of:
  • configurator
  • guide
  • mobile
modes   object[]  optional  
id   string  optional  

Config mode ID. The id of an existing record in the App\Models\ConfigMode table. Example: 1

config   string  optional  

Config specific for mode as JSON string. MUST_BE_JSON. Example: {"gripPairsConfig": [1, 4, 2, 3, 6, 7, 9, 8], "controlConfig": [0, 1, 0, 0, 0], "gripSequentialConfig": [1, 2, 4, 3, 0, 255, 6, 7, 9, 8, 255, 255]

Response

Response Fields

id   integer   

Support ticket ID.

sender_id   integer   

ID of the user who created the ticket.

recipient_id   integer   

ID of the recipient user.

device_id   integer   

Associated device ID.

meeting_date   string   

Scheduled meeting date.

meeting_type   string   

Meeting type.

Must be one of:
  • online
  • in-person
contact_email   string   

Contact email address.

status   string   

Ticket status.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

sender   object   

User who created the ticket.

recipient   object   

Recipient user.

device   object   

Associated device.

messages   object[]   

Ticket messages.

Convert config

requires authentication

Convert config JSON to match given Firmware Version. Keys are moved between common config and modes.

Example request:
curl --request POST \
    "http://localhost:8000/api/config/convert" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"config\": \"[\\\"dolorum\\\",\\\"modi\\\"]\",
    \"firmware\": 1
}"
const url = new URL(
    "http://localhost:8000/api/config/convert"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "config": "[\"dolorum\",\"modi\"]",
    "firmware": 1
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, OK):


{
    "common": {
        "gripPairsConfig": [
            1,
            4,
            2,
            3,
            6,
            7,
            9,
            8
        ],
        "controlConfig": [
            0,
            1,
            0,
            0,
            0
        ],
        "emgThresholds": [
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0
        ],
        "interval": [
            100
        ],
        "gripSequentialConfig": [
            1,
            2,
            4,
            3,
            0,
            255,
            6,
            7,
            9,
            8,
            255,
            255
        ]
    },
    "modes": [
        {
            "id": 100,
            "name": "Mode 1",
            "slot": 0,
            "config": {
                "interval": [
                    300
                ],
                "fingerStrength": [
                    1,
                    100
                ],
                "autoGrasp": [
                    0,
                    100
                ],
                "emgSpike": [
                    0,
                    300
                ]
            }
        },
        {
            "id": 101,
            "name": "Mode 2",
            "slot": 1,
            "config": {
                "interval": [
                    400
                ],
                "fingerStrength": [
                    1,
                    100
                ],
                "autoGrasp": [
                    0,
                    100
                ],
                "emgSpike": [
                    0,
                    300
                ]
            }
        },
        {
            "id": 102,
            "name": "Mode 3",
            "slot": 2,
            "config": {
                "interval": [
                    500
                ],
                "fingerStrength": [
                    1,
                    100
                ],
                "autoGrasp": [
                    0,
                    100
                ],
                "emgSpike": [
                    0,
                    300
                ]
            }
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to update device config",
    "code": "CONFIG:CONVERT:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Firmware version not found):


{
    "message": "Firmware version not found",
    "code": "CONFIG:CONVERT:FIRMWARE_NOT_FOUND"
}
 

Request   

POST api/config/convert

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

config   string   

Full config JSON. MUST_BE_JSON. Example: ["dolorum","modi"]

firmware   string   

Firmware Version ID to which config should be adjusted. The id of an existing record in the App\Models\FirmwareVersion table. Example: 1

Get config snapshot

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/device/1/config-snapshot" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/device/1/config-snapshot"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "id": 1,
    "device_id": 65,
    "config": "{\"common\":[],\"modes\":[]}",
    "custom_grips": "[]",
    "created_at": "2026-11-03T17:24:41.000000Z",
    "updated_at": "2026-11-03T17:24:41.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view device config",
    "code": "CONFIG_SNAPSHOT:GET:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "CONFIG_SNAPSHOT:GET:DEVICE_NOT_FOUND"
}
 

Example response (404, Snapshot not found):


{
    "message": "Config snapshot not found",
    "code": "CONFIG_SNAPSHOT:GET:SNAPSHOT_NOT_FOUND"
}
 

Request   

GET api/device/{deviceId}/config-snapshot

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

deviceId   integer   

Device ID. Example: 1

Update config snapshot

requires authentication

Creates a snapshot of the device's config and custom grips. Replaces any existing snapshot for this device.

Example request:
curl --request POST \
    "http://localhost:8000/api/device/1/config-snapshot" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/device/1/config-snapshot"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200):


{
    "id": 2,
    "device_id": 67,
    "config": "{\"common\":[],\"modes\":[]}",
    "custom_grips": "[]",
    "created_at": "2026-11-03T17:24:41.000000Z",
    "updated_at": "2026-11-03T17:24:41.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to update device config",
    "code": "CONFIG_SNAPSHOT:UPDATE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "CONFIG_SNAPSHOT:UPDATE:DEVICE_NOT_FOUND"
}
 

Request   

POST api/device/{deviceId}/config-snapshot

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

deviceId   integer   

Device ID. Example: 1

Config Demo

API endpoints for managing config demos

List config demos

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/device/1/config/demos?accepted=18" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/device/1/config/demos"
);

const params = {
    "accepted": "18",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 1,
            "user_id": null,
            "device_id": 85,
            "message_id": 8,
            "config": "Doloribus dolorum quod in a eum voluptate.",
            "is_accepted": 1,
            "notes": "Magnam eum nihil facere et in dignissimos veritatis.",
            "created_at": "2026-11-03T17:24:48.000000Z",
            "updated_at": "2026-11-03T17:24:48.000000Z",
            "message": {
                "id": 8,
                "ticket_id": 15,
                "sender_id": 92,
                "title": "Prof.",
                "content": "Omnis praesentium voluptatum quia.",
                "is_read": false,
                "created_at": "2026-11-03T17:24:48.000000Z",
                "updated_at": "2026-11-03T17:24:48.000000Z"
            }
        },
        {
            "id": 2,
            "user_id": null,
            "device_id": 86,
            "message_id": 10,
            "config": "Provident voluptas commodi voluptate dolor facere perferendis.",
            "is_accepted": 0,
            "notes": "Voluptatum non rerum eos recusandae libero at nulla odio.",
            "created_at": "2026-11-03T17:24:51.000000Z",
            "updated_at": "2026-11-03T17:24:51.000000Z",
            "message": {
                "id": 10,
                "ticket_id": 18,
                "sender_id": 97,
                "title": "Mrs.",
                "content": "Unde id placeat veniam rem esse rem non non.",
                "is_read": false,
                "created_at": "2026-11-03T17:24:51.000000Z",
                "updated_at": "2026-11-03T17:24:51.000000Z"
            }
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to access config demos",
    "code": "CONFIG_DEMO:LIST:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "CONFIG_DEMO:LIST:DEVICE_NOT_FOUND"
}
 

Request   

GET api/device/{deviceId}/config/demos

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

deviceId   integer   

Device ID. Example: 1

Query Parameters

accepted   integer  optional  

Filter config demos by accepted status. If not specified, all entries will be returned. Pass -1 to get entries not accepted or rejected yet. The value must be one of -1, 0 or 1. Example: 18

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

extend   string  optional  

Comma-separated list of relation extensions (available: message, message.ticket, message.attachments).

Update config demo

requires authentication

Example request:
curl --request PUT \
    "http://localhost:8000/api/device/1/config/demos/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"is_accepted\": false,
    \"notes\": \"Something is still not working\"
}"
const url = new URL(
    "http://localhost:8000/api/device/1/config/demos/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "is_accepted": false,
    "notes": "Something is still not working"
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (202):


{
    "id": 3,
    "user_id": null,
    "device_id": 87,
    "message_id": 11,
    "config": "Et fugiat architecto omnis ipsa officia culpa impedit.",
    "is_accepted": 1,
    "notes": "Commodi quo vitae quidem nihil sed eveniet.",
    "created_at": "2026-11-03T17:24:53.000000Z",
    "updated_at": "2026-11-03T17:24:53.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to access config demos",
    "code": "CONFIG_DEMO:UPDATE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "CONFIG_DEMO:UPDATE:DEVICE_NOT_FOUND"
}
 

Example response (404, Config demo not found):


{
    "message": "Config demo not found",
    "code": "CONFIG_DEMO:UPDATE:DEMO_NOT_FOUND"
}
 

Request   

PUT api/device/{deviceId}/config/demos/{demoId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

deviceId   integer   

Device ID. Example: 1

demoId   integer   

Config demo ID. Example: 1

Body Parameters

is_accepted   boolean  optional  

Determines if demo config was accepted by patient. Example: false

notes   string  optional  

Patient notes about tested config. Example: Something is still not working

Config Modes

API endpoints for managing config modes

List config modes

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/device/1/config-modes" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/device/1/config-modes"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


[
    {
        "id": 72,
        "device_id": 116,
        "slot": null,
        "name": "Recusandae ipsa amet vel distinctio voluptas aliquid aliquam.",
        "active": 0,
        "created_at": "2026-11-03T17:24:59.000000Z",
        "updated_at": "2026-11-03T17:24:59.000000Z",
        "device": {
            "id": 116,
            "serial": "96d11ede-aab6-3d50-88c8-a071ea04bf69",
            "bluetooth_id": "fac8337a-d346-300c-8ed4-bbf9068c5d3c",
            "company_id": null,
            "model_id": null,
            "amputee_id": 110,
            "clinician_id": null,
            "firmware_version_id": null,
            "pcb_version_id": null,
            "reverse_magnets": 0,
            "is_electrode": 0,
            "active": 1,
            "last_activity_at": "0000-00-00 00:00:00",
            "first_connected_at": null,
            "measurements": null,
            "created_at": "2026-11-03T17:24:59.000000Z",
            "updated_at": "2026-11-03T17:24:59.000000Z",
            "first_config_change_at": null,
            "amputee": {
                "id": 110,
                "mrn": "2WWATPZC1793726698",
                "name": "Dr. Lenore Walter",
                "email": "1793726698donnelly.alexander@example.org",
                "language": "en",
                "phone": "+1 (248) 745-2137",
                "phone_country": "AQ",
                "phone_verified_at": null,
                "address1": "3375 Idell Ridge Apt. 492",
                "address2": "Irmashire, VA 65380-0761",
                "postal_code": "22581-4985",
                "city": "Goldner Ltd",
                "country": "MT",
                "clinic_name": "Noahchester",
                "clinic_location": "1210 Hodkiewicz Pike Suite 627\nKemmermouth, HI 59242",
                "image": null,
                "public_image": null,
                "mfa_enabled": 0,
                "mfa_method": null,
                "mfa_verified_to": null,
                "location_id": null,
                "created_by": null,
                "active": 1,
                "is_ambassador": 0,
                "test_account": false,
                "notifications_timezone": null,
                "notifications_at": null,
                "created_at": "2026-11-03T17:24:58.000000Z",
                "updated_at": "2026-11-03T17:24:58.000000Z",
                "invitation_status": null,
                "acadle_invitation_status": null,
                "roles": []
            }
        }
    },
    {
        "id": 73,
        "device_id": 118,
        "slot": null,
        "name": "Odio neque et voluptas quis officia quia accusamus.",
        "active": 0,
        "created_at": "2026-11-03T17:24:59.000000Z",
        "updated_at": "2026-11-03T17:24:59.000000Z",
        "config": {}
    }
]
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to list config modes",
    "code": "CONFIG_MODES:LIST:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "CONFIG_MODES:LIST:DEVICE_NOT_FOUND"
}
 

Request   

GET api/device/{deviceId}/config-modes

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

deviceId   integer   

Device ID. Example: 1

Response

Response Fields

id   integer   

Config mode ID.

device_id   integer   

Associated device ID.

slot   integer   

Mode slot index (0, 1 or 2).

name   string   

Mode name.

active   boolean   

Whether the mode is active.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Get config mode

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/device/1/config-modes/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/device/1/config-modes/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "id": 74,
    "device_id": 119,
    "slot": null,
    "name": "Assumenda sint dolorem sit sed cum.",
    "active": 0,
    "created_at": "2026-11-03T17:24:59.000000Z",
    "updated_at": "2026-11-03T17:24:59.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to list config modes",
    "code": "CONFIG_MODES:GET:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "CONFIG_MODES:GET:DEVICE_NOT_FOUND"
}
 

Example response (404, Config mode not found):


{
    "message": "Config mode not found",
    "code": "CONFIG_MODES:GET:MODE_NOT_FOUND"
}
 

Request   

GET api/device/{deviceId}/config-modes/{modeId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

deviceId   integer   

Device ID. Example: 1

modeId   integer   

Config mode ID. Example: 1

Response

Response Fields

id   integer   

Config mode ID.

device_id   integer   

Associated device ID.

slot   integer   

Mode slot index (0, 1 or 2).

name   string   

Mode name.

active   boolean   

Whether the mode is active.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Create config mode

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/device/1/config-modes" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"slot\": 0,
    \"name\": \"Sport mode\",
    \"active\": true
}"
const url = new URL(
    "http://localhost:8000/api/device/1/config-modes"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "slot": 0,
    "name": "Sport mode",
    "active": true
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 75,
    "device_id": 120,
    "slot": null,
    "name": "Et architecto amet aut non reprehenderit.",
    "active": 1,
    "created_at": "2026-11-03T17:24:59.000000Z",
    "updated_at": "2026-11-03T17:24:59.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to create config modes",
    "code": "CONFIG_MODES:CREATE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "CONFIG_MODES:CREATE:DEVICE_NOT_FOUND"
}
 

Request   

POST api/device/{deviceId}/config-modes

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

deviceId   integer   

Device ID. Example: 1

Body Parameters

slot   integer  optional  

Mode index on device. Example: 0

Must be one of:
  • 0
  • 1
  • 2
name   string   

Name of mode. Example: Sport mode

active   boolean  optional  

Active status. Default: 1. Example: true

Response

Response Fields

id   integer   

Config mode ID.

device_id   integer   

Associated device ID.

slot   integer   

Mode slot index (0, 1 or 2).

name   string   

Mode name.

active   boolean   

Whether the mode is active.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Update config mode

requires authentication

Example request:
curl --request PUT \
    "http://localhost:8000/api/device/1/config-modes/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"slot\": 0,
    \"name\": \"Sport mode\",
    \"active\": true
}"
const url = new URL(
    "http://localhost:8000/api/device/1/config-modes/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "slot": 0,
    "name": "Sport mode",
    "active": true
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (202):


{
    "id": 76,
    "device_id": 121,
    "slot": null,
    "name": "Voluptatum exercitationem est dicta amet aut a.",
    "active": 0,
    "created_at": "2026-11-03T17:24:59.000000Z",
    "updated_at": "2026-11-03T17:24:59.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to update config mode",
    "code": "CONFIG_MODES:UPDATE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "CONFIG_MODES:UPDATE:DEVICE_NOT_FOUND"
}
 

Example response (404, Config mode not found):


{
    "message": "Config mode not found",
    "code": "CONFIG_MODES:UPDATE:MODE_NOT_FOUND"
}
 

Request   

PUT api/device/{deviceId}/config-modes/{modeId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

deviceId   integer   

Device ID. Example: 1

modeId   integer   

Config mode ID. Example: 1

Body Parameters

slot   integer  optional  

Mode index on device. Example: 0

Must be one of:
  • 0
  • 1
  • 2
name   string  optional  

Name of mode. Example: Sport mode

active   boolean  optional  

Active status. Default: 1. Example: true

Response

Response Fields

id   integer   

Config mode ID.

device_id   integer   

Associated device ID.

slot   integer   

Mode slot index (0, 1 or 2).

name   string   

Mode name.

active   boolean   

Whether the mode is active.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Copy device config from template

requires authentication

Copy config template into selected config mode. Sends support ticket if patient is assigned to device, returns config instead.

Example request:
curl --request POST \
    "http://localhost:8000/api/device/1/config-modes/1/from-template/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/device/1/config-modes/1/from-template/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200, Patient not assigned, returns config):


{
    "common": {
        "gripPairsConfig": [
            1,
            4,
            2,
            3,
            6,
            7,
            9,
            8
        ],
        "controlConfig": [
            0,
            1,
            0,
            0,
            0
        ],
        "emgThresholds": [
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0
        ],
        "interval": [
            100
        ],
        "gripSequentialConfig": [
            1,
            2,
            4,
            3,
            0,
            255,
            6,
            7,
            9,
            8,
            255,
            255
        ]
    },
    "modes": [
        {
            "id": 100,
            "name": "Mode 1",
            "slot": 0,
            "config": {
                "interval": [
                    300
                ],
                "fingerStrength": [
                    1,
                    100
                ],
                "autoGrasp": [
                    0,
                    100
                ],
                "emgSpike": [
                    0,
                    300
                ]
            }
        },
        {
            "id": 101,
            "name": "Mode 2",
            "slot": 1,
            "config": {
                "interval": [
                    400
                ],
                "fingerStrength": [
                    1,
                    100
                ],
                "autoGrasp": [
                    0,
                    100
                ],
                "emgSpike": [
                    0,
                    300
                ]
            }
        },
        {
            "id": 102,
            "name": "Mode 3",
            "slot": 2,
            "config": {
                "interval": [
                    500
                ],
                "fingerStrength": [
                    1,
                    100
                ],
                "autoGrasp": [
                    0,
                    100
                ],
                "emgSpike": [
                    0,
                    300
                ]
            }
        }
    ]
}
 

Example response (202):


{
    "id": 21,
    "sender_id": 112,
    "recipient_id": 113,
    "device_id": null,
    "meeting_date": "2026-11-03 17:25:00",
    "meeting_type": "online_meeting",
    "contact_email": "mcclure.shea@williamson.biz",
    "status": "new",
    "created_at": "2026-11-03T17:25:00.000000Z",
    "updated_at": "2026-11-03T17:25:00.000000Z",
    "sender": {
        "id": 112,
        "mrn": "YKTUBAU91793726699",
        "name": "Darien Blick",
        "email": "1793726699hansen.magdalena@example.net",
        "language": "en",
        "phone": "+13517866662",
        "phone_country": "EE",
        "phone_verified_at": null,
        "address1": "249 Schuyler Knoll Suite 713",
        "address2": "New Florineburgh, UT 00599",
        "postal_code": "08973-3539",
        "city": "Sauer, Homenick and Reichel",
        "country": "LV",
        "clinic_name": "Sterlingmouth",
        "clinic_location": "90310 Hyman Roads Apt. 499\nNyahberg, TN 84829",
        "image": null,
        "public_image": null,
        "mfa_enabled": 0,
        "mfa_method": null,
        "mfa_verified_to": null,
        "location_id": null,
        "created_by": null,
        "active": 1,
        "is_ambassador": 0,
        "test_account": false,
        "notifications_timezone": null,
        "notifications_at": null,
        "created_at": "2026-11-03T17:25:00.000000Z",
        "updated_at": "2026-11-03T17:25:00.000000Z",
        "invitation_status": null,
        "acadle_invitation_status": null,
        "roles": []
    },
    "recipient": {
        "id": 113,
        "mrn": "XWCE8GBL1793726700",
        "name": "Humberto Kessler",
        "email": "1793726700damore.kaylah@example.com",
        "language": "en",
        "phone": "+1.734.341.4487",
        "phone_country": "AW",
        "phone_verified_at": null,
        "address1": "59748 Bergstrom River Apt. 795",
        "address2": "Coleview, CA 74845",
        "postal_code": "14852",
        "city": "Rice, Zieme and Brekke",
        "country": "NO",
        "clinic_name": "North Cletaport",
        "clinic_location": "6002 Jeremie Fields\nPort Patricia, MN 44983",
        "image": null,
        "public_image": null,
        "mfa_enabled": 0,
        "mfa_method": null,
        "mfa_verified_to": null,
        "location_id": null,
        "created_by": null,
        "active": 1,
        "is_ambassador": 0,
        "test_account": false,
        "notifications_timezone": null,
        "notifications_at": null,
        "created_at": "2026-11-03T17:25:00.000000Z",
        "updated_at": "2026-11-03T17:25:00.000000Z",
        "invitation_status": null,
        "acadle_invitation_status": null,
        "roles": []
    },
    "device": null,
    "messages": [
        {
            "id": 12,
            "ticket_id": 21,
            "sender_id": 114,
            "title": "Prof.",
            "content": "Sunt consequuntur tenetur laboriosam omnis temporibus.",
            "is_read": false,
            "created_at": "2026-11-03T17:25:02.000000Z",
            "updated_at": "2026-11-03T17:25:02.000000Z"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to update config mode",
    "code": "CONFIG_MODES:COPY_TEMPLATE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "CONFIG_MODES:COPY_TEMPLATE:DEVICE_NOT_FOUND"
}
 

Example response (404, Config mode not found):


{
    "message": "Config mode not found",
    "code": "CONFIG_MODES:COPY_TEMPLATE:MODE_NOT_FOUND"
}
 

Example response (404, Config template not found):


{
    "message": "Config template not found",
    "code": "CONFIG_MODES:COPY_TEMPLATE:TEMPLATE_NOT_FOUND"
}
 

Request   

POST api/device/{deviceId}/config-modes/{modeId}/from-template/{templateId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

deviceId   integer   

Device ID. Example: 1

modeId   integer   

Config mode ID. Example: 1

templateId   integer   

Config template ID. Example: 1

Response

Response Fields

id   integer   

Support ticket ID.

sender_id   integer   

ID of the user who created the ticket.

recipient_id   integer   

ID of the recipient user.

device_id   integer   

Associated device ID.

meeting_date   string   

Scheduled meeting date.

meeting_type   string   

Meeting type.

Must be one of:
  • online
  • in-person
contact_email   string   

Contact email address.

status   string   

Ticket status.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

sender   object   

User who created the ticket.

recipient   object   

Recipient user.

device   object   

Associated device.

messages   object[]   

Ticket messages.

Config Notes

API endpoints for config history notes

Get config entry notes list

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/device/1/config/1/notes?user=1&type=public" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/device/1/config/1/notes"
);

const params = {
    "user": "1",
    "type": "public",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 1,
            "config_history_id": 7,
            "user_id": 81,
            "note": "Voluptatibus aut eos non enim.",
            "type": "public",
            "created_at": "2026-11-03T17:24:42.000000Z",
            "updated_at": "2026-11-03T17:24:42.000000Z",
            "author": {
                "id": 81,
                "mrn": "EDFFT92R1793726682",
                "name": "Dr. Sedrick Barton DVM",
                "email": "1793726682njenkins@example.net",
                "language": "en",
                "phone": "+1.458.736.4992",
                "phone_country": "AD",
                "phone_verified_at": null,
                "address1": "349 Schumm Square Suite 415",
                "address2": "Beierport, OR 07676-1867",
                "postal_code": "74574-9903",
                "city": "O'Connell-VonRueden",
                "country": "RO",
                "clinic_name": "Anjalitown",
                "clinic_location": "647 Price Circle Apt. 988\nPort Maybelleshire, ND 29476-4515",
                "image": null,
                "public_image": null,
                "mfa_enabled": 0,
                "mfa_method": null,
                "mfa_verified_to": null,
                "location_id": null,
                "created_by": null,
                "active": 1,
                "is_ambassador": 0,
                "test_account": false,
                "notifications_timezone": null,
                "notifications_at": null,
                "created_at": "2026-11-03T17:24:42.000000Z",
                "updated_at": "2026-11-03T17:24:42.000000Z",
                "invitation_status": null,
                "acadle_invitation_status": null,
                "roles": []
            }
        },
        {
            "id": 2,
            "config_history_id": 8,
            "user_id": 83,
            "note": "In inventore aut minima tempora sint.",
            "type": "public",
            "created_at": "2026-11-03T17:24:43.000000Z",
            "updated_at": "2026-11-03T17:24:43.000000Z",
            "author": {
                "id": 83,
                "mrn": "VDV9SGM61793726683",
                "name": "Madelynn Aufderhar",
                "email": "1793726683ethelyn16@example.net",
                "language": "en",
                "phone": "989-918-4028",
                "phone_country": "ST",
                "phone_verified_at": null,
                "address1": "10252 Gaylord Squares Apt. 492",
                "address2": "Terrellmouth, AZ 88651",
                "postal_code": "65501",
                "city": "Heaney-Brekke",
                "country": "LT",
                "clinic_name": "East Joycebury",
                "clinic_location": "35542 Reichert Squares Apt. 016\nSchadenfurt, WA 64168-6750",
                "image": null,
                "public_image": null,
                "mfa_enabled": 0,
                "mfa_method": null,
                "mfa_verified_to": null,
                "location_id": null,
                "created_by": null,
                "active": 1,
                "is_ambassador": 0,
                "test_account": false,
                "notifications_timezone": null,
                "notifications_at": null,
                "created_at": "2026-11-03T17:24:43.000000Z",
                "updated_at": "2026-11-03T17:24:43.000000Z",
                "invitation_status": null,
                "acadle_invitation_status": null,
                "roles": []
            }
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to access config notes",
    "code": "CONFIG_NOTES:LIST:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "CONFIG_NOTES:LIST:DEVICE_NOT_FOUND"
}
 

Example response (404, Config history entry not found):


{
    "message": "Config history entry not found",
    "code": "CONFIG_NOTES:LIST:HISTORY_ENTRY_NOT_FOUND"
}
 

Request   

GET api/device/{deviceId}/config/{configId}/notes

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

deviceId   integer   

Device ID. Example: 1

configId   integer   

Config history entry ID. Example: 1

Query Parameters

user   integer  optional  

Filter notes by user. Example: 1

type   string  optional  

Filter notes by type (available: public and private) Example: public

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

extend   string  optional  

Comma-separated list of relation extensions (available: author).

sortby   string  optional  

Sort by field (available: date). Default: date, desc.

sortdir   string  optional  

Sort direction (available: asc, desc).

Response

Response Fields

items   object   
id   integer   

Note ID.

config_history_id   integer   

Associated config history entry ID.

user_id   integer   

ID of the user who wrote the note.

note   string   

Note content.

type   string   

Note visibility.

Must be one of:
  • public
  • private
created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

author   object   

User who wrote the note.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

Get config entry note

requires authentication

Returns single config history entry note in response.

Example request:
curl --request GET \
    --get "http://localhost:8000/api/device/1/config/1/notes/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/device/1/config/1/notes/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "id": 3,
    "config_history_id": 9,
    "user_id": 85,
    "note": "Assumenda magnam in modi at sed.",
    "type": "public",
    "created_at": "2026-11-03T17:24:45.000000Z",
    "updated_at": "2026-11-03T17:24:45.000000Z",
    "author": {
        "id": 85,
        "mrn": "95DXZARA1793726684",
        "name": "Iva Howell I",
        "email": "1793726684josue.morissette@example.com",
        "language": "en",
        "phone": "854.248.5179",
        "phone_country": "SB",
        "phone_verified_at": null,
        "address1": "85507 Bartell Squares Apt. 204",
        "address2": "Moenton, MA 61733-0693",
        "postal_code": "24049",
        "city": "Wilkinson, Doyle and Schmidt",
        "country": "HU",
        "clinic_name": "Quitzonfort",
        "clinic_location": "250 Joesph Brooks Apt. 886\nRebeccaton, OR 44016-3886",
        "image": null,
        "public_image": null,
        "mfa_enabled": 0,
        "mfa_method": null,
        "mfa_verified_to": null,
        "location_id": null,
        "created_by": null,
        "active": 1,
        "is_ambassador": 0,
        "test_account": false,
        "notifications_timezone": null,
        "notifications_at": null,
        "created_at": "2026-11-03T17:24:44.000000Z",
        "updated_at": "2026-11-03T17:24:44.000000Z",
        "invitation_status": null,
        "acadle_invitation_status": null,
        "roles": []
    }
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to access config notes",
    "code": "CONFIG_NOTES:GET_ENTRY:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "CONFIG_NOTES:GET_ENTRY:DEVICE_NOT_FOUND"
}
 

Example response (404, Config history entry not found):


{
    "message": "Config history entry not found",
    "code": "CONFIG_NOTES:GET_ENTRY:HISTORY_ENTRY_NOT_FOUND"
}
 

Example response (404, Config history note not found):


{
    "message": "Config history note not found",
    "code": "CONFIG_NOTES:GET_ENTRY:HISTORY_NOTE_NOT_FOUND"
}
 

Request   

GET api/device/{deviceId}/config/{configId}/notes/{noteId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

deviceId   integer   

Device ID. Example: 1

configId   integer   

Config history entry ID. Example: 1

noteId   integer   

Device config entry note ID. Example: 1

Query Parameters

extend   string  optional  

Comma-separated list of relation extensions (available: author).

Response

Response Fields

id   integer   

Note ID.

config_history_id   integer   

Associated config history entry ID.

user_id   integer   

ID of the user who wrote the note.

note   string   

Note content.

type   string   

Note visibility.

Must be one of:
  • public
  • private
created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

author   object   

User who wrote the note.

Create config entry note

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/device/1/config/1/notes" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"note\": \"Voluptatem debitis est fugit deserunt minus officia.\",
    \"type\": \"public\"
}"
const url = new URL(
    "http://localhost:8000/api/device/1/config/1/notes"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "note": "Voluptatem debitis est fugit deserunt minus officia.",
    "type": "public"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "id": 4,
    "config_history_id": 10,
    "user_id": 87,
    "note": "Omnis id eos deserunt qui molestias pariatur.",
    "type": "public",
    "created_at": "2026-11-03T17:24:46.000000Z",
    "updated_at": "2026-11-03T17:24:46.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to add config notes",
    "code": "CONFIG_NOTES:CREATE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "CONFIG_NOTES:CREATE:DEVICE_NOT_FOUND"
}
 

Example response (404, Config history entry not found):


{
    "message": "Config history entry not found",
    "code": "CONFIG_NOTES:CREATE:HISTORY_ENTRY_NOT_FOUND"
}
 

Request   

POST api/device/{deviceId}/config/{configId}/notes

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

deviceId   integer   

Device ID. Example: 1

configId   integer   

Config history entry ID. Example: 1

Body Parameters

note   string  optional  

Note text. Example: Voluptatem debitis est fugit deserunt minus officia.

type   string  optional  

Type of the note. Default: public. Example: public

Must be one of:
  • public
  • private

Response

Response Fields

id   integer   

Note ID.

config_history_id   integer   

Associated config history entry ID.

user_id   integer   

ID of the user who wrote the note.

note   string   

Note content.

type   string   

Note visibility.

Must be one of:
  • public
  • private
created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

author   object   

User who wrote the note.

Delete config note

requires authentication

Example request:
curl --request DELETE \
    "http://localhost:8000/api/device/1/config/1/notes/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/device/1/config/1/notes/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (202, OK):


{
    "message": "Config history note deleted",
    "code": "CONFIG_NOTES:DELETE:DELETED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to delete config notes",
    "code": "CONFIG_NOTES:DELETE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "CONFIG_NOTES:DELETE:DEVICE_NOT_FOUND"
}
 

Example response (404, Config history entry not found):


{
    "message": "Config history entry not found",
    "code": "CONFIG_NOTES:DELETE:HISTORY_ENTRY_NOT_FOUND"
}
 

Example response (404, Config history note not found):


{
    "message": "Config history note not found",
    "code": "CONFIG_NOTES:DELETE:HISTORY_NOTE_NOT_FOUND"
}
 

Request   

DELETE api/device/{deviceId}/config/{configId}/notes/{noteId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

deviceId   integer   

Device ID. Example: 1

configId   integer   

Config history entry ID. Example: 1

noteId   integer   

Device config entry note ID. Example: 1

Config Schema

API endpoints for config schema management

Get config schema

requires authentication

Returns list of config schema entries for given firmware version.

Example request:
curl --request GET \
    --get "http://localhost:8000/api/versions/firmware/1/schema?filter=modes" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/versions/firmware/1/schema"
);

const params = {
    "filter": "modes",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


[
    {
        "id": 1,
        "firmware_id": 7,
        "key": "eos",
        "is_common": 0,
        "type": "firmware",
        "created_at": "2026-11-03T17:26:17.000000Z",
        "updated_at": "2026-11-03T17:26:17.000000Z"
    },
    {
        "id": 2,
        "firmware_id": 9,
        "key": "velit",
        "is_common": 0,
        "type": "firmware",
        "created_at": "2026-11-03T17:26:17.000000Z",
        "updated_at": "2026-11-03T17:26:17.000000Z"
    }
]
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view config schema",
    "code": "CONFIG_SCHEMA:GET:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Firmware version not found):


{
    "message": "Firmware version not found",
    "code": "CONFIG_SCHEMA:GET:FIRMWARE_NOT_FOUND"
}
 

Request   

GET api/versions/firmware/{firmwareId}/schema

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

firmwareId   integer   

Firmware version ID. Example: 1

Query Parameters

filter   string  optional  

Filter entries by type (available: common, modes). By default all entries all returned. Example: modes

Response

Response Fields

id   integer   

Config schema ID.

firmware_id   integer   

Associated firmware version ID.

key   string   

Config schema key.

is_common   boolean   

Whether this key is shared across all modes.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

firmware   object   

Associated firmware version.

Add config schema

requires authentication

Add one or many config schema entries. Each entry is one key in config. Body of this request is simple array of objects:


            [
                {"key": "key_name", "is_common": 1},
                {"key": "another_name", "is_common": 0},
                ...
            ]
        
Example request:
curl --request POST \
    "http://localhost:8000/api/versions/firmware/modi/schema" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "[
    {
        \"key\": \"gripsPosition.0.initial\",
        \"is_common\": 1,
        \"type\": \"firmware\"
    }
]"
const url = new URL(
    "http://localhost:8000/api/versions/firmware/modi/schema"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = [
    {
        "key": "gripsPosition.0.initial",
        "is_common": 1,
        "type": "firmware"
    }
];

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


[
    {
        "id": 3,
        "firmware_id": 11,
        "key": "rem",
        "is_common": 0,
        "type": "firmware",
        "created_at": "2026-11-03T17:26:17.000000Z",
        "updated_at": "2026-11-03T17:26:17.000000Z"
    },
    {
        "id": 4,
        "firmware_id": 13,
        "key": "esse",
        "is_common": 0,
        "type": "firmware",
        "created_at": "2026-11-03T17:26:17.000000Z",
        "updated_at": "2026-11-03T17:26:17.000000Z"
    }
]
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage config schema",
    "code": "CONFIG_SCHEMA:ADD:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Firmware version not found):


{
    "message": "Firmware version not found",
    "code": "CONFIG_SCHEMA:ADD:FIRMWARE_NOT_FOUND"
}
 

Request   

POST api/versions/firmware/{firmwareId}/schema

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

firmwareId   string   

Example: modi

Body Parameters

The request body is an array (object[]`). Each item has the following properties:

key   string  optional  

Config key. Example: gripsPosition.0.initial

is_common   integer  optional  

Information if the key belongs to common config (1) or to modes (0). Default: 1. Example: 1

type   string  optional  

Key type: firmware for keys tied to the hand's firmware config, virtual for keys that are not. Default: firmware. Example: firmware

Response

Response Fields

id   integer   

Config schema ID.

firmware_id   integer   

Associated firmware version ID.

key   string   

Config schema key.

is_common   boolean   

Whether this key is shared across all modes.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

firmware   object   

Associated firmware version.

Delete config schema

requires authentication

Example request:
curl --request DELETE \
    "http://localhost:8000/api/versions/firmware/1/schema/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/versions/firmware/1/schema/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (202, OK):


{
    "message": "Config schema entry deleted",
    "code": "CONFIG_SCHEMA:DELETE:DELETED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage config schema",
    "code": "CONFIG_SCHEMA:DELETE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Firmware version not found):


{
    "message": "Firmware version not found",
    "code": "CONFIG_SCHEMA:DELETE:FIRMWARE_NOT_FOUND"
}
 

Example response (404, Config schema entry not found):


{
    "message": "Config schema entry not found",
    "code": "CONFIG_SCHEMA:DELETE:SCHEMA_NOT_FOUND"
}
 

Request   

DELETE api/versions/firmware/{firmwareId}/schema/{schemaId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

firmwareId   integer   

Firmware version ID. Example: 1

schemaId   integer   

Config schema ID. Example: 1

Config Templates

API endpoints for managing config templates

Get config templates list

requires authentication

Entries where author is present are private and owned by its author. Entries where author is null should be considered as global templates prepared by Aether team.

Example request:
curl --request GET \
    --get "http://localhost:8000/api/config/templates?search=sport&author=1&scope=me" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/config/templates"
);

const params = {
    "search": "sport",
    "author": "1",
    "scope": "me",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 1,
            "name": "Saepe deserunt consectetur vel nihil.",
            "description": "Ut et voluptas eius tempore ratione aut.",
            "author_id": 101,
            "company_id": null,
            "config": "{\"autoGrasp\":[0,100],\"coContractionTimings\":[500,500],\"controlMode\":[0],\"emgGains\":[100,100],\"emgSpike\":[0,300],\"emgThresholds\":[40,0,50,100,70,0,90,100,70,20],\"gripPairsConfig\":[4,1,9,10,11,8,7,3],\"gripSequentialConfig\":[5,10,255,11,255,1,12,4,13,7,9,255],\"gripSwitchingMode\":[3],\"holdOpen\":[1500,1500],\"pulseTimings\":[580,790,240,320],\"softGrip\":[0],\"speedControlStrategy\":[1]}",
            "created_at": "2026-11-03T17:24:53.000000Z",
            "updated_at": "2026-11-03T17:24:53.000000Z",
            "author": {
                "id": 101,
                "mrn": "D397VZXV1793726693",
                "name": "Ms. Stacey Jerde",
                "email": "1793726693eda.rolfson@example.org",
                "language": "en",
                "phone": "+15349430852",
                "phone_country": "BA",
                "phone_verified_at": null,
                "address1": "9859 Mae Mills Apt. 103",
                "address2": "East Kelton, AR 47565",
                "postal_code": "20700-1212",
                "city": "Predovic-Beer",
                "country": "NO",
                "clinic_name": "North Jon",
                "clinic_location": "58183 Delphia Skyway Apt. 985\nJerrodville, NM 13900",
                "image": null,
                "public_image": null,
                "mfa_enabled": 0,
                "mfa_method": null,
                "mfa_verified_to": null,
                "location_id": null,
                "created_by": null,
                "active": 1,
                "is_ambassador": 0,
                "test_account": false,
                "notifications_timezone": null,
                "notifications_at": null,
                "created_at": "2026-11-03T17:24:53.000000Z",
                "updated_at": "2026-11-03T17:24:53.000000Z",
                "invitation_status": null,
                "acadle_invitation_status": null,
                "roles": []
            }
        },
        {
            "id": 2,
            "name": "Quasi sunt ipsam ut illo ut perferendis molestias modi.",
            "description": "Distinctio rerum sequi tempore adipisci.",
            "author_id": 102,
            "company_id": null,
            "config": "{\"autoGrasp\":[0,100],\"coContractionTimings\":[300,100],\"controlMode\":[1],\"emgGains\":[100,100],\"emgSpike\":[1,300],\"emgThresholds\":[50,50,10,60,20,70,90,30,60,0],\"gripPairsConfig\":[4,6,9,5,11,1,13,12],\"gripSequentialConfig\":[6,255,5,11,255,13,255,1,255,7,4,12],\"gripSwitchingMode\":[2],\"holdOpen\":[1500,2500],\"pulseTimings\":[360,540,860,350],\"softGrip\":[1],\"speedControlStrategy\":[0]}",
            "created_at": "2026-11-03T17:24:54.000000Z",
            "updated_at": "2026-11-03T17:24:54.000000Z",
            "author": {
                "id": 102,
                "mrn": "7LEU7UV51793726693",
                "name": "Sharon Berge I",
                "email": "1793726693jammie.flatley@example.com",
                "language": "en",
                "phone": "+14846494001",
                "phone_country": "AE",
                "phone_verified_at": null,
                "address1": "561 Kasey Pass",
                "address2": "Aliyafort, KY 84600-9504",
                "postal_code": "42474",
                "city": "Marquardt, Reichert and Kirlin",
                "country": "CZ",
                "clinic_name": "Balistreristad",
                "clinic_location": "75847 Tina Fork Suite 440\nNew Hattieberg, NC 95405",
                "image": null,
                "public_image": null,
                "mfa_enabled": 0,
                "mfa_method": null,
                "mfa_verified_to": null,
                "location_id": null,
                "created_by": null,
                "active": 1,
                "is_ambassador": 0,
                "test_account": false,
                "notifications_timezone": null,
                "notifications_at": null,
                "created_at": "2026-11-03T17:24:53.000000Z",
                "updated_at": "2026-11-03T17:24:53.000000Z",
                "invitation_status": null,
                "acadle_invitation_status": null,
                "roles": []
            }
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to list config templates",
    "code": "CONFIG_TEMPLATES:LIST:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/config/templates

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

search   string  optional  

Filter config templates by name. Example: sport

author   integer  optional  

Super Admin only: Filter config templates by author. Example: 1

scope   string  optional  

ClinicAdmin/Clinician/ClinicianSupport only: Filter config templates by scope. The value must be one of:

  • me (entries where current user is author),
  • global (entries added by Aether).
Example: `me`
perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

extend   string  optional  

Comma-separated list of relation extensions (available: author).

Response

Response Fields

items   object   
id   integer   

Config template ID.

name   string   

Template name.

description   string   

Template description.

author_id   integer   

ID of the user who created the template.

company_id   integer   

Associated company ID.

config   string   

Serialized config data.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

author   object   

User who created the template.

notes   object[]   

Notes attached to this template.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

Get config template

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/config/templates/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/config/templates/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "id": 3,
    "name": "Dolorum molestiae quia nisi aut.",
    "description": "Voluptate molestiae reprehenderit culpa voluptatem ea unde.",
    "author_id": 103,
    "company_id": null,
    "config": "{\"autoGrasp\":[1,0],\"coContractionTimings\":[300,200],\"controlMode\":[0],\"emgGains\":[100,100],\"emgSpike\":[0,300],\"emgThresholds\":[100,80,10,70,50,0,90,0,50,0],\"gripPairsConfig\":[5,1,10,2,3,12,4,13],\"gripSequentialConfig\":[12,9,255,6,7,255,255,4,3,11,8,255],\"gripSwitchingMode\":[2],\"holdOpen\":[2500,2500],\"pulseTimings\":[270,980,50,350],\"softGrip\":[1],\"speedControlStrategy\":[0]}",
    "created_at": "2026-11-03T17:24:54.000000Z",
    "updated_at": "2026-11-03T17:24:54.000000Z",
    "author": {
        "id": 103,
        "mrn": "H35YDP431793726694",
        "name": "Kaylah Rath",
        "email": "1793726694gloria.rau@example.net",
        "language": "en",
        "phone": "1-708-867-9787",
        "phone_country": "CZ",
        "phone_verified_at": null,
        "address1": "82857 Celine Track",
        "address2": "New Electa, IN 28005-4320",
        "postal_code": "73839-2240",
        "city": "Lang-Jaskolski",
        "country": "LU",
        "clinic_name": "South Caden",
        "clinic_location": "1790 Berge Motorway\nPort Roberta, DE 70305",
        "image": null,
        "public_image": null,
        "mfa_enabled": 0,
        "mfa_method": null,
        "mfa_verified_to": null,
        "location_id": null,
        "created_by": null,
        "active": 1,
        "is_ambassador": 0,
        "test_account": false,
        "notifications_timezone": null,
        "notifications_at": null,
        "created_at": "2026-11-03T17:24:54.000000Z",
        "updated_at": "2026-11-03T17:24:54.000000Z",
        "invitation_status": null,
        "acadle_invitation_status": null,
        "roles": []
    }
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view config template",
    "code": "CONFIG_TEMPLATES:GET:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Config template not found):


{
    "message": "Config template not found",
    "code": "CONFIG_TEMPLATES:GET:TEMPLATE_NOT_FOUND"
}
 

Request   

GET api/config/templates/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Config template ID. Example: 1

Query Parameters

extend   string  optional  

Comma-separated list of relation extensions (available: author).

Response

Response Fields

id   integer   

Config template ID.

name   string   

Template name.

description   string   

Template description.

author_id   integer   

ID of the user who created the template.

company_id   integer   

Associated company ID.

config   string   

Serialized config data.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

author   object   

User who created the template.

notes   object[]   

Notes attached to this template.

Create new config template

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/config/templates" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Default config for Aether Zeus\",
    \"description\": \"Description of the config template.\",
    \"owner\": \"company\",
    \"author\": 1,
    \"config\": \"{\\\"param_1\\\": [100, 200], \\\"param_2\\\": [100, 200, 300]}\"
}"
const url = new URL(
    "http://localhost:8000/api/config/templates"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Default config for Aether Zeus",
    "description": "Description of the config template.",
    "owner": "company",
    "author": 1,
    "config": "{\"param_1\": [100, 200], \"param_2\": [100, 200, 300]}"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 4,
    "name": "Dolores recusandae iusto omnis dicta earum sit.",
    "description": "Ratione officia similique quod recusandae.",
    "author_id": 104,
    "company_id": null,
    "config": "{\"autoGrasp\":[0,100],\"coContractionTimings\":[200,200],\"controlMode\":[0],\"emgGains\":[100,100],\"emgSpike\":[1,300],\"emgThresholds\":[10,40,80,90,10,100,30,40,90,40],\"gripPairsConfig\":[12,7,4,10,6,1,9,3],\"gripSequentialConfig\":[255,6,10,255,7,11,255,255,255,255,13,2],\"gripSwitchingMode\":[3],\"holdOpen\":[1500,2500],\"pulseTimings\":[900,840,910,810],\"softGrip\":[1],\"speedControlStrategy\":[0]}",
    "created_at": "2026-11-03T17:24:55.000000Z",
    "updated_at": "2026-11-03T17:24:55.000000Z",
    "author": {
        "id": 104,
        "mrn": "5EKEXU6X1793726695",
        "name": "Marie Hirthe V",
        "email": "1793726695okeefe.ariel@example.net",
        "language": "en",
        "phone": "+1-234-282-8163",
        "phone_country": "TM",
        "phone_verified_at": null,
        "address1": "904 Mueller Mills",
        "address2": "Lindaview, MS 79331",
        "postal_code": "22728-1867",
        "city": "Beatty and Sons",
        "country": "DK",
        "clinic_name": "Juliemouth",
        "clinic_location": "7787 Marvin Green Suite 137\nKeeblerside, KY 35659-5152",
        "image": null,
        "public_image": null,
        "mfa_enabled": 0,
        "mfa_method": null,
        "mfa_verified_to": null,
        "location_id": null,
        "created_by": null,
        "active": 1,
        "is_ambassador": 0,
        "test_account": false,
        "notifications_timezone": null,
        "notifications_at": null,
        "created_at": "2026-11-03T17:24:55.000000Z",
        "updated_at": "2026-11-03T17:24:55.000000Z",
        "invitation_status": null,
        "acadle_invitation_status": null,
        "roles": []
    }
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to create config template",
    "code": "CONFIG_TEMPLATES:CREATE:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/config/templates

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

name   string   

Name of the config template. Example: Default config for Aether Zeus

description   string  optional  

Description of the config template. Example: Description of the config template.

owner   string  optional  

Mark config template owned by clinician or company. Default: me (clinician). Example: company

Must be one of:
  • me
  • company
author   string  optional  

Super Admin only: User ID to be the author of template. The id of an existing record in the App\Models\User table. Example: 1

config   string   

Full config. MUST_BE_JSON. Example: {"param_1": [100, 200], "param_2": [100, 200, 300]}

Response

Response Fields

id   integer   

Config template ID.

name   string   

Template name.

description   string   

Template description.

author_id   integer   

ID of the user who created the template.

company_id   integer   

Associated company ID.

config   string   

Serialized config data.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

author   object   

User who created the template.

notes   object[]   

Notes attached to this template.

Update config template

requires authentication

Example request:
curl --request PUT \
    "http://localhost:8000/api/config/templates/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Default config for Aether Zeus\",
    \"description\": \"Description of the config template.\",
    \"owner\": \"company\",
    \"author\": 1,
    \"config\": \"{\\\"param_1\\\": [100, 200], \\\"param_2\\\": [100, 200, 300]}\"
}"
const url = new URL(
    "http://localhost:8000/api/config/templates/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Default config for Aether Zeus",
    "description": "Description of the config template.",
    "owner": "company",
    "author": 1,
    "config": "{\"param_1\": [100, 200], \"param_2\": [100, 200, 300]}"
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (202):


{
    "id": 5,
    "name": "Autem reiciendis sit quo in ipsam.",
    "description": "Et voluptatem qui et eum.",
    "author_id": 105,
    "company_id": null,
    "config": "{\"autoGrasp\":[0,100],\"coContractionTimings\":[300,100],\"controlMode\":[1],\"emgGains\":[100,100],\"emgSpike\":[0,300],\"emgThresholds\":[60,50,30,40,60,50,100,100,60,100],\"gripPairsConfig\":[9,1,12,13,2,4,5,8],\"gripSequentialConfig\":[8,11,255,7,255,12,255,1,9,2,255,4],\"gripSwitchingMode\":[1],\"holdOpen\":[2000,2500],\"pulseTimings\":[30,820,170,530],\"softGrip\":[1],\"speedControlStrategy\":[0]}",
    "created_at": "2026-11-03T17:24:56.000000Z",
    "updated_at": "2026-11-03T17:24:56.000000Z",
    "author": {
        "id": 105,
        "mrn": "NJNAB3FM1793726695",
        "name": "Wiley Paucek",
        "email": "1793726695fred.simonis@example.net",
        "language": "en",
        "phone": "820.640.4731",
        "phone_country": "DE",
        "phone_verified_at": null,
        "address1": "770 Flavie Ridge",
        "address2": "Murphyshire, AL 47207",
        "postal_code": "97491-4525",
        "city": "Bins-Vandervort",
        "country": "SI",
        "clinic_name": "New Michael",
        "clinic_location": "772 Yasmine Divide\nMullermouth, MI 63634",
        "image": null,
        "public_image": null,
        "mfa_enabled": 0,
        "mfa_method": null,
        "mfa_verified_to": null,
        "location_id": null,
        "created_by": null,
        "active": 1,
        "is_ambassador": 0,
        "test_account": false,
        "notifications_timezone": null,
        "notifications_at": null,
        "created_at": "2026-11-03T17:24:55.000000Z",
        "updated_at": "2026-11-03T17:24:55.000000Z",
        "invitation_status": null,
        "acadle_invitation_status": null,
        "roles": []
    }
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to update config template",
    "code": "CONFIG_TEMPLATES:UPDATE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Config template not found):


{
    "message": "Config template not found",
    "code": "CONFIG_TEMPLATES:UPDATE:TEMPLATE_NOT_FOUND"
}
 

Request   

PUT api/config/templates/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Config template ID. Example: 1

Body Parameters

name   string  optional  

Name of the config template. Example: Default config for Aether Zeus

description   string  optional  

Description of the config template. Example: Description of the config template.

owner   string  optional  

Mark config template owned by clinician or company. Default: me (clinician). Example: company

Must be one of:
  • me
  • company
author   string  optional  

Super Admin only: User ID to be the author of template. The id of an existing record in the App\Models\User table. Example: 1

config   string  optional  

Full config. MUST_BE_JSON. Example: {"param_1": [100, 200], "param_2": [100, 200, 300]}

Response

Response Fields

id   integer   

Config template ID.

name   string   

Template name.

description   string   

Template description.

author_id   integer   

ID of the user who created the template.

company_id   integer   

Associated company ID.

config   string   

Serialized config data.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

author   object   

User who created the template.

notes   object[]   

Notes attached to this template.

Delete config template

requires authentication

Example request:
curl --request DELETE \
    "http://localhost:8000/api/config/templates/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/config/templates/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (202, OK):


{
    "message": "Config template deleted",
    "code": "CONFIG_TEMPLATES:DELETE:DELETED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to delete config template",
    "code": "CONFIG_TEMPLATES:DELETE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Config template not found):


{
    "message": "Config template not found",
    "code": "CONFIG_TEMPLATES:DELETE:TEMPLATE_NOT_FOUND"
}
 

Request   

DELETE api/config/templates/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Config template ID. Example: 1

Config Templates Notes

API endpoints for config templates notes

Get config templates notes list

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/config/templates/1/notes?user=1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/config/templates/1/notes"
);

const params = {
    "user": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 1,
            "template_id": 6,
            "user_id": 106,
            "note": "Tempore eum saepe officia quia aut.",
            "created_at": "2026-11-03T17:24:56.000000Z",
            "updated_at": "2026-11-03T17:24:56.000000Z",
            "author": {
                "id": 106,
                "mrn": "R8FWVCDN1793726696",
                "name": "Leanna Heaney",
                "email": "1793726696annabel.wyman@example.com",
                "language": "en",
                "phone": "1-252-925-4778",
                "phone_country": "GB",
                "phone_verified_at": null,
                "address1": "697 Rosa Loop Suite 561",
                "address2": "Hauckton, CA 03842-7430",
                "postal_code": "87047-5864",
                "city": "Carroll LLC",
                "country": "ES",
                "clinic_name": "Isaiahfort",
                "clinic_location": "9711 Maxime Ville Apt. 579\nNew Jaleelchester, DC 55211-6048",
                "image": null,
                "public_image": null,
                "mfa_enabled": 0,
                "mfa_method": null,
                "mfa_verified_to": null,
                "location_id": null,
                "created_by": null,
                "active": 1,
                "is_ambassador": 0,
                "test_account": false,
                "notifications_timezone": null,
                "notifications_at": null,
                "created_at": "2026-11-03T17:24:56.000000Z",
                "updated_at": "2026-11-03T17:24:56.000000Z",
                "invitation_status": null,
                "acadle_invitation_status": null,
                "roles": []
            }
        },
        {
            "id": 2,
            "template_id": 7,
            "user_id": 107,
            "note": "Quia laudantium ad ad ratione reiciendis laudantium.",
            "created_at": "2026-11-03T17:24:57.000000Z",
            "updated_at": "2026-11-03T17:24:57.000000Z",
            "author": {
                "id": 107,
                "mrn": "3VST9QHA1793726696",
                "name": "Dr. Kenyon Bergstrom PhD",
                "email": "1793726696rickie72@example.net",
                "language": "en",
                "phone": "+1-708-924-4546",
                "phone_country": "IM",
                "phone_verified_at": null,
                "address1": "44913 Vance Spurs",
                "address2": "Johnstonchester, MN 50921",
                "postal_code": "92271",
                "city": "Berge, O'Kon and Herman",
                "country": "IT",
                "clinic_name": "West Merle",
                "clinic_location": "252 Robel Ford\nFelixhaven, CO 42514-4474",
                "image": null,
                "public_image": null,
                "mfa_enabled": 0,
                "mfa_method": null,
                "mfa_verified_to": null,
                "location_id": null,
                "created_by": null,
                "active": 1,
                "is_ambassador": 0,
                "test_account": false,
                "notifications_timezone": null,
                "notifications_at": null,
                "created_at": "2026-11-03T17:24:56.000000Z",
                "updated_at": "2026-11-03T17:24:56.000000Z",
                "invitation_status": null,
                "acadle_invitation_status": null,
                "roles": []
            }
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to access config templates notes",
    "code": "CONFIG_TEMPLATE_NOTES:LIST:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Config template not found):


{
    "message": "Config template not found",
    "code": "CONFIG_TEMPLATE_NOTES:LIST:TEMPLATE_NOT_FOUND"
}
 

Request   

GET api/config/templates/{id}/notes

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Config template ID. Example: 1

Query Parameters

user   integer  optional  

Filter notes by user. Example: 1

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

extend   string  optional  

Comma-separated list of relation extensions (available: author).

sortby   string  optional  

Sort by field (available: date). Default: date, desc.

sortdir   string  optional  

Sort direction (available: asc, desc).

Response

Response Fields

items   object   
id   integer   

Note ID.

template_id   integer   

Associated config template ID.

user_id   integer   

ID of the user who wrote the note.

note   string   

Note content.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

author   object   

User who wrote the note.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

Get config template note

requires authentication

Returns single config template note in response.

Example request:
curl --request GET \
    --get "http://localhost:8000/api/config/templates/1/notes/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/config/templates/1/notes/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "id": 3,
    "template_id": 8,
    "user_id": 108,
    "note": "Ex sapiente ullam suscipit sed.",
    "created_at": "2026-11-03T17:24:58.000000Z",
    "updated_at": "2026-11-03T17:24:58.000000Z",
    "author": {
        "id": 108,
        "mrn": "D9ZX6VAL1793726697",
        "name": "Mr. Darrin Beer",
        "email": "1793726697lamar.vonrueden@example.com",
        "language": "en",
        "phone": "+19293660534",
        "phone_country": "KR",
        "phone_verified_at": null,
        "address1": "801 Lourdes Overpass Apt. 234",
        "address2": "Lake Lonniechester, TN 51621",
        "postal_code": "59798-2398",
        "city": "Hahn-Hyatt",
        "country": "LV",
        "clinic_name": "Croninview",
        "clinic_location": "21328 Smitham Ridge Apt. 990\nEast Herminioport, VA 01110",
        "image": null,
        "public_image": null,
        "mfa_enabled": 0,
        "mfa_method": null,
        "mfa_verified_to": null,
        "location_id": null,
        "created_by": null,
        "active": 1,
        "is_ambassador": 0,
        "test_account": false,
        "notifications_timezone": null,
        "notifications_at": null,
        "created_at": "2026-11-03T17:24:57.000000Z",
        "updated_at": "2026-11-03T17:24:57.000000Z",
        "invitation_status": null,
        "acadle_invitation_status": null,
        "roles": []
    }
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to access config templates notes",
    "code": "CONFIG_TEMPLATE_NOTES:GET:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Config template not found):


{
    "message": "Config template not found",
    "code": "CONFIG_TEMPLATE_NOTES:GET:TEMPLATE_NOT_FOUND"
}
 

Example response (404, Config template note not found):


{
    "message": "Config template note not found",
    "code": "CONFIG_TEMPLATE_NOTES:GET:NOTE_NOT_FOUND"
}
 

Request   

GET api/config/templates/{id}/notes/{noteId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Config template ID. Example: 1

noteId   integer   

Config template note ID. Example: 1

Query Parameters

extend   string  optional  

Comma-separated list of relation extensions (available: author).

Response

Response Fields

id   integer   

Note ID.

template_id   integer   

Associated config template ID.

user_id   integer   

ID of the user who wrote the note.

note   string   

Note content.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

author   object   

User who wrote the note.

Create new config template note

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/config/templates/1/notes" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"note\": \"Atque illum quidem rerum ea.\"
}"
const url = new URL(
    "http://localhost:8000/api/config/templates/1/notes"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "note": "Atque illum quidem rerum ea."
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 4,
    "template_id": 9,
    "user_id": 109,
    "note": "Voluptatem eveniet ipsam est sed.",
    "created_at": "2026-11-03T17:24:58.000000Z",
    "updated_at": "2026-11-03T17:24:58.000000Z",
    "author": {
        "id": 109,
        "mrn": "A7NLB7Z71793726698",
        "name": "Ms. Adriana Schulist V",
        "email": "1793726698sschoen@example.net",
        "language": "en",
        "phone": "339-527-6557",
        "phone_country": "CO",
        "phone_verified_at": null,
        "address1": "84756 Betsy Keys",
        "address2": "North Stone, NH 32751",
        "postal_code": "00703-4999",
        "city": "Berge, Lemke and Keeling",
        "country": "IN",
        "clinic_name": "Barrowschester",
        "clinic_location": "11230 Bulah Lane\nKaylahmouth, CT 75599-3185",
        "image": null,
        "public_image": null,
        "mfa_enabled": 0,
        "mfa_method": null,
        "mfa_verified_to": null,
        "location_id": null,
        "created_by": null,
        "active": 1,
        "is_ambassador": 0,
        "test_account": false,
        "notifications_timezone": null,
        "notifications_at": null,
        "created_at": "2026-11-03T17:24:58.000000Z",
        "updated_at": "2026-11-03T17:24:58.000000Z",
        "invitation_status": null,
        "acadle_invitation_status": null,
        "roles": []
    }
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to add config templates notes",
    "code": "CONFIG_TEMPLATE_NOTES:CREATE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Config template not found):


{
    "message": "Config template not found",
    "code": "CONFIG_TEMPLATE_NOTES:CREATE:TEMPLATE_NOT_FOUND"
}
 

Request   

POST api/config/templates/{id}/notes

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Config template ID. Example: 1

Body Parameters

note   string  optional  

Note text. Example: Atque illum quidem rerum ea.

Response

Response Fields

id   integer   

Note ID.

template_id   integer   

Associated config template ID.

user_id   integer   

ID of the user who wrote the note.

note   string   

Note content.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

author   object   

User who wrote the note.

Delete config template note

requires authentication

Example request:
curl --request DELETE \
    "http://localhost:8000/api/config/templates/1/notes/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/config/templates/1/notes/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (202, OK):


{
    "message": "Config template note deleted",
    "code": "CONFIG_TEMPLATE_NOTES:DELETE:DELETED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to delete config templates notes",
    "code": "CONFIG_TEMPLATE_NOTES:DELETE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Config template not found):


{
    "message": "Config template not found",
    "code": "CONFIG_TEMPLATE_NOTES:DELETE:TEMPLATE_NOT_FOUND"
}
 

Example response (404, Config template note not found):


{
    "message": "Config template note not found",
    "code": "CONFIG_TEMPLATE_NOTES:DELETE:NOTE_NOT_FOUND"
}
 

Request   

DELETE api/config/templates/{id}/notes/{noteId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Config template ID. Example: 1

noteId   integer   

Config template note ID. Example: 1

Custom Grips

API endpoints for custom grips management

List custom grips templates

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/custom-grips-templates" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/custom-grips-templates"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 1,
            "user_id": 260,
            "name": "julia76",
            "initial_position": "[50, 50, 50, 50, 50]",
            "limit_position": "[900, 900, 900, 900, 900]",
            "active_fingers": "[0, 1, 1, 1, 1]",
            "created_at": "2026-11-03T17:26:19.000000Z",
            "updated_at": "2026-11-03T17:26:19.000000Z"
        },
        {
            "id": 2,
            "user_id": 261,
            "name": "irving.wisoky",
            "initial_position": "[50, 50, 50, 50, 50]",
            "limit_position": "[900, 900, 900, 900, 900]",
            "active_fingers": "[0, 1, 1, 1, 1]",
            "created_at": "2026-11-03T17:26:19.000000Z",
            "updated_at": "2026-11-03T17:26:19.000000Z"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage custom grips templates",
    "code": "CUSTOM_GRIPS_TEMPLATES:LIST:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/custom-grips-templates

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

Response

Response Fields

items   object   
id   integer   

Custom grip template ID.

user_id   integer   

Owner user ID.

name   string   

Template name.

initial_position   string   

Initial finger positions.

limit_position   string   

Limit finger positions.

active_fingers   string   

Active fingers configuration.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

Create custom grip template

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/custom-grips-templates" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Custom Grip Template 1\",
    \"initial_position\": \"[50, 50, 50, 50, 50]\",
    \"limit_position\": \"[900, 900, 900, 900, 900]\",
    \"active_fingers\": \"[0, 1, 1, 1, 1]\"
}"
const url = new URL(
    "http://localhost:8000/api/custom-grips-templates"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Custom Grip Template 1",
    "initial_position": "[50, 50, 50, 50, 50]",
    "limit_position": "[900, 900, 900, 900, 900]",
    "active_fingers": "[0, 1, 1, 1, 1]"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 3,
    "user_id": 262,
    "name": "lynch.josianne",
    "initial_position": "[50, 50, 50, 50, 50]",
    "limit_position": "[900, 900, 900, 900, 900]",
    "active_fingers": "[0, 1, 1, 1, 1]",
    "created_at": "2026-11-03T17:26:20.000000Z",
    "updated_at": "2026-11-03T17:26:20.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage custom grips templates",
    "code": "CUSTOM_GRIPS_TEMPLATES:CREATE:INSUFFICIENT_PERMISSION"
}
 

Example response (403, Custom grip template name in use):


{
    "message": "Custom grip template name already in use",
    "code": "CUSTOM_GRIPS_TEMPLATES:CREATE:NAME_IN_USE"
}
 

Request   

POST api/custom-grips-templates

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

name   string   

Name of custom grip template. Example: Custom Grip Template 1

initial_position   string   

Grip initial position. Value should be a string containing the array of values. Example: "[50, 50, 50, 50, 50]". Must start with one of [ Must end with one of ]. Example: [50, 50, 50, 50, 50]

limit_position   string   

Grip limit position. Value should be a string containing the array of values. Example: "[900, 900, 900, 900, 900]". Must start with one of [ Must end with one of ]. Example: [900, 900, 900, 900, 900]

active_fingers   string   

Grip active fingers. Value should be a string containing the array of values. Example: "[0, 1, 1, 1, 1]". Must start with one of [ Must end with one of ]. Example: [0, 1, 1, 1, 1]

Response

Response Fields

id   integer   

Custom grip template ID.

user_id   integer   

Owner user ID.

name   string   

Template name.

initial_position   string   

Initial finger positions.

limit_position   string   

Limit finger positions.

active_fingers   string   

Active fingers configuration.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Delete custom grip template

requires authentication

Example request:
curl --request DELETE \
    "http://localhost:8000/api/custom-grips-templates/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/custom-grips-templates/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (202, OK):


{
    "message": "Custom grip deleted",
    "code": "CUSTOM_GRIPS_TEMPLATES:DELETE:DELETED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage custom grips templates",
    "code": "CUSTOM_GRIPS_TEMPLATES:DELETE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Custom grip template not found):


{
    "message": "Custom grip template not found",
    "code": "CUSTOM_GRIPS_TEMPLATES:DELETE:TEMPLATE_NOT_FOUND"
}
 

Request   

DELETE api/custom-grips-templates/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Custom Grip Template ID. Example: 1

List custom grips

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/device/1/custom-grips" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/device/1/custom-grips"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 1,
            "device_id": 143,
            "name": "christopher54",
            "opposed": 1,
            "grip_number": 0,
            "created_at": "2026-11-03T17:26:20.000000Z",
            "updated_at": "2026-11-03T17:26:20.000000Z"
        },
        {
            "id": 2,
            "device_id": 144,
            "name": "priscilla33",
            "opposed": 1,
            "grip_number": 0,
            "created_at": "2026-11-03T17:26:20.000000Z",
            "updated_at": "2026-11-03T17:26:20.000000Z"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view custom grips",
    "code": "CUSTOM_GRIPS:LIST:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "CUSTOM_GRIPS:LIST:DEVICE_NOT_FOUND"
}
 

Request   

GET api/device/{deviceId}/custom-grips

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

deviceId   integer   

Device ID. Example: 1

Query Parameters

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

Response

Response Fields

items   object   
id   integer   

Custom grip ID.

device_id   integer   

Associated device ID.

name   string   

Custom grip name.

opposed   boolean   

Whether the grip is opposed.

grip_number   integer   

Grip slot number.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

Create custom grip

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/device/1/custom-grips" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Custom Grip 1\",
    \"opposed\": true,
    \"grip_number\": 1
}"
const url = new URL(
    "http://localhost:8000/api/device/1/custom-grips"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Custom Grip 1",
    "opposed": true,
    "grip_number": 1
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 3,
    "device_id": 145,
    "name": "kirstin55",
    "opposed": 1,
    "grip_number": 0,
    "created_at": "2026-11-03T17:26:20.000000Z",
    "updated_at": "2026-11-03T17:26:20.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage custom grips",
    "code": "CUSTOM_GRIPS:CREATE:INSUFFICIENT_PERMISSION"
}
 

Example response (403, Custom grip name in use):


{
    "message": "Custom grip name already in use",
    "code": "CUSTOM_GRIPS:CREATE:NAME_IN_USE"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "CUSTOM_GRIPS:CREATE:DEVICE_NOT_FOUND"
}
 

Request   

POST api/device/{deviceId}/custom-grips

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

deviceId   integer   

Device ID. Example: 1

Body Parameters

name   string   

Name of custom grip. Example: Custom Grip 1

opposed   boolean   

Grip opposed status. Example: true

grip_number   integer   

Grip number (not ID). Example: 1

Response

Response Fields

id   integer   

Custom grip ID.

device_id   integer   

Associated device ID.

name   string   

Custom grip name.

opposed   boolean   

Whether the grip is opposed.

grip_number   integer   

Grip slot number.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Update custom grip

requires authentication

Example request:
curl --request PUT \
    "http://localhost:8000/api/device/1/custom-grips/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Custom Grip 1\",
    \"opposed\": true,
    \"grip_number\": 1
}"
const url = new URL(
    "http://localhost:8000/api/device/1/custom-grips/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Custom Grip 1",
    "opposed": true,
    "grip_number": 1
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (202):


{
    "id": 4,
    "device_id": 146,
    "name": "ybode",
    "opposed": 1,
    "grip_number": 0,
    "created_at": "2026-11-03T17:26:20.000000Z",
    "updated_at": "2026-11-03T17:26:20.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage custom grips",
    "code": "CUSTOM_GRIPS:UPDATE:INSUFFICIENT_PERMISSION"
}
 

Example response (403, Custom grip name in use):


{
    "message": "Custom grip name already in use",
    "code": "CUSTOM_GRIPS:UPDATE:NAME_IN_USE"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "CUSTOM_GRIPS:UPDATE:DEVICE_NOT_FOUND"
}
 

Example response (404, Custom grip not found):


{
    "message": "Custom grip not found",
    "code": "CUSTOM_GRIPS:UPDATE:GRIP_NOT_FOUND"
}
 

Request   

PUT api/device/{deviceId}/custom-grips/{gripId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

deviceId   integer   

Device ID. Example: 1

gripId   integer   

Custom Grip ID. Example: 1

Body Parameters

name   string  optional  

Name of custom grip. Example: Custom Grip 1

opposed   boolean  optional  

Grip opposed status. Example: true

grip_number   integer  optional  

Grip number (not ID). Example: 1

Response

Response Fields

id   integer   

Custom grip ID.

device_id   integer   

Associated device ID.

name   string   

Custom grip name.

opposed   boolean   

Whether the grip is opposed.

grip_number   integer   

Grip slot number.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Delete custom grip

requires authentication

Example request:
curl --request DELETE \
    "http://localhost:8000/api/device/1/custom-grips/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/device/1/custom-grips/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (202, OK):


{
    "message": "Custom grip template deleted",
    "code": "CUSTOM_GRIPS:DELETE:DELETED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage custom grips",
    "code": "CUSTOM_GRIPS:DELETE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "CUSTOM_GRIPS:DELETE:DEVICE_NOT_FOUND"
}
 

Example response (404, Custom grip not found):


{
    "message": "Custom grip not found",
    "code": "CUSTOM_GRIPS:DELETE:GRIP_NOT_FOUND"
}
 

Request   

DELETE api/device/{deviceId}/custom-grips/{gripId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

deviceId   integer   

Device ID. Example: 1

gripId   integer   

Custom Grip ID. Example: 1

Device Measurements

API endpoints for device measurement history

List device measurements

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/devices/1/measurements" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/devices/1/measurements"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 1,
            "device_id": 17,
            "data": {
                "unit": "cm",
                "length": 43.72
            },
            "created_at": "2026-11-03T17:24:20.000000Z"
        },
        {
            "id": 2,
            "device_id": 18,
            "data": {
                "unit": "cm",
                "length": 26.83
            },
            "created_at": "2026-11-03T17:24:20.000000Z"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view device measurements",
    "code": "DEVICE_MEASUREMENTS:LIST:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "DEVICE_MEASUREMENTS:LIST:DEVICE_NOT_FOUND"
}
 

Request   

GET api/devices/{deviceId}/measurements

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

deviceId   integer   

Device ID. Example: 1

Query Parameters

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

Create device measurement

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/devices/1/measurements" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"data\": {
        \"length\": 25.4,
        \"unit\": \"cm\"
    }
}"
const url = new URL(
    "http://localhost:8000/api/devices/1/measurements"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "data": {
        "length": 25.4,
        "unit": "cm"
    }
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 3,
    "device_id": 19,
    "data": {
        "unit": "cm",
        "length": 42.97
    },
    "created_at": "2026-11-03T17:24:20.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to create device measurements",
    "code": "DEVICE_MEASUREMENTS:CREATE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "DEVICE_MEASUREMENTS:CREATE:DEVICE_NOT_FOUND"
}
 

Request   

POST api/devices/{deviceId}/measurements

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

deviceId   integer   

Device ID. Example: 1

Body Parameters

data   object   

Measurement payload (any JSON object).

Delete device measurement

requires authentication

Example request:
curl --request DELETE \
    "http://localhost:8000/api/measurements/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/measurements/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (202, OK):


{
    "message": "Device measurement deleted",
    "code": "DEVICE_MEASUREMENTS:DELETE:DELETED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to delete device measurements",
    "code": "DEVICE_MEASUREMENTS:DELETE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Measurement not found):


{
    "message": "Device measurement not found",
    "code": "DEVICE_MEASUREMENTS:DELETE:MEASUREMENT_NOT_FOUND"
}
 

Request   

DELETE api/measurements/{measurementId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

measurementId   integer   

Measurement ID. Example: 1

Device Models

API endpoints for device models management

Get device models list

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/devices/models?active=1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/devices/models"
);

const params = {
    "active": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


[
    {
        "id": 3,
        "name": "Zeus hand v1",
        "type": "leg",
        "orientation": "right",
        "active": 1,
        "created_at": "2026-11-03T17:24:17.000000Z",
        "updated_at": "2026-11-03T17:24:17.000000Z"
    },
    {
        "id": 4,
        "name": "Zeus hand v1",
        "type": "leg",
        "orientation": "left",
        "active": 1,
        "created_at": "2026-11-03T17:24:17.000000Z",
        "updated_at": "2026-11-03T17:24:17.000000Z"
    }
]
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to list device models",
    "code": "DEVICE_MODELS:LIST:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/devices/models

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

active   integer  optional  

Filter device models by active status (available: 0 - only inactive, 1 - only active, any - all users). Default: 1. Example: 1

Response

Response Fields

id   integer   

Device model ID.

name   string   

Model name.

type   string   

Model type.

orientation   string   

Model orientation.

active   boolean   

Whether the model is active.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Create device model

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/devices/models" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Zeus hand v1\",
    \"type\": \"hand\",
    \"orientation\": \"left\",
    \"active\": true
}"
const url = new URL(
    "http://localhost:8000/api/devices/models"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Zeus hand v1",
    "type": "hand",
    "orientation": "left",
    "active": true
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 5,
    "name": "Zeus hand v1",
    "type": "arm",
    "orientation": "left",
    "active": 1,
    "created_at": "2026-11-03T17:24:17.000000Z",
    "updated_at": "2026-11-03T17:24:17.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to create device model",
    "code": "DEVICE_MODELS:CREATE:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/devices/models

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

name   string   

Model name. Example: Zeus hand v1

type   string  optional  

Model type (e.g. hand). Example: hand

orientation   string  optional  

Model orientation if specified. Example: left

active   boolean  optional  

Device model active status (0 - inactive, 1 - active). Example: true

Response

Response Fields

id   integer   

Device model ID.

name   string   

Model name.

type   string   

Model type.

orientation   string   

Model orientation.

active   boolean   

Whether the model is active.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Update device model

requires authentication

Example request:
curl --request PUT \
    "http://localhost:8000/api/devices/models/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Zeus hand v1\",
    \"type\": \"hand\",
    \"orientation\": \"right\",
    \"active\": true
}"
const url = new URL(
    "http://localhost:8000/api/devices/models/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Zeus hand v1",
    "type": "hand",
    "orientation": "right",
    "active": true
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (202):


{
    "id": 6,
    "name": "Zeus hand v1",
    "type": "leg",
    "orientation": "left",
    "active": 1,
    "created_at": "2026-11-03T17:24:17.000000Z",
    "updated_at": "2026-11-03T17:24:17.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to update device model",
    "code": "DEVICE_MODELS:UPDATE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device model not found):


{
    "message": "Device model not found",
    "code": "DEVICE_MODELS:UPDATE:MODEL_NOT_FOUND"
}
 

Request   

PUT api/devices/models/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

DeviceModel ID. Example: 1

Body Parameters

name   string  optional  

Model name. Example: Zeus hand v1

type   string  optional  

Model type (e.g. hand). Example: hand

orientation   string  optional  

Model orientation if specified. Example: right

active   boolean  optional  

Device model active status (0 - inactive, 1 - active). Example: true

Response

Response Fields

id   integer   

Device model ID.

name   string   

Model name.

type   string   

Model type.

orientation   string   

Model orientation.

active   boolean   

Whether the model is active.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Device Peripherals

API endpoints for device peripherals management

List device peripherals

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/devices/peripherals" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/devices/peripherals"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 1,
            "name": "et",
            "company": "Okuneva and Sons",
            "internal_id": "1585c0a3-9756-3779-b446-1db1a2c47b82",
            "type": "ea",
            "created_at": "2026-11-03T17:24:18.000000Z",
            "updated_at": "2026-11-03T17:24:18.000000Z"
        },
        {
            "id": 2,
            "name": "impedit",
            "company": "Fritsch LLC",
            "internal_id": "4adfa84e-a5c9-34c9-8dc1-04b3e01cf0a9",
            "type": "doloremque",
            "created_at": "2026-11-03T17:24:18.000000Z",
            "updated_at": "2026-11-03T17:24:18.000000Z"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view device peripherals",
    "code": "DEVICE_PERIPHERALS:LIST:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/devices/peripherals

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

Create device peripheral

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/devices/peripherals" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Bluetooth Dongle\",
    \"company\": \"Acme Corp\",
    \"internal_id\": \"BT-DONGLE-001\",
    \"type\": \"bluetooth_dongle\"
}"
const url = new URL(
    "http://localhost:8000/api/devices/peripherals"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Bluetooth Dongle",
    "company": "Acme Corp",
    "internal_id": "BT-DONGLE-001",
    "type": "bluetooth_dongle"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 3,
    "name": "deserunt",
    "company": "Pacocha-Hackett",
    "internal_id": "34eee06a-4561-3896-ae1a-3a2296b76bb5",
    "type": "est",
    "created_at": "2026-11-03T17:24:18.000000Z",
    "updated_at": "2026-11-03T17:24:18.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage device peripherals",
    "code": "DEVICE_PERIPHERALS:CREATE:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/devices/peripherals

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

name   string   

Peripheral device name. Example: Bluetooth Dongle

company   string   

Manufacturer of the peripheral device. Example: Acme Corp

internal_id   string   

Internal identifier of the peripheral device. Example: BT-DONGLE-001

type   string   

Type of the peripheral device. Example: bluetooth_dongle

Update device peripheral

requires authentication

Example request:
curl --request PUT \
    "http://localhost:8000/api/devices/peripherals/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Bluetooth Dongle\",
    \"company\": \"Acme Corp\",
    \"internal_id\": \"BT-DONGLE-001\",
    \"type\": \"bluetooth_dongle\"
}"
const url = new URL(
    "http://localhost:8000/api/devices/peripherals/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Bluetooth Dongle",
    "company": "Acme Corp",
    "internal_id": "BT-DONGLE-001",
    "type": "bluetooth_dongle"
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (202):


{
    "id": 4,
    "name": "aliquam",
    "company": "Kohler PLC",
    "internal_id": "e423a82f-b2e3-3bff-a217-e194a76b8546",
    "type": "consectetur",
    "created_at": "2026-11-03T17:24:18.000000Z",
    "updated_at": "2026-11-03T17:24:18.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage device peripherals",
    "code": "DEVICE_PERIPHERALS:UPDATE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Peripheral not found):


{
    "message": "Device peripheral not found",
    "code": "DEVICE_PERIPHERALS:UPDATE:PERIPHERAL_NOT_FOUND"
}
 

Request   

PUT api/devices/peripherals/{peripheralId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

peripheralId   integer   

Device Peripheral ID. Example: 1

Body Parameters

name   string  optional  

Peripheral device name. Example: Bluetooth Dongle

company   string  optional  

Manufacturer of the peripheral device. Example: Acme Corp

internal_id   string  optional  

Internal identifier of the peripheral device. Example: BT-DONGLE-001

type   string  optional  

Type of the peripheral device. Example: bluetooth_dongle

Delete device peripheral

requires authentication

Example request:
curl --request DELETE \
    "http://localhost:8000/api/devices/peripherals/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/devices/peripherals/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (202, OK):


{
    "message": "Device peripheral deleted",
    "code": "DEVICE_PERIPHERALS:DELETE:DELETED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage device peripherals",
    "code": "DEVICE_PERIPHERALS:DELETE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Peripheral not found):


{
    "message": "Device peripheral not found",
    "code": "DEVICE_PERIPHERALS:DELETE:PERIPHERAL_NOT_FOUND"
}
 

Request   

DELETE api/devices/peripherals/{peripheralId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

peripheralId   integer   

Device Peripheral ID. Example: 1

Devices

API endpoints for devices management

Check serial number or bluetooth ID

Public endpoint responding with status of given device serial number or bluetooth ID. If any of these numbers can be found in database, status will be true. Otherwise, status will be false (device does not exist).

Example request:
curl --request GET \
    --get "http://localhost:8000/api/device/check/S3R1AL-NUM83R" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"type\": \"serial\"
}"
const url = new URL(
    "http://localhost:8000/api/device/check/S3R1AL-NUM83R"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "type": "serial"
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, OK):


{
    "status": true
}
 

Request   

GET api/device/check/{serial}

Headers

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

serial   string   

Device serial number or bluetooth ID. Example: S3R1AL-NUM83R

Body Parameters

type   string  optional  

Type of checked identifier. Example: serial

Must be one of:
  • serial
  • bluetooth_id

Get devices list

requires authentication

Possible extend options:

Example request:
curl --request GET \
    --get "http://localhost:8000/api/devices?search=S3R1AL-NUM83R&active=-1&amputee=1&clinician=1&model=1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/devices"
);

const params = {
    "search": "S3R1AL-NUM83R",
    "active": "-1",
    "amputee": "1",
    "clinician": "1",
    "model": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 5,
            "serial": "c40cfeba-0366-35da-af78-9d9a0eeb6165",
            "bluetooth_id": "aed43d33-6b7f-3ce0-bbc2-6de4907a6fc6",
            "company_id": null,
            "model_id": 7,
            "amputee_id": 38,
            "clinician_id": null,
            "firmware_version_id": null,
            "pcb_version_id": null,
            "reverse_magnets": 0,
            "is_electrode": 0,
            "active": 1,
            "last_activity_at": "0000-00-00 00:00:00",
            "first_connected_at": null,
            "measurements": null,
            "created_at": "2026-11-03T17:24:18.000000Z",
            "updated_at": "2026-11-03T17:24:18.000000Z",
            "first_config_change_at": null,
            "model": {
                "id": 7,
                "name": "Zeus hand v1",
                "type": "leg",
                "orientation": "right",
                "active": 1,
                "created_at": "2026-11-03T17:24:18.000000Z",
                "updated_at": "2026-11-03T17:24:18.000000Z"
            },
            "amputee": {
                "id": 38,
                "mrn": "8NKHW8AM1793726658",
                "name": "Adrain Morissette",
                "email": "1793726658ikling@example.org",
                "language": "en",
                "phone": "1-228-842-9966",
                "phone_country": "ZM",
                "phone_verified_at": null,
                "address1": "6274 Era Shores",
                "address2": "Lisaburgh, MD 14395",
                "postal_code": "87603-0149",
                "city": "Schumm Inc",
                "country": "EE",
                "clinic_name": "West Karihaven",
                "clinic_location": "553 Rafaela Manor Apt. 155\nSammyton, ID 94945",
                "image": null,
                "public_image": null,
                "mfa_enabled": 0,
                "mfa_method": null,
                "mfa_verified_to": null,
                "location_id": null,
                "created_by": null,
                "active": 1,
                "is_ambassador": 0,
                "test_account": false,
                "notifications_timezone": null,
                "notifications_at": null,
                "created_at": "2026-11-03T17:24:18.000000Z",
                "updated_at": "2026-11-03T17:24:18.000000Z",
                "invitation_status": null,
                "acadle_invitation_status": null,
                "roles": []
            }
        },
        {
            "id": 6,
            "serial": "03a7792d-2fe0-3443-83fe-17f18f936912",
            "bluetooth_id": "dad50e9c-7003-3717-85c5-2d596123d7c6",
            "company_id": null,
            "model_id": 8,
            "amputee_id": 39,
            "clinician_id": null,
            "firmware_version_id": null,
            "pcb_version_id": null,
            "reverse_magnets": 0,
            "is_electrode": 0,
            "active": 1,
            "last_activity_at": "0000-00-00 00:00:00",
            "first_connected_at": null,
            "measurements": null,
            "created_at": "2026-11-03T17:24:19.000000Z",
            "updated_at": "2026-11-03T17:24:19.000000Z",
            "first_config_change_at": null,
            "model": {
                "id": 8,
                "name": "Zeus hand v1",
                "type": "arm",
                "orientation": "right",
                "active": 1,
                "created_at": "2026-11-03T17:24:18.000000Z",
                "updated_at": "2026-11-03T17:24:18.000000Z"
            },
            "amputee": {
                "id": 39,
                "mrn": "ZYP4JWPC1793726658",
                "name": "Stephanie Wolf IV",
                "email": "1793726658ewisoky@example.net",
                "language": "en",
                "phone": "1-848-378-4571",
                "phone_country": "FK",
                "phone_verified_at": null,
                "address1": "72453 McClure Crossing",
                "address2": "Danniemouth, ND 63136-9259",
                "postal_code": "18690-1340",
                "city": "O'Hara, Reichert and Spinka",
                "country": "EE",
                "clinic_name": "Kailynbury",
                "clinic_location": "81029 Wunsch Streets Apt. 594\nDavisstad, CA 53274",
                "image": null,
                "public_image": null,
                "mfa_enabled": 0,
                "mfa_method": null,
                "mfa_verified_to": null,
                "location_id": null,
                "created_by": null,
                "active": 1,
                "is_ambassador": 0,
                "test_account": false,
                "notifications_timezone": null,
                "notifications_at": null,
                "created_at": "2026-11-03T17:24:18.000000Z",
                "updated_at": "2026-11-03T17:24:18.000000Z",
                "invitation_status": null,
                "acadle_invitation_status": null,
                "roles": []
            }
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to list devices",
    "code": "DEVICES:LIST:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/devices

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

search   string  optional  

Filter devices by searching in: serial number, bluetooth ID. Example: S3R1AL-NUM83R

active   integer  optional  

Filter devices by active status (available: 0 - only inactive, 1 - only active, -1 - all users). Default: 1. Example: -1

amputee   string  optional  

Filter devices by amputees. Provide single ID (amputee=1), array of IDs (amputee[]=1&amputee[]=2) or comma-separated list of IDs (amputee=1,2). Pass value 0 to get devices unassigned to any amputee. Example: 1

clinician   string  optional  

Filter devices by clinicians. Provide single ID (clinician=1), array of IDs (clinician[]=1&clinician[]=2) or comma-separated list of IDs (clinician=1,2). Pass value 0 to get devices unassigned to any clinician. Example: 1

model   string  optional  

Filter devices by device models. Provide single ID (model=1), array of IDs (model[]=1&model[]=2) or comma-separated list of IDs (model=1,2). Pass value 0 to get devices without model. Example: 1

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

extend   string  optional  

Comma-separated list of relation extensions (available: model, amputee, clinicians, peripherals, firmwareVersion, pcbVersion, config, joinedDevices, joinedElectrodes).

sortby   string  optional  

Sort by field (available: serial, amputee_name, date). Default: date, desc.

sortdir   string  optional  

Sort direction (available: asc, desc).

Response

Response Fields

items   object   
id   integer   

Device ID.

serial   string   

Device serial number.

bluetooth_id   string   

Bluetooth identifier.

model_id   integer   

Device model ID.

amputee_id   integer   

Assigned patient (amputee) user ID.

firmware_version_id   integer   

Firmware version ID.

pcb_version_id   integer   

PCB version ID.

company_id   integer   

Company ID.

reverse_magnets   boolean   

Whether magnets are reversed.

is_electrode   boolean   

Whether this device is an electrode.

active   boolean   

Whether the device is active.

last_activity_at   string   

Last activity timestamp.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

model   object   

Device model details.

id   integer   

Device model ID.

name   string   

Model name.

type   string   

Model type.

orientation   string   

Model orientation.

active   boolean   

Whether the model is active.

amputee   object   

Assigned patient (amputee) user.

id   integer   

User ID.

name   string   

User full name.

email   string   

User email address.

clinicians   object[]   

Clinicians assigned to this device.

firmwareVersion   object   

Firmware version details.

id   integer   

Firmware version ID.

name   string   

Version name.

file_firmware   string   

Firmware file URL.

file_firmware_v2   string   

Firmware v2 file URL.

file_firmware_v3   string   

Firmware v3 file URL.

file_firmware_v4   string   

Firmware v4 file URL.

file_firmware_v5   string   

Firmware v5 file URL.

file_firmware_new_pcb   string   

New PCB firmware file URL.

file_bootloader   string   

Bootloader file URL.

file_bootloader_v2   string   

Bootloader v2 file URL.

file_bootloader_v3   string   

Bootloader v3 file URL.

file_bootloader_v4   string   

Bootloader v4 file URL.

changelog   string   

Changelog file URL.

pcbVersion   object   

PCB version details.

id   integer   

PCB version ID.

name   string   

Version name.

hardware_id   string   

Hardware identifier.

joinedDevices   object[]   

Joined hand devices.

joinedElectrodes   object[]   

Joined electrode devices.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

Get device information

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/device/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/device/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "id": 7,
    "serial": "d7f80fac-d51f-3347-bf27-82dcf826eaaf",
    "bluetooth_id": "de84cb22-11bc-350b-bc1e-714fc9acd453",
    "company_id": null,
    "model_id": 9,
    "amputee_id": 40,
    "clinician_id": null,
    "firmware_version_id": 1,
    "pcb_version_id": 1,
    "reverse_magnets": 0,
    "is_electrode": 0,
    "active": 1,
    "last_activity_at": "0000-00-00 00:00:00",
    "first_connected_at": null,
    "measurements": null,
    "created_at": "2026-11-03T17:24:19.000000Z",
    "updated_at": "2026-11-03T17:24:19.000000Z",
    "first_config_change_at": null,
    "model": {
        "id": 9,
        "name": "Zeus hand v1",
        "type": "arm",
        "orientation": "left",
        "active": 1,
        "created_at": "2026-11-03T17:24:19.000000Z",
        "updated_at": "2026-11-03T17:24:19.000000Z"
    },
    "amputee": {
        "id": 40,
        "mrn": "R24V7YTF1793726659",
        "name": "Alexandrine Mertz",
        "email": "1793726659hreilly@example.org",
        "language": "en",
        "phone": "(952) 650-8664",
        "phone_country": "HN",
        "phone_verified_at": null,
        "address1": "6245 Dina Valleys Suite 418",
        "address2": "Marianemouth, HI 10854",
        "postal_code": "27751",
        "city": "Kirlin-Hoppe",
        "country": "GR",
        "clinic_name": "Mariamfort",
        "clinic_location": "390 Boyer Forks Apt. 284\nPort Elijah, SD 28445",
        "image": null,
        "public_image": null,
        "mfa_enabled": 0,
        "mfa_method": null,
        "mfa_verified_to": null,
        "location_id": null,
        "created_by": null,
        "active": 1,
        "is_ambassador": 0,
        "test_account": false,
        "notifications_timezone": null,
        "notifications_at": null,
        "created_at": "2026-11-03T17:24:19.000000Z",
        "updated_at": "2026-11-03T17:24:19.000000Z",
        "invitation_status": null,
        "acadle_invitation_status": null,
        "roles": []
    },
    "pcb_version": {
        "id": 1,
        "name": "6.17.49",
        "hardware_id": "",
        "created_at": "2026-11-03T17:24:19.000000Z",
        "updated_at": "2026-11-03T17:24:19.000000Z"
    }
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view device data",
    "code": "DEVICES:GET:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "DEVICES:GET:DEVICE_NOT_FOUND"
}
 

Request   

GET api/device/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Device ID. Example: 1

Query Parameters

extend   string  optional  

Comma-separated list of relation extensions (available: model, amputee, firmwareVersion, pcbVersion, clinicians, peripherals, joinedDevices, joinedElectrodes, analyticsContext).

Response

Response Fields

id   integer   

Device ID.

serial   string   

Device serial number.

bluetooth_id   string   

Bluetooth identifier.

model_id   integer   

Device model ID.

amputee_id   integer   

Assigned patient (amputee) user ID.

firmware_version_id   integer   

Firmware version ID.

pcb_version_id   integer   

PCB version ID.

company_id   integer   

Company ID.

reverse_magnets   boolean   

Whether magnets are reversed.

is_electrode   boolean   

Whether this device is an electrode.

active   boolean   

Whether the device is active.

last_activity_at   string   

Last activity timestamp.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

model   object   

Device model details.

id   integer   

Device model ID.

name   string   

Model name.

type   string   

Model type.

orientation   string   

Model orientation.

active   boolean   

Whether the model is active.

amputee   object   

Assigned patient (amputee) user.

id   integer   

User ID.

name   string   

User full name.

email   string   

User email address.

clinicians   object[]   

Clinicians assigned to this device.

firmwareVersion   object   

Firmware version details.

id   integer   

Firmware version ID.

name   string   

Version name.

file_firmware   string   

Firmware file URL.

file_firmware_v2   string   

Firmware v2 file URL.

file_firmware_v3   string   

Firmware v3 file URL.

file_firmware_v4   string   

Firmware v4 file URL.

file_firmware_v5   string   

Firmware v5 file URL.

file_firmware_new_pcb   string   

New PCB firmware file URL.

file_bootloader   string   

Bootloader file URL.

file_bootloader_v2   string   

Bootloader v2 file URL.

file_bootloader_v3   string   

Bootloader v3 file URL.

file_bootloader_v4   string   

Bootloader v4 file URL.

changelog   string   

Changelog file URL.

pcbVersion   object   

PCB version details.

id   integer   

PCB version ID.

name   string   

Version name.

hardware_id   string   

Hardware identifier.

joinedDevices   object[]   

Joined hand devices.

joinedElectrodes   object[]   

Joined electrode devices.

Create new device

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/device" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"serial\": \"S3R1AL-NUM83R\",
    \"bluetooth_id\": \"BL0123456789\",
    \"model_id\": 1,
    \"amputee_id\": 1,
    \"clinicians\": [
        2
    ],
    \"peripherals\": [
        1
    ],
    \"firmware_version_id\": 1,
    \"pcb_version_id\": 1,
    \"reverse_magnets\": false,
    \"is_electrode\": false,
    \"active\": true,
    \"last_activity_at\": \"2022-08-15 12:00:00\",
    \"first_connected_at\": \"2022-08-15 12:00:00\",
    \"measurements\": \"{\\\"length\\\": 25.4, \\\"unit\\\": \\\"cm\\\"}\"
}"
const url = new URL(
    "http://localhost:8000/api/device"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "serial": "S3R1AL-NUM83R",
    "bluetooth_id": "BL0123456789",
    "model_id": 1,
    "amputee_id": 1,
    "clinicians": [
        2
    ],
    "peripherals": [
        1
    ],
    "firmware_version_id": 1,
    "pcb_version_id": 1,
    "reverse_magnets": false,
    "is_electrode": false,
    "active": true,
    "last_activity_at": "2022-08-15 12:00:00",
    "first_connected_at": "2022-08-15 12:00:00",
    "measurements": "{\"length\": 25.4, \"unit\": \"cm\"}"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 8,
    "serial": "c1c74ef4-112c-3367-b00f-2e4a1c5c2ee4",
    "bluetooth_id": "3f726a24-8a09-3cf0-a7b6-9e8c9bf921d9",
    "company_id": null,
    "model_id": null,
    "amputee_id": null,
    "clinician_id": null,
    "firmware_version_id": null,
    "pcb_version_id": null,
    "reverse_magnets": 0,
    "is_electrode": 0,
    "active": 1,
    "last_activity_at": "0000-00-00 00:00:00",
    "first_connected_at": null,
    "measurements": null,
    "created_at": "2026-11-03T17:24:19.000000Z",
    "updated_at": "2026-11-03T17:24:19.000000Z",
    "first_config_change_at": null
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to create device",
    "code": "DEVICES:CREATE:INSUFFICIENT_PERMISSION"
}
 

Example response (403, Firmware has no schema):


{
    "message": "Cannot create: firmware has no schema",
    "code": "DEVICES:CREATE:NO_FIRMWARE_SCHEMA"
}
 

Example response (500, Server error):


{
    "message": "Server error: device not created",
    "code": "DEVICES:CREATE:SERVER_ERROR"
}
 

Request   

POST api/device

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

serial   string   

Device serial number. Example: S3R1AL-NUM83R

bluetooth_id   string   

Device Bluetooth ID. Example: BL0123456789

model_id   string   

Device Model ID. The id of an existing record in the App\Models\DeviceModel table. Example: 1

amputee_id   string  optional  

Device amputee ID. The id of an existing record in the App\Models\User table. Example: 1

clinicians   integer[]  optional  

Clinician ID. The id of an existing record in the App\Models\User table.

peripherals   integer[]  optional  

Peripheral device ID. The id of an existing record in the App\Models\DevicePeripheral table.

firmware_version_id   string  optional  

Firmware Version ID. The id of an existing record in the App\Models\FirmwareVersion table. Example: 1

pcb_version_id   string  optional  

PCB Version ID. The id of an existing record in the App\Models\PCBVersion table. Example: 1

reverse_magnets   boolean  optional  

Device reverse magnets. Default: 0. Example: false

is_electrode   boolean  optional  

Super Admin only: Device is electrode. Default: 0. Example: false

active   boolean  optional  

Device active status (0 - inactive, 1 - active). Example: true

last_activity_at   string  optional  

Device last activity date. Update this value each time device connects to the mobile app. Always shift local datetime to UTC timezone. Must be a valid date in the format Y-m-d H:i:s. Example: 2022-08-15 12:00:00

first_connected_at   string  optional  

Date the device first connected to ADP. Must be a valid date in the format Y-m-d H:i:s. Example: 2022-08-15 12:00:00

measurements   string  optional  

Device measurements. MUST_BE_JSON. Example: {"length": 25.4, "unit": "cm"}

Response

Response Fields

id   integer   

Device ID.

serial   string   

Device serial number.

bluetooth_id   string   

Bluetooth identifier.

model_id   integer   

Device model ID.

amputee_id   integer   

Assigned patient (amputee) user ID.

firmware_version_id   integer   

Firmware version ID.

pcb_version_id   integer   

PCB version ID.

company_id   integer   

Company ID.

reverse_magnets   boolean   

Whether magnets are reversed.

is_electrode   boolean   

Whether this device is an electrode.

active   boolean   

Whether the device is active.

last_activity_at   string   

Last activity timestamp.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

model   object   

Device model details.

id   integer   

Device model ID.

name   string   

Model name.

type   string   

Model type.

orientation   string   

Model orientation.

active   boolean   

Whether the model is active.

amputee   object   

Assigned patient (amputee) user.

id   integer   

User ID.

name   string   

User full name.

email   string   

User email address.

clinicians   object[]   

Clinicians assigned to this device.

firmwareVersion   object   

Firmware version details.

id   integer   

Firmware version ID.

name   string   

Version name.

file_firmware   string   

Firmware file URL.

file_firmware_v2   string   

Firmware v2 file URL.

file_firmware_v3   string   

Firmware v3 file URL.

file_firmware_v4   string   

Firmware v4 file URL.

file_firmware_v5   string   

Firmware v5 file URL.

file_firmware_new_pcb   string   

New PCB firmware file URL.

file_bootloader   string   

Bootloader file URL.

file_bootloader_v2   string   

Bootloader v2 file URL.

file_bootloader_v3   string   

Bootloader v3 file URL.

file_bootloader_v4   string   

Bootloader v4 file URL.

changelog   string   

Changelog file URL.

pcbVersion   object   

PCB version details.

id   integer   

PCB version ID.

name   string   

Version name.

hardware_id   string   

Hardware identifier.

joinedDevices   object[]   

Joined hand devices.

joinedElectrodes   object[]   

Joined electrode devices.

Update device

requires authentication

Amputee of device can update only these fields: serial, bluetooth_id, firmware_version_id, pcb_version_id

Example request:
curl --request PUT \
    "http://localhost:8000/api/device/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"serial\": \"S3R1AL-NUM83R\",
    \"bluetooth_id\": \"BL0123456789\",
    \"model_id\": 1,
    \"amputee_id\": 1,
    \"clinicians\": [
        2
    ],
    \"peripherals\": [
        1
    ],
    \"firmware_version_id\": 1,
    \"pcb_version_id\": 1,
    \"reverse_magnets\": false,
    \"is_electrode\": false,
    \"active\": true,
    \"last_activity_at\": \"2022-08-15 12:00:00\",
    \"first_connected_at\": \"2022-08-15 12:00:00\",
    \"measurements\": \"{\\\"length\\\": 25.4, \\\"unit\\\": \\\"cm\\\"}\"
}"
const url = new URL(
    "http://localhost:8000/api/device/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "serial": "S3R1AL-NUM83R",
    "bluetooth_id": "BL0123456789",
    "model_id": 1,
    "amputee_id": 1,
    "clinicians": [
        2
    ],
    "peripherals": [
        1
    ],
    "firmware_version_id": 1,
    "pcb_version_id": 1,
    "reverse_magnets": false,
    "is_electrode": false,
    "active": true,
    "last_activity_at": "2022-08-15 12:00:00",
    "first_connected_at": "2022-08-15 12:00:00",
    "measurements": "{\"length\": 25.4, \"unit\": \"cm\"}"
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (202):


{
    "id": 9,
    "serial": "e6160938-ea2d-307d-9dd9-f9b79892dab5",
    "bluetooth_id": "7913679a-5d6e-3619-ab1d-ce21137f4805",
    "company_id": null,
    "model_id": null,
    "amputee_id": null,
    "clinician_id": null,
    "firmware_version_id": null,
    "pcb_version_id": null,
    "reverse_magnets": 0,
    "is_electrode": 0,
    "active": 1,
    "last_activity_at": "0000-00-00 00:00:00",
    "first_connected_at": null,
    "measurements": null,
    "created_at": "2026-11-03T17:24:19.000000Z",
    "updated_at": "2026-11-03T17:24:19.000000Z",
    "first_config_change_at": null
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to update device",
    "code": "DEVICES:UPDATE:INSUFFICIENT_PERMISSION"
}
 

Example response (403, Firmware has no schema):


{
    "message": "Cannot update: firmware has no schema",
    "code": "DEVICES:UPDATE:NO_FIRMWARE_SCHEMA"
}
 

Example response (403):


{
    "message": "Cannot update: no clinicians left for patient relation",
    "code": "DEVICES:UPDATE:NO_CLINICIANS"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "DEVICES:UPDATE:DEVICE_NOT_FOUND"
}
 

Request   

PUT api/device/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Device ID. Example: 1

Body Parameters

serial   string  optional  

Device serial number. Example: S3R1AL-NUM83R

bluetooth_id   string  optional  

Device Bluetooth ID. Example: BL0123456789

model_id   string  optional  

Device Model ID. The id of an existing record in the App\Models\DeviceModel table. Example: 1

amputee_id   string  optional  

Device amputee ID. Pass null to remove assignment. The id of an existing record in the App\Models\User table. Example: 1

clinicians   integer[]  optional  

Clinician ID. The id of an existing record in the App\Models\User table.

peripherals   integer[]  optional  

Peripheral device ID. The id of an existing record in the App\Models\DevicePeripheral table.

firmware_version_id   string  optional  

Firmware Version ID. The id of an existing record in the App\Models\FirmwareVersion table. Example: 1

pcb_version_id   string  optional  

PCB Version ID. The id of an existing record in the App\Models\PCBVersion table. Example: 1

reverse_magnets   boolean  optional  

Device reverse magnets. Default: 0. Example: false

is_electrode   boolean  optional  

Super Admin only: Device is electrode. Default: 0. Example: false

active   boolean  optional  

Device active status (0 - inactive, 1 - active). Example: true

last_activity_at   string  optional  

Device last activity date. Update this value each time device connects to the mobile app. Always shift local datetime to UTC timezone. Must be a valid date in the format Y-m-d H:i:s. Example: 2022-08-15 12:00:00

first_connected_at   string  optional  

Date the device first connected to ADP. Must be a valid date in the format Y-m-d H:i:s. Example: 2022-08-15 12:00:00

measurements   string  optional  

Device measurements. MUST_BE_JSON. Example: {"length": 25.4, "unit": "cm"}

Response

Response Fields

id   integer   

Device ID.

serial   string   

Device serial number.

bluetooth_id   string   

Bluetooth identifier.

model_id   integer   

Device model ID.

amputee_id   integer   

Assigned patient (amputee) user ID.

firmware_version_id   integer   

Firmware version ID.

pcb_version_id   integer   

PCB version ID.

company_id   integer   

Company ID.

reverse_magnets   boolean   

Whether magnets are reversed.

is_electrode   boolean   

Whether this device is an electrode.

active   boolean   

Whether the device is active.

last_activity_at   string   

Last activity timestamp.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

model   object   

Device model details.

id   integer   

Device model ID.

name   string   

Model name.

type   string   

Model type.

orientation   string   

Model orientation.

active   boolean   

Whether the model is active.

amputee   object   

Assigned patient (amputee) user.

id   integer   

User ID.

name   string   

User full name.

email   string   

User email address.

clinicians   object[]   

Clinicians assigned to this device.

firmwareVersion   object   

Firmware version details.

id   integer   

Firmware version ID.

name   string   

Version name.

file_firmware   string   

Firmware file URL.

file_firmware_v2   string   

Firmware v2 file URL.

file_firmware_v3   string   

Firmware v3 file URL.

file_firmware_v4   string   

Firmware v4 file URL.

file_firmware_v5   string   

Firmware v5 file URL.

file_firmware_new_pcb   string   

New PCB firmware file URL.

file_bootloader   string   

Bootloader file URL.

file_bootloader_v2   string   

Bootloader v2 file URL.

file_bootloader_v3   string   

Bootloader v3 file URL.

file_bootloader_v4   string   

Bootloader v4 file URL.

changelog   string   

Changelog file URL.

pcbVersion   object   

PCB version details.

id   integer   

PCB version ID.

name   string   

Version name.

hardware_id   string   

Hardware identifier.

joinedDevices   object[]   

Joined hand devices.

joinedElectrodes   object[]   

Joined electrode devices.

Delete device

requires authentication

Example request:
curl --request DELETE \
    "http://localhost:8000/api/device/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/device/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (202, OK):


{
    "message": "Device deleted",
    "code": "DEVICES:DELETE:DELETED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to delete device",
    "code": "DEVICES:DELETE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "DEVICES:DELETE:DEVICE_NOT_FOUND"
}
 

Request   

DELETE api/device/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Device ID. Example: 1

Get device hashes

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/device/1/hash" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/device/1/hash"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "hash_global": "123456789012345",
    "hash_common_settings": "123456789012345",
    "hash_common_grips": "123456789012345",
    "hash_mode1": "123456789012345",
    "hash_mode2": "123456789012345",
    "hash_mode3": "123456789012345"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to access device hashes",
    "code": "DEVICES:GET_HASHES:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "DEVICES:GET_HASHES:DEVICE_NOT_FOUND"
}
 

Request   

GET api/device/{id}/hash

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Device ID. Example: 1

Update device hashes

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/device/1/hash" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"hash_global\": \"123456789012345\",
    \"hash_common_settings\": \"123456789012345\",
    \"hash_common_grips\": \"123456789012345\",
    \"hash_mode1\": \"123456789012345\",
    \"hash_mode2\": \"123456789012345\",
    \"hash_mode3\": \"123456789012345\"
}"
const url = new URL(
    "http://localhost:8000/api/device/1/hash"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "hash_global": "123456789012345",
    "hash_common_settings": "123456789012345",
    "hash_common_grips": "123456789012345",
    "hash_mode1": "123456789012345",
    "hash_mode2": "123456789012345",
    "hash_mode3": "123456789012345"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (202, OK):


{
    "hash_global": "123456789012345",
    "hash_common_settings": "123456789012345",
    "hash_common_grips": "123456789012345",
    "hash_mode1": "123456789012345",
    "hash_mode2": "123456789012345",
    "hash_mode3": "123456789012345"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to access device hashes",
    "code": "DEVICES:SET_HASHES:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "DEVICES:SET_HASHES:DEVICE_NOT_FOUND"
}
 

Request   

POST api/device/{id}/hash

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Device ID. Example: 1

Body Parameters

hash_global   string  optional  

Global hash. Example: 123456789012345

hash_common_settings   string  optional  

Common settings hash. Example: 123456789012345

hash_common_grips   string  optional  

Common grips hash. Example: 123456789012345

hash_mode1   string  optional  

Mode 1 hash. Example: 123456789012345

hash_mode2   string  optional  

Mode 2 hash. Example: 123456789012345

hash_mode3   string  optional  

Mode 3 hash. Example: 123456789012345

Detach device

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/device/1/detach" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/device/1/detach"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (202, OK):


{
    "message": "Device detached",
    "code": "DEVICES:DETACH:DETACHED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to detach device",
    "code": "DEVICES:DETACH:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "DEVICES:DETACH:DEVICE_NOT_FOUND"
}
 

Request   

POST api/device/{id}/detach

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Device ID. Example: 1

Add device

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/device/add/S3R1AL-NUM83R" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/device/add/S3R1AL-NUM83R"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (202):


{
    "id": 10,
    "serial": "912ef93d-dc59-3875-b1a5-06a2bfbbec63",
    "bluetooth_id": "aeffcaab-ba63-3b10-ad5d-df2eb5f6d790",
    "company_id": null,
    "model_id": null,
    "amputee_id": null,
    "clinician_id": null,
    "firmware_version_id": null,
    "pcb_version_id": null,
    "reverse_magnets": 0,
    "is_electrode": 0,
    "active": 1,
    "last_activity_at": "0000-00-00 00:00:00",
    "first_connected_at": null,
    "measurements": null,
    "created_at": "2026-11-03T17:24:20.000000Z",
    "updated_at": "2026-11-03T17:24:20.000000Z",
    "first_config_change_at": null
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to assign devices with code",
    "code": "DEVICES:ASSIGN:INSUFFICIENT_PERMISSION"
}
 

Example response (403, User reached the temporary limit of attached devices):


{
    "message": "Reached the limit of assigned devices",
    "code": "DEVICES:ASSIGN:LIMIT_REACHED"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "DEVICES:ASSIGN:DEVICE_NOT_FOUND"
}
 

Request   

POST api/device/add/{serial}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

serial   string   

Device serial number or bluetooth ID. Example: S3R1AL-NUM83R

Response

Response Fields

id   integer   

Device ID.

serial   string   

Device serial number.

bluetooth_id   string   

Bluetooth identifier.

model_id   integer   

Device model ID.

amputee_id   integer   

Assigned patient (amputee) user ID.

firmware_version_id   integer   

Firmware version ID.

pcb_version_id   integer   

PCB version ID.

company_id   integer   

Company ID.

reverse_magnets   boolean   

Whether magnets are reversed.

is_electrode   boolean   

Whether this device is an electrode.

active   boolean   

Whether the device is active.

last_activity_at   string   

Last activity timestamp.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

model   object   

Device model details.

id   integer   

Device model ID.

name   string   

Model name.

type   string   

Model type.

orientation   string   

Model orientation.

active   boolean   

Whether the model is active.

amputee   object   

Assigned patient (amputee) user.

id   integer   

User ID.

name   string   

User full name.

email   string   

User email address.

clinicians   object[]   

Clinicians assigned to this device.

firmwareVersion   object   

Firmware version details.

id   integer   

Firmware version ID.

name   string   

Version name.

file_firmware   string   

Firmware file URL.

file_firmware_v2   string   

Firmware v2 file URL.

file_firmware_v3   string   

Firmware v3 file URL.

file_firmware_v4   string   

Firmware v4 file URL.

file_firmware_v5   string   

Firmware v5 file URL.

file_firmware_new_pcb   string   

New PCB firmware file URL.

file_bootloader   string   

Bootloader file URL.

file_bootloader_v2   string   

Bootloader v2 file URL.

file_bootloader_v3   string   

Bootloader v3 file URL.

file_bootloader_v4   string   

Bootloader v4 file URL.

changelog   string   

Changelog file URL.

pcbVersion   object   

PCB version details.

id   integer   

PCB version ID.

name   string   

Version name.

hardware_id   string   

Hardware identifier.

joinedDevices   object[]   

Joined hand devices.

joinedElectrodes   object[]   

Joined electrode devices.

Connect device

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/device/connect/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/device/connect/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "message": "Device connected",
    "code": "DEVICES:CONNECT:CONNECTED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view device data",
    "code": "DEVICES:CONNECT:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "DEVICES:CONNECT:DEVICE_NOT_FOUND"
}
 

Request   

POST api/device/connect/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Device ID. Example: 1

Disconnect device

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/device/disconnect/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/device/disconnect/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "message": "Device disconnected",
    "code": "DEVICES:DISCONNECT:DISCONNECTED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view device data",
    "code": "DEVICES:DISCONNECT:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "DEVICES:DISCONNECT:DEVICE_NOT_FOUND"
}
 

Request   

POST api/device/disconnect/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Device ID. Example: 1

Join devices

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/device/join/1/2" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"force\": false,
    \"patient\": 1
}"
const url = new URL(
    "http://localhost:8000/api/device/join/1/2"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "force": false,
    "patient": 1
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "id": 11,
    "serial": "e3c33f7c-438e-37be-a4e7-54abc905bca6",
    "bluetooth_id": "274dd928-9e70-3e7a-a9d4-792f7ad3973b",
    "company_id": null,
    "model_id": null,
    "amputee_id": null,
    "clinician_id": null,
    "firmware_version_id": null,
    "pcb_version_id": null,
    "reverse_magnets": 0,
    "is_electrode": 0,
    "active": 1,
    "last_activity_at": "0000-00-00 00:00:00",
    "first_connected_at": null,
    "measurements": null,
    "created_at": "2026-11-03T17:24:20.000000Z",
    "updated_at": "2026-11-03T17:24:20.000000Z",
    "first_config_change_at": null,
    "joined_devices": [
        {
            "id": 12,
            "serial": "c6fdce75-f62d-302d-9167-7514c99ac2f3",
            "bluetooth_id": "03ece6c4-31f3-3c07-9cc8-105c2840304a",
            "company_id": null,
            "model_id": null,
            "amputee_id": null,
            "clinician_id": null,
            "firmware_version_id": null,
            "pcb_version_id": null,
            "reverse_magnets": 0,
            "is_electrode": 0,
            "active": 1,
            "last_activity_at": "0000-00-00 00:00:00",
            "first_connected_at": null,
            "measurements": null,
            "created_at": "2026-11-03T17:24:20.000000Z",
            "updated_at": "2026-11-03T17:24:20.000000Z",
            "first_config_change_at": null,
            "pivot": {
                "electrode_id": 11,
                "device_id": 12,
                "created_at": "2026-11-03T17:24:20.000000Z",
                "updated_at": "2026-11-03T17:24:20.000000Z"
            }
        }
    ],
    "joined_electrodes": [
        {
            "id": 13,
            "serial": "3836a4b5-9a51-33fd-9daa-19a6367f05b3",
            "bluetooth_id": "5a172f56-d802-39d5-b73b-4c7b13249d16",
            "company_id": null,
            "model_id": null,
            "amputee_id": null,
            "clinician_id": null,
            "firmware_version_id": null,
            "pcb_version_id": null,
            "reverse_magnets": 0,
            "is_electrode": 0,
            "active": 1,
            "last_activity_at": "0000-00-00 00:00:00",
            "first_connected_at": null,
            "measurements": null,
            "created_at": "2026-11-03T17:24:20.000000Z",
            "updated_at": "2026-11-03T17:24:20.000000Z",
            "first_config_change_at": null,
            "pivot": {
                "device_id": 11,
                "electrode_id": 13,
                "created_at": "2026-11-03T17:24:20.000000Z",
                "updated_at": "2026-11-03T17:24:20.000000Z"
            }
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to join devices",
    "code": "DEVICES:JOIN:INSUFFICIENT_PERMISSION"
}
 

Example response (403, Device 2 has a patient assigned):


{
    "message": "Device 2 has a patient assigned",
    "code": "DEVICES:JOIN:DEVICE2_HAS_PATIENT"
}
 

Example response (403, Device already has an electrode joined):


{
    "message": "Device already has an electrode joined",
    "code": "DEVICES:JOIN:ALREADY_JOINED"
}
 

Example response (403, Server error):


{
    "message": "Server error",
    "code": "DEVICES:JOIN:SERVER_ERROR"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "DEVICES:JOIN:DEVICE_NOT_FOUND"
}
 

Example response (404, Device 1 cannot be an electrode):


{
    "message": "Device 1 cannot be an electrode",
    "code": "DEVICES:JOIN:INCORRECT_DEVICE1_TYPE"
}
 

Example response (404, Device 2 must be an electrode):


{
    "message": "Device 1 must be an electrode",
    "code": "DEVICES:JOIN:INCORRECT_DEVICE2_TYPE"
}
 

Example response (404, Both devices have no patient assigned):


{
    "message": "Both devices have no patient assigned",
    "code": "DEVICES:JOIN:NO_PATIENT"
}
 

Request   

POST api/device/join/{deviceId}/{electrodeId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

deviceId   integer   

Device ID (hand). Example: 1

electrodeId   integer   

Device ID (electrode). Example: 2

Body Parameters

force   boolean  optional  

Force assignment even if one or both devices are already joined with other devices. Example: false

patient   integer  optional  

Add or replace patient assignment in joined devices. The id of an existing record in the App\Models\User table. Example: 1

Response

Response Fields

id   integer   

Device ID.

serial   string   

Device serial number.

bluetooth_id   string   

Bluetooth identifier.

model_id   integer   

Device model ID.

amputee_id   integer   

Assigned patient (amputee) user ID.

firmware_version_id   integer   

Firmware version ID.

pcb_version_id   integer   

PCB version ID.

company_id   integer   

Company ID.

reverse_magnets   boolean   

Whether magnets are reversed.

is_electrode   boolean   

Whether this device is an electrode.

active   boolean   

Whether the device is active.

last_activity_at   string   

Last activity timestamp.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

model   object   

Device model details.

id   integer   

Device model ID.

name   string   

Model name.

type   string   

Model type.

orientation   string   

Model orientation.

active   boolean   

Whether the model is active.

amputee   object   

Assigned patient (amputee) user.

id   integer   

User ID.

name   string   

User full name.

email   string   

User email address.

clinicians   object[]   

Clinicians assigned to this device.

firmwareVersion   object   

Firmware version details.

id   integer   

Firmware version ID.

name   string   

Version name.

file_firmware   string   

Firmware file URL.

file_firmware_v2   string   

Firmware v2 file URL.

file_firmware_v3   string   

Firmware v3 file URL.

file_firmware_v4   string   

Firmware v4 file URL.

file_firmware_v5   string   

Firmware v5 file URL.

file_firmware_new_pcb   string   

New PCB firmware file URL.

file_bootloader   string   

Bootloader file URL.

file_bootloader_v2   string   

Bootloader v2 file URL.

file_bootloader_v3   string   

Bootloader v3 file URL.

file_bootloader_v4   string   

Bootloader v4 file URL.

changelog   string   

Changelog file URL.

pcbVersion   object   

PCB version details.

id   integer   

PCB version ID.

name   string   

Version name.

hardware_id   string   

Hardware identifier.

joinedDevices   object[]   

Joined hand devices.

joinedElectrodes   object[]   

Joined electrode devices.

Detach joined devices

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/device/unjoin/1/2" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/device/unjoin/1/2"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "message": "Devices unjoined",
    "code": "DEVICES:UNJOIN:UNJOINED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to join devices",
    "code": "DEVICES:UNJOIN:INSUFFICIENT_PERMISSION"
}
 

Example response (403, OK):


{
    "message": "Devices are not joined",
    "code": "DEVICES:UNJOIN:NOT_JOINED"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "DEVICES:UNJOIN:DEVICE_NOT_FOUND"
}
 

Request   

POST api/device/unjoin/{deviceId}/{electrodeId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

deviceId   integer   

Device ID. Example: 1

electrodeId   integer   

Electrode ID. Example: 2

Create demo patient

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/device/1/dummy-patient" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/device/1/dummy-patient"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (201, OK):


{
    "email": "SERIAL@gmail.com",
    "password": "Demo@123"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to create dummy patient",
    "code": "DEVICES:DUMMY_PATIENT:INSUFFICIENT_PERMISSION"
}
 

Example response (403, Device already has a patient):


{
    "message": "Device already has a patient",
    "code": "DEVICES:DUMMY_PATIENT:PATIENT_EXISTS"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "DEVICES:DUMMY_PATIENT:DEVICE_NOT_FOUND"
}
 

Example response (500, Server error):


{
    "message": "Server error: dummy patient not created",
    "code": "DEVICES:DUMMY_PATIENT:SERVER_ERROR"
}
 

Request   

POST api/device/{id}/dummy-patient

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Device ID. Example: 1

Get device internal note

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/device/8/internal-note" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/device/8/internal-note"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "id": 1,
    "device_id": 20,
    "note": "Quibusdam dolor fugiat aut eligendi tenetur eligendi quod voluptas. Quia commodi velit aut molestiae vero. Similique quo ipsa voluptatum asperiores enim. Ad eius et et.",
    "created_at": "2026-11-03T17:24:20.000000Z",
    "updated_at": "2026-11-03T17:24:20.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to access internal notes",
    "code": "INTERNAL_NOTE:GET:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "INTERNAL_NOTE:GET:DEVICE_NOT_FOUND"
}
 

Request   

GET api/device/{id}/internal-note

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

The ID of the device. Example: 8

deviceId   integer   

Device ID. Example: 1

Update device internal note

requires authentication

Example request:
curl --request PUT \
    "http://localhost:8000/api/device/1/internal-note" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"note\": \"Device returned from clinic for inspection.\"
}"
const url = new URL(
    "http://localhost:8000/api/device/1/internal-note"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "note": "Device returned from clinic for inspection."
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (202):


{
    "id": 2,
    "device_id": 21,
    "note": "Vero ut perspiciatis provident non et. Quisquam est aut tenetur iste repellendus. Qui dicta quasi non in similique et tempora. Sequi quos temporibus qui impedit aut fugit consequatur.",
    "created_at": "2026-11-03T17:24:20.000000Z",
    "updated_at": "2026-11-03T17:24:20.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to access internal notes",
    "code": "INTERNAL_NOTE:UPDATE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "INTERNAL_NOTE:UPDATE:DEVICE_NOT_FOUND"
}
 

Request   

PUT api/device/{id}/internal-note

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Device ID. Example: 1

Body Parameters

note   string  optional  

Super Admin only: Internal note for this device. Maximum length: 50 000 characters. MAXIMUM:STRING_LENGTH:50000. Example: Device returned from clinic for inspection.

Documents

API endpoints for documents management

List documents

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/documents?type=web" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/documents"
);

const params = {
    "type": "web",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 1,
            "name": "Vice President Of Marketing",
            "type": "web",
            "created_at": "2026-11-03T17:26:18.000000Z",
            "updated_at": "2026-11-03T17:26:18.000000Z"
        },
        {
            "id": 2,
            "name": "Designer",
            "type": "web",
            "created_at": "2026-11-03T17:26:18.000000Z",
            "updated_at": "2026-11-03T17:26:18.000000Z"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage documents",
    "code": "DOCUMENTS:LIST:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/documents

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

type   string  optional  

Filter documents by type. Example: web

Must be one of:
  • web
  • mobile
perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

Response

Response Fields

items   object   
id   integer   

Document ID.

name   string   

Document name.

type   string   

Document target platform.

Must be one of:
  • web
  • mobile
created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

versions   object[]   

Document versions.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

Create document

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/documents" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Privacy Policy\",
    \"type\": \"web\"
}"
const url = new URL(
    "http://localhost:8000/api/documents"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Privacy Policy",
    "type": "web"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 3,
    "name": "Offset Lithographic Press Operator",
    "type": "web",
    "created_at": "2026-11-03T17:26:18.000000Z",
    "updated_at": "2026-11-03T17:26:18.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage documents",
    "code": "DOCUMENTS:CREATE:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/documents

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

name   string   

Document name. Example: Privacy Policy

type   string   

Document destination. Example: web

Must be one of:
  • web
  • mobile

Response

Response Fields

id   integer   

Document ID.

name   string   

Document name.

type   string   

Document target platform.

Must be one of:
  • web
  • mobile
created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

versions   object[]   

Document versions.

Delete document

requires authentication

Example request:
curl --request DELETE \
    "http://localhost:8000/api/documents/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/documents/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (202, OK):


{
    "message": "Document deleted",
    "code": "DOCUMENTS:DELETE:DELETED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage documents",
    "code": "DOCUMENTS:DELETE:INSUFFICIENT_PERMISSION"
}
 

Example response (403, Document has existing versions):


{
    "message": "Cannot delete: document has existing versions (1)",
    "code": "DOCUMENTS:DELETE:HAS_VERSIONS"
}
 

Example response (404, Document not found):


{
    "message": "Document not found",
    "code": "DOCUMENTS:DELETE:DOCUMENT_NOT_FOUND"
}
 

Request   

DELETE api/documents/{documentId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

documentId   integer   

Document ID. Example: 1

List document versions

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/documents/1/versions" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/documents/1/versions"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


[
    {
        "id": 1,
        "document_id": null,
        "index": 219405,
        "file": "http://haley.net/doloremque-earum-saepe-quia",
        "created_at": "2026-11-03T17:26:18.000000Z",
        "updated_at": "2026-11-03T17:26:18.000000Z"
    },
    {
        "id": 2,
        "document_id": null,
        "index": 8607,
        "file": "http://rodriguez.net/in-quo-sint-qui-enim-ut-ratione-eos.html",
        "created_at": "2026-11-03T17:26:18.000000Z",
        "updated_at": "2026-11-03T17:26:18.000000Z"
    }
]
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage documents",
    "code": "DOCUMENTS:LIST_VERSIONS:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Document not found):


{
    "message": "Document not found",
    "code": "DOCUMENTS:LIST_VERSIONS:DOCUMENT_NOT_FOUND"
}
 

Request   

GET api/documents/{documentId}/versions

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

documentId   integer   

Document ID. Example: 1

Query Parameters

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

Response

Response Fields

id   integer   

Document version ID.

document_id   integer   

Associated document ID.

index   integer   

Version index number.

file   string   

File path or URL.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

document   object   

Parent document.

Create document version

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/documents/1/versions" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"file\": \"https:\\/\\/www.aetherbiomedical.com\\/privacy-policy\"
}"
const url = new URL(
    "http://localhost:8000/api/documents/1/versions"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "file": "https:\/\/www.aetherbiomedical.com\/privacy-policy"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 3,
    "document_id": null,
    "index": 5942,
    "file": "http://www.rippin.com/nemo-aspernatur-nihil-voluptas-laboriosam",
    "created_at": "2026-11-03T17:26:18.000000Z",
    "updated_at": "2026-11-03T17:26:18.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage documents",
    "code": "DOCUMENTS:CREATE_VERSION:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Document not found):


{
    "message": "Document not found",
    "code": "DOCUMENTS:CREATE_VERSION:DOCUMENT_NOT_FOUND"
}
 

Request   

POST api/documents/{documentId}/versions

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

documentId   integer   

Document ID. Example: 1

Body Parameters

file   string   

URL to document file. Must be a valid URL. Example: https://www.aetherbiomedical.com/privacy-policy

Response

Response Fields

id   integer   

Document version ID.

document_id   integer   

Associated document ID.

index   integer   

Version index number.

file   string   

File path or URL.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

document   object   

Parent document.

Delete document version

requires authentication

Example request:
curl --request DELETE \
    "http://localhost:8000/api/documents/1/versions/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/documents/1/versions/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (202, OK):


{
    "message": "Document version deleted",
    "code": "DOCUMENTS:DELETE_VERSION:DELETED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage documents",
    "code": "DOCUMENTS:DELETE_VERSION:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Document not found):


{
    "message": "Document not found",
    "code": "DOCUMENTS:DELETE_VERSION:DOCUMENT_NOT_FOUND"
}
 

Example response (404, Document version not found):


{
    "message": "Document version not found",
    "code": "DOCUMENTS:DELETE_VERSION:VERSION_NOT_FOUND"
}
 

Request   

DELETE api/documents/{documentId}/versions/{versionId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

documentId   integer   

Document ID. Example: 1

versionId   integer   

DocumentVersion ID. Example: 1

Get documents status

requires authentication

Any document on the list has to be accepted. Use POST /documents/accept endpoint to mark them as accepted once user agrees to that. Empty list means that user is up-to-date with all required documents and nothing has to be accepted.

Example request:
curl --request GET \
    --get "http://localhost:8000/api/documents/status?type=web" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/documents/status"
);

const params = {
    "type": "web",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "documents": []
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view documents status",
    "code": "DOCUMENTS:STATUS:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/documents/status

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

type   string  optional  

Filter documents by type. Example: web

Must be one of:
  • web
  • mobile

Accept documents

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/documents/accept" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"documents\": [
        1
    ]
}"
const url = new URL(
    "http://localhost:8000/api/documents/accept"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "documents": [
        1
    ]
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (202, OK):


{
    "message": "Accepted 1 document(s)",
    "code": "DOCUMENTS:ACCEPT:ACCEPTED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to update documents status",
    "code": "DOCUMENTS:ACCEPT:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/documents/accept

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

documents   integer[]   

Document Version ID. The id of an existing record in the App\Models\DocumentVersion table.

Accept Terms of Service for patients

requires authentication

This endpoint does not save any information to the database. Its purpose is to make sure the patient will be saved in the HubSpot.

Example request:
curl --request POST \
    "http://localhost:8000/api/documents/accept/tos" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/documents/accept/tos"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (202, OK):


{
    "message": "Patients' \"Terms of Service\" accepted",
    "code": "DOCUMENTS:ACCEPT_PATIENT_TOS:ACCEPTED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to accept patients' \"Terms of Service\"",
    "code": "DOCUMENTS:ACCEPT_PATIENT_TOS:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/documents/accept/tos

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Feedback

Endpoints related to feedback collecting

Check feedback token

Example request:
curl --request POST \
    "http://localhost:8000/api/feedback/4KJ2YLM0MA64Y6D6FUY2OFY690IICO1OJ2DHR6T68IZNI528H3SKUFJMY0C5DHOA/check" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/feedback/4KJ2YLM0MA64Y6D6FUY2OFY690IICO1OJ2DHR6T68IZNI528H3SKUFJMY0C5DHOA/check"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "valid": true
}
 

Request   

POST api/feedback/{token}/check

Headers

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

token   string   

Authentication token Example: 4KJ2YLM0MA64Y6D6FUY2OFY690IICO1OJ2DHR6T68IZNI528H3SKUFJMY0C5DHOA

Send feedback with token

Example request:
curl --request POST \
    "http://localhost:8000/api/feedback/4KJ2YLM0MA64Y6D6FUY2OFY690IICO1OJ2DHR6T68IZNI528H3SKUFJMY0C5DHOA" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"type\": \"contextual\",
    \"trigger\": \"remote_session\",
    \"rate\": 5,
    \"description\": \"That was amazing!\",
    \"skipped\": false,
    \"training_day_id\": 1
}"
const url = new URL(
    "http://localhost:8000/api/feedback/4KJ2YLM0MA64Y6D6FUY2OFY690IICO1OJ2DHR6T68IZNI528H3SKUFJMY0C5DHOA"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "type": "contextual",
    "trigger": "remote_session",
    "rate": 5,
    "description": "That was amazing!",
    "skipped": false,
    "training_day_id": 1
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 1,
    "user_id": 6,
    "type": "on_demand",
    "trigger": "patient_create",
    "platform": "mobile",
    "rate": 2,
    "description": "A asperiores autem molestiae quis explicabo.",
    "skipped": 1,
    "training_day_id": null,
    "created_at": "2026-11-03T17:23:59.000000Z",
    "updated_at": "2026-11-03T17:23:59.000000Z"
}
 

Example response (403, Invalid token):


{
    "message": "Invalid token",
    "code": "FEEDBACK:SEND_WITH_TOKEN:INVALID_TOKEN"
}
 

Request   

POST api/feedback/{token}

Headers

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

token   string   

Authentication token Example: 4KJ2YLM0MA64Y6D6FUY2OFY690IICO1OJ2DHR6T68IZNI528H3SKUFJMY0C5DHOA

Body Parameters

type   string   

Type of feedback. Example: contextual

Must be one of:
  • contextual
  • periodic
trigger   string  optional  

Feedback trigger. This field is required when type is contextual. Example: remote_session

Must be one of:
  • remote_session
  • local_session
  • async_session
  • patient_create
  • clinician_invite
  • firmware_update
  • new_config
  • grip_change
  • training_failed
  • training_success
rate   integer  optional  

User's rating. MINIMUM:NUMBER:0 MAXIMUM:NUMBER:5. Example: 5

description   string  optional  

User's description. Example: That was amazing!

skipped   boolean  optional  

Feedback skipped by the user. Example: false

training_day_id   integer  optional  

Training day ID (for training feedback only). The id of an existing record in the App\Models\TrainingDay table. Example: 1

Response

Response Fields

id   integer   

Feedback ID.

user_id   integer   

User who submitted the feedback.

type   string   

Feedback type.

trigger   string   

What triggered the feedback.

platform   string   

Platform the feedback was submitted from.

rate   integer   

Feedback rating.

description   string   

Feedback description.

skipped   boolean   

Whether the feedback was skipped.

training_day_id   integer   

Associated training day ID.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Get feedback status

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/feedback/status" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/feedback/status"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "remote_session": true,
    "firmware_update": true,
    "local_session": true,
    "async_session": true,
    "patient_create": false,
    "clinician_invite": true
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to check feedback status",
    "code": "FEEDBACK:STATUS:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/feedback/status

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Send feedback

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/feedback" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"type\": \"contextual\",
    \"trigger\": \"remote_session\",
    \"platform\": \"web\",
    \"rate\": 5,
    \"description\": \"That was amazing!\",
    \"skipped\": false,
    \"training_day_id\": 1
}"
const url = new URL(
    "http://localhost:8000/api/feedback"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "type": "contextual",
    "trigger": "remote_session",
    "platform": "web",
    "rate": 5,
    "description": "That was amazing!",
    "skipped": false,
    "training_day_id": 1
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 2,
    "user_id": 311,
    "type": "on_demand",
    "trigger": "firmware_update",
    "platform": "mobile",
    "rate": 1,
    "description": "Explicabo velit voluptatibus est nisi nihil animi vel.",
    "skipped": 0,
    "training_day_id": null,
    "created_at": "2026-11-03T17:26:51.000000Z",
    "updated_at": "2026-11-03T17:26:51.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to send feedback",
    "code": "FEEDBACK:SEND:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/feedback

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

type   string   

Type of feedback. Example: contextual

Must be one of:
  • contextual
  • on_demand
  • periodic
  • training
trigger   string  optional  

Feedback trigger. Example: remote_session

Must be one of:
  • remote_session
  • local_session
  • async_session
  • patient_create
  • clinician_invite
  • firmware_update
  • new_config
  • grip_change
  • training_failed
  • training_success
platform   string   

Feedback platform. Example: web

Must be one of:
  • web
  • mobile
rate   integer  optional  

User's rating. MINIMUM:NUMBER:0 MAXIMUM:NUMBER:5. Example: 5

description   string  optional  

User's description. Example: That was amazing!

skipped   boolean  optional  

Feedback skipped by the user. Example: false

training_day_id   integer  optional  

Training day ID (for training feedback only). The id of an existing record in the App\Models\TrainingDay table. Example: 1

Response

Response Fields

id   integer   

Feedback ID.

user_id   integer   

User who submitted the feedback.

type   string   

Feedback type.

trigger   string   

What triggered the feedback.

platform   string   

Platform the feedback was submitted from.

rate   integer   

Feedback rating.

description   string   

Feedback description.

skipped   boolean   

Whether the feedback was skipped.

training_day_id   integer   

Associated training day ID.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Schedule feedback notification

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/feedback/schedule" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"trigger\": \"remote_session\"
}"
const url = new URL(
    "http://localhost:8000/api/feedback/schedule"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "trigger": "remote_session"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, OK):


{
    "scheduled_at": "2026-04-13 12:00:00"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to schedule feedback notification",
    "code": "FEEDBACK:SCHEDULE:INSUFFICIENT_PERMISSION"
}
 

Example response (403, Feedback with this trigger was already scheduled today):


{
    "message": "Feedback with this trigger was already scheduled today",
    "code": "FEEDBACK:SCHEDULE:ALREADY_SCHEDULED"
}
 

Request   

POST api/feedback/schedule

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

trigger   string   

Feedback trigger. Example: remote_session

Must be one of:
  • remote_session
  • local_session
  • async_session
  • patient_create
  • clinician_invite
  • firmware_update
  • new_config
  • grip_change
  • training_failed
  • training_success

List feedback

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/feedback" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/feedback"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (201):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 25,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 3,
            "user_id": 312,
            "type": "contextual",
            "trigger": "grip_change",
            "platform": "web",
            "rate": 2,
            "description": "Qui a et qui corporis iure omnis.",
            "skipped": 0,
            "training_day_id": null,
            "created_at": "2026-11-03T17:26:52.000000Z",
            "updated_at": "2026-11-03T17:26:52.000000Z"
        },
        {
            "id": 4,
            "user_id": 313,
            "type": "on_demand",
            "trigger": "grip_change",
            "platform": "web",
            "rate": 2,
            "description": "Illum ullam illo commodi maiores.",
            "skipped": 0,
            "training_day_id": null,
            "created_at": "2026-11-03T17:26:52.000000Z",
            "updated_at": "2026-11-03T17:26:52.000000Z"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to list feedback",
    "code": "FEEDBACK:LIST:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/feedback

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

perpage   integer  optional  

Elements per page (Default: 25).

page   integer  optional  

Page number (Default: 1).

sortby   string  optional  

Sort by field (available: date). Default: date, desc.

sortdir   string  optional  

Sort direction (available: asc, desc).

Response

Response Fields

items   object   
id   integer   

Feedback ID.

user_id   integer   

User who submitted the feedback.

type   string   

Feedback type.

trigger   string   

What triggered the feedback.

platform   string   

Platform the feedback was submitted from.

rate   integer   

Feedback rating.

description   string   

Feedback description.

skipped   boolean   

Whether the feedback was skipped.

training_day_id   integer   

Associated training day ID.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

Export feedback to CSV

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/feedback/csv" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/feedback/csv"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, CSV file):


{
    "file": "https://staging-us-east-2-aether-biomedical-s3-us-bucket.s3.us-east-2.amazonaws.com/feedback/feedback-1759228216.csv",
    "expires": "2025-09-30 10:40:00"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to export feedback",
    "code": "FEEDBACK:EXPORT_CSV:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/feedback/csv

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Get feedback cooldowns

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/feedback/cooldowns" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/feedback/cooldowns"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "remote_session": 1,
    "local_session": 1,
    "async_session": 1,
    "patient_create": 1,
    "clinician_invite": 1,
    "firmware_update": 1,
    "new_config": 1,
    "grip_change": 1
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage feedback cooldowns",
    "code": "FEEDBACK:GET_COOLDOWNS:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/feedback/cooldowns

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Update feedback cooldowns

requires authentication

Example request:
curl --request PUT \
    "http://localhost:8000/api/feedback/cooldowns" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"remote_session\": 1,
    \"local_session\": 1,
    \"async_session\": 1,
    \"patient_create\": 1,
    \"clinician_invite\": 1,
    \"firmware_update\": 1,
    \"new_config\": 1,
    \"grip_change\": 1
}"
const url = new URL(
    "http://localhost:8000/api/feedback/cooldowns"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "remote_session": 1,
    "local_session": 1,
    "async_session": 1,
    "patient_create": 1,
    "clinician_invite": 1,
    "firmware_update": 1,
    "new_config": 1,
    "grip_change": 1
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, OK):


{
    "remote_session": 1,
    "local_session": 1,
    "async_session": 1,
    "patient_create": 1,
    "clinician_invite": 1,
    "firmware_update": 1,
    "new_config": 1,
    "grip_change": 1
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage feedback cooldowns",
    "code": "FEEDBACK:UPDATE_COOLDOWNS:INSUFFICIENT_PERMISSION"
}
 

Request   

PUT api/feedback/cooldowns

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

remote_session   integer  optional  

Cooldown for remote session trigger. MINIMUM:NUMBER:1. Example: 1

local_session   integer  optional  

Cooldown for local session trigger. MINIMUM:NUMBER:1. Example: 1

async_session   integer  optional  

Cooldown for async session trigger. MINIMUM:NUMBER:1. Example: 1

patient_create   integer  optional  

Cooldown for patient create trigger. MINIMUM:NUMBER:1. Example: 1

clinician_invite   integer  optional  

Cooldown for clinician invite trigger. MINIMUM:NUMBER:1. Example: 1

firmware_update   integer  optional  

Cooldown for firmware update trigger. MINIMUM:NUMBER:1. Example: 1

new_config   integer  optional  

Cooldown for new config trigger. MINIMUM:NUMBER:1. Example: 1

grip_change   integer  optional  

Cooldown for grip change trigger. MINIMUM:NUMBER:1. Example: 1

Get feedback enabled

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/feedback/enabled" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/feedback/enabled"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "remote_session": true,
    "local_session": true,
    "async_session": true,
    "patient_create": true,
    "clinician_invite": true,
    "firmware_update": true,
    "new_config": true,
    "grip_change": true
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage feedback cooldowns",
    "code": "FEEDBACK:GET_ENABLED:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/feedback/enabled

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Set feedback enabled

requires authentication

Example request:
curl --request PUT \
    "http://localhost:8000/api/feedback/enabled" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"remote_session\": false,
    \"local_session\": false,
    \"async_session\": false,
    \"patient_create\": false,
    \"clinician_invite\": false,
    \"firmware_update\": false,
    \"new_config\": false,
    \"grip_change\": false
}"
const url = new URL(
    "http://localhost:8000/api/feedback/enabled"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "remote_session": false,
    "local_session": false,
    "async_session": false,
    "patient_create": false,
    "clinician_invite": false,
    "firmware_update": false,
    "new_config": false,
    "grip_change": false
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, OK):


{
    "remote_session": true,
    "local_session": true,
    "async_session": true,
    "patient_create": true,
    "clinician_invite": true,
    "firmware_update": true,
    "new_config": true,
    "grip_change": true
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage feedback cooldowns",
    "code": "FEEDBACK:SET_ENABLED:INSUFFICIENT_PERMISSION"
}
 

Request   

PUT api/feedback/enabled

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

remote_session   boolean  optional  

Enabled state for remote session trigger. Example: false

local_session   boolean  optional  

Enabled state for local session trigger. Example: false

async_session   boolean  optional  

Enabled state for async session trigger. Example: false

patient_create   boolean  optional  

Enabled state for patient create trigger. Example: false

clinician_invite   boolean  optional  

Enabled state for clinician invite trigger. Example: false

firmware_update   boolean  optional  

Enabled state for firmware update trigger. Example: false

new_config   boolean  optional  

Enabled state for new config trigger. Example: false

grip_change   boolean  optional  

Enabled state for grip change trigger. Example: false

Feedback statistics

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/feedback/statistics" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/feedback/statistics"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "submitted": 100,
    "skipped": 150,
    "total": 250,
    "average_rating": 4.76
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view feedback statistics",
    "code": "FEEDBACK:STATISTICS:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/feedback/statistics

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Finger Calibrations

API endpoints for managing finger calibration results

Get finger calibration

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/device/1/finger-calibration" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/device/1/finger-calibration"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 1,
            "device_id": 14,
            "result": "culpa",
            "created_at": "2026-11-03T17:24:20.000000Z",
            "updated_at": "2026-11-03T17:24:20.000000Z"
        },
        {
            "id": 2,
            "device_id": 15,
            "result": "sit",
            "created_at": "2026-11-03T17:24:20.000000Z",
            "updated_at": "2026-11-03T17:24:20.000000Z"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view finger calibration data",
    "code": "FINGER_CALIBRATION:VIEW:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "FINGER_CALIBRATION:VIEW:DEVICE_NOT_FOUND"
}
 

Request   

GET api/device/{id}/finger-calibration

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Device ID. Example: 1

Query Parameters

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

Store finger calibration

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/device/1/finger-calibration" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"result\": \"test\"
}"
const url = new URL(
    "http://localhost:8000/api/device/1/finger-calibration"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "result": "test"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 3,
    "device_id": 16,
    "result": "eius",
    "created_at": "2026-11-03T17:24:20.000000Z",
    "updated_at": "2026-11-03T17:24:20.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to create finger calibration",
    "code": "FINGER_CALIBRATION:CREATE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "FINGER_CALIBRATION:CREATE:DEVICE_NOT_FOUND"
}
 

Request   

POST api/device/{id}/finger-calibration

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Device ID. Example: 1

Body Parameters

result   string   

Calibration result. Example: test

GeoData

API endpoints for geodata

Get supported timezones list

Example request:
curl --request GET \
    --get "http://localhost:8000/api/timezones" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/timezones"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, OK):


[
    "Africa/Abidjan",
    "Africa/Accra",
    "Africa/Addis_Ababa",
    "Africa/Algiers",
    "Africa/Asmara",
    "Africa/Bamako",
    "Africa/Bangui",
    "Africa/Banjul",
    "Africa/Bissau",
    "Africa/Blantyre",
    "Africa/Brazzaville",
    "Africa/Bujumbura",
    "Africa/Cairo",
    "Africa/Casablanca",
    "Africa/Ceuta",
    "Africa/Conakry",
    "Africa/Dakar",
    "Africa/Dar_es_Salaam",
    "Africa/Djibouti",
    "Africa/Douala",
    "Africa/El_Aaiun",
    "Africa/Freetown",
    "Africa/Gaborone",
    "Africa/Harare",
    "Africa/Johannesburg",
    "Africa/Juba",
    "Africa/Kampala",
    "Africa/Khartoum",
    "Africa/Kigali",
    "Africa/Kinshasa",
    "Africa/Lagos",
    "Africa/Libreville",
    "Africa/Lome",
    "Africa/Luanda",
    "Africa/Lubumbashi",
    "Africa/Lusaka",
    "Africa/Malabo",
    "Africa/Maputo",
    "Africa/Maseru",
    "Africa/Mbabane",
    "Africa/Mogadishu",
    "Africa/Monrovia",
    "Africa/Nairobi",
    "Africa/Ndjamena",
    "Africa/Niamey",
    "Africa/Nouakchott",
    "Africa/Ouagadougou",
    "Africa/Porto-Novo",
    "Africa/Sao_Tome",
    "Africa/Tripoli",
    "Africa/Tunis",
    "Africa/Windhoek",
    "America/Adak",
    "America/Anchorage",
    "America/Anguilla",
    "America/Antigua",
    "America/Araguaina",
    "America/Argentina/Buenos_Aires",
    "America/Argentina/Catamarca",
    "America/Argentina/Cordoba",
    "America/Argentina/Jujuy",
    "America/Argentina/La_Rioja",
    "America/Argentina/Mendoza",
    "America/Argentina/Rio_Gallegos",
    "America/Argentina/Salta",
    "America/Argentina/San_Juan",
    "America/Argentina/San_Luis",
    "America/Argentina/Tucuman",
    "America/Argentina/Ushuaia",
    "America/Aruba",
    "America/Asuncion",
    "America/Atikokan",
    "America/Bahia",
    "America/Bahia_Banderas",
    "America/Barbados",
    "America/Belem",
    "America/Belize",
    "America/Blanc-Sablon",
    "America/Boa_Vista",
    "America/Bogota",
    "America/Boise",
    "America/Cambridge_Bay",
    "America/Campo_Grande",
    "America/Cancun",
    "America/Caracas",
    "America/Cayenne",
    "America/Cayman",
    "America/Chicago",
    "America/Chihuahua",
    "America/Costa_Rica",
    "America/Creston",
    "America/Cuiaba",
    "America/Curacao",
    "America/Danmarkshavn",
    "America/Dawson",
    "America/Dawson_Creek",
    "America/Denver",
    "America/Detroit",
    "America/Dominica",
    "America/Edmonton",
    "America/Eirunepe",
    "America/El_Salvador",
    "America/Fort_Nelson",
    "America/Fortaleza",
    "America/Glace_Bay",
    "America/Goose_Bay",
    "America/Grand_Turk",
    "America/Grenada",
    "America/Guadeloupe",
    "America/Guatemala",
    "America/Guayaquil",
    "America/Guyana",
    "America/Halifax",
    "America/Havana",
    "America/Hermosillo",
    "America/Indiana/Indianapolis",
    "America/Indiana/Knox",
    "America/Indiana/Marengo",
    "America/Indiana/Petersburg",
    "America/Indiana/Tell_City",
    "America/Indiana/Vevay",
    "America/Indiana/Vincennes",
    "America/Indiana/Winamac",
    "America/Inuvik",
    "America/Iqaluit",
    "America/Jamaica",
    "America/Juneau",
    "America/Kentucky/Louisville",
    "America/Kentucky/Monticello",
    "America/Kralendijk",
    "America/La_Paz",
    "America/Lima",
    "America/Los_Angeles",
    "America/Lower_Princes",
    "America/Maceio",
    "America/Managua",
    "America/Manaus",
    "America/Marigot",
    "America/Martinique",
    "America/Matamoros",
    "America/Mazatlan",
    "America/Menominee",
    "America/Merida",
    "America/Metlakatla",
    "America/Mexico_City",
    "America/Miquelon",
    "America/Moncton",
    "America/Monterrey",
    "America/Montevideo",
    "America/Montserrat",
    "America/Nassau",
    "America/New_York",
    "America/Nipigon",
    "America/Nome",
    "America/Noronha",
    "America/North_Dakota/Beulah",
    "America/North_Dakota/Center",
    "America/North_Dakota/New_Salem",
    "America/Nuuk",
    "America/Ojinaga",
    "America/Panama",
    "America/Pangnirtung",
    "America/Paramaribo",
    "America/Phoenix",
    "America/Port-au-Prince",
    "America/Port_of_Spain",
    "America/Porto_Velho",
    "America/Puerto_Rico",
    "America/Punta_Arenas",
    "America/Rainy_River",
    "America/Rankin_Inlet",
    "America/Recife",
    "America/Regina",
    "America/Resolute",
    "America/Rio_Branco",
    "America/Santarem",
    "America/Santiago",
    "America/Santo_Domingo",
    "America/Sao_Paulo",
    "America/Scoresbysund",
    "America/Sitka",
    "America/St_Barthelemy",
    "America/St_Johns",
    "America/St_Kitts",
    "America/St_Lucia",
    "America/St_Thomas",
    "America/St_Vincent",
    "America/Swift_Current",
    "America/Tegucigalpa",
    "America/Thule",
    "America/Thunder_Bay",
    "America/Tijuana",
    "America/Toronto",
    "America/Tortola",
    "America/Vancouver",
    "America/Whitehorse",
    "America/Winnipeg",
    "America/Yakutat",
    "America/Yellowknife",
    "Antarctica/Casey",
    "Antarctica/Davis",
    "Antarctica/DumontDUrville",
    "Antarctica/Macquarie",
    "Antarctica/Mawson",
    "Antarctica/McMurdo",
    "Antarctica/Palmer",
    "Antarctica/Rothera",
    "Antarctica/Syowa",
    "Antarctica/Troll",
    "Antarctica/Vostok",
    "Arctic/Longyearbyen",
    "Asia/Aden",
    "Asia/Almaty",
    "Asia/Amman",
    "Asia/Anadyr",
    "Asia/Aqtau",
    "Asia/Aqtobe",
    "Asia/Ashgabat",
    "Asia/Atyrau",
    "Asia/Baghdad",
    "Asia/Bahrain",
    "Asia/Baku",
    "Asia/Bangkok",
    "Asia/Barnaul",
    "Asia/Beirut",
    "Asia/Bishkek",
    "Asia/Brunei",
    "Asia/Chita",
    "Asia/Choibalsan",
    "Asia/Colombo",
    "Asia/Damascus",
    "Asia/Dhaka",
    "Asia/Dili",
    "Asia/Dubai",
    "Asia/Dushanbe",
    "Asia/Famagusta",
    "Asia/Gaza",
    "Asia/Hebron",
    "Asia/Ho_Chi_Minh",
    "Asia/Hong_Kong",
    "Asia/Hovd",
    "Asia/Irkutsk",
    "Asia/Jakarta",
    "Asia/Jayapura",
    "Asia/Jerusalem",
    "Asia/Kabul",
    "Asia/Kamchatka",
    "Asia/Karachi",
    "Asia/Kathmandu",
    "Asia/Khandyga",
    "Asia/Kolkata",
    "Asia/Krasnoyarsk",
    "Asia/Kuala_Lumpur",
    "Asia/Kuching",
    "Asia/Kuwait",
    "Asia/Macau",
    "Asia/Magadan",
    "Asia/Makassar",
    "Asia/Manila",
    "Asia/Muscat",
    "Asia/Nicosia",
    "Asia/Novokuznetsk",
    "Asia/Novosibirsk",
    "Asia/Omsk",
    "Asia/Oral",
    "Asia/Phnom_Penh",
    "Asia/Pontianak",
    "Asia/Pyongyang",
    "Asia/Qatar",
    "Asia/Qostanay",
    "Asia/Qyzylorda",
    "Asia/Riyadh",
    "Asia/Sakhalin",
    "Asia/Samarkand",
    "Asia/Seoul",
    "Asia/Shanghai",
    "Asia/Singapore",
    "Asia/Srednekolymsk",
    "Asia/Taipei",
    "Asia/Tashkent",
    "Asia/Tbilisi",
    "Asia/Tehran",
    "Asia/Thimphu",
    "Asia/Tokyo",
    "Asia/Tomsk",
    "Asia/Ulaanbaatar",
    "Asia/Urumqi",
    "Asia/Ust-Nera",
    "Asia/Vientiane",
    "Asia/Vladivostok",
    "Asia/Yakutsk",
    "Asia/Yangon",
    "Asia/Yekaterinburg",
    "Asia/Yerevan",
    "Atlantic/Azores",
    "Atlantic/Bermuda",
    "Atlantic/Canary",
    "Atlantic/Cape_Verde",
    "Atlantic/Faroe",
    "Atlantic/Madeira",
    "Atlantic/Reykjavik",
    "Atlantic/South_Georgia",
    "Atlantic/St_Helena",
    "Atlantic/Stanley",
    "Australia/Adelaide",
    "Australia/Brisbane",
    "Australia/Broken_Hill",
    "Australia/Darwin",
    "Australia/Eucla",
    "Australia/Hobart",
    "Australia/Lindeman",
    "Australia/Lord_Howe",
    "Australia/Melbourne",
    "Australia/Perth",
    "Australia/Sydney",
    "Europe/Amsterdam",
    "Europe/Andorra",
    "Europe/Astrakhan",
    "Europe/Athens",
    "Europe/Belgrade",
    "Europe/Berlin",
    "Europe/Bratislava",
    "Europe/Brussels",
    "Europe/Bucharest",
    "Europe/Budapest",
    "Europe/Busingen",
    "Europe/Chisinau",
    "Europe/Copenhagen",
    "Europe/Dublin",
    "Europe/Gibraltar",
    "Europe/Guernsey",
    "Europe/Helsinki",
    "Europe/Isle_of_Man",
    "Europe/Istanbul",
    "Europe/Jersey",
    "Europe/Kaliningrad",
    "Europe/Kiev",
    "Europe/Kirov",
    "Europe/Lisbon",
    "Europe/Ljubljana",
    "Europe/London",
    "Europe/Luxembourg",
    "Europe/Madrid",
    "Europe/Malta",
    "Europe/Mariehamn",
    "Europe/Minsk",
    "Europe/Monaco",
    "Europe/Moscow",
    "Europe/Oslo",
    "Europe/Paris",
    "Europe/Podgorica",
    "Europe/Prague",
    "Europe/Riga",
    "Europe/Rome",
    "Europe/Samara",
    "Europe/San_Marino",
    "Europe/Sarajevo",
    "Europe/Saratov",
    "Europe/Simferopol",
    "Europe/Skopje",
    "Europe/Sofia",
    "Europe/Stockholm",
    "Europe/Tallinn",
    "Europe/Tirane",
    "Europe/Ulyanovsk",
    "Europe/Uzhgorod",
    "Europe/Vaduz",
    "Europe/Vatican",
    "Europe/Vienna",
    "Europe/Vilnius",
    "Europe/Volgograd",
    "Europe/Warsaw",
    "Europe/Zagreb",
    "Europe/Zaporozhye",
    "Europe/Zurich",
    "Indian/Antananarivo",
    "Indian/Chagos",
    "Indian/Christmas",
    "Indian/Cocos",
    "Indian/Comoro",
    "Indian/Kerguelen",
    "Indian/Mahe",
    "Indian/Maldives",
    "Indian/Mauritius",
    "Indian/Mayotte",
    "Indian/Reunion",
    "Pacific/Apia",
    "Pacific/Auckland",
    "Pacific/Bougainville",
    "Pacific/Chatham",
    "Pacific/Chuuk",
    "Pacific/Easter",
    "Pacific/Efate",
    "Pacific/Fakaofo",
    "Pacific/Fiji",
    "Pacific/Funafuti",
    "Pacific/Galapagos",
    "Pacific/Gambier",
    "Pacific/Guadalcanal",
    "Pacific/Guam",
    "Pacific/Honolulu",
    "Pacific/Kanton",
    "Pacific/Kiritimati",
    "Pacific/Kosrae",
    "Pacific/Kwajalein",
    "Pacific/Majuro",
    "Pacific/Marquesas",
    "Pacific/Midway",
    "Pacific/Nauru",
    "Pacific/Niue",
    "Pacific/Norfolk",
    "Pacific/Noumea",
    "Pacific/Pago_Pago",
    "Pacific/Palau",
    "Pacific/Pitcairn",
    "Pacific/Pohnpei",
    "Pacific/Port_Moresby",
    "Pacific/Rarotonga",
    "Pacific/Saipan",
    "Pacific/Tahiti",
    "Pacific/Tarawa",
    "Pacific/Tongatapu",
    "Pacific/Wake",
    "Pacific/Wallis",
    "UTC"
]
 

Request   

GET api/timezones

Headers

Content-Type      

Example: application/json

Accept      

Example: application/json

Goals

API endpoints for goals

The goals module consists of:

List grips

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/grips?model=1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/grips"
);

const params = {
    "model": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


[
    {
        "id": 1,
        "number": 2926,
        "name": "quia grip",
        "description": null,
        "opposed": 0,
        "created_at": "2026-11-03T17:26:18.000000Z",
        "updated_at": "2026-11-03T17:26:18.000000Z"
    },
    {
        "id": 2,
        "number": 6873,
        "name": "quis grip",
        "description": null,
        "opposed": 0,
        "created_at": "2026-11-03T17:26:18.000000Z",
        "updated_at": "2026-11-03T17:26:18.000000Z"
    }
]
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view grips",
    "code": "GOALS:LIST_GRIPS:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/grips

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

model   integer  optional  

Device Model ID. Use this parameter to receive the correct grip images in the image field. When not present, all images will be listed in images array. Example: 1

Response

Response Fields

id   integer   

Grip ID.

number   integer   

Grip number identifier.

name   string   

Grip name.

description   string   

Grip description.

opposed   boolean   

Whether the grip is opposed.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

List exercises

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/exercises" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/exercises"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 1,
            "name": "exercises.Berge Road",
            "description": "exercises.Perspiciatis commodi necessitatibus dicta quas.",
            "icon": "😣",
            "created_at": "2026-11-03T17:26:20.000000Z",
            "updated_at": "2026-11-03T17:26:20.000000Z"
        },
        {
            "id": 2,
            "name": "exercises.Lucile Manors",
            "description": "exercises.Alias dicta ea sint quam possimus.",
            "icon": "😱",
            "created_at": "2026-11-03T17:26:20.000000Z",
            "updated_at": "2026-11-03T17:26:20.000000Z"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view exercises",
    "code": "GOALS:LIST_EXERCISES:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/exercises

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

Response

Response Fields

items   object   
id   integer   

Exercise ID.

name   string   

Exercise name.

description   string   

Exercise description.

icon   string   

Exercise icon identifier.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

Create exercise

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/exercises" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Water plants\",
    \"description\": \"Use Tripod Close Grip and water your plants\",
    \"icon\": \"🪴\"
}"
const url = new URL(
    "http://localhost:8000/api/exercises"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Water plants",
    "description": "Use Tripod Close Grip and water your plants",
    "icon": "🪴"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 3,
    "name": "exercises.Reilly Cliff",
    "description": "exercises.Laborum odio ut sequi sed quaerat nisi ut.",
    "icon": "😄",
    "created_at": "2026-11-03T17:26:20.000000Z",
    "updated_at": "2026-11-03T17:26:20.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage exercises",
    "code": "GOALS:CREATE_EXERCISE:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/exercises

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

name   string   

Exercise name. Example: Water plants

description   string   

Exercise description. Example: Use Tripod Close Grip and water your plants

icon   string   

Emoji icon. Example: 🪴

Response

Response Fields

id   integer   

Exercise ID.

name   string   

Exercise name.

description   string   

Exercise description.

icon   string   

Exercise icon identifier.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Update exercise

requires authentication

Example request:
curl --request PUT \
    "http://localhost:8000/api/exercises/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Water plants\",
    \"description\": \"Use Tripod Close Grip and water your plants\",
    \"icon\": \"🪴\"
}"
const url = new URL(
    "http://localhost:8000/api/exercises/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Water plants",
    "description": "Use Tripod Close Grip and water your plants",
    "icon": "🪴"
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (202):


{
    "id": 4,
    "name": "exercises.Stark Mission",
    "description": "exercises.Qui error molestias mollitia recusandae.",
    "icon": "😉",
    "created_at": "2026-11-03T17:26:20.000000Z",
    "updated_at": "2026-11-03T17:26:20.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage exercises",
    "code": "GOALS:UPDATE_EXERCISE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Exercise not found):


{
    "message": "Exercise not found",
    "code": "GOALS:UPDATE_EXERCISE:EXERCISE_NOT_FOUND"
}
 

Request   

PUT api/exercises/{exerciseId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

exerciseId   integer   

Exercise ID. Example: 1

Body Parameters

name   string  optional  

Exercise name. Example: Water plants

description   string  optional  

Exercise description. Example: Use Tripod Close Grip and water your plants

icon   string  optional  

Emoji icon. Example: 🪴

Response

Response Fields

id   integer   

Exercise ID.

name   string   

Exercise name.

description   string   

Exercise description.

icon   string   

Exercise icon identifier.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Delete exercise

requires authentication

Example request:
curl --request DELETE \
    "http://localhost:8000/api/exercises/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/exercises/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (202, OK):


{
    "message": "Exercise deleted",
    "code": "GOALS:DELETE_EXERCISE:DELETED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage exercises",
    "code": "GOALS:DELETE_EXERCISE:INSUFFICIENT_PERMISSION"
}
 

Example response (403, Exercise is used in goals):


{
    "message": "Cannot delete: exercise is used in goals (1)",
    "code": "GOALS:DELETE_EXERCISE:HAS_GOALS"
}
 

Example response (404, Exercise not found):


{
    "message": "Exercise not found",
    "code": "GOALS:DELETE_EXERCISE:EXERCISE_NOT_FOUND"
}
 

Request   

DELETE api/exercises/{exerciseId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

exerciseId   integer   

Exercise ID. Example: 1

List user goals

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/user/1/goals?active=1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/user/1/goals"
);

const params = {
    "active": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 1,
            "amputee_id": 263,
            "clinician_id": 264,
            "start_date": "2009-06-23",
            "end_date": "1993-11-26",
            "active": 1,
            "created_at": "2026-11-03T17:26:21.000000Z",
            "updated_at": "2026-11-03T17:26:21.000000Z"
        },
        {
            "id": 2,
            "amputee_id": 265,
            "clinician_id": 266,
            "start_date": "1996-11-19",
            "end_date": "1983-09-21",
            "active": 0,
            "created_at": "2026-11-03T17:26:22.000000Z",
            "updated_at": "2026-11-03T17:26:22.000000Z"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view user goals",
    "code": "GOALS:LIST_GOALS:INSUFFICIENT_PERMISSION"
}
 

Example response (404, User not found):


{
    "message": "User not found",
    "code": "GOALS:LIST_GOALS:USER_NOT_FOUND"
}
 

Request   

GET api/user/{userId}/goals

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

userId   integer   

User ID. Example: 1

Query Parameters

active   boolean  optional  

Goal active status. Example: 1

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

sortby   string  optional  

Sort by field (available: start_date). Default: start_date, desc.

sortdir   string  optional  

Sort direction (available: asc, desc).

Response

Response Fields

items   object   
id   integer   

Goal ID.

amputee_id   integer   

Patient (amputee) user ID.

clinician_id   integer   

Clinician user ID.

start_date   string   

Goal start date.

end_date   string   

Goal end date.

active   boolean   

Whether the goal is active.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

Create user goal

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/user/1/goals" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"start_date\": \"2023-05-12\",
    \"end_date\": \"2023-05-15\",
    \"active\": true
}"
const url = new URL(
    "http://localhost:8000/api/user/1/goals"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "start_date": "2023-05-12",
    "end_date": "2023-05-15",
    "active": true
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 3,
    "amputee_id": 267,
    "clinician_id": 268,
    "start_date": "1999-03-05",
    "end_date": "1982-08-08",
    "active": 0,
    "created_at": "2026-11-03T17:26:23.000000Z",
    "updated_at": "2026-11-03T17:26:23.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage user goals",
    "code": "GOALS:CREATE_GOAL:INSUFFICIENT_PERMISSION"
}
 

Example response (404, User not found):


{
    "message": "User not found",
    "code": "GOALS:CREATE_GOAL:USER_NOT_FOUND"
}
 

Request   

POST api/user/{userId}/goals

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

userId   integer   

User ID. Example: 1

Body Parameters

start_date   string   

Start date of the goal tracking. MUST_BE_DATE Must be a valid date in the format Y-m-d. MUST_BE_AFTER_OR_EQUAL:today. Example: 2023-05-12

end_date   string   

End date of goal tracking. MUST_BE_DATE Must be a valid date in the format Y-m-d. MUST_BE_AFTER:start_date. Example: 2023-05-15

active   boolean  optional  

Status of goal activity. Example: true

Response

Response Fields

id   integer   

Goal ID.

amputee_id   integer   

Patient (amputee) user ID.

clinician_id   integer   

Clinician user ID.

start_date   string   

Goal start date.

end_date   string   

Goal end date.

active   boolean   

Whether the goal is active.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Update user goal

requires authentication

Example request:
curl --request PUT \
    "http://localhost:8000/api/user/1/goals/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"start_date\": \"2023-05-12\",
    \"end_date\": \"2023-05-15\",
    \"active\": true
}"
const url = new URL(
    "http://localhost:8000/api/user/1/goals/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "start_date": "2023-05-12",
    "end_date": "2023-05-15",
    "active": true
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (202):


{
    "id": 4,
    "amputee_id": 269,
    "clinician_id": 270,
    "start_date": "1980-02-05",
    "end_date": "1979-03-19",
    "active": 0,
    "created_at": "2026-11-03T17:26:25.000000Z",
    "updated_at": "2026-11-03T17:26:25.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage user goals",
    "code": "GOALS:UPDATE_GOAL:INSUFFICIENT_PERMISSION"
}
 

Example response (403, Cannot activate ongoing goal):


{
    "message": "Cannot activate ongoing goal",
    "code": "GOALS:UPDATE_GOAL:GOAL_IS_ONGOING"
}
 

Example response (404, User not found):


{
    "message": "User not found",
    "code": "GOALS:UPDATE_GOAL:USER_NOT_FOUND"
}
 

Example response (404, Goal not found):


{
    "message": "Goal not found",
    "code": "GOALS:UPDATE_GOAL:GOAL_NOT_FOUND"
}
 

Request   

PUT api/user/{userId}/goals/{goalId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

userId   integer   

User ID. Example: 1

goalId   integer   

Goal ID. Example: 1

Body Parameters

start_date   string  optional  

Start date of the goal tracking. MUST_BE_DATE Must be a valid date in the format Y-m-d. MUST_BE_AFTER_OR_EQUAL:today. Example: 2023-05-12

end_date   string  optional  

End date of goal tracking. MUST_BE_DATE Must be a valid date in the format Y-m-d. MUST_BE_AFTER:start_date. Example: 2023-05-15

active   boolean  optional  

Status of goal activity. Example: true

Response

Response Fields

id   integer   

Goal ID.

amputee_id   integer   

Patient (amputee) user ID.

clinician_id   integer   

Clinician user ID.

start_date   string   

Goal start date.

end_date   string   

Goal end date.

active   boolean   

Whether the goal is active.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Delete user goal

requires authentication

Example request:
curl --request DELETE \
    "http://localhost:8000/api/user/1/goals/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/user/1/goals/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (202, OK):


{
    "message": "Goal deleted",
    "code": "GOALS:DELETE_GOAL:DELETED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage user goals",
    "code": "GOALS:DELETE_GOAL:INSUFFICIENT_PERMISSION"
}
 

Example response (403, Cannot delete ongoing goal):


{
    "message": "Cannot delete active ongoing goal",
    "code": "GOALS:DELETE_GOAL:GOAL_IS_ONGOING"
}
 

Example response (404, User not found):


{
    "message": "User not found",
    "code": "GOALS:DELETE_GOAL:USER_NOT_FOUND"
}
 

Example response (404, Goal not found):


{
    "message": "Goal not found",
    "code": "GOALS:DELETE_GOAL:GOAL_NOT_FOUND"
}
 

Request   

DELETE api/user/{userId}/goals/{goalId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

userId   integer   

User ID. Example: 1

goalId   integer   

Goal ID. Example: 1

List user goal conditions

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/user/1/goals/1/conditions" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/user/1/goals/1/conditions"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 1,
            "goal_id": 5,
            "type": "grip",
            "grip_id": 3,
            "grips_frequency": "d",
            "grips_count": 798,
            "switches_frequency": "m",
            "switches_count": 725,
            "exercise_id": 5,
            "exercise_frequency": "d",
            "exercise_count": 9,
            "created_at": "2026-11-03T17:26:26.000000Z",
            "updated_at": "2026-11-03T17:26:26.000000Z"
        },
        {
            "id": 2,
            "goal_id": 6,
            "type": "exercise",
            "grip_id": 4,
            "grips_frequency": "d",
            "grips_count": 388,
            "switches_frequency": "a",
            "switches_count": 869,
            "exercise_id": 6,
            "exercise_frequency": "d",
            "exercise_count": 1,
            "created_at": "2026-11-03T17:26:27.000000Z",
            "updated_at": "2026-11-03T17:26:27.000000Z"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view user goals",
    "code": "GOALS:LIST_CONDITIONS:INSUFFICIENT_PERMISSION"
}
 

Example response (404, User not found):


{
    "message": "User not found",
    "code": "GOALS:LIST_CONDITIONS:USER_NOT_FOUND"
}
 

Example response (404, Goal not found):


{
    "message": "Goal not found",
    "code": "GOALS:LIST_CONDITIONS:GOAL_NOT_FOUND"
}
 

Request   

GET api/user/{userId}/goals/{goalId}/conditions

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

userId   integer   

User ID. Example: 1

goalId   integer   

Goal ID. Example: 1

Query Parameters

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

Response

Response Fields

items   object   
id   integer   

Goal condition ID.

goal_id   integer   

Associated goal ID.

type   string   

Condition type.

Must be one of:
  • grip
  • switch
  • exercise
grip_id   integer   

Target grip ID.

grips_frequency   string   

Grip frequency period.

Must be one of:
  • d
  • w
  • m
  • a
grips_count   integer   

Required grip count.

switches_frequency   string   

Switch frequency period.

Must be one of:
  • d
  • w
  • m
  • a
switches_count   integer   

Required switch count.

exercise_id   integer   

Target exercise ID.

exercise_frequency   string   

Exercise frequency period.

Must be one of:
  • d
  • w
  • m
exercise_count   integer   

Required exercise count.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

Create user goal condition

requires authentication

Each goal condition could be one of type: grip, switch or exercise.

If goal condition is type of grip, fill only grip_id (optional), grips_frequency and grips_count.
If grip_id is null or missing, patient can perform any grip to fulfill objective.

If goal condition is type of switch, fill only switches_frequency and switches_count.

If goal condition is type of exercise, fill only exercise_id, exercise_frequency and exercise_count.


Restrictions:

  • you can add one grip-any condition (grip_id=null, any grip is counted) and many grip-specific conditions (for example: grip 1 - 100 times and grip 2 - 50 times),
  • all grips conditions must have same frequency (grips_frequency field),
  • sum of grip-specific conditions (grips_count field) cannot be greater than grip-any condition for same goal
  • you can add only one switch condition for same goal,
  • you can add multiple exercise conditions, but each exercise can be used only once.
Example request:
curl --request POST \
    "http://localhost:8000/api/user/1/goals/1/conditions" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"type\": \"grip\",
    \"grip_id\": 1,
    \"grips_frequency\": \"d\",
    \"grips_count\": 100,
    \"switches_frequency\": \"d\",
    \"switches_count\": 100,
    \"exercise_id\": 1,
    \"exercise_frequency\": \"d\",
    \"exercise_count\": 5
}"
const url = new URL(
    "http://localhost:8000/api/user/1/goals/1/conditions"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "type": "grip",
    "grip_id": 1,
    "grips_frequency": "d",
    "grips_count": 100,
    "switches_frequency": "d",
    "switches_count": 100,
    "exercise_id": 1,
    "exercise_frequency": "d",
    "exercise_count": 5
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 3,
    "goal_id": 7,
    "type": "exercise",
    "grip_id": 5,
    "grips_frequency": "w",
    "grips_count": 806,
    "switches_frequency": "d",
    "switches_count": 272,
    "exercise_id": 7,
    "exercise_frequency": "w",
    "exercise_count": 6,
    "created_at": "2026-11-03T17:26:28.000000Z",
    "updated_at": "2026-11-03T17:26:28.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage user goals",
    "code": "GOALS:CREATE_CONDITION:INSUFFICIENT_PERMISSION"
}
 

Example response (403, Cannot create condition: all grip conditions have to be same frequency):


{
    "message": "Cannot create condition: all grip conditions have to be same frequency",
    "code": "GOALS:CREATE_CONDITION:INVALID_FREQUENCY"
}
 

Example response (403, Cannot create condition: grip-any condition for this goal already exist):


{
    "message": "Cannot create condition: grip-any condition for this goal already exist",
    "code": "GOALS:CREATE_CONDITION:GRIP_ANY_ALREADY_EXISTS"
}
 

Example response (403, Cannot create condition: condition with this grip already exist):


{
    "message": "Cannot create condition: condition with this grip already exist",
    "code": "GOALS:CREATE_CONDITION:GRIP_CONDITION_ALREADY_EXISTS"
}
 

Example response (403, Cannot create condition: grip-any value cannot be lower than sum of grip-specific conditions):


{
    "message": "Cannot create condition: grip-any value cannot be lower than sum of grip-specific conditions",
    "code": "GOALS:CREATE_CONDITION:GRIP_ANY_LOWER_THAN_GRIPS_SUM"
}
 

Example response (403, Cannot create condition: sum of grip-specific conditions cannot be greater than value of grip-any condition):


{
    "message": "Cannot create condition: sum of grip-specific conditions cannot be greater than value of grip-any condition",
    "code": "GOALS:CREATE_CONDITION:GRIPS_SUM_GREATER_THAN_GRIP_ANY"
}
 

Example response (403, Cannot create condition: switch condition for this goal already exist):


{
    "message": "Cannot create condition: switch condition for this goal already exist",
    "code": "GOALS:CREATE_CONDITION:SWITCH_CONDITION_ALREADY_EXISTS"
}
 

Example response (403, Cannot create condition: condition with this exercise already exist):


{
    "message": "Cannot create condition: condition with this exercise already exist",
    "code": "GOALS:CREATE_CONDITION:EXERCISE_CONDITION_ALREADY_EXISTS"
}
 

Example response (404, User not found):


{
    "message": "User not found",
    "code": "GOALS:CREATE_CONDITION:USER_NOT_FOUND"
}
 

Example response (404, Goal not found):


{
    "message": "Goal not found",
    "code": "GOALS:CREATE_CONDITION:GOAL_NOT_FOUND"
}
 

Request   

POST api/user/{userId}/goals/{goalId}/conditions

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

userId   integer   

User ID. Example: 1

goalId   integer   

Goal ID. Example: 1

Body Parameters

type   string   

Goal condition type. Example: grip

Must be one of:
  • grip
  • switch
  • exercise
grip_id   integer  optional  

Grip number required for condition. Pass null to allow any grip. The id of an existing record in the App\Models\Grip table. Example: 1

grips_frequency   string  optional  

Grips frequency unit. d - daily, w - weekly, m - monthly, a - all time (within goal). This field is required when type is grip. Example: d

Must be one of:
  • d
  • w
  • m
  • a
grips_count   integer  optional  

Required number of grips per frequency unit. This field is required when type is grip. MINIMUM:NUMBER:1. Example: 100

switches_frequency   string  optional  

Switches frequency unit. d - daily, w - weekly, m - monthly, a - all time (within goal). This field is required when type is switch. Example: d

Must be one of:
  • d
  • w
  • m
  • a
switches_count   integer  optional  

Required number of switches per frequency unit. This field is required when type is switch. MINIMUM:NUMBER:1. Example: 100

exercise_id   integer  optional  

Exercise ID. This field is required when type is exercise. The id of an existing record in the App\Models\Exercise table. Example: 1

exercise_frequency   string  optional  

Exercise frequency unit. d - daily, w - weekly, m - monthly. This field is required when type is exercise. Example: d

Must be one of:
  • d
  • w
  • m
exercise_count   integer  optional  

Required number of performed exercises per frequency unit. This field is required when type is exercise. MINIMUM:NUMBER:1. Example: 5

Response

Response Fields

id   integer   

Goal condition ID.

goal_id   integer   

Associated goal ID.

type   string   

Condition type.

Must be one of:
  • grip
  • switch
  • exercise
grip_id   integer   

Target grip ID.

grips_frequency   string   

Grip frequency period.

Must be one of:
  • d
  • w
  • m
  • a
grips_count   integer   

Required grip count.

switches_frequency   string   

Switch frequency period.

Must be one of:
  • d
  • w
  • m
  • a
switches_count   integer   

Required switch count.

exercise_id   integer   

Target exercise ID.

exercise_frequency   string   

Exercise frequency period.

Must be one of:
  • d
  • w
  • m
exercise_count   integer   

Required exercise count.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Delete user goal condition

requires authentication

Example request:
curl --request DELETE \
    "http://localhost:8000/api/user/1/goals/1/conditions/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/user/1/goals/1/conditions/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (202, OK):


{
    "message": "Goal condition deleted",
    "code": "GOALS:DELETE_CONDITION:DELETED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage user goals",
    "code": "GOALS:DELETE_CONDITION:INSUFFICIENT_PERMISSION"
}
 

Example response (404, User not found):


{
    "message": "User not found",
    "code": "GOALS:DELETE_CONDITION:USER_NOT_FOUND"
}
 

Example response (404, Goal not found):


{
    "message": "Goal not found",
    "code": "GOALS:DELETE_CONDITION:GOAL_NOT_FOUND"
}
 

Request   

DELETE api/user/{userId}/goals/{goalId}/conditions/{conditionId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

userId   integer   

User ID. Example: 1

goalId   integer   

Goal ID. Example: 1

conditionId   integer   

Goal condition ID. Example: 1

Get user goal progress

requires authentication

Data is grouped into three parts:
  • grips - data about grips progress,
  • switches - data about switches progress,
  • exercises - data about exercises progress and attempts made.
For grips and switches there is summary, which is divided into 4 parts:
  • overall - summary of whole goal time-frame,
  • period - summary of given period, according to frequency (for example: if frequency of conditions is "weekly", these data is summary of current's week).
  • today - summary of today's, used mainly to send daily summary notifications,
  • conditions - goal conditions with progress for each one.
There are also extra fields inside the summary parts:
  • type for grips overall summary, which points which conditions were used to calculate the summary. If grip-any condition exists, it has priority over grip-specific conditions, otherwise summary contains sum of all grip-specific conditions,
  • frequency, frequency_from and frequency_to for period summary for both grips and switches, which describe time-frame of frequency and period,
  • done for today's summary for both grips and switches, indicates if today's goal is reached (it's calculated only for conditions of frequency "daily").
Example request:
curl --request GET \
    --get "http://localhost:8000/api/goals/1/progress" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/goals/1/progress"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "grips": {
        "meta": {
            "overall": {
                "from": "2023-09-01 00:00:00",
                "to": "2023-08-30 23:59:59"
            },
            "period": {
                "from": "2023-09-15 23:59:59",
                "to": "2023-09-21 23:59:59",
                "frequency": "w"
            }
        },
        "summary": {
            "type": "grips-specific",
            "progress": 222,
            "goal": 2535
        },
        "progress": [
            {
                "grip": {
                    "id": 1,
                    "number": 0,
                    "name": "Rest Opposition",
                    "description": null,
                    "created_at": "2023-08-30T12:20:00.000000Z",
                    "updated_at": "2023-08-30T12:20:00.000000Z"
                },
                "overall": {
                    "progress": 125,
                    "goal": 200
                },
                "period": {
                    "progress": 22,
                    "goal": 50
                }
            },
            {
                "grip": {
                    "id": 2,
                    "number": 1,
                    "name": "Power",
                    "description": null,
                    "created_at": "2023-05-18T11:24:11.000000Z",
                    "updated_at": "2023-05-18T11:24:11.000000Z"
                },
                "overall": {
                    "progress": 90,
                    "goal": 150
                },
                "period": {
                    "progress": 10,
                    "goal": 30
                }
            },
            {
                "grip": null,
                "overall": {
                    "progress": 78,
                    "goal": 1250
                },
                "period": {
                    "progress": 17,
                    "goal": 240
                }
            }
        ],
        "today": {
            "performed": 0,
            "goal": 0,
            "done": true
        }
    },
    "switches": {
        "overall": {
            "performed": 55,
            "goal": 300
        },
        "period": {
            "performed": 25,
            "goal": 30,
            "frequency": "d",
            "frequency_from": "2023-09-20 00:00:00",
            "frequency_to": "2023-09-20 23:59:59"
        },
        "today": {
            "performed": 25,
            "goal": 30,
            "done": false
        },
        "condition": {
            "type": "switch",
            "switches_frequency": "d",
            "switches_count": 30
        }
    },
    "exercises": {
        "performed": 1,
        "goal": 3,
        "done": false,
        "conditions": [
            {
                "type": "exercise",
                "exercise_id": 1,
                "exercise_frequency": "d",
                "exercise_count": 3,
                "attempts": [
                    {
                        "date_from": "2023-06-06",
                        "date_to": "2023-06-12",
                        "count_done": 1,
                        "count_not_done": 1
                    },
                    {
                        "date_from": "2023-06-13",
                        "date_to": "2023-06-19",
                        "count_done": 0,
                        "count_not_done": 0
                    },
                    {
                        "date_from": "2023-06-20",
                        "date_to": "2023-06-26",
                        "count_done": 0,
                        "count_not_done": 0
                    },
                    {
                        "date_from": "2023-06-27",
                        "date_to": "2023-07-03",
                        "count_done": 0,
                        "count_not_done": 0
                    }
                ],
                "exercise": {
                    "id": 1,
                    "name": "Water plants",
                    "description": "Use Tripod Grip and water your plants",
                    "icon": "🪴",
                    "created_at": "2023-05-19T10:25:37.000000Z",
                    "updated_at": "2023-05-19T10:25:37.000000Z"
                }
            }
        ]
    }
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view goal progress",
    "code": "GOALS:GET_PROGRESS:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Goal not found):


{
    "message": "Goal not found",
    "code": "GOALS:GET_PROGRESS:GOAL_NOT_FOUND"
}
 

Request   

GET api/goals/{goalId}/progress

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

goalId   integer   

Goal ID. Example: 1

Update user goal progress

requires authentication

Use this endpoint to update goal progress as patient. Add exercise attempts and mark them as done or not done.

Example request:
curl --request POST \
    "http://localhost:8000/api/goals/1/progress" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"exercise_id\": 1,
    \"exercise_done\": true
}"
const url = new URL(
    "http://localhost:8000/api/goals/1/progress"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "exercise_id": 1,
    "exercise_done": true
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (202):


{
    "id": 1,
    "user_id": 277,
    "goal_id": 8,
    "type": "switch",
    "grip_id": null,
    "grips": 16,
    "switches": 147,
    "exercise_id": 8,
    "exercise_done": 1,
    "created_at": "2026-11-03T17:26:29.000000Z",
    "updated_at": "2026-11-03T17:26:29.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to update goal progress",
    "code": "GOALS:UPDATE_PROGRESS:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Goal not found):


{
    "message": "Goal not found",
    "code": "GOALS:UPDATE_PROGRESS:GOAL_NOT_FOUND"
}
 

Example response (422, User timezone not set):


{
    "message": "User timezone not set",
    "code": "GOALS:UPDATE_PROGRESS:NO_TIMEZONE"
}
 

Request   

POST api/goals/{goalId}/progress

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

goalId   integer   

Goal ID. Example: 1

Body Parameters

exercise_id   integer   

Exercise ID. The id of an existing record in the App\Models\Exercise table. Example: 1

exercise_done   boolean  optional  

Status of exercise attempt. Example: true

Response

Response Fields

id   integer   

User goal ID.

user_id   integer   

Associated user ID.

goal_id   integer   

Associated goal ID.

type   string   

Goal condition type.

Must be one of:
  • grip
  • switch
  • exercise
grip_id   integer   

Target grip ID.

grips   integer   

Grip count progress.

switches   integer   

Switch count progress.

exercise_id   integer   

Target exercise ID.

exercise_done   boolean   

Whether the exercise is completed.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Invitations

Accept clinician invitation

Example request:
curl --request POST \
    "http://localhost:8000/api/invite/accept" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"token\": \"ABC123DEF456GHI789JKL0\",
    \"email\": \"example@domain.com\",
    \"password\": \"Test123!\",
    \"language\": \"en\",
    \"name\": \"Tom Smith\",
    \"clinic_name\": \"My clinic Ltd\",
    \"clinic_location\": \"Example St 1\\/345 New York, NY\",
    \"address1\": \"433 Peyton Landing Apt. 035\",
    \"address2\": \"South Edwardo, CA 99993-6547\",
    \"mfa_enabled\": true,
    \"mfa_method\": \"email\"
}"
const url = new URL(
    "http://localhost:8000/api/invite/accept"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "token": "ABC123DEF456GHI789JKL0",
    "email": "example@domain.com",
    "password": "Test123!",
    "language": "en",
    "name": "Tom Smith",
    "clinic_name": "My clinic Ltd",
    "clinic_location": "Example St 1\/345 New York, NY",
    "address1": "433 Peyton Landing Apt. 035",
    "address2": "South Edwardo, CA 99993-6547",
    "mfa_enabled": true,
    "mfa_method": "email"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "id": 4,
    "mrn": "W6VH444T1793726637",
    "name": "Maximo Hane",
    "email": "1793726637kshlerin.maybell@example.org",
    "language": "en",
    "phone": "1-586-792-5465",
    "phone_country": "KG",
    "phone_verified_at": null,
    "address1": "901 Schamberger Groves",
    "address2": "New Rupertchester, TN 24819",
    "postal_code": "52903",
    "city": "Rippin-Bartell",
    "country": "GR",
    "clinic_name": "Ernserburgh",
    "clinic_location": "743 Sipes Light Suite 868\nWest Tamara, WA 45722-9962",
    "image": null,
    "public_image": null,
    "mfa_enabled": 0,
    "mfa_method": null,
    "mfa_verified_to": null,
    "location_id": null,
    "created_by": null,
    "active": 1,
    "is_ambassador": 0,
    "test_account": false,
    "notifications_timezone": null,
    "notifications_at": null,
    "created_at": "2026-11-03T17:23:57.000000Z",
    "updated_at": "2026-11-03T17:23:57.000000Z",
    "invitation_status": "accepted",
    "acadle_invitation_status": null,
    "roles": [
        {
            "id": 4,
            "name": "Clinician"
        }
    ]
}
 

Example response (403, Invitation expired):


{
    "message": "Invitation expired",
    "code": "INVITATIONS:ACCEPT:INVITATION_EXPIRED"
}
 

Example response (404, Invitation not found):


{
    "message": "Invitation not found",
    "code": "INVITATIONS:ACCEPT:INVITATION_NOT_FOUND"
}
 

Example response (500, Server error):


{
    "message": "Server error: invitation not accepted",
    "code": "INVITATIONS:ACCEPT:SERVER_ERROR"
}
 

Request   

POST api/invite/accept

Headers

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

token   string   

Invitation token sent by e-mail. SIZE:STRING_LENGTH:24. Example: ABC123DEF456GHI789JKL0

email   string   

User e-mail. MUST_BE_EMAIL. Example: example@domain.com

password   string   

User password. Example: Test123!

language   string   

User language. Example: en

name   string   

User name. Example: Tom Smith

clinic_name   string  optional  

Clinic name. Example: My clinic Ltd

clinic_location   string  optional  

Clinic location. Example: Example St 1/345 New York, NY

address1   string  optional  

User address line 1. Example: 433 Peyton Landing Apt. 035

address2   string  optional  

User address line 2. Example: South Edwardo, CA 99993-6547

mfa_enabled   boolean  optional  

MFA enabled. Example: true

mfa_method   string  optional  

MFA method. Example: email

Must be one of:
  • email
  • sms

Response

Response Fields

id   integer   

User ID.

mrn   string   

Medical Record Number.

name   string   

User full name.

email   string   

User email address.

language   string   

User preferred language.

phone   string   

User phone number.

phone_country   string   

Phone country code.

phone_verified_at   string   

Phone verification date.

address1   string   

Address line 1.

address2   string   

Address line 2.

postal_code   string   

Postal code.

city   string   

City.

country   string   

Country code (ISO 3166 Alpha-2).

clinic_name   string   

Clinic name.

clinic_location   string   

Clinic location.

image   string   

User profile image URL.

mfa_enabled   boolean   

Whether MFA is enabled.

mfa_method   string   

Preferred MFA method.

mfa_verified_to   string   

MFA session verified until.

location_id   integer   

Location ID.

created_by   integer   

ID of the user who created this account.

active   boolean   

Whether the user account is active.

is_internal   boolean   

Super Admin only: Whether the user is an internal user (QA/ADP team).

notifications_timezone   string   

Timezone for notifications.

notifications_at   string   

Preferred notification time.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

invitation_status   string   

User invitation status.

acadle_invitation_status   string   

Acadle invitation status.

roles   object[]   

User roles.

id   integer   

Role ID.

name   string   

Role name.

permissions   object[]   

User permissions.

clinicians   object[]   

Assigned clinicians.

devices   object[]   

Assigned devices.

patients   object[]   

Assigned patients.

invitations   object[]   

User invitations.

Accept Acadle invitation

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/invite/acadle/accept" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"token\": \"a1b2c3d4e5f6g7h8i9j0k1l2m3\",
    \"email\": \"user@example.com\",
    \"password\": \"securePassword123\",
    \"phone\": \"+1 (380) 549-1795\",
    \"phone_country\": \"US\",
    \"name\": \"John Doe\",
    \"language\": \"en\"
}"
const url = new URL(
    "http://localhost:8000/api/invite/acadle/accept"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "token": "a1b2c3d4e5f6g7h8i9j0k1l2m3",
    "email": "user@example.com",
    "password": "securePassword123",
    "phone": "+1 (380) 549-1795",
    "phone_country": "US",
    "name": "John Doe",
    "language": "en"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "id": 5,
    "mrn": "U5YJW97B1793726637",
    "name": "Maudie Block",
    "email": "1793726637melody.connelly@example.org",
    "language": "en",
    "phone": "774-713-0059",
    "phone_country": "HU",
    "phone_verified_at": null,
    "address1": "31835 Douglas Vista Apt. 864",
    "address2": "Katarinamouth, UT 73869-1572",
    "postal_code": "59014-2192",
    "city": "Dach Inc",
    "country": "NO",
    "clinic_name": "Quitzonburgh",
    "clinic_location": "38956 Gust Trafficway\nMaryamport, TN 82801",
    "image": null,
    "public_image": null,
    "mfa_enabled": 0,
    "mfa_method": null,
    "mfa_verified_to": null,
    "location_id": null,
    "created_by": null,
    "active": 1,
    "is_ambassador": 0,
    "test_account": false,
    "notifications_timezone": null,
    "notifications_at": null,
    "created_at": "2026-11-03T17:23:57.000000Z",
    "updated_at": "2026-11-03T17:23:57.000000Z",
    "invitation_status": null,
    "acadle_invitation_status": null,
    "roles": [
        {
            "id": 2,
            "name": "CommunityAdmin"
        }
    ]
}
 

Example response (403, Invitation expired):


{
    "message": "Invitation expired",
    "code": "INVITATIONS_ACADLE:ACCEPT:INVITATION_EXPIRED"
}
 

Example response (404, Invitation not found):


{
    "message": "Invitation not found",
    "code": "INVITATIONS_ACADLE:ACCEPT:INVITATION_NOT_FOUND"
}
 

Example response (500, Server error):


{
    "message": "Server error: invitation not accepted",
    "code": "INVITATIONS_ACADLE:ACCEPT:SERVER_ERROR"
}
 

Request   

POST api/invite/acadle/accept

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

token   string   

Invitation token. SIZE:STRING_LENGTH:24. Example: a1b2c3d4e5f6g7h8i9j0k1l2m3

email   string   

Email address of the Acadle user. MUST_BE_EMAIL. Example: user@example.com

password   string   

User password. Example: securePassword123

phone   string   

User phone number. Example: +1 (380) 549-1795

phone_country   string   

Phone number's country (2 characters). SIZE:STRING_LENGTH:2. Example: US

name   string   

User name. Example: John Doe

language   string   

User preferred language. Example: en

Response

Response Fields

id   integer   

User ID.

mrn   string   

Medical Record Number.

name   string   

User full name.

email   string   

User email address.

language   string   

User preferred language.

phone   string   

User phone number.

phone_country   string   

Phone country code.

phone_verified_at   string   

Phone verification date.

address1   string   

Address line 1.

address2   string   

Address line 2.

postal_code   string   

Postal code.

city   string   

City.

country   string   

Country code (ISO 3166 Alpha-2).

clinic_name   string   

Clinic name.

clinic_location   string   

Clinic location.

image   string   

User profile image URL.

mfa_enabled   boolean   

Whether MFA is enabled.

mfa_method   string   

Preferred MFA method.

mfa_verified_to   string   

MFA session verified until.

location_id   integer   

Location ID.

created_by   integer   

ID of the user who created this account.

active   boolean   

Whether the user account is active.

is_internal   boolean   

Super Admin only: Whether the user is an internal user (QA/ADP team).

notifications_timezone   string   

Timezone for notifications.

notifications_at   string   

Preferred notification time.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

invitation_status   string   

User invitation status.

acadle_invitation_status   string   

Acadle invitation status.

roles   object[]   

User roles.

id   integer   

Role ID.

name   string   

Role name.

permissions   object[]   

User permissions.

clinicians   object[]   

Assigned clinicians.

devices   object[]   

Assigned devices.

patients   object[]   

Assigned patients.

invitations   object[]   

User invitations.

List users for invitations

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/invite/users" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/invite/users"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 15,
            "mrn": "82ASJNLK1793726645",
            "name": "Wilton Turcotte",
            "email": "1793726645feest.isadore@example.net",
            "language": "en",
            "phone": "509-515-4770",
            "phone_country": "SR",
            "phone_verified_at": null,
            "address1": "12743 Tatum Turnpike Apt. 202",
            "address2": "Harrisville, AR 95783-5734",
            "postal_code": "98915-5477",
            "city": "Witting Ltd",
            "country": "GR",
            "clinic_name": "Port Jewell",
            "clinic_location": "4431 Crist Plains Suite 094\nZemlakville, IN 14333-4921",
            "image": null,
            "public_image": null,
            "mfa_enabled": 0,
            "mfa_method": null,
            "mfa_verified_to": null,
            "location_id": null,
            "created_by": null,
            "active": 1,
            "is_ambassador": 0,
            "test_account": false,
            "notifications_timezone": null,
            "notifications_at": null,
            "created_at": "2026-11-03T17:24:05.000000Z",
            "updated_at": "2026-11-03T17:24:05.000000Z",
            "invitation_status": null,
            "acadle_invitation_status": null,
            "roles": [
                {
                    "id": 2,
                    "name": "CommunityAdmin"
                }
            ]
        },
        {
            "id": 16,
            "mrn": "G5UWUKN81793726645",
            "name": "Bartholome Eichmann",
            "email": "1793726645carroll.heber@example.org",
            "language": "en",
            "phone": "1-360-775-6319",
            "phone_country": "ER",
            "phone_verified_at": null,
            "address1": "365 Haag Crossroad Suite 986",
            "address2": "Port Elijah, NJ 33768",
            "postal_code": "50842-9479",
            "city": "Connelly-Gaylord",
            "country": "LT",
            "clinic_name": "Mustafaland",
            "clinic_location": "1611 Ludie Isle\nWest Abel, CO 08340-5058",
            "image": null,
            "public_image": null,
            "mfa_enabled": 0,
            "mfa_method": null,
            "mfa_verified_to": null,
            "location_id": null,
            "created_by": null,
            "active": 1,
            "is_ambassador": 0,
            "test_account": false,
            "notifications_timezone": null,
            "notifications_at": null,
            "created_at": "2026-11-03T17:24:05.000000Z",
            "updated_at": "2026-11-03T17:24:05.000000Z",
            "invitation_status": null,
            "acadle_invitation_status": null,
            "roles": [
                {
                    "id": 1,
                    "name": "SuperAdmin"
                }
            ]
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to list users for invitations",
    "code": "INVITATIONS:USERS:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/invite/users

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

extend   string  optional  

Comma-separated list of relation extensions (available: clinicians, patients, devices, devicesAsClinician, roles, permissions).

Response

Response Fields

items   object   
id   integer   

User ID.

mrn   string   

Medical Record Number.

name   string   

User full name.

email   string   

User email address.

language   string   

User preferred language.

phone   string   

User phone number.

phone_country   string   

Phone country code.

phone_verified_at   string   

Phone verification date.

address1   string   

Address line 1.

address2   string   

Address line 2.

postal_code   string   

Postal code.

city   string   

City.

country   string   

Country code (ISO 3166 Alpha-2).

clinic_name   string   

Clinic name.

clinic_location   string   

Clinic location.

image   string   

User profile image URL.

mfa_enabled   boolean   

Whether MFA is enabled.

mfa_method   string   

Preferred MFA method.

mfa_verified_to   string   

MFA session verified until.

location_id   integer   

Location ID.

created_by   integer   

ID of the user who created this account.

active   boolean   

Whether the user account is active.

is_internal   boolean   

Super Admin only: Whether the user is an internal user (QA/ADP team).

notifications_timezone   string   

Timezone for notifications.

notifications_at   string   

Preferred notification time.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

invitation_status   string   

User invitation status.

acadle_invitation_status   string   

Acadle invitation status.

roles   object[]   

User roles.

id   integer   

Role ID.

name   string   

Role name.

permissions   object[]   

User permissions.

clinicians   object[]   

Assigned clinicians.

devices   object[]   

Assigned devices.

patients   object[]   

Assigned patients.

invitations   object[]   

User invitations.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

Create clinician invitations

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/invite" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"patient_id\": 1,
    \"invitations\": [
        {
            \"user_id\": 1,
            \"user_email\": \"newuser@domain.com\",
            \"role\": \"Clinician\",
            \"training_confirmed\": true,
            \"permissions\": [
                \"reprehenderit\"
            ]
        }
    ]
}"
const url = new URL(
    "http://localhost:8000/api/invite"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "patient_id": 1,
    "invitations": [
        {
            "user_id": 1,
            "user_email": "newuser@domain.com",
            "role": "Clinician",
            "training_confirmed": true,
            "permissions": [
                "reprehenderit"
            ]
        }
    ]
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 10,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 17,
            "mrn": "SPUAAJR61793726646",
            "name": "Elta Farrell",
            "email": "1793726646katrine.mayert@example.com",
            "language": "en",
            "phone": "727-706-5503",
            "phone_country": "BE",
            "phone_verified_at": null,
            "address1": "3513 Aufderhar Station",
            "address2": "New Angusside, ND 72480-2203",
            "postal_code": "18918",
            "city": "Feeney LLC",
            "country": "US",
            "clinic_name": "Cruickshankmouth",
            "clinic_location": "621 Giovanny Island Suite 705\nLukasland, MN 35509-2915",
            "image": null,
            "public_image": null,
            "mfa_enabled": 0,
            "mfa_method": null,
            "mfa_verified_to": null,
            "location_id": null,
            "created_by": null,
            "active": 1,
            "is_ambassador": 0,
            "test_account": false,
            "notifications_timezone": null,
            "notifications_at": null,
            "created_at": "2026-11-03T17:24:06.000000Z",
            "updated_at": "2026-11-03T17:24:06.000000Z",
            "invitation_status": "accepted",
            "acadle_invitation_status": null,
            "invitations": [
                {
                    "id": 1,
                    "user_id": 18,
                    "invited_user_id": 17,
                    "type": "clinician",
                    "training_confirmed": 1,
                    "created_at": "2026-11-03T17:24:07.000000Z",
                    "updated_at": "2026-11-03T17:24:07.000000Z"
                }
            ],
            "roles": [
                {
                    "id": 5,
                    "name": "ClinicianSupport"
                }
            ]
        },
        {
            "id": 20,
            "mrn": "8T9UQD7L1793726647",
            "name": "Anika Ziemann",
            "email": "1793726647oaltenwerth@example.com",
            "language": "en",
            "phone": "(641) 662-4661",
            "phone_country": "IM",
            "phone_verified_at": null,
            "address1": "73277 Jeffry Cliffs",
            "address2": "North Cassie, NM 99851",
            "postal_code": "54624-5749",
            "city": "McKenzie, Terry and Grady",
            "country": "ES",
            "clinic_name": "West Bryce",
            "clinic_location": "14924 Rosemary Keys Apt. 932\nNorth Laurel, TN 55272",
            "image": null,
            "public_image": null,
            "mfa_enabled": 0,
            "mfa_method": null,
            "mfa_verified_to": null,
            "location_id": null,
            "created_by": null,
            "active": 1,
            "is_ambassador": 0,
            "test_account": false,
            "notifications_timezone": null,
            "notifications_at": null,
            "created_at": "2026-11-03T17:24:07.000000Z",
            "updated_at": "2026-11-03T17:24:07.000000Z",
            "invitation_status": "accepted",
            "acadle_invitation_status": null,
            "invitations": [
                {
                    "id": 2,
                    "user_id": 21,
                    "invited_user_id": 20,
                    "type": "clinician",
                    "training_confirmed": 0,
                    "created_at": "2026-11-03T17:24:09.000000Z",
                    "updated_at": "2026-11-03T17:24:09.000000Z"
                }
            ],
            "roles": [
                {
                    "id": 4,
                    "name": "Clinician"
                }
            ]
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to create invitations",
    "code": "INVITATIONS:CREATE:INSUFFICIENT_PERMISSION"
}
 

Example response (403, No access to given patient):


{
    "message": "No access to given patient",
    "code": "INVITATIONS:CREATE:NO_ACCESS_TO_PATIENT"
}
 

Request   

POST api/invite

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

patient_id   string  optional  

Patient user ID. The id of an existing record in the App\Models\User table. Example: 1

invitations   object[]   

List of invitations (maximum entries: 10, array of objects).

user_id   integer  optional  

Invited user ID. Use only for existing users. Don't use together with user_email. This field is required when invitations.*.user_email is not present. The id of an existing record in the App\Models\User table. Example: 1

user_email   string  optional  

Invited user e-mail. Use only for non-existing users. Don't use together with user_id. This field is required when invitations.*.user_id is not present. MUST_BE_EMAIL. Example: newuser@domain.com

role   string  optional  

Invited user role. Use only for non-existing users. This field is required when invitations.*.user_id is not present. Example: Clinician

Must be one of:
  • ClinicAdmin
  • Clinician
  • ClinicianSupport
training_confirmed   boolean   

Confirmation that invited user was properly trained to use ADP. Must be accepted. Example: true

permissions   string[]  optional  

List of permissions to assign to the user (use that for ClinicianSupport role).

permissions[]   string  optional  

Permission name. Example: porro

Response

Response Fields

items   object   
id   integer   

User ID.

mrn   string   

Medical Record Number.

name   string   

User full name.

email   string   

User email address.

language   string   

User preferred language.

phone   string   

User phone number.

phone_country   string   

Phone country code.

phone_verified_at   string   

Phone verification date.

address1   string   

Address line 1.

address2   string   

Address line 2.

postal_code   string   

Postal code.

city   string   

City.

country   string   

Country code (ISO 3166 Alpha-2).

clinic_name   string   

Clinic name.

clinic_location   string   

Clinic location.

image   string   

User profile image URL.

mfa_enabled   boolean   

Whether MFA is enabled.

mfa_method   string   

Preferred MFA method.

mfa_verified_to   string   

MFA session verified until.

location_id   integer   

Location ID.

created_by   integer   

ID of the user who created this account.

active   boolean   

Whether the user account is active.

is_internal   boolean   

Super Admin only: Whether the user is an internal user (QA/ADP team).

notifications_timezone   string   

Timezone for notifications.

notifications_at   string   

Preferred notification time.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

invitation_status   string   

User invitation status.

acadle_invitation_status   string   

Acadle invitation status.

roles   object[]   

User roles.

id   integer   

Role ID.

name   string   

Role name.

permissions   object[]   

User permissions.

clinicians   object[]   

Assigned clinicians.

devices   object[]   

Assigned devices.

patients   object[]   

Assigned patients.

invitations   object[]   

User invitations.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

Resend invitation

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/invite/1/resend" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/invite/1/resend"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (201):


{
    "id": 23,
    "mrn": "ZJFD7T3L1793726649",
    "name": "Kattie Lynch",
    "email": "1793726649howell.jena@example.net",
    "language": "en",
    "phone": "517.260.2597",
    "phone_country": "QA",
    "phone_verified_at": null,
    "address1": "382 Bergnaum Curve",
    "address2": "Kraigland, WA 73592",
    "postal_code": "45664",
    "city": "Schmidt, Schowalter and Schoen",
    "country": "BE",
    "clinic_name": "Joyceland",
    "clinic_location": "37283 Verda Stravenue\nBoganfort, DC 53128-2590",
    "image": null,
    "public_image": null,
    "mfa_enabled": 0,
    "mfa_method": null,
    "mfa_verified_to": null,
    "location_id": null,
    "created_by": null,
    "active": 1,
    "is_ambassador": 0,
    "test_account": false,
    "notifications_timezone": null,
    "notifications_at": null,
    "created_at": "2026-11-03T17:24:09.000000Z",
    "updated_at": "2026-11-03T17:24:09.000000Z",
    "invitation_status": null,
    "acadle_invitation_status": "accepted",
    "invitations": [
        {
            "id": 3,
            "user_id": 24,
            "invited_user_id": 23,
            "type": "clinician",
            "training_confirmed": 0,
            "created_at": "2026-11-03T17:24:11.000000Z",
            "updated_at": "2026-11-03T17:24:11.000000Z"
        }
    ],
    "roles": [
        {
            "id": 7,
            "name": "AcadleUser"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to resend invitations",
    "code": "INVITATIONS:RESEND:INSUFFICIENT_PERMISSION"
}
 

Example response (403, User not found):


{
    "message": "User not found",
    "code": "INVITATIONS:RESEND:USER_NOT_FOUND"
}
 

Example response (403, Invitation already accepted):


{
    "message": "Invitation already accepted",
    "code": "INVITATIONS:RESEND:INVITATION_ALREADY_ACCEPTED"
}
 

Request   

POST api/invite/{userId}/resend

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

userId   integer   

User ID. Example: 1

Response

Response Fields

id   integer   

User ID.

mrn   string   

Medical Record Number.

name   string   

User full name.

email   string   

User email address.

language   string   

User preferred language.

phone   string   

User phone number.

phone_country   string   

Phone country code.

phone_verified_at   string   

Phone verification date.

address1   string   

Address line 1.

address2   string   

Address line 2.

postal_code   string   

Postal code.

city   string   

City.

country   string   

Country code (ISO 3166 Alpha-2).

clinic_name   string   

Clinic name.

clinic_location   string   

Clinic location.

image   string   

User profile image URL.

mfa_enabled   boolean   

Whether MFA is enabled.

mfa_method   string   

Preferred MFA method.

mfa_verified_to   string   

MFA session verified until.

location_id   integer   

Location ID.

created_by   integer   

ID of the user who created this account.

active   boolean   

Whether the user account is active.

is_internal   boolean   

Super Admin only: Whether the user is an internal user (QA/ADP team).

notifications_timezone   string   

Timezone for notifications.

notifications_at   string   

Preferred notification time.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

invitation_status   string   

User invitation status.

acadle_invitation_status   string   

Acadle invitation status.

roles   object[]   

User roles.

id   integer   

Role ID.

name   string   

Role name.

permissions   object[]   

User permissions.

clinicians   object[]   

Assigned clinicians.

devices   object[]   

Assigned devices.

patients   object[]   

Assigned patients.

invitations   object[]   

User invitations.

List all users invited to Acadle

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/invite/acadle/users?search=john" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/invite/acadle/users"
);

const params = {
    "search": "john",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 26,
            "mrn": "CGJAQ3QR1793726651",
            "name": "Dulce Hoppe",
            "email": "1793726651ally66@example.net",
            "language": "en",
            "phone": "678-808-5141",
            "phone_country": "CK",
            "phone_verified_at": null,
            "address1": "5879 Donna Court Apt. 289",
            "address2": "West Chase, NE 66204-2388",
            "postal_code": "29593",
            "city": "Treutel, Pollich and Price",
            "country": "FR",
            "clinic_name": "East Maud",
            "clinic_location": "1548 Gutkowski Ranch\nZemlakmouth, WA 75227",
            "image": null,
            "public_image": null,
            "mfa_enabled": 0,
            "mfa_method": null,
            "mfa_verified_to": null,
            "location_id": null,
            "created_by": null,
            "active": 1,
            "is_ambassador": 0,
            "test_account": false,
            "notifications_timezone": null,
            "notifications_at": null,
            "created_at": "2026-11-03T17:24:11.000000Z",
            "updated_at": "2026-11-03T17:24:11.000000Z",
            "invitation_status": null,
            "acadle_invitation_status": "accepted",
            "roles": [
                {
                    "id": 7,
                    "name": "AcadleUser"
                }
            ]
        },
        {
            "id": 27,
            "mrn": "BDVQL7QD1793726651",
            "name": "Benjamin Sporer",
            "email": "1793726651ljohnston@example.net",
            "language": "en",
            "phone": "+12798825486",
            "phone_country": "GL",
            "phone_verified_at": null,
            "address1": "2586 Victoria Glens",
            "address2": "Bashirianberg, AZ 88839-1196",
            "postal_code": "82602-9435",
            "city": "Gleason-Kuphal",
            "country": "SI",
            "clinic_name": "Casperton",
            "clinic_location": "2974 Halle Street Apt. 819\nNorth Hazle, MO 54348",
            "image": null,
            "public_image": null,
            "mfa_enabled": 0,
            "mfa_method": null,
            "mfa_verified_to": null,
            "location_id": null,
            "created_by": null,
            "active": 1,
            "is_ambassador": 0,
            "test_account": false,
            "notifications_timezone": null,
            "notifications_at": null,
            "created_at": "2026-11-03T17:24:11.000000Z",
            "updated_at": "2026-11-03T17:24:11.000000Z",
            "invitation_status": null,
            "acadle_invitation_status": null,
            "roles": [
                {
                    "id": 1,
                    "name": "SuperAdmin"
                }
            ]
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to list users for invitations",
    "code": "INVITATIONS_ACADLE:USERS:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/invite/acadle/users

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

search   string  optional  

Filter users by searching in: user name, user email, contact name, contact surname, contact email, CPO number. Example: john

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

extend   string  optional  

Comma-separated list of relation extensions (available: roles, acadleSurvey, latestAcadleSurvey).

sortby   string  optional  

Sort by field (available: survey_completion_date). Default: survey_completion_date, desc.

sortdir   string  optional  

Sort direction (available: asc, desc).

Response

Response Fields

items   object   
id   integer   

User ID.

mrn   string   

Medical Record Number.

name   string   

User full name.

email   string   

User email address.

language   string   

User preferred language.

phone   string   

User phone number.

phone_country   string   

Phone country code.

phone_verified_at   string   

Phone verification date.

address1   string   

Address line 1.

address2   string   

Address line 2.

postal_code   string   

Postal code.

city   string   

City.

country   string   

Country code (ISO 3166 Alpha-2).

clinic_name   string   

Clinic name.

clinic_location   string   

Clinic location.

image   string   

User profile image URL.

mfa_enabled   boolean   

Whether MFA is enabled.

mfa_method   string   

Preferred MFA method.

mfa_verified_to   string   

MFA session verified until.

location_id   integer   

Location ID.

created_by   integer   

ID of the user who created this account.

active   boolean   

Whether the user account is active.

is_internal   boolean   

Super Admin only: Whether the user is an internal user (QA/ADP team).

notifications_timezone   string   

Timezone for notifications.

notifications_at   string   

Preferred notification time.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

invitation_status   string   

User invitation status.

acadle_invitation_status   string   

Acadle invitation status.

roles   object[]   

User roles.

id   integer   

Role ID.

name   string   

Role name.

permissions   object[]   

User permissions.

clinicians   object[]   

Assigned clinicians.

devices   object[]   

Assigned devices.

patients   object[]   

Assigned patients.

invitations   object[]   

User invitations.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

Create Acadle invitation

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/invite/acadle" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"email\": \"ohoppe@example.com\"
}"
const url = new URL(
    "http://localhost:8000/api/invite/acadle"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "email": "ohoppe@example.com"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 28,
    "mrn": "TEDMDYN61793726652",
    "name": "Nola Willms Jr.",
    "email": "1793726652ken72@example.com",
    "language": "en",
    "phone": "430.956.7594",
    "phone_country": "ZA",
    "phone_verified_at": null,
    "address1": "230 Lora Passage",
    "address2": "South Francescafort, MT 68602-7503",
    "postal_code": "79710-9040",
    "city": "Robel and Sons",
    "country": "PT",
    "clinic_name": "Malikaville",
    "clinic_location": "516 Bailee Springs\nMaritzaland, NJ 35702",
    "image": null,
    "public_image": null,
    "mfa_enabled": 0,
    "mfa_method": null,
    "mfa_verified_to": null,
    "location_id": null,
    "created_by": null,
    "active": 1,
    "is_ambassador": 0,
    "test_account": false,
    "notifications_timezone": null,
    "notifications_at": null,
    "created_at": "2026-11-03T17:24:12.000000Z",
    "updated_at": "2026-11-03T17:24:12.000000Z",
    "invitation_status": null,
    "acadle_invitation_status": null,
    "invitations": [
        {
            "id": 4,
            "user_id": 29,
            "invited_user_id": 28,
            "type": "clinician",
            "training_confirmed": 0,
            "created_at": "2026-11-03T17:24:13.000000Z",
            "updated_at": "2026-11-03T17:24:13.000000Z"
        }
    ],
    "roles": [
        {
            "id": 6,
            "name": "Amputee"
        }
    ]
}
 

Example response (400, User already exists):


{
    "message": "User with this email already exists in the system",
    "code": "INVITATIONS_ACADLE:CREATE:USER_ALREADY_EXISTS"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to create invitations",
    "code": "INVITATIONS_ACADLE:CREATE:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/invite/acadle

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

email   string   

Email address of the Acadle user. Example: ohoppe@example.com

Response

Response Fields

id   integer   

User ID.

mrn   string   

Medical Record Number.

name   string   

User full name.

email   string   

User email address.

language   string   

User preferred language.

phone   string   

User phone number.

phone_country   string   

Phone country code.

phone_verified_at   string   

Phone verification date.

address1   string   

Address line 1.

address2   string   

Address line 2.

postal_code   string   

Postal code.

city   string   

City.

country   string   

Country code (ISO 3166 Alpha-2).

clinic_name   string   

Clinic name.

clinic_location   string   

Clinic location.

image   string   

User profile image URL.

mfa_enabled   boolean   

Whether MFA is enabled.

mfa_method   string   

Preferred MFA method.

mfa_verified_to   string   

MFA session verified until.

location_id   integer   

Location ID.

created_by   integer   

ID of the user who created this account.

active   boolean   

Whether the user account is active.

is_internal   boolean   

Super Admin only: Whether the user is an internal user (QA/ADP team).

notifications_timezone   string   

Timezone for notifications.

notifications_at   string   

Preferred notification time.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

invitation_status   string   

User invitation status.

acadle_invitation_status   string   

Acadle invitation status.

roles   object[]   

User roles.

id   integer   

Role ID.

name   string   

Role name.

permissions   object[]   

User permissions.

clinicians   object[]   

Assigned clinicians.

devices   object[]   

Assigned devices.

patients   object[]   

Assigned patients.

invitations   object[]   

User invitations.

Resend Acadle invitation

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/invite/acadle/1/resend" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/invite/acadle/1/resend"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (201):


{
    "id": 31,
    "mrn": "XP46A7GS1793726653",
    "name": "Annalise Nolan",
    "email": "1793726653iritchie@example.org",
    "language": "en",
    "phone": "+1 (936) 215-5162",
    "phone_country": "CA",
    "phone_verified_at": null,
    "address1": "4999 Fisher Light",
    "address2": "Kirstinshire, MT 75408",
    "postal_code": "32405-3739",
    "city": "Rath, Renner and Becker",
    "country": "HR",
    "clinic_name": "West Reid",
    "clinic_location": "4546 Crist Springs Apt. 304\nHildegardville, OH 79788-2883",
    "image": null,
    "public_image": null,
    "mfa_enabled": 0,
    "mfa_method": null,
    "mfa_verified_to": null,
    "location_id": null,
    "created_by": null,
    "active": 1,
    "is_ambassador": 0,
    "test_account": false,
    "notifications_timezone": null,
    "notifications_at": null,
    "created_at": "2026-11-03T17:24:13.000000Z",
    "updated_at": "2026-11-03T17:24:13.000000Z",
    "invitation_status": "accepted",
    "acadle_invitation_status": null,
    "invitations": [
        {
            "id": 5,
            "user_id": 32,
            "invited_user_id": 31,
            "type": "clinician",
            "training_confirmed": 1,
            "created_at": "2026-11-03T17:24:15.000000Z",
            "updated_at": "2026-11-03T17:24:15.000000Z"
        }
    ],
    "roles": [
        {
            "id": 4,
            "name": "Clinician"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to resend invitations",
    "code": "INVITATIONS_ACADLE:RESEND:INSUFFICIENT_PERMISSION"
}
 

Example response (403, User not found):


{
    "message": "User not found",
    "code": "INVITATIONS_ACADLE:RESEND:USER_NOT_FOUND"
}
 

Example response (403, Invitation already accepted):


{
    "message": "Invitation already accepted",
    "code": "INVITATIONS_ACADLE:RESEND:INVITATION_ALREADY_ACCEPTED"
}
 

Request   

POST api/invite/acadle/{userId}/resend

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

userId   integer   

User ID. Example: 1

Response

Response Fields

id   integer   

User ID.

mrn   string   

Medical Record Number.

name   string   

User full name.

email   string   

User email address.

language   string   

User preferred language.

phone   string   

User phone number.

phone_country   string   

Phone country code.

phone_verified_at   string   

Phone verification date.

address1   string   

Address line 1.

address2   string   

Address line 2.

postal_code   string   

Postal code.

city   string   

City.

country   string   

Country code (ISO 3166 Alpha-2).

clinic_name   string   

Clinic name.

clinic_location   string   

Clinic location.

image   string   

User profile image URL.

mfa_enabled   boolean   

Whether MFA is enabled.

mfa_method   string   

Preferred MFA method.

mfa_verified_to   string   

MFA session verified until.

location_id   integer   

Location ID.

created_by   integer   

ID of the user who created this account.

active   boolean   

Whether the user account is active.

is_internal   boolean   

Super Admin only: Whether the user is an internal user (QA/ADP team).

notifications_timezone   string   

Timezone for notifications.

notifications_at   string   

Preferred notification time.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

invitation_status   string   

User invitation status.

acadle_invitation_status   string   

Acadle invitation status.

roles   object[]   

User roles.

id   integer   

Role ID.

name   string   

Role name.

permissions   object[]   

User permissions.

clinicians   object[]   

Assigned clinicians.

devices   object[]   

Assigned devices.

patients   object[]   

Assigned patients.

invitations   object[]   

User invitations.

Admin access to invitation tokens

requires authentication

Requires admin.invitations_tokens_access permission.
Fetches only non-accepted and non-expired invitations.

Example request:
curl --request GET \
    --get "http://localhost:8000/api/invite/tokens/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/invite/tokens/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, OK):


[
    {
        "id": 1,
        "user_id": 1,
        "invited_user_id": 2,
        "token": "I0V25NUVMHC0F8RRF1YEY5BT",
        "training_confirmed": 1,
        "created_at": "2025-05-12T12:00:00.000000Z",
        "updated_at": "2025-05-12T12:00:00.000000Z"
    }
]
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to get invitation tokens",
    "code": "INVITATIONS:ADMIN_TOKENS:INSUFFICIENT_PERMISSION"
}
 

Example response (404, User not found):


{
    "message": "User not found",
    "code": "INVITATIONS:ADMIN_TOKENS:USER_NOT_FOUND"
}
 

Request   

GET api/invite/tokens/{userId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

userId   integer   

User ID. Example: 1

Logs

API endpoints for managing event logs

Get events log

requires authentication

user - user account that performed the action
element - element model that has been affected by the action

Example request:
curl --request GET \
    --get "http://localhost:8000/api/logs?search=Test&ip=200.200.100.5&user=1&type=user&date_from=1642003200&date_to=1642003200&include_sessions=1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
const url = new URL(
    "http://localhost:8000/api/logs"
);

const params = {
    "search": "Test",
    "ip": "200.200.100.5",
    "user": "1",
    "type": "user",
    "date_from": "1642003200",
    "date_to": "1642003200",
    "include_sessions": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 1,
            "user_id": 282,
            "event_name": "event_factory",
            "element_type": "App\\Models\\User",
            "element_id": 281,
            "comments": "",
            "ip_address": "231.107.238.59",
            "created_at": "2007-04-16T00:53:25.000000Z",
            "updated_at": "2026-11-03T17:26:31.000000Z",
            "username": "Bruce Deckow",
            "user": {
                "id": 282,
                "mrn": "YCBVQCRU1793726791",
                "name": "Bruce Deckow",
                "email": "1793726791larson.clemens@example.net",
                "language": "en",
                "phone": "409-958-1482",
                "phone_country": "SZ",
                "phone_verified_at": null,
                "address1": "77935 Evert Meadow",
                "address2": "Lake Korbin, GA 86349-6292",
                "postal_code": "57774-5887",
                "city": "Johns, Borer and Rohan",
                "country": "HR",
                "clinic_name": "West Adalineberg",
                "clinic_location": "282 Bogisich Path Suite 088\nEast Mossiemouth, MI 29405",
                "image": null,
                "public_image": null,
                "mfa_enabled": 0,
                "mfa_method": null,
                "mfa_verified_to": null,
                "location_id": null,
                "created_by": null,
                "active": 1,
                "is_ambassador": 0,
                "test_account": false,
                "notifications_timezone": null,
                "notifications_at": null,
                "created_at": "2026-11-03T17:26:31.000000Z",
                "updated_at": "2026-11-03T17:26:31.000000Z",
                "invitation_status": null,
                "acadle_invitation_status": null,
                "roles": []
            },
            "element": {
                "id": 281,
                "mrn": "AB4WYJH51793726790",
                "name": "Leta Metz",
                "email": "1793726790yshanahan@example.net",
                "language": "en",
                "phone": "(971) 775-1551",
                "phone_country": "IS",
                "phone_verified_at": null,
                "address1": "75916 Marjorie Square Apt. 105",
                "address2": "Port Rosaburgh, CA 90694-6701",
                "postal_code": "07977",
                "city": "Homenick, Hahn and Wehner",
                "country": "SI",
                "clinic_name": "South Aniyahburgh",
                "clinic_location": "97777 Jayson Court Suite 326\nAnnamarieland, TN 32878",
                "image": null,
                "public_image": null,
                "mfa_enabled": 0,
                "mfa_method": null,
                "mfa_verified_to": null,
                "location_id": null,
                "created_by": null,
                "active": 1,
                "is_ambassador": 0,
                "test_account": false,
                "notifications_timezone": null,
                "notifications_at": null,
                "created_at": "2026-11-03T17:26:30.000000Z",
                "updated_at": "2026-11-03T17:26:30.000000Z",
                "invitation_status": null,
                "acadle_invitation_status": null,
                "roles": []
            }
        },
        {
            "id": 2,
            "user_id": 285,
            "event_name": "event_factory",
            "element_type": "App\\Models\\User",
            "element_id": 284,
            "comments": "",
            "ip_address": "131.70.147.81",
            "created_at": "2002-07-24T20:21:01.000000Z",
            "updated_at": "2026-11-03T17:26:33.000000Z",
            "username": "Paxton Jenkins",
            "user": {
                "id": 285,
                "mrn": "ZAAXA85D1793726792",
                "name": "Paxton Jenkins",
                "email": "1793726792beier.leo@example.com",
                "language": "en",
                "phone": "732.742.6327",
                "phone_country": "BN",
                "phone_verified_at": null,
                "address1": "407 Tillman Mall Suite 337",
                "address2": "South Callie, MO 81728",
                "postal_code": "22658",
                "city": "Johnson, Feeney and Kshlerin",
                "country": "LV",
                "clinic_name": "North Nicolemouth",
                "clinic_location": "6791 Jayden Rapids Suite 850\nPasqualemouth, ME 77470",
                "image": null,
                "public_image": null,
                "mfa_enabled": 0,
                "mfa_method": null,
                "mfa_verified_to": null,
                "location_id": null,
                "created_by": null,
                "active": 1,
                "is_ambassador": 0,
                "test_account": false,
                "notifications_timezone": null,
                "notifications_at": null,
                "created_at": "2026-11-03T17:26:32.000000Z",
                "updated_at": "2026-11-03T17:26:32.000000Z",
                "invitation_status": null,
                "acadle_invitation_status": null,
                "roles": []
            },
            "element": {
                "id": 284,
                "mrn": "RV8PMF9W1793726792",
                "name": "Mr. Robb Schumm I",
                "email": "1793726792marlene.beer@example.net",
                "language": "en",
                "phone": "+1 (405) 282-6240",
                "phone_country": "SC",
                "phone_verified_at": null,
                "address1": "5924 Hintz Mountain Suite 372",
                "address2": "South Velma, VA 72714",
                "postal_code": "78943",
                "city": "Moore, O'Reilly and Beahan",
                "country": "GR",
                "clinic_name": "Jaydechester",
                "clinic_location": "269 Bernita Green\nWindlerside, ND 69200",
                "image": null,
                "public_image": null,
                "mfa_enabled": 0,
                "mfa_method": null,
                "mfa_verified_to": null,
                "location_id": null,
                "created_by": null,
                "active": 1,
                "is_ambassador": 0,
                "test_account": false,
                "notifications_timezone": null,
                "notifications_at": null,
                "created_at": "2026-11-03T17:26:32.000000Z",
                "updated_at": "2026-11-03T17:26:32.000000Z",
                "invitation_status": null,
                "acadle_invitation_status": null,
                "roles": []
            }
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to access event logs",
    "code": "EVENT_LOGS:LIST:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/logs

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

search   string  optional  

Filter logs by related element data:

  • for users: search by name or email
  • for devices: search by serial or bluetooth_id
  • for P2P sessions: search by amputee_uuid or clinician_uuid
Example: `Test`
ip   string  optional  

Filter logs by IP address. Example: 200.200.100.5

user   integer  optional  

Filter logs by user. Example: 1

type   string  optional  

Filter logs by event type (available: user, device, p2p_session. Example: user

date_from   integer  optional  

Filter logs from date (timestamp). Example: 1642003200

date_to   integer  optional  

Filter logs to date (timestamp). Example: 1642003200

include_sessions   integer  optional  

Login and logout events are filtered out by default. Set this parameter to 1 to include these events. Example: 1

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

sortby   string  optional  

Sort by field (available: type, username, ip_address, date). Default: date, desc.

sortdir   string  optional  

Sort direction (available: asc, desc).

Body Parameters

date_from   string  optional  
date_to   string  optional  

Response

Response Fields

items   object   
id   integer   

Event log entry ID.

user_id   integer   

ID of the user who triggered the event.

event_name   string   

Event name.

element_type   string   

Type of the related element.

element_id   integer   

ID of the related element.

comments   string   

Additional event comments.

ip_address   string   

IP address of the request.

created_at   string   

Event timestamp.

updated_at   string   

Last update timestamp.

user   object   

User who triggered the event.

element   object   

Related element.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

Messages

API endpoints for message center

Get user messages list

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/messages" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"filter\": \"all\"
}"
const url = new URL(
    "http://localhost:8000/api/messages"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "filter": "all"
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 1,
            "user_id": null,
            "message_id": 1,
            "is_read": 0,
            "is_archived": 0,
            "is_deleted": 0,
            "message": {
                "id": 1,
                "sender_id": null,
                "title": "Quidem veritatis voluptatem nulla repudiandae illo ratione.",
                "content": "Rerum tempore et aliquam omnis ullam aut minima cum.",
                "created_at": "2026-11-03T17:25:18.000000Z",
                "updated_at": "2026-11-03T17:25:18.000000Z",
                "sender": null
            }
        },
        {
            "id": 2,
            "user_id": null,
            "message_id": 2,
            "is_read": 0,
            "is_archived": 0,
            "is_deleted": 0,
            "message": {
                "id": 2,
                "sender_id": null,
                "title": "Illo est aut impedit expedita unde iusto itaque.",
                "content": "Totam nisi deleniti officia qui vitae aliquid error.",
                "created_at": "2026-11-03T17:25:18.000000Z",
                "updated_at": "2026-11-03T17:25:18.000000Z",
                "sender": null
            }
        }
    ]
}
 

Request   

GET api/messages

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

sortby   string  optional  

Sort by field (available: date). Default: date, desc.

sortdir   string  optional  

Sort direction (available: asc, desc).

Body Parameters

filter   string  optional  

Filter results by message status (available: archived, all). Defaults to non-archived only. Example: all

Response

Response Fields

items   object   
id   integer   

User message ID.

user_id   integer   

Recipient user ID.

message_id   integer   

Associated message ID.

is_read   boolean   

Whether the message has been read.

is_archived   boolean   

Whether the message is archived.

is_deleted   boolean   

Whether the message is deleted.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

message   object   

The message content.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

Send message to multiple users

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/message" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"title\": \"Reminder about your vaccination\",
    \"content\": \"Lorem ipsum dolor sit amet\",
    \"roles\": \"Clinician,Amputee\"
}"
const url = new URL(
    "http://localhost:8000/api/message"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "title": "Reminder about your vaccination",
    "content": "Lorem ipsum dolor sit amet",
    "roles": "Clinician,Amputee"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (202, OK):


{
    "message": "Messages sent",
    "count": 3,
    "code": "MESSAGES:SEND:SENT"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to send messages",
    "code": "MESSAGES:SEND:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/message

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

title   string   

Message title. MINIMUM:STRING_LENGTH:3. Example: Reminder about your vaccination

content   string  optional  

Message content text. Example: Lorem ipsum dolor sit amet

roles   string  optional  

Filter recipients with given role (comma-separated). Example: Clinician,Amputee

Mark message as read

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/message/1/read" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"state\": true
}"
const url = new URL(
    "http://localhost:8000/api/message/1/read"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "state": true
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (202):


{
    "id": 3,
    "user_id": 148,
    "message_id": 3,
    "is_read": 0,
    "is_archived": 0,
    "is_deleted": 0
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to mark this message as read",
    "code": "MESSAGES:READ:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Message not found):


{
    "message": "Message not found",
    "code": "MESSAGES:READ:MESSAGE_NOT_FOUND"
}
 

Request   

POST api/message/{id}/read

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

User Message ID (id from messages list; do not confuse with message_id). Example: 1

Body Parameters

state   boolean  optional  

Explicit read state. Default: 1 (true). Example: true

Response

Response Fields

id   integer   

User message ID.

user_id   integer   

Recipient user ID.

message_id   integer   

Associated message ID.

is_read   boolean   

Whether the message has been read.

is_archived   boolean   

Whether the message is archived.

is_deleted   boolean   

Whether the message is deleted.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

message   object   

The message content.

Mark message as archived

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/message/1/archive" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"state\": true
}"
const url = new URL(
    "http://localhost:8000/api/message/1/archive"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "state": true
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (202):


{
    "id": 4,
    "user_id": 149,
    "message_id": 4,
    "is_read": 0,
    "is_archived": 0,
    "is_deleted": 0
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to mark this message as archived",
    "code": "MESSAGES:ARCHIVE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Message not found):


{
    "message": "Message not found",
    "code": "MESSAGES:ARCHIVE:MESSAGE_NOT_FOUND"
}
 

Request   

POST api/message/{id}/archive

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

User Message ID (id from messages list; do not confuse with message_id). Example: 1

Body Parameters

state   boolean  optional  

Explicit archived state. Default: 1 (true). Example: true

Response

Response Fields

id   integer   

User message ID.

user_id   integer   

Recipient user ID.

message_id   integer   

Associated message ID.

is_read   boolean   

Whether the message has been read.

is_archived   boolean   

Whether the message is archived.

is_deleted   boolean   

Whether the message is deleted.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

message   object   

The message content.

List of messages and tickets

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/messages-and-tickets" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/messages-and-tickets"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 2
    },
    "items": [
        {
            "type": "UserMessage",
            "date": "2024-11-02T10:00:00.000000Z",
            "item": {
                "id": 1,
                "user_id": 1,
                "message_id": 1,
                "is_read": 0,
                "is_archived": 0,
                "is_deleted": 0,
                "message": {
                    "id": 1,
                    "sender_id": 1,
                    "title": "Message title",
                    "content": "Message content",
                    "created_at": "2024-11-02T10:00:00.000000Z",
                    "updated_at": "2024-11-02T10:00:00.000000Z",
                    "sender": {
                        "id": 1,
                        "mrn": "MRN",
                        "name": "User name",
                        "email": "user@domain.com",
                        "language": "en",
                        "phone": "",
                        "phone_verified_at": null,
                        "address1": "",
                        "address2": "",
                        "postal_code": "",
                        "city": "",
                        "clinic_name": "Test Company",
                        "clinic_location": "Test Company Location",
                        "image": null,
                        "mfa_enabled": 0,
                        "mfa_method": "email",
                        "mfa_verified_to": null,
                        "created_by": null,
                        "active": 1,
                        "notifications_timezone": "Europe/Warsaw",
                        "notifications_at": "08:00:00",
                        "created_at": "2024-09-01T15:00:00.000000Z",
                        "updated_at": "2024-10-10T10:30:00.000000Z",
                        "invitation_status": "accepted",
                        "roles": [
                            {
                                "id": 3,
                                "name": "Clinician",
                                "guard_name": "web",
                                "created_at": "2024-01-01T12:00:00.000000Z",
                                "updated_at": "2024-01-01T12:00:00.000000Z",
                                "pivot": {
                                    "model_id": 1,
                                    "role_id": 3,
                                    "model_type": "App\\Models\\User"
                                }
                            }
                        ]
                    }
                }
            }
        },
        {
            "type": "SupportTicket",
            "date": "2024-11-01T15:15:00.000000Z",
            "item": {
                "id": 1,
                "sender_id": 1,
                "recipient_id": 999,
                "device_id": null,
                "meeting_date": "2024-11-10T15:00:00.000000Z",
                "meeting_type": "none",
                "contact_email": null,
                "status": "new",
                "created_at": "2024-11-01T15:15:00.000000Z",
                "updated_at": "2024-11-01T15:15:00.000000Z",
                "sender": {
                    "id": 1,
                    "mrn": "MRN",
                    "name": "User name",
                    "email": "user@domain.com",
                    "language": "en",
                    "phone": "",
                    "phone_verified_at": null,
                    "address1": "",
                    "address2": "",
                    "postal_code": "",
                    "city": "",
                    "clinic_name": "Test Company",
                    "clinic_location": "Test Company Location",
                    "image": null,
                    "mfa_enabled": 0,
                    "mfa_method": "email",
                    "mfa_verified_to": null,
                    "created_by": null,
                    "active": 1,
                    "notifications_timezone": "Europe/Warsaw",
                    "notifications_at": "08:00:00",
                    "created_at": "2024-09-01T15:00:00.000000Z",
                    "updated_at": "2024-10-10T10:30:00.000000Z",
                    "invitation_status": "accepted",
                    "roles": [
                        {
                            "id": 3,
                            "name": "Clinician",
                            "guard_name": "web",
                            "created_at": "2024-01-01T12:00:00.000000Z",
                            "updated_at": "2024-01-01T12:00:00.000000Z",
                            "pivot": {
                                "model_id": 1,
                                "role_id": 3,
                                "model_type": "App\\Models\\User"
                            }
                        }
                    ]
                },
                "recipient": {
                    "id": 2,
                    "mrn": "MRN2",
                    "name": "Patient",
                    "email": "patient4@domain.com",
                    "language": "en",
                    "phone": "",
                    "phone_verified_at": null,
                    "address1": "",
                    "address2": "",
                    "postal_code": "",
                    "city": "",
                    "clinic_name": null,
                    "clinic_location": null,
                    "image": null,
                    "mfa_enabled": 0,
                    "mfa_method": null,
                    "mfa_verified_to": null,
                    "created_by": 1,
                    "active": 1,
                    "notifications_timezone": null,
                    "notifications_at": null,
                    "created_at": "2024-10-01T15:00:00.000000Z",
                    "updated_at": "2024-10-10T10:30:00.000000Z",
                    "invitation_status": null,
                    "roles": [
                        {
                            "id": 5,
                            "name": "Amputee",
                            "guard_name": "web",
                            "created_at": "2024-01-01T12:00:00.000000Z",
                            "updated_at": "2024-01-01T12:00:00.000000Z",
                            "pivot": {
                                "model_id": 2,
                                "role_id": 5,
                                "model_type": "App\\Models\\User"
                            }
                        }
                    ]
                },
                "device": null,
                "messages": [
                    {
                        "id": 1,
                        "ticket_id": 1,
                        "sender_id": 1,
                        "title": "Communication channel",
                        "content": "Welcome to the digital platform. Please use this channel for communicating with your clinician.",
                        "is_read": false,
                        "created_at": "2024-11-01T15:15:00.000000Z",
                        "updated_at": "2024-11-01T15:15:00.000000Z",
                        "attachments": [],
                        "sender": {
                            "id": 1,
                            "mrn": "MRN",
                            "name": "User name",
                            "email": "user@domain.com",
                            "language": "en",
                            "phone": "",
                            "phone_verified_at": null,
                            "address1": "",
                            "address2": "",
                            "postal_code": "",
                            "city": "",
                            "clinic_name": "Test Company",
                            "clinic_location": "Test Company Location",
                            "image": null,
                            "mfa_enabled": 0,
                            "mfa_method": "email",
                            "mfa_verified_to": null,
                            "created_by": null,
                            "active": 1,
                            "notifications_timezone": "Europe/Warsaw",
                            "notifications_at": "08:00:00",
                            "created_at": "2024-09-01T15:00:00.000000Z",
                            "updated_at": "2024-10-10T10:30:00.000000Z",
                            "invitation_status": "accepted",
                            "roles": [
                                {
                                    "id": 3,
                                    "name": "Clinician",
                                    "guard_name": "web",
                                    "created_at": "2024-01-01T12:00:00.000000Z",
                                    "updated_at": "2024-01-01T12:00:00.000000Z",
                                    "pivot": {
                                        "model_id": 1,
                                        "role_id": 3,
                                        "model_type": "App\\Models\\User"
                                    }
                                }
                            ]
                        }
                    }
                ]
            }
        }
    ]
}
 

Request   

GET api/messages-and-tickets

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

Status of messages and tickets

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/messages-and-tickets/status" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/messages-and-tickets/status"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "messages": 5,
    "tickets": 2
}
 

Request   

GET api/messages-and-tickets/status

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Mobile logs

API endpoints for managing mobile logs

Store log

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/mobile-logs" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: multipart/form-data" \
    --header "Accept: application/json" \
    --form "device_id=1"\
    --form "date_start=2024-01-23 02:01:44"\
    --form "date_end=2020-11-23 16:11:24"\
    --form "encrypt_key=quidem"\
    --form "encrypt_iv=quidem"\
    --form "file=@/tmp/phpKmv5nk" 
const url = new URL(
    "http://localhost:8000/api/mobile-logs"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "multipart/form-data",
    "Accept": "application/json",
};

const body = new FormData();
body.append('device_id', '1');
body.append('date_start', '2024-01-23 02:01:44');
body.append('date_end', '2020-11-23 16:11:24');
body.append('encrypt_key', 'quidem');
body.append('encrypt_iv', 'quidem');
body.append('file', document.querySelector('input[name="file"]').files[0]);

fetch(url, {
    method: "POST",
    headers,
    body,
}).then(response => response.json());

Example response (202):


{
    "id": 1,
    "user_id": 286,
    "device_id": 147,
    "file": "/tmp/faker320eoD",
    "date_start": "2017-02-21 22:09:08",
    "date_end": "1981-02-18 04:30:26",
    "created_at": "2026-11-03T17:26:33.000000Z",
    "updated_at": "2026-11-03T17:26:33.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to write mobile logs",
    "code": "MOBILE_LOGS:STORE:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/mobile-logs

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: multipart/form-data

Accept      

Example: application/json

Body Parameters

device_id   string  optional  

Device ID. The id of an existing record in the App\Models\Device table. Example: 1

file   file   

Log file. Must be a file. MAXIMUM:FILE_KB:102400. Example: /tmp/phpKmv5nk

date_start   string   

Log start date. MUST_BE_DATE Must be a valid date in the format Y-m-d H:i:s. Example: 2024-01-23 02:01:44

date_end   string   

Log end date. MUST_BE_DATE Must be a valid date in the format Y-m-d H:i:s. MUST_BE_AFTER:date_start. Example: 2020-11-23 16:11:24

encrypt_key   string   

Encryption key. Example: quidem

encrypt_iv   string   

Encryption IV (Initialization Vector). Example: quidem

Response

Response Fields

id   integer   

Mobile log ID.

user_id   integer   

Associated user ID.

device_id   integer   

Associated device ID.

file   string   

Log file URL.

date_start   string   

Log start date.

date_end   string   

Log end date.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Get logs

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/mobile-logs" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/mobile-logs"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 2,
            "user_id": 287,
            "device_id": 148,
            "file": "/tmp/fakerbNnEUn",
            "date_start": "2009-07-08 06:26:49",
            "date_end": "2012-05-25 00:01:05",
            "created_at": "2026-11-03T17:26:34.000000Z",
            "updated_at": "2026-11-03T17:26:34.000000Z"
        },
        {
            "id": 3,
            "user_id": 288,
            "device_id": 149,
            "file": "/tmp/fakerP4MLRW",
            "date_start": "2006-08-24 02:55:24",
            "date_end": "2002-08-03 09:28:29",
            "created_at": "2026-11-03T17:26:34.000000Z",
            "updated_at": "2026-11-03T17:26:34.000000Z"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view mobile logs",
    "code": "MOBILE_LOGS:GET:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/mobile-logs

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

extend   string  optional  

Comma-separated list of relation extensions (available: device).

sortby   string  optional  

Sort by field (available: date). Default: date, desc.

sortdir   string  optional  

Sort direction (available: asc, desc).

Response

Response Fields

items   object   
id   integer   

Mobile log ID.

user_id   integer   

Associated user ID.

device_id   integer   

Associated device ID.

file   string   

Log file URL.

date_start   string   

Log start date.

date_end   string   

Log end date.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

Get logs by user

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/mobile-logs/user/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/mobile-logs/user/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 4,
            "user_id": 289,
            "device_id": 150,
            "file": "/tmp/faker4ICome",
            "date_start": "2023-12-25 14:01:13",
            "date_end": "1986-12-12 22:31:30",
            "created_at": "2026-11-03T17:26:35.000000Z",
            "updated_at": "2026-11-03T17:26:35.000000Z"
        },
        {
            "id": 5,
            "user_id": 290,
            "device_id": 151,
            "file": "/tmp/fakerkA7igH",
            "date_start": "1982-10-12 05:00:05",
            "date_end": "2022-07-05 02:21:23",
            "created_at": "2026-11-03T17:26:35.000000Z",
            "updated_at": "2026-11-03T17:26:35.000000Z"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view mobile logs",
    "code": "MOBILE_LOGS:GET_BY_USER:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/mobile-logs/user/{userId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

userId   integer   

User ID. Example: 1

Query Parameters

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

extend   string  optional  

Comma-separated list of relation extensions (available: device).

sortby   string  optional  

Sort by field (available: date). Default: date, desc.

sortdir   string  optional  

Sort direction (available: asc, desc).

Response

Response Fields

items   object   
id   integer   

Mobile log ID.

user_id   integer   

Associated user ID.

device_id   integer   

Associated device ID.

file   string   

Log file URL.

date_start   string   

Log start date.

date_end   string   

Log end date.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

Get logs by device

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/mobile-logs/device/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/mobile-logs/device/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 6,
            "user_id": 291,
            "device_id": 152,
            "file": "/tmp/fakerY9mQr3",
            "date_start": "1992-11-09 08:37:53",
            "date_end": "2024-05-07 06:32:10",
            "created_at": "2026-11-03T17:26:36.000000Z",
            "updated_at": "2026-11-03T17:26:36.000000Z"
        },
        {
            "id": 7,
            "user_id": 292,
            "device_id": 153,
            "file": "/tmp/fakerye5A4O",
            "date_start": "1984-01-25 10:01:40",
            "date_end": "2004-12-01 07:49:30",
            "created_at": "2026-11-03T17:26:37.000000Z",
            "updated_at": "2026-11-03T17:26:37.000000Z"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view mobile logs",
    "code": "MOBILE_LOGS:GET_BY_DEVICE:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/mobile-logs/device/{deviceId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

deviceId   integer   

Device ID. Example: 1

Query Parameters

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

sortby   string  optional  

Sort by field (available: date). Default: date, desc.

sortdir   string  optional  

Sort direction (available: asc, desc).

Response

Response Fields

items   object   
id   integer   

Mobile log ID.

user_id   integer   

Associated user ID.

device_id   integer   

Associated device ID.

file   string   

Log file URL.

date_start   string   

Log start date.

date_end   string   

Log end date.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

Old Configurator Statistics

Endpoints for old configurator statistics

Create old configurator statistic

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/old-configurator-statistics" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"entry_id\": \"soluta\",
    \"comments\": \"sequi\"
}"
const url = new URL(
    "http://localhost:8000/api/old-configurator-statistics"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "entry_id": "soluta",
    "comments": "sequi"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 1,
    "entry_id": "f6a0dd79-1b44-3430-97e1-9808d4669048",
    "comments": "{\"key\":\"accusamus\",\"value\":732424}",
    "created_at": "2026-11-03T17:23:59.000000Z",
    "updated_at": "2026-11-03T17:23:59.000000Z"
}
 

Request   

POST api/old-configurator-statistics

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

entry_id   string   

Example: soluta

comments   string  optional  

Example: sequi

List old configurator statistics

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/old-configurator-statistics" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/old-configurator-statistics"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 2,
            "entry_id": "b3f987ef-f3a0-35b8-882e-bf5e958f0eb2",
            "comments": "{\"key\":\"repudiandae\",\"value\":18704}",
            "created_at": "2026-11-03T17:27:02.000000Z",
            "updated_at": "2026-11-03T17:27:02.000000Z"
        },
        {
            "id": 3,
            "entry_id": "2e696e7c-878f-32d7-8a36-7730a0d0157c",
            "comments": "{\"key\":\"odit\",\"value\":63272}",
            "created_at": "2026-11-03T17:27:02.000000Z",
            "updated_at": "2026-11-03T17:27:02.000000Z"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view old configurator statistics",
    "code": "OLD_CONFIGURATOR_STATISTIC:LIST:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/old-configurator-statistics

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

P2P Sessions

API endpoints for managing P2P (peer-to-peer) sessions

Get active session data

requires authentication

Returns P2P sessions with status "waiting_for_decision" or "in_progress".

Example request:
curl --request GET \
    --get "http://localhost:8000/api/p2p/user/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/p2p/user/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "id": 1,
    "amputee_id": null,
    "device_id": null,
    "clinician_id": null,
    "amputee_uuid": "7241673f-ac5b-3b3b-8468-99347d5c9345",
    "clinician_uuid": "273fa920-e7c4-3d22-a61e-4fad25cbea4d",
    "token": "AL5EGTK95T7LFK6R8UTACPDB7PNYSYY463YYP7MDDLXYSYHART5UZLV8FCJGDC9D",
    "status": "waiting_for_decision",
    "created_at": "2026-11-03T17:25:05.000000Z",
    "updated_at": "2026-11-03T17:25:05.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view active P2P session",
    "code": "P2P_SESSIONS:GET_ACTIVE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Session not found):


{
    "message": "P2P session not found",
    "code": "P2P_SESSIONS:GET_ACTIVE:SESSION_NOT_FOUND"
}
 

Request   

GET api/p2p/user/{userId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

userId   integer   

User ID. Example: 1

Response

Response Fields

id   integer   

P2P session ID.

device_id   integer   

Associated device ID.

amputee_id   integer   

Patient (amputee) user ID.

clinician_id   integer   

Clinician user ID.

amputee_uuid   string   

Amputee WebRTC UUID.

clinician_uuid   string   

Clinician WebRTC UUID.

token   string   

Session token.

status   string   

Session status.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

amputee   object   

Patient user.

clinician   object   

Clinician user.

Get session data

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/p2p/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/p2p/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "id": 2,
    "amputee_id": null,
    "device_id": null,
    "clinician_id": null,
    "amputee_uuid": "426f4358-11e2-3272-b2b9-b8d4b12ecf6b",
    "clinician_uuid": "7fc95a33-6cdc-3c7a-adef-4e9149abb270",
    "token": "MLJ4CG6FWCFMDP2M4LQ7TH6BJDESBYSV6CZSYFEY6NWYF9QNX7UJ4H2JL77GDLL2",
    "status": "waiting_for_decision",
    "created_at": "2026-11-03T17:25:05.000000Z",
    "updated_at": "2026-11-03T17:25:05.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view P2P session",
    "code": "P2P_SESSIONS:GET:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Session not found):


{
    "message": "P2P session not found",
    "code": "P2P_SESSIONS:GET:SESSION_NOT_FOUND"
}
 

Request   

GET api/p2p/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

P2P session ID. Example: 1

Response

Response Fields

id   integer   

P2P session ID.

device_id   integer   

Associated device ID.

amputee_id   integer   

Patient (amputee) user ID.

clinician_id   integer   

Clinician user ID.

amputee_uuid   string   

Amputee WebRTC UUID.

clinician_uuid   string   

Clinician WebRTC UUID.

token   string   

Session token.

status   string   

Session status.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

amputee   object   

Patient user.

clinician   object   

Clinician user.

Create new P2P session

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/p2p/create" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"amputee_id\": 2,
    \"device_id\": 5,
    \"amputee_uuid\": \"1d862f27-5959-39e2-9d05-fa30df4620f4\",
    \"clinician_uuid\": \"c6e8b93c-8596-3f67-a7ce-aa05f1fdfa3a\"
}"
const url = new URL(
    "http://localhost:8000/api/p2p/create"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "amputee_id": 2,
    "device_id": 5,
    "amputee_uuid": "1d862f27-5959-39e2-9d05-fa30df4620f4",
    "clinician_uuid": "c6e8b93c-8596-3f67-a7ce-aa05f1fdfa3a"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 3,
    "amputee_id": 123,
    "device_id": null,
    "clinician_id": 124,
    "amputee_uuid": "0ad17b49-a696-37f3-a2d4-75d62e18fe84",
    "clinician_uuid": "6cd99408-7db0-3704-8dc2-0dec5a96ecc8",
    "token": "MSQ8RZ52W3BSZHBLU62Z45P5FZ8ZJA246MXAZXGR5TTY6CWMK5ZT2HGTYPCPWTVU",
    "status": "waiting_for_decision",
    "created_at": "2026-11-03T17:25:06.000000Z",
    "updated_at": "2026-11-03T17:25:06.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to create P2P session",
    "code": "P2P_SESSIONS:CREATE:INSUFFICIENT_PERMISSION"
}
 

Example response (403, Device not assigned to patient):


{
    "message": "Device is not assigned to the patient",
    "code": "P2P_SESSIONS:CREATE:DEVICE_NOT_ASSIGNED"
}
 

Example response (404, User not found):


{
    "message": "User not found",
    "code": "P2P_SESSIONS:CREATE:USER_NOT_FOUND"
}
 

Request   

POST api/p2p/create

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

amputee_id   string   

Amputee ID. The id of an existing record in the App\Models\User table. Example: 2

device_id   string   

Device ID. The id of an existing record in the App\Models\Device table. Example: 5

amputee_uuid   string   

Amputee's UUID generated by integration platform. Example: 1d862f27-5959-39e2-9d05-fa30df4620f4

clinician_uuid   string   

Clinician's UUID generated by integration platform. Example: c6e8b93c-8596-3f67-a7ce-aa05f1fdfa3a

Response

Response Fields

id   integer   

P2P session ID.

device_id   integer   

Associated device ID.

amputee_id   integer   

Patient (amputee) user ID.

clinician_id   integer   

Clinician user ID.

amputee_uuid   string   

Amputee WebRTC UUID.

clinician_uuid   string   

Clinician WebRTC UUID.

token   string   

Session token.

status   string   

Session status.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

amputee   object   

Patient user.

clinician   object   

Clinician user.

Update P2P session

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/p2p/1/update" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"status\": \"closed\"
}"
const url = new URL(
    "http://localhost:8000/api/p2p/1/update"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "status": "closed"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (202):


{
    "id": 4,
    "amputee_id": null,
    "device_id": null,
    "clinician_id": null,
    "amputee_uuid": "b3e73203-3e89-3dea-8d35-948f4d7c7406",
    "clinician_uuid": "8a34ecb7-38a8-34cd-a2d2-d7613424987e",
    "token": "5P5FBWX5ZUEGM5BJZ2DYJDMVNLYYZ3QP9ETAFVYQCRFZMQ5TGVZFNJX44A8LQJP4",
    "status": "waiting_for_decision",
    "created_at": "2026-11-03T17:25:06.000000Z",
    "updated_at": "2026-11-03T17:25:06.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to update P2P session",
    "code": "P2P_SESSIONS:UPDATE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Session not found):


{
    "message": "P2P session not found",
    "code": "P2P_SESSIONS:UPDATE:SESSION_NOT_FOUND"
}
 

Request   

POST api/p2p/{id}/update

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

P2P session ID. Example: 1

Body Parameters

status   string   

Session status. Example: closed

Must be one of:
  • waiting_for_decision
  • in_progress
  • closed

Response

Response Fields

id   integer   

P2P session ID.

device_id   integer   

Associated device ID.

amputee_id   integer   

Patient (amputee) user ID.

clinician_id   integer   

Clinician user ID.

amputee_uuid   string   

Amputee WebRTC UUID.

clinician_uuid   string   

Clinician WebRTC UUID.

token   string   

Session token.

status   string   

Session status.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

amputee   object   

Patient user.

clinician   object   

Clinician user.

Product features and toggles

API endpoints for product features and toggles management

Definitions:

List public feature toggles

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/product-features/public" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/product-features/public"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, Global state of publicly exposed feature toggles):


{
    "features": [
        {
            "slug": "matomo_analytics_web",
            "enabled": true
        }
    ]
}
 

Request   

GET api/product-features/public

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

List product features

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/product/features" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/product/features"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


[
    {
        "id": 1,
        "name": "teal",
        "slug": "qui-expedita-rerum-voluptatum-quo-assumenda",
        "created_at": "2026-11-03T17:26:14.000000Z",
        "updated_at": "2026-11-03T17:26:14.000000Z"
    },
    {
        "id": 2,
        "name": "fuchsia",
        "slug": "aspernatur-sit-commodi-est-est-doloremque-accusamus",
        "created_at": "2026-11-03T17:26:14.000000Z",
        "updated_at": "2026-11-03T17:26:14.000000Z"
    }
]
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view product features and toggles",
    "code": "PRODUCT_FEATURES:LIST:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/product/features

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Response

Response Fields

id   integer   

Product feature ID.

name   string   

Feature name.

slug   string   

Feature slug (unique identifier).

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Create product feature

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/product/features" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Remote config\",
    \"slug\": \"remote_config\"
}"
const url = new URL(
    "http://localhost:8000/api/product/features"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Remote config",
    "slug": "remote_config"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 3,
    "name": "maroon",
    "slug": "autem-consequatur-unde-dolores",
    "created_at": "2026-11-03T17:26:14.000000Z",
    "updated_at": "2026-11-03T17:26:14.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage product features and toggles",
    "code": "PRODUCT_FEATURES:CREATE:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/product/features

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

name   string   

Name of product feature. Example: Remote config

slug   string   

Simplified name of product feature without spaces (e.g. remote_config for Remote config name). Example: remote_config

Response

Response Fields

id   integer   

Product feature ID.

name   string   

Feature name.

slug   string   

Feature slug (unique identifier).

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Update product feature

requires authentication

Example request:
curl --request PUT \
    "http://localhost:8000/api/product/features/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Remote config\",
    \"slug\": \"remote_config\"
}"
const url = new URL(
    "http://localhost:8000/api/product/features/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Remote config",
    "slug": "remote_config"
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 4,
    "name": "maroon",
    "slug": "mollitia-rerum-dignissimos-molestias-provident",
    "created_at": "2026-11-03T17:26:14.000000Z",
    "updated_at": "2026-11-03T17:26:14.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage product features and toggles",
    "code": "PRODUCT_FEATURES:UPDATE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Product feature not found):


{
    "message": "Product feature not found",
    "code": "PRODUCT_FEATURES:UPDATE:FEATURE_NOT_FOUND"
}
 

Request   

PUT api/product/features/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Product Feature ID. Example: 1

Body Parameters

name   string  optional  

Name of product feature. Example: Remote config

slug   string  optional  

Simplified name of product feature without spaces (e.g. remote_config for Remote config name). Example: remote_config

Response

Response Fields

id   integer   

Product feature ID.

name   string   

Feature name.

slug   string   

Feature slug (unique identifier).

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Delete product feature

requires authentication

Example request:
curl --request DELETE \
    "http://localhost:8000/api/product/features/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/product/features/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (202, OK):


{
    "message": "Product feature deleted",
    "code": "PRODUCT_FEATURES:DELETE:DELETED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage product features and toggles",
    "code": "PRODUCT_FEATURES:DELETE:INSUFFICIENT_PERMISSION"
}
 

Example response (403, Product feature belongs to compatibility entries):


{
    "message": "Cannot delete: product feature belongs to existing compatibility entries (1)",
    "code": "PRODUCT_FEATURES:DELETE:HAS_COMPATIBILITY_ENTRIES"
}
 

Example response (404, Product feature not found):


{
    "message": "Product feature not found",
    "code": "PRODUCT_FEATURES:DELETE:FEATURE_NOT_FOUND"
}
 

Request   

DELETE api/product/features/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Product Feature ID. Example: 1

List product toggles

requires authentication

This endpoint returns list of global toggles. For some users there could exist user toggle which overrides these settings.

Example request:
curl --request GET \
    --get "http://localhost:8000/api/product/toggles?global=1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/product/toggles"
);

const params = {
    "global": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


[
    {
        "id": 1,
        "name": "silver",
        "slug": "rerum-et-temporibus-soluta-velit-minus-eos",
        "enabled": 1,
        "created_at": "2026-11-03T17:26:14.000000Z",
        "updated_at": "2026-11-03T17:26:14.000000Z"
    },
    {
        "id": 2,
        "name": "purple",
        "slug": "qui-cumque-modi-laboriosam-eaque",
        "enabled": 0,
        "created_at": "2026-11-03T17:26:14.000000Z",
        "updated_at": "2026-11-03T17:26:14.000000Z"
    }
]
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view product features and toggles",
    "code": "PRODUCT_TOGGLES:LIST:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/product/toggles

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

global   integer  optional  

Pass value 1 to fetch list of global toggles without user overrides Example: 1

Response

Response Fields

id   integer   

Product toggle ID.

name   string   

Toggle name.

slug   string   

Toggle slug (unique identifier).

enabled   boolean   

Whether the toggle is enabled globally.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Create product toggle

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/product/toggles" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Remote config\",
    \"slug\": \"remote_config\",
    \"enabled\": true
}"
const url = new URL(
    "http://localhost:8000/api/product/toggles"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Remote config",
    "slug": "remote_config",
    "enabled": true
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 3,
    "name": "silver",
    "slug": "officia-nihil-est-eligendi",
    "enabled": 1,
    "created_at": "2026-11-03T17:26:14.000000Z",
    "updated_at": "2026-11-03T17:26:14.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage product features and toggles",
    "code": "PRODUCT_TOGGLES:CREATE:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/product/toggles

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

name   string   

Name of product toggle. Example: Remote config

slug   string   

Simplified name of product toggle without spaces (e.g. remote_config for Remote config name). Example: remote_config

enabled   boolean   

Is toggle enabled on this environment?. Example: true

Response

Response Fields

id   integer   

Product toggle ID.

name   string   

Toggle name.

slug   string   

Toggle slug (unique identifier).

enabled   boolean   

Whether the toggle is enabled globally.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Update product toggle

requires authentication

Example request:
curl --request PUT \
    "http://localhost:8000/api/product/toggles/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Remote config\",
    \"slug\": \"remote_config\",
    \"enabled\": true
}"
const url = new URL(
    "http://localhost:8000/api/product/toggles/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Remote config",
    "slug": "remote_config",
    "enabled": true
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 4,
    "name": "blue",
    "slug": "aut-vero-ducimus-qui-accusantium-suscipit",
    "enabled": 0,
    "created_at": "2026-11-03T17:26:14.000000Z",
    "updated_at": "2026-11-03T17:26:14.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage product features and toggles",
    "code": "PRODUCT_TOGGLES:UPDATE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Product toggle not found):


{
    "message": "Product toggle not found",
    "code": "PRODUCT_TOGGLES:UPDATE:TOGGLE_NOT_FOUND"
}
 

Request   

PUT api/product/toggles/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Product Toggle ID. Example: 1

Body Parameters

name   string  optional  

Name of product toggle. Example: Remote config

slug   string  optional  

Simplified name of product toggle without spaces (e.g. remote_config for Remote config name). Example: remote_config

enabled   boolean  optional  

Is toggle enabled on this environment?. Example: true

Response

Response Fields

id   integer   

Product toggle ID.

name   string   

Toggle name.

slug   string   

Toggle slug (unique identifier).

enabled   boolean   

Whether the toggle is enabled globally.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Delete product toggle

requires authentication

Example request:
curl --request DELETE \
    "http://localhost:8000/api/product/toggles/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/product/toggles/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (202, OK):


{
    "message": "Product toggle deleted",
    "code": "PRODUCT_TOGGLES:DELETE:DELETED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage product features and toggles",
    "code": "PRODUCT_TOGGLES:DELETE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Product toggle not found):


{
    "message": "Product toggle not found",
    "code": "PRODUCT_TOGGLES:DELETE:TOGGLE_NOT_FOUND"
}
 

Request   

DELETE api/product/toggles/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Product Toggle ID. Example: 1

List product FAQ

requires authentication

This endpoint returns list of product FAQ.

Example request:
curl --request GET \
    --get "http://localhost:8000/api/product/faq" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/product/faq"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 1,
            "question": "Assumenda quibusdam veniam delectus aut laborum. Consequatur sunt amet magnam mollitia veniam voluptatem. Nisi quis et ratione rem nihil dolorem. Mollitia commodi id quia eaque culpa dolorem quam.",
            "answer": "Ratione voluptas sequi molestiae blanditiis impedit. Dolorem ipsum repellendus dignissimos numquam.",
            "created_at": "2026-11-03T17:26:14.000000Z",
            "updated_at": "2026-11-03T17:26:14.000000Z"
        },
        {
            "id": 2,
            "question": "Vitae consequuntur hic eligendi maxime sit. Et architecto est a vitae. Distinctio soluta amet reprehenderit fugit.",
            "answer": "Enim esse perspiciatis tempore quia quia suscipit omnis. Omnis ipsa qui quia magni ex quidem. Voluptate perferendis nihil eius sed eos optio reprehenderit.",
            "created_at": "2026-11-03T17:26:14.000000Z",
            "updated_at": "2026-11-03T17:26:14.000000Z"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view product features and toggles",
    "code": "FAQ:LIST:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/product/faq

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

Response

Response Fields

items   object   
id   integer   

FAQ entry ID.

question   string   

FAQ question.

answer   string   

FAQ answer.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

List user toggles

requires authentication

This endpoint returns list of user toggles with their global toggles.

Example request:
curl --request GET \
    --get "http://localhost:8000/api/user/1/toggles" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/user/1/toggles"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


[
    {
        "id": 1,
        "toggle_id": 6,
        "user_id": 256,
        "enabled": 0,
        "created_at": "2026-11-03T17:26:15.000000Z",
        "updated_at": "2026-11-03T17:26:15.000000Z",
        "toggle": {
            "id": 6,
            "name": "olive",
            "slug": "quidem-quia-quas-facere-omnis-qui-quis-quas",
            "enabled": 1,
            "created_at": "2026-11-03T17:26:15.000000Z",
            "updated_at": "2026-11-03T17:26:15.000000Z"
        }
    },
    {
        "id": 2,
        "toggle_id": 8,
        "user_id": 257,
        "enabled": 0,
        "created_at": "2026-11-03T17:26:15.000000Z",
        "updated_at": "2026-11-03T17:26:15.000000Z",
        "toggle": {
            "id": 8,
            "name": "maroon",
            "slug": "aut-laudantium-illum-neque-est-officiis-est-esse",
            "enabled": 0,
            "created_at": "2026-11-03T17:26:15.000000Z",
            "updated_at": "2026-11-03T17:26:15.000000Z"
        }
    }
]
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view product features and toggles",
    "code": "USER_TOGGLES:LIST:INSUFFICIENT_PERMISSION"
}
 

Example response (404, User not found):


{
    "message": "User not found",
    "code": "USER_TOGGLES:LIST:USER_NOT_FOUND"
}
 

Request   

GET api/user/{userId}/toggles

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

userId   integer   

User ID. Example: 1

Response

Response Fields

id   integer   

User product toggle ID.

toggle_id   integer   

Associated product toggle ID.

user_id   integer   

Associated user ID.

enabled   boolean   

Whether the toggle is enabled for this user.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

toggle   object   

Associated product toggle.

Create user toggle

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/user/1/toggles" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"toggle_id\": 1,
    \"enabled\": true
}"
const url = new URL(
    "http://localhost:8000/api/user/1/toggles"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "toggle_id": 1,
    "enabled": true
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 3,
    "toggle_id": 9,
    "user_id": 258,
    "enabled": 0,
    "created_at": "2026-11-03T17:26:16.000000Z",
    "updated_at": "2026-11-03T17:26:16.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage product features and toggles",
    "code": "USER_TOGGLES:CREATE:INSUFFICIENT_PERMISSION"
}
 

Example response (403, Cannot create user toggles for SuperAdmin):


{
    "message": "Cannot create user toggles for SuperAdmin",
    "code": "USER_TOGGLES:CREATE:CANNOT_CREATE_FOR_SUPER_ADMIN"
}
 

Example response (404, User not found):


{
    "message": "User not found",
    "code": "USER_TOGGLES:CREATE:USER_NOT_FOUND"
}
 

Request   

POST api/user/{userId}/toggles

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

userId   integer   

User ID. Example: 1

Body Parameters

toggle_id   integer   

Product toggle ID. The id of an existing record in the App\Models\ProductToggle table. Example: 1

enabled   boolean   

Is user toggle enabled on this environment?. Example: true

Response

Response Fields

id   integer   

User product toggle ID.

toggle_id   integer   

Associated product toggle ID.

user_id   integer   

Associated user ID.

enabled   boolean   

Whether the toggle is enabled for this user.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

toggle   object   

Associated product toggle.

Update user toggle

requires authentication

Example request:
curl --request PUT \
    "http://localhost:8000/api/user/1/toggles/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"enabled\": true
}"
const url = new URL(
    "http://localhost:8000/api/user/1/toggles/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "enabled": true
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 4,
    "toggle_id": 10,
    "user_id": 259,
    "enabled": 0,
    "created_at": "2026-11-03T17:26:16.000000Z",
    "updated_at": "2026-11-03T17:26:16.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage product features and toggles",
    "code": "USER_TOGGLES:UPDATE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, User not found):


{
    "message": "User not found",
    "code": "USER_TOGGLES:UPDATE:USER_NOT_FOUND"
}
 

Example response (404, User toggle not found):


{
    "message": "User toggle not found",
    "code": "USER_TOGGLES:UPDATE:TOGGLE_NOT_FOUND"
}
 

Request   

PUT api/user/{userId}/toggles/{toggleId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

userId   integer   

User ID. Example: 1

toggleId   integer   

User Toggle ID. Example: 1

Body Parameters

enabled   boolean  optional  

Is user toggle enabled on this environment?. Example: true

Response

Response Fields

id   integer   

User product toggle ID.

toggle_id   integer   

Associated product toggle ID.

user_id   integer   

Associated user ID.

enabled   boolean   

Whether the toggle is enabled for this user.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

toggle   object   

Associated product toggle.

Delete user toggle

requires authentication

Example request:
curl --request DELETE \
    "http://localhost:8000/api/user/1/toggles/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/user/1/toggles/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (202, OK):


{
    "message": "User toggle deleted",
    "code": "USER_TOGGLES:DELETE:DELETED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage product features and toggles",
    "code": "USER_TOGGLES:DELETE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, User not found):


{
    "message": "User not found",
    "code": "USER_TOGGLES:DELETE:USER_NOT_FOUND"
}
 

Example response (404, User toggle not found):


{
    "message": "User toggle not found",
    "code": "USER_TOGGLES:DELETE:TOGGLE_NOT_FOUND"
}
 

Request   

DELETE api/user/{userId}/toggles/{toggleId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

userId   integer   

User ID. Example: 1

toggleId   integer   

User Toggle ID. Example: 1

Release notes

Endpoints related to release notes

List release notes

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/release-notes" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"status\": \"aut\"
}"
const url = new URL(
    "http://localhost:8000/api/release-notes"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "status": "aut"
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 1,
            "sync_uuid": "c97698b3-1fbf-4d99-94ff-e3e57855259b",
            "title": "Illum omnis et.",
            "body": "<p>Similique placeat explicabo ea sit. Ea dolorem ducimus possimus id reprehenderit. Cupiditate eius beatae odio sed enim quae reiciendis.</p>",
            "video": null,
            "status": "draft",
            "translation_status": "STARTED",
            "activated_at": null,
            "archived_at": null,
            "created_by": null,
            "created_at": "2026-11-03T17:27:01.000000Z",
            "updated_at": "2026-11-03T17:27:01.000000Z"
        },
        {
            "id": 2,
            "sync_uuid": "39313d70-b876-4dbe-9776-c97e1e61579e",
            "title": "Voluptas nulla maiores optio.",
            "body": "<p>Delectus adipisci quidem ut autem voluptas repellendus et. Nihil doloremque illo aut natus doloribus maiores dolorem earum. Ullam est delectus quia sed molestiae veniam ab.</p>",
            "video": null,
            "status": "draft",
            "translation_status": "STARTED",
            "activated_at": null,
            "archived_at": null,
            "created_by": null,
            "created_at": "2026-11-03T17:27:01.000000Z",
            "updated_at": "2026-11-03T17:27:01.000000Z"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view release notes",
    "code": "RELEASE_NOTES:LIST:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/release-notes

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

Body Parameters

status   string  optional  

Example: aut

Create release note

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/release-notes" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: multipart/form-data" \
    --header "Accept: application/json" \
    --form "title=New grip control available"\
    --form "body=<p>We just shipped a new grip control.</p>"\
    --form "sync_uuid=f3429ee9-6b39-378a-b802-925889911852"\
    --form "video=@/tmp/phpmYD1O0" 
const url = new URL(
    "http://localhost:8000/api/release-notes"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "multipart/form-data",
    "Accept": "application/json",
};

const body = new FormData();
body.append('title', 'New grip control available');
body.append('body', '<p>We just shipped a new grip control.</p>');
body.append('sync_uuid', 'f3429ee9-6b39-378a-b802-925889911852');
body.append('video', document.querySelector('input[name="video"]').files[0]);

fetch(url, {
    method: "POST",
    headers,
    body,
}).then(response => response.json());

Example response (201):


{
    "id": 3,
    "sync_uuid": "21df4cbc-b6e4-4a9e-bc64-269ee1b21624",
    "title": "Est unde reiciendis.",
    "body": "<p>Ex sequi ut distinctio animi est. Esse labore ipsa cupiditate. Qui ipsum aut nisi ratione perspiciatis molestiae. Qui mollitia facere similique impedit iure autem enim.</p>",
    "video": null,
    "status": "draft",
    "translation_status": "STARTED",
    "activated_at": null,
    "archived_at": null,
    "created_by": null,
    "created_at": "2026-11-03T17:27:01.000000Z",
    "updated_at": "2026-11-03T17:27:01.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage release notes",
    "code": "RELEASE_NOTES:CREATE:INSUFFICIENT_PERMISSION"
}
 

Example response (500, Server error):


{
    "message": "Server error: release note not created",
    "code": "RELEASE_NOTES:CREATE:SERVER_ERROR"
}
 

Request   

POST api/release-notes

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: multipart/form-data

Accept      

Example: application/json

Body Parameters

title   string   

Release note title, authored in English. Example: New grip control available

body   string   

Release note body (rich text HTML), authored in English. Example: <p>We just shipped a new grip control.</p>

video   file  optional  

Optional video attachment (mp4, max 250MB). Must be a file. MAXIMUM:FILE_KB:256000. Example: /tmp/phpmYD1O0

sync_uuid   string  optional  

MUST_BE_UUID. Example: f3429ee9-6b39-378a-b802-925889911852

Update release note

requires authentication

Example request:
curl --request PUT \
    "http://localhost:8000/api/release-notes/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: multipart/form-data" \
    --header "Accept: application/json" \
    --form "title=New grip control available"\
    --form "body=<p>We just shipped a new grip control.</p>"\
    --form "video=@/tmp/phpTmuUxg" 
const url = new URL(
    "http://localhost:8000/api/release-notes/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "multipart/form-data",
    "Accept": "application/json",
};

const body = new FormData();
body.append('title', 'New grip control available');
body.append('body', '<p>We just shipped a new grip control.</p>');
body.append('video', document.querySelector('input[name="video"]').files[0]);

fetch(url, {
    method: "PUT",
    headers,
    body,
}).then(response => response.json());

Example response (202):


{
    "id": 4,
    "sync_uuid": "cdf04ff4-e5cc-4a6d-948f-3eda875c470d",
    "title": "Voluptatibus eos voluptatum velit.",
    "body": "<p>Cum harum eum impedit. Saepe consequatur dolor voluptatem necessitatibus. Vel numquam fuga mollitia beatae porro. Placeat asperiores quia totam odio.</p>",
    "video": null,
    "status": "draft",
    "translation_status": "STARTED",
    "activated_at": null,
    "archived_at": null,
    "created_by": null,
    "created_at": "2026-11-03T17:27:01.000000Z",
    "updated_at": "2026-11-03T17:27:01.000000Z"
}
 

Example response (403, Release note is not editable):


{
    "message": "Release note is not editable",
    "code": "RELEASE_NOTES:UPDATE:NOT_EDITABLE"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage release notes",
    "code": "RELEASE_NOTES:UPDATE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Release note not found):


{
    "message": "Release note not found",
    "code": "RELEASE_NOTES:UPDATE:NOT_FOUND"
}
 

Example response (500, Server error):


{
    "message": "Server error: release note not updated",
    "code": "RELEASE_NOTES:UPDATE:SERVER_ERROR"
}
 

Request   

PUT api/release-notes/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: multipart/form-data

Accept      

Example: application/json

URL Parameters

id   integer   

Release note ID. Example: 1

Body Parameters

title   string  optional  

Release note title, authored in English. Example: New grip control available

body   string  optional  

Release note body (rich text HTML), authored in English. Example: <p>We just shipped a new grip control.</p>

video   file  optional  

Optional video attachment (mp4, max 250MB). Must be a file. MAXIMUM:FILE_KB:256000. Example: /tmp/phpTmuUxg

Delete release note

requires authentication

Example request:
curl --request DELETE \
    "http://localhost:8000/api/release-notes/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/release-notes/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "message": "Release note deleted",
    "code": "RELEASE_NOTES:DELETE:DELETED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage release notes",
    "code": "RELEASE_NOTES:DELETE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Release note not found):


{
    "message": "Release note not found",
    "code": "RELEASE_NOTES:DELETE:NOT_FOUND"
}
 

Example response (500, Server error):


{
    "message": "Server error: release note not deleted",
    "code": "RELEASE_NOTES:DELETE:SERVER_ERROR"
}
 

Request   

DELETE api/release-notes/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Release note ID. Example: 1

Activate release note

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/release-notes/1/activate" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/release-notes/1/activate"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200):


{
    "id": 5,
    "sync_uuid": "9186a5a0-b42b-498d-b90e-396d39431ce0",
    "title": "Sit alias possimus.",
    "body": "<p>Impedit voluptates a ut rerum architecto recusandae esse. Et qui quo ut qui facere nemo consequuntur veniam. Molestiae qui eos repellat cupiditate laudantium quasi perspiciatis voluptates. Quas et ut consequuntur et officiis non eos. Maxime provident eligendi non quisquam nostrum.</p>",
    "video": null,
    "status": "draft",
    "translation_status": "STARTED",
    "activated_at": null,
    "archived_at": null,
    "created_by": null,
    "created_at": "2026-11-03T17:27:01.000000Z",
    "updated_at": "2026-11-03T17:27:01.000000Z"
}
 

Example response (403, Release note is already active):


{
    "message": "Release note is already active",
    "code": "RELEASE_NOTES:ACTIVATE:ALREADY_ACTIVE"
}
 

Example response (403, Translation is not ready yet):


{
    "message": "Release note translation is not ready yet",
    "code": "RELEASE_NOTES:ACTIVATE:TRANSLATION_NOT_READY"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage release notes",
    "code": "RELEASE_NOTES:ACTIVATE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Release note not found):


{
    "message": "Release note not found",
    "code": "RELEASE_NOTES:ACTIVATE:NOT_FOUND"
}
 

Example response (500, Server error):


{
    "message": "Server error: release note not activated",
    "code": "RELEASE_NOTES:ACTIVATE:SERVER_ERROR"
}
 

Request   

POST api/release-notes/{id}/activate

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Release note ID. Example: 1

Archive release note

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/release-notes/1/archive" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/release-notes/1/archive"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200):


{
    "id": 6,
    "sync_uuid": "becb0e49-e7e5-4c3f-b330-184a5fd0e371",
    "title": "Sunt dolor laborum.",
    "body": "<p>Deserunt consequatur nihil sed amet dolor nisi. Deserunt non adipisci dolores maxime. Id officia consequatur ipsum voluptatum facilis.</p>",
    "video": null,
    "status": "draft",
    "translation_status": "STARTED",
    "activated_at": null,
    "archived_at": null,
    "created_by": null,
    "created_at": "2026-11-03T17:27:01.000000Z",
    "updated_at": "2026-11-03T17:27:01.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage release notes",
    "code": "RELEASE_NOTES:ARCHIVE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Release note not found):


{
    "message": "Release note not found",
    "code": "RELEASE_NOTES:ARCHIVE:NOT_FOUND"
}
 

Example response (500, Server error):


{
    "message": "Server error: release note not archived",
    "code": "RELEASE_NOTES:ARCHIVE:SERVER_ERROR"
}
 

Request   

POST api/release-notes/{id}/archive

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Release note ID. Example: 1

Get release notes status

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/release-notes/status" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/release-notes/status"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
vary: Origin
 

{
    "message": "api.responses.general.unauthenticated",
    "code": "GENERAL:UNAUTHENTICATED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view release notes",
    "code": "RELEASE_NOTES:STATUS:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/release-notes/status

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Dismiss release note popup

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/release-notes/1/dismiss" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/release-notes/1/dismiss"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "message": "Release note dismissed",
    "code": "RELEASE_NOTES:DISMISS:DISMISSED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view release notes",
    "code": "RELEASE_NOTES:DISMISS:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Release note not found):


{
    "message": "Release note not found",
    "code": "RELEASE_NOTES:DISMISS:NOT_FOUND"
}
 

Request   

POST api/release-notes/{id}/dismiss

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Release note ID. Example: 1

Mark release notes as seen

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/release-notes/mark-seen" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/release-notes/mark-seen"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "message": "Release notes marked as seen",
    "code": "RELEASE_NOTES:MARK_SEEN:MARKED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view release notes",
    "code": "RELEASE_NOTES:MARK_SEEN:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/release-notes/mark-seen

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Releases

API endpoints for releases management

List releases

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/releases" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/releases"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 1,
            "version_type": "App\\Models\\SoftwareVersion",
            "version_id": 14,
            "description": "Ut suscipit consequatur amet quam. Veritatis labore aut ut accusantium nemo. Repellendus dolorum enim quo sit eum.",
            "created_at": "2026-11-03T17:26:18.000000Z",
            "updated_at": "2026-11-03T17:26:18.000000Z",
            "version": {
                "id": 14,
                "name": "7.49.74",
                "created_at": "2026-11-03T17:26:18.000000Z",
                "updated_at": "2026-11-03T17:26:18.000000Z"
            }
        },
        {
            "id": 2,
            "version_type": "App\\Models\\SoftwareVersion",
            "version_id": 15,
            "description": "Aut in harum impedit expedita sint eligendi. Hic reiciendis doloremque enim ea aliquid. Aperiam omnis quos quidem aliquid cumque quia distinctio corporis. Voluptatum est porro asperiores voluptate.",
            "created_at": "2026-11-03T17:26:18.000000Z",
            "updated_at": "2026-11-03T17:26:18.000000Z",
            "version": {
                "id": 15,
                "name": "7.59.27",
                "created_at": "2026-11-03T17:26:18.000000Z",
                "updated_at": "2026-11-03T17:26:18.000000Z"
            }
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to list releases",
    "code": "RELEASES:LIST:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/releases

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

Response

Response Fields

items   object   
id   integer   

Release ID.

version_type   string   

Version model type (morph class name).

version_id   integer   

Version model ID.

description   string   

Release notes.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

version   object   

Associated version (software, firmware or PCB).

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

Get release

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/releases/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/releases/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "id": 3,
    "version_type": "App\\Models\\SoftwareVersion",
    "version_id": 16,
    "description": "Cum autem tenetur similique ut. Quas et voluptas enim distinctio. Dolores dolores nostrum aut dolor. Omnis nihil dolore ipsa esse nihil in.",
    "created_at": "2026-11-03T17:26:18.000000Z",
    "updated_at": "2026-11-03T17:26:18.000000Z",
    "version": {
        "id": 16,
        "name": "8.63.53",
        "created_at": "2026-11-03T17:26:18.000000Z",
        "updated_at": "2026-11-03T17:26:18.000000Z"
    }
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view release",
    "code": "RELEASES:GET:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Release not found):


{
    "message": "Release not found",
    "code": "RELEASES:GET:RELEASE_NOT_FOUND"
}
 

Request   

GET api/releases/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Release ID. Example: 1

Response

Response Fields

id   integer   

Release ID.

version_type   string   

Version model type (morph class name).

version_id   integer   

Version model ID.

description   string   

Release notes.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

version   object   

Associated version (software, firmware or PCB).

Create release

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/releases" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"version_type\": \"SoftwareVersion\",
    \"version_id\": 1,
    \"description\": \"This version fixes minor bugs.\"
}"
const url = new URL(
    "http://localhost:8000/api/releases"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "version_type": "SoftwareVersion",
    "version_id": 1,
    "description": "This version fixes minor bugs."
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 4,
    "version_type": "App\\Models\\SoftwareVersion",
    "version_id": 17,
    "description": "Debitis non omnis quas voluptates. Ullam et deserunt expedita aut. Corporis deleniti voluptate accusantium molestiae rerum quaerat dolores.",
    "created_at": "2026-11-03T17:26:18.000000Z",
    "updated_at": "2026-11-03T17:26:18.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to create release",
    "code": "RELEASES:CREATE:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/releases

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

version_type   string   

Version type. Example: SoftwareVersion

Must be one of:
  • DeviceModel
  • SoftwareVersion
  • FirmwareVersion
  • PCBVersion
version_id   integer   

Version ID. Example: 1

description   string  optional  

Release description. Example: This version fixes minor bugs.

Response

Response Fields

id   integer   

Release ID.

version_type   string   

Version model type (morph class name).

version_id   integer   

Version model ID.

description   string   

Release notes.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

version   object   

Associated version (software, firmware or PCB).

Update release

requires authentication

Example request:
curl --request PUT \
    "http://localhost:8000/api/releases/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"version_type\": \"SoftwareVersion\",
    \"version_id\": 1,
    \"description\": \"This version fixes minor bugs.\"
}"
const url = new URL(
    "http://localhost:8000/api/releases/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "version_type": "SoftwareVersion",
    "version_id": 1,
    "description": "This version fixes minor bugs."
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (202):


{
    "id": 5,
    "version_type": "App\\Models\\SoftwareVersion",
    "version_id": 18,
    "description": "Ipsum quis est ut dolores id. Quia qui magni est eos distinctio odio. Incidunt repellat est ea voluptatum reprehenderit.",
    "created_at": "2026-11-03T17:26:18.000000Z",
    "updated_at": "2026-11-03T17:26:18.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to update release",
    "code": "RELEASES:UPDATE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Release not found):


{
    "message": "Release not found",
    "code": "RELEASES:UPDATE:RELEASE_NOT_FOUND"
}
 

Request   

PUT api/releases/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Release ID. Example: 1

Body Parameters

version_type   string  optional  

Version type. Example: SoftwareVersion

Must be one of:
  • DeviceModel
  • SoftwareVersion
  • FirmwareVersion
  • PCBVersion
version_id   integer  optional  

Version ID. Example: 1

description   string  optional  

Release description. Example: This version fixes minor bugs.

Response

Response Fields

id   integer   

Release ID.

version_type   string   

Version model type (morph class name).

version_id   integer   

Version model ID.

description   string   

Release notes.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

version   object   

Associated version (software, firmware or PCB).

Delete release

requires authentication

Example request:
curl --request DELETE \
    "http://localhost:8000/api/releases/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/releases/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (202, OK):


{
    "message": "Version deleted",
    "code": "RELEASES:DELETE:DELETED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to delete release",
    "code": "RELEASES:DELETE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Release not found):


{
    "message": "Release not found",
    "code": "RELEASES:DELETE:RELEASE_NOT_FOUND"
}
 

Request   

DELETE api/releases/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Release ID. Example: 1

Search

API endpoints for search

requires authentication

Searches in:
  • users (by name and email)
  • devices (by serial number)

Returned collection contains entries of type User or Device.

If the device has a patient assigned, this patient is included in the results as an entry of type User. If the device has no patient, the device is included in the results.

Users included in the results, but not found directly have "devices" relation filled in with the devices that match the search query.

Example request:
curl --request GET \
    --get "http://localhost:8000/api/search?query=Tom" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"query\": \"zeoiunhivnyaeehadffocitvobouarpvpjsjllrsgohrymqwmkvjqdzaqktgxqzspegroipaqyjphfxhklfnyfhy\"
}"
const url = new URL(
    "http://localhost:8000/api/search"
);

const params = {
    "query": "Tom",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "query": "zeoiunhivnyaeehadffocitvobouarpvpjsjllrsgohrymqwmkvjqdzaqktgxqzspegroipaqyjphfxhklfnyfhy"
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, OK):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "type": "User",
            "item": {
                "id": 1,
                "mrn": "MRN",
                "name": "User name",
                "email": "user@domain.com",
                "language": "en",
                "phone": "",
                "phone_verified_at": null,
                "address1": "",
                "address2": "",
                "postal_code": "",
                "city": "",
                "clinic_name": "Test Company",
                "clinic_location": "Test Company Location",
                "image": null,
                "mfa_enabled": 0,
                "mfa_method": "email",
                "mfa_verified_to": null,
                "created_by": null,
                "active": 1,
                "notifications_timezone": "Europe/Warsaw",
                "notifications_at": "08:00:00",
                "created_at": "2024-09-01T15:00:00.000000Z",
                "updated_at": "2024-10-10T10:30:00.000000Z",
                "invitation_status": "accepted",
                "pivot": {
                    "assigned_user_id": 2,
                    "user_id": 1
                },
                "devices": [],
                "roles": [
                    {
                        "id": 5,
                        "name": "Amputee",
                        "guard_name": "web",
                        "created_at": "2024-01-01T12:00:00.000000Z",
                        "updated_at": "2024-01-01T12:00:00.000000Z",
                        "pivot": {
                            "model_id": 1,
                            "role_id": 5,
                            "model_type": "App\\Models\\User"
                        }
                    }
                ]
            }
        },
        {
            "type": "Device",
            "item": {
                "id": 1,
                "serial": "SERIAL-NUMBER",
                "bluetooth_id": "BLUETOOTH_ID",
                "model_id": 1,
                "amputee_id": 1,
                "firmware_version_id": 1,
                "pcb_version_id": 1,
                "active": 1,
                "last_activity_at": "2024-11-11 12:00:00",
                "created_at": "2024-08-30T15:00:00.000000Z",
                "updated_at": "2024-09-01T16:00:00.000000Z",
                "pivot": {
                    "user_id": 2,
                    "device_id": 1
                }
            }
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to search",
    "code": "SEARCH:SEARCH:INSUFFICIENT_PERMISSION"
}
 

Servicing

API endpoints for servicing

List of parts

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/servicing/parts" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/servicing/parts"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 1,
            "device_model": null,
            "name": "Visa",
            "created_at": "2026-11-03T17:25:15.000000Z",
            "updated_at": "2026-11-03T17:25:15.000000Z"
        },
        {
            "id": 2,
            "device_model": null,
            "name": "Discover Card",
            "created_at": "2026-11-03T17:25:15.000000Z",
            "updated_at": "2026-11-03T17:25:15.000000Z"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to list service parts",
    "code": "SERVICING:LIST_PARTS:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/servicing/parts

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

Response

Response Fields

items   object   
id   integer   

Service part ID.

device_model   integer   

Associated device model ID.

name   string   

Part name.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

Report service repair

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/servicing/repair" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: multipart/form-data" \
    --header "Accept: application/json" \
    --form "user_id=1"\
    --form "device_id=1"\
    --form "parts[][part_id]=1"\
    --form "parts[][reason]=Mechanical issue"\
    --form "files[]=@/tmp/phpWN0fd2" 
const url = new URL(
    "http://localhost:8000/api/servicing/repair"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "multipart/form-data",
    "Accept": "application/json",
};

const body = new FormData();
body.append('user_id', '1');
body.append('device_id', '1');
body.append('parts[][part_id]', '1');
body.append('parts[][reason]', 'Mechanical issue');
body.append('files[]', document.querySelector('input[name="files[]"]').files[0]);

fetch(url, {
    method: "POST",
    headers,
    body,
}).then(response => response.json());

Example response (201):


{
    "id": 1,
    "user_id": 141,
    "device_id": 128,
    "created_at": "2026-11-03T17:25:15.000000Z",
    "updated_at": "2026-11-03T17:25:15.000000Z",
    "parts": [
        {
            "id": 1,
            "repair_id": 1,
            "part_id": 3,
            "reason": "Ut distinctio et iure. Quis fugit tenetur quisquam cupiditate minus perspiciatis consequatur. Id est maiores ipsam nesciunt nostrum dolores.",
            "created_at": "2026-11-03T17:25:16.000000Z",
            "updated_at": "2026-11-03T17:25:16.000000Z",
            "part": {
                "id": 3,
                "device_model": null,
                "name": "JCB",
                "created_at": "2026-11-03T17:25:16.000000Z",
                "updated_at": "2026-11-03T17:25:16.000000Z"
            }
        },
        {
            "id": 2,
            "repair_id": 1,
            "part_id": 4,
            "reason": "Autem dolor asperiores dignissimos et in voluptatum consequuntur velit. Neque repellendus quibusdam officia suscipit odio quibusdam. Sed sed omnis atque tempore a quia.",
            "created_at": "2026-11-03T17:25:17.000000Z",
            "updated_at": "2026-11-03T17:25:17.000000Z",
            "part": {
                "id": 4,
                "device_model": null,
                "name": "MasterCard",
                "created_at": "2026-11-03T17:25:17.000000Z",
                "updated_at": "2026-11-03T17:25:17.000000Z"
            }
        },
        {
            "id": 3,
            "repair_id": 1,
            "part_id": 5,
            "reason": "Suscipit in est et eos. Aut eum voluptates deserunt ullam. Sed quaerat porro alias exercitationem est.",
            "created_at": "2026-11-03T17:25:17.000000Z",
            "updated_at": "2026-11-03T17:25:17.000000Z",
            "part": {
                "id": 5,
                "device_model": null,
                "name": "JCB",
                "created_at": "2026-11-03T17:25:17.000000Z",
                "updated_at": "2026-11-03T17:25:17.000000Z"
            }
        }
    ],
    "attachments": [
        {
            "id": 1,
            "repair_id": 1,
            "file": "/tmp/fakertpwShD",
            "created_at": "2026-11-03T17:25:16.000000Z",
            "updated_at": "2026-11-03T17:25:16.000000Z"
        },
        {
            "id": 2,
            "repair_id": 1,
            "file": "/tmp/fakerDjmBV4",
            "created_at": "2026-11-03T17:25:18.000000Z",
            "updated_at": "2026-11-03T17:25:18.000000Z"
        },
        {
            "id": 3,
            "repair_id": 1,
            "file": "/tmp/fakerdaHnEW",
            "created_at": "2026-11-03T17:25:18.000000Z",
            "updated_at": "2026-11-03T17:25:18.000000Z"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to report service repair",
    "code": "SERVICING:REPORT_REPAIR:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/servicing/repair

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: multipart/form-data

Accept      

Example: application/json

Body Parameters

user_id   integer   

User ID. Example: 1

device_id   integer   

Device ID. Example: 1

files   file[]  optional  

Array of attachment files.

parts   object[]  optional  

Array of replaced parts.

part_id   integer  optional  

Service part ID. Example: 1

reason   string  optional  

Reason of part replacement. Example: Mechanical issue

Response

Response Fields

id   integer   

Service repair ID.

user_id   integer   

Associated user ID.

device_id   integer   

Associated device ID.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

parts   object[]   

Service parts included in the repair.

attachments   object[]   

Repair attachments.

Settings

API endpoints for app settings

Get app version

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/settings/app-version" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/settings/app-version"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "version": "1.6.0"
}
 

Request   

GET api/settings/app-version

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Get silent push timeout

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/settings/silent-push" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/settings/silent-push"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "timeout": "15"
}
 

Request   

GET api/settings/silent-push

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Get available languages

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/settings/languages" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/settings/languages"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "languages": [
        "de",
        "en",
        "es",
        "it",
        "pl",
        "ru",
        "uk"
    ]
}
 

Request   

GET api/settings/languages

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Get mobile stores versions

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/mobile/versions" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/mobile/versions"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "ios": "1.0",
    "android": "1.0"
}
 

Request   

GET api/mobile/versions

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Update app version

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/settings/app-version" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"version\": \"1.6.0\"
}"
const url = new URL(
    "http://localhost:8000/api/settings/app-version"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "version": "1.6.0"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, OK):


{
    "version": "1.6.0"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to update settings",
    "code": "SETTINGS:UPDATE_APP_VERSION:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/settings/app-version

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

version   string  optional  

App version. Example: 1.6.0

Update silent push timeout

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/settings/silent-push" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"timeout\": 15
}"
const url = new URL(
    "http://localhost:8000/api/settings/silent-push"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "timeout": 15
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, OK):


{
    "timeout": "15"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to update settings",
    "code": "SETTINGS:UPDATE_SILENT_PUSH:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/settings/silent-push

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

timeout   integer  optional  

Silent push timeout in minutes. Minimum value is 1 minute (15 for production environment), maximum is 60 minutes. MINIMUM:NUMBER:1 MAXIMUM:NUMBER:60. Example: 15

Update mobile stores versions

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/mobile/versions" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"ios\": \"1.1\",
    \"android\": \"1.1\"
}"
const url = new URL(
    "http://localhost:8000/api/mobile/versions"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "ios": "1.1",
    "android": "1.1"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, OK):


{
    "ios": "1.0",
    "android": "1.0"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to update settings",
    "code": "SETTINGS:UPDATE_MOBILE_STORES_VERSION:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/mobile/versions

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

ios   string  optional  

Apple AppStore current version. Example: 1.1

android   string  optional  

Google Play current version. Example: 1.1

Get glove mode config

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/glove-mode-config" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/glove-mode-config"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "config": "{\"key\": \"value\"}"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view settings",
    "code": "GLOVE_MODE_CONFIG:GET:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/glove-mode-config

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Update glove mode config

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/glove-mode-config" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"config\": \"{\\\"key\\\": \\\"value\\\"}\"
}"
const url = new URL(
    "http://localhost:8000/api/glove-mode-config"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "config": "{\"key\": \"value\"}"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, OK):


{
    "config": "{\"key\": \"value\"}"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to update settings",
    "code": "GLOVE_MODE_CONFIG:UPDATE:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/glove-mode-config

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

config   string   

Glove mode config as JSON string. MUST_BE_JSON. Example: {"key": "value"}

Support Ticket

API endpoints for managing support tickets

Get tickets list

requires authentication

Possible extend options:

Example request:
curl --request GET \
    --get "http://localhost:8000/api/tickets?status=new&sender=1&recipient=1&device=1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/tickets"
);

const params = {
    "status": "new",
    "sender": "1",
    "recipient": "1",
    "device": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 27,
            "sender_id": 150,
            "recipient_id": 151,
            "device_id": 135,
            "meeting_date": "2026-11-03 17:25:20",
            "meeting_type": "online_meeting",
            "contact_email": "romaguera.vern@gmail.com",
            "status": "new",
            "created_at": "2026-11-03T17:25:21.000000Z",
            "updated_at": "2026-11-03T17:25:21.000000Z",
            "sender": {
                "id": 150,
                "mrn": "G7S9FJRX1793726720",
                "name": "Angeline Weber",
                "email": "1793726720vita.powlowski@example.net",
                "language": "en",
                "phone": "909-237-8770",
                "phone_country": "ER",
                "phone_verified_at": null,
                "address1": "772 Amalia Islands Suite 297",
                "address2": "Lake Lisettehaven, MA 66787-8532",
                "postal_code": "37269-3150",
                "city": "Berge Inc",
                "country": "UA",
                "clinic_name": "Jarenstad",
                "clinic_location": "637 Lola Valleys Suite 531\nSouth Coralie, OR 75975-0585",
                "image": null,
                "public_image": null,
                "mfa_enabled": 0,
                "mfa_method": null,
                "mfa_verified_to": null,
                "location_id": null,
                "created_by": null,
                "active": 1,
                "is_ambassador": 0,
                "test_account": false,
                "notifications_timezone": null,
                "notifications_at": null,
                "created_at": "2026-11-03T17:25:20.000000Z",
                "updated_at": "2026-11-03T17:25:20.000000Z",
                "invitation_status": null,
                "acadle_invitation_status": null,
                "roles": []
            },
            "recipient": {
                "id": 151,
                "mrn": "BQHZACRJ1793726720",
                "name": "Earl Konopelski",
                "email": "1793726720adele.reilly@example.com",
                "language": "en",
                "phone": "(361) 961-6763",
                "phone_country": "LI",
                "phone_verified_at": null,
                "address1": "779 Beahan Dam Suite 199",
                "address2": "Kesslerview, ME 36889-6687",
                "postal_code": "26448-6153",
                "city": "Lakin-Swaniawski",
                "country": "NO",
                "clinic_name": "Claudiachester",
                "clinic_location": "27873 Kristin Lock\nHaagchester, GA 08938-7440",
                "image": null,
                "public_image": null,
                "mfa_enabled": 0,
                "mfa_method": null,
                "mfa_verified_to": null,
                "location_id": null,
                "created_by": null,
                "active": 1,
                "is_ambassador": 0,
                "test_account": false,
                "notifications_timezone": null,
                "notifications_at": null,
                "created_at": "2026-11-03T17:25:20.000000Z",
                "updated_at": "2026-11-03T17:25:20.000000Z",
                "invitation_status": null,
                "acadle_invitation_status": null,
                "roles": []
            },
            "device": {
                "id": 135,
                "serial": "3b088b82-0fae-373b-b626-6ca3406681c0",
                "bluetooth_id": "05586cee-0fd9-3268-9d96-2061df8e3b6a",
                "company_id": null,
                "model_id": null,
                "amputee_id": null,
                "clinician_id": null,
                "firmware_version_id": null,
                "pcb_version_id": null,
                "reverse_magnets": 0,
                "is_electrode": 0,
                "active": 1,
                "last_activity_at": "0000-00-00 00:00:00",
                "first_connected_at": null,
                "measurements": null,
                "created_at": "2026-11-03T17:25:21.000000Z",
                "updated_at": "2026-11-03T17:25:21.000000Z",
                "first_config_change_at": null
            },
            "messages": [
                {
                    "id": 15,
                    "ticket_id": 27,
                    "sender_id": 152,
                    "title": "Dr.",
                    "content": "Quo porro ab occaecati.",
                    "is_read": false,
                    "created_at": "2026-11-03T17:25:22.000000Z",
                    "updated_at": "2026-11-03T17:25:22.000000Z"
                }
            ]
        },
        {
            "id": 35,
            "sender_id": 164,
            "recipient_id": 165,
            "device_id": 136,
            "meeting_date": "2026-11-03 17:25:27",
            "meeting_type": "online_meeting",
            "contact_email": "agleason@gmail.com",
            "status": "new",
            "created_at": "2026-11-03T17:25:28.000000Z",
            "updated_at": "2026-11-03T17:25:28.000000Z",
            "sender": {
                "id": 164,
                "mrn": "EQMYV32E1793726727",
                "name": "Mr. Sydney Cummerata PhD",
                "email": "1793726727pattie00@example.org",
                "language": "en",
                "phone": "1-903-643-7949",
                "phone_country": "BT",
                "phone_verified_at": null,
                "address1": "583 Jevon Trafficway Suite 195",
                "address2": "West Freedachester, PA 50378-8699",
                "postal_code": "89048-4470",
                "city": "Bernier LLC",
                "country": "DK",
                "clinic_name": "West Assunta",
                "clinic_location": "912 Stark Path\nAudreannefurt, NV 98344",
                "image": null,
                "public_image": null,
                "mfa_enabled": 0,
                "mfa_method": null,
                "mfa_verified_to": null,
                "location_id": null,
                "created_by": null,
                "active": 1,
                "is_ambassador": 0,
                "test_account": false,
                "notifications_timezone": null,
                "notifications_at": null,
                "created_at": "2026-11-03T17:25:27.000000Z",
                "updated_at": "2026-11-03T17:25:27.000000Z",
                "invitation_status": null,
                "acadle_invitation_status": null,
                "roles": []
            },
            "recipient": {
                "id": 165,
                "mrn": "JU8LW66H1793726727",
                "name": "Mallory Fahey",
                "email": "1793726727alyce99@example.org",
                "language": "en",
                "phone": "+1-216-462-0698",
                "phone_country": "SS",
                "phone_verified_at": null,
                "address1": "9343 Paige Camp Apt. 500",
                "address2": "New Jacquestown, WI 80548-7740",
                "postal_code": "61332-3294",
                "city": "Conroy, Nicolas and Jacobi",
                "country": "NL",
                "clinic_name": "Reichelshire",
                "clinic_location": "58207 Alfonso Courts Apt. 123\nColleenburgh, UT 98656",
                "image": null,
                "public_image": null,
                "mfa_enabled": 0,
                "mfa_method": null,
                "mfa_verified_to": null,
                "location_id": null,
                "created_by": null,
                "active": 1,
                "is_ambassador": 0,
                "test_account": false,
                "notifications_timezone": null,
                "notifications_at": null,
                "created_at": "2026-11-03T17:25:27.000000Z",
                "updated_at": "2026-11-03T17:25:27.000000Z",
                "invitation_status": null,
                "acadle_invitation_status": null,
                "roles": []
            },
            "device": {
                "id": 136,
                "serial": "44989ead-d9fe-325d-acd3-b9eaff99981d",
                "bluetooth_id": "76facc52-dfcd-3e40-b31d-11f89b2918fa",
                "company_id": null,
                "model_id": null,
                "amputee_id": null,
                "clinician_id": null,
                "firmware_version_id": null,
                "pcb_version_id": null,
                "reverse_magnets": 0,
                "is_electrode": 0,
                "active": 1,
                "last_activity_at": "0000-00-00 00:00:00",
                "first_connected_at": null,
                "measurements": null,
                "created_at": "2026-11-03T17:25:28.000000Z",
                "updated_at": "2026-11-03T17:25:28.000000Z",
                "first_config_change_at": null
            },
            "messages": [
                {
                    "id": 19,
                    "ticket_id": 35,
                    "sender_id": 166,
                    "title": "Mrs.",
                    "content": "Aut explicabo et rem nostrum nihil eveniet.",
                    "is_read": false,
                    "created_at": "2026-11-03T17:25:29.000000Z",
                    "updated_at": "2026-11-03T17:25:29.000000Z"
                }
            ]
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to list support tickets",
    "code": "TICKETS:LIST:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/tickets

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

status   string  optional  

Filter tickets by status (available: new, in_progress, closed, reopened. Example: new

sender   integer  optional  

Filter tickets by sender. Example: 1

recipient   integer  optional  

Filter tickets by recipient. Example: 1

device   string  optional  

Filter tickets by devices. Provide single ID (device=1), array of IDs (device[]=1&device[]=2) or comma-separated list of IDs (device=1,2). Pass value 0 to get tickets without device. Note: tickets without a device are also included when filtering by specific device IDs. Example: 1

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

extend   string  optional  

Comma-separated list of relation extensions (available: sender, recipient, device, messages, messages.attachments).

sortby   string  optional  

Sort by field (available: sender_name, recipient_name, date, last_message). Default: last_message, desc.

sortdir   string  optional  

Sort direction (available: asc, desc).

Response

Response Fields

items   object   
id   integer   

Support ticket ID.

sender_id   integer   

ID of the user who created the ticket.

recipient_id   integer   

ID of the recipient user.

device_id   integer   

Associated device ID.

meeting_date   string   

Scheduled meeting date.

meeting_type   string   

Meeting type.

Must be one of:
  • online
  • in-person
contact_email   string   

Contact email address.

status   string   

Ticket status.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

sender   object   

User who created the ticket.

recipient   object   

Recipient user.

device   object   

Associated device.

messages   object[]   

Ticket messages.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

Get tickets status

requires authentication

Counts tickets by their status

Example request:
curl --request GET \
    --get "http://localhost:8000/api/tickets/status" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/tickets/status"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "unread": 1
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to list support tickets",
    "code": "TICKETS:STATUS:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/tickets/status

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Get support ticket

requires authentication

Returns single support ticket in the response.

Example request:
curl --request GET \
    --get "http://localhost:8000/api/ticket/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/ticket/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "id": 43,
    "sender_id": 178,
    "recipient_id": 179,
    "device_id": 137,
    "meeting_date": "2026-11-03 17:25:35",
    "meeting_type": "online_meeting",
    "contact_email": "istoltenberg@hayes.info",
    "status": "new",
    "created_at": "2026-11-03T17:25:35.000000Z",
    "updated_at": "2026-11-03T17:25:35.000000Z",
    "sender": {
        "id": 178,
        "mrn": "LGYUT6ME1793726734",
        "name": "Caesar Cruickshank",
        "email": "1793726734durward81@example.com",
        "language": "en",
        "phone": "1-610-952-3917",
        "phone_country": "RE",
        "phone_verified_at": null,
        "address1": "66669 Zemlak Course",
        "address2": "North Edwardo, KY 94079-7843",
        "postal_code": "14632-3334",
        "city": "Bruen-Klocko",
        "country": "RO",
        "clinic_name": "North Meganeland",
        "clinic_location": "7120 Zemlak Extensions Apt. 269\nSouth Eric, FL 58063",
        "image": null,
        "public_image": null,
        "mfa_enabled": 0,
        "mfa_method": null,
        "mfa_verified_to": null,
        "location_id": null,
        "created_by": null,
        "active": 1,
        "is_ambassador": 0,
        "test_account": false,
        "notifications_timezone": null,
        "notifications_at": null,
        "created_at": "2026-11-03T17:25:34.000000Z",
        "updated_at": "2026-11-03T17:25:34.000000Z",
        "invitation_status": null,
        "acadle_invitation_status": null,
        "roles": []
    },
    "recipient": {
        "id": 179,
        "mrn": "Q2BG68SY1793726735",
        "name": "Greg Nader I",
        "email": "1793726735nquitzon@example.net",
        "language": "en",
        "phone": "+1-757-238-9774",
        "phone_country": "RE",
        "phone_verified_at": null,
        "address1": "129 Dietrich Park",
        "address2": "Melvinburgh, HI 92199-9608",
        "postal_code": "63108",
        "city": "Stanton-Tromp",
        "country": "LV",
        "clinic_name": "North Olen",
        "clinic_location": "6167 Raegan Roads Suite 799\nTerrymouth, GA 44452-6633",
        "image": null,
        "public_image": null,
        "mfa_enabled": 0,
        "mfa_method": null,
        "mfa_verified_to": null,
        "location_id": null,
        "created_by": null,
        "active": 1,
        "is_ambassador": 0,
        "test_account": false,
        "notifications_timezone": null,
        "notifications_at": null,
        "created_at": "2026-11-03T17:25:35.000000Z",
        "updated_at": "2026-11-03T17:25:35.000000Z",
        "invitation_status": null,
        "acadle_invitation_status": null,
        "roles": []
    },
    "device": {
        "id": 137,
        "serial": "4bab9c6d-9c1a-3b1d-b6b6-32cb55dcf57c",
        "bluetooth_id": "77921d93-0c47-3441-9035-04065acd8297",
        "company_id": null,
        "model_id": null,
        "amputee_id": null,
        "clinician_id": null,
        "firmware_version_id": null,
        "pcb_version_id": null,
        "reverse_magnets": 0,
        "is_electrode": 0,
        "active": 1,
        "last_activity_at": "0000-00-00 00:00:00",
        "first_connected_at": null,
        "measurements": null,
        "created_at": "2026-11-03T17:25:35.000000Z",
        "updated_at": "2026-11-03T17:25:35.000000Z",
        "first_config_change_at": null
    },
    "messages": [
        {
            "id": 23,
            "ticket_id": 43,
            "sender_id": 180,
            "title": "Prof.",
            "content": "Alias deleniti excepturi voluptatem.",
            "is_read": false,
            "created_at": "2026-11-03T17:25:37.000000Z",
            "updated_at": "2026-11-03T17:25:37.000000Z"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view support ticket",
    "code": "TICKETS:GET:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Support ticket not found):


{
    "message": "Support ticket not found",
    "code": "TICKETS:GET:TICKET_NOT_FOUND"
}
 

Request   

GET api/ticket/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Support ticket ID. Example: 1

Query Parameters

extend   string  optional  

Comma-separated list of relation extensions (available: sender, recipient, device, messages, messages.attachments, messages.sender).

Response

Response Fields

id   integer   

Support ticket ID.

sender_id   integer   

ID of the user who created the ticket.

recipient_id   integer   

ID of the recipient user.

device_id   integer   

Associated device ID.

meeting_date   string   

Scheduled meeting date.

meeting_type   string   

Meeting type.

Must be one of:
  • online
  • in-person
contact_email   string   

Contact email address.

status   string   

Ticket status.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

sender   object   

User who created the ticket.

recipient   object   

Recipient user.

device   object   

Associated device.

messages   object[]   

Ticket messages.

Get support ticket history

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/ticket/1/history" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/ticket/1/history"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 1,
            "ticket_id": 51,
            "author_id": 193,
            "action": "sapiente",
            "reason": "Libero tempore et necessitatibus.",
            "created_at": "2026-11-03T17:25:42.000000Z",
            "updated_at": "2026-11-03T17:25:42.000000Z"
        },
        {
            "id": 2,
            "ticket_id": 52,
            "author_id": 195,
            "action": "dignissimos",
            "reason": "Commodi pariatur voluptatem quia magni assumenda corrupti voluptate.",
            "created_at": "2026-11-03T17:25:43.000000Z",
            "updated_at": "2026-11-03T17:25:43.000000Z"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view support ticket",
    "code": "TICKETS:HISTORY:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Support ticket not found):


{
    "message": "Support ticket not found",
    "code": "TICKETS:HISTORY:TICKET_NOT_FOUND"
}
 

Request   

GET api/ticket/{id}/history

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Support ticket ID. Example: 1

Query Parameters

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

sortby   string  optional  

Sort by field (available: date). Default: date, desc.

sortdir   string  optional  

Sort direction (available: asc, desc).

Response

Response Fields

items   object   
id   integer   

History entry ID.

ticket_id   integer   

Associated support ticket ID.

author_id   integer   

ID of the user who made this change.

action   string   

Action performed.

reason   string   

Reason for the action.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

author   object   

User who made this change.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

Get support ticket available filters

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/tickets/available-filters" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/tickets/available-filters"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "clinicians": [
        {
            "id": 95,
            "mrn": null,
            "name": "Name",
            "email": "email",
            "region": "us",
            "language": "pl",
            "phone": "+48-555555555",
            "phone_verified_at": null,
            "address1": "Address",
            "address2": "Address 2",
            "postal_code": "",
            "city": "",
            "clinic_name": "Name",
            "clinic_location": "Name",
            "image": "https://aether-dev-bucket.s3.amazonaws.com/users/LDueuv1uG218G7owaiLAaWRkpaGxjB0jEFwzZsT1.png",
            "mfa_enabled": 0,
            "mfa_method": "sms",
            "mfa_verified_to": null,
            "location_id": 2,
            "created_by": 1,
            "active": 1,
            "notifications_timezone": "America/Adak",
            "notifications_at": null,
            "created_at": "2022-07-19T14:43:37.000000Z",
            "updated_at": "2024-09-27T05:52:51.000000Z",
            "invitation_status": "expired",
            "roles": [
                {
                    "id": 2,
                    "name": "Clinician",
                    "guard_name": "web",
                    "created_at": "2022-03-21T17:15:47.000000Z",
                    "updated_at": "2022-03-21T17:15:47.000000Z",
                    "pivot": {
                        "model_id": 95,
                        "role_id": 2,
                        "model_type": "App\\Models\\User"
                    }
                }
            ]
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to create support ticket",
    "code": "TICKETS:AVAILABLE_FILTERS:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/tickets/available-filters

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Create new support ticket

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/tickets" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: multipart/form-data" \
    --header "Accept: application/json" \
    --form "recipient=1"\
    --form "device=1"\
    --form "meeting_type=online_meeting"\
    --form "meeting_date=2026-11-03 17:25:43"\
    --form "contact_email=neal73@hotmail.com"\
    --form "message[content]=Dolores veritatis porro et eveniet aperiam voluptatem iste aut."\
    --form "message[title]=Magnam nulla odio."\
    --form "message[attachments][]=@/tmp/phpHlwQUk" 
const url = new URL(
    "http://localhost:8000/api/tickets"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "multipart/form-data",
    "Accept": "application/json",
};

const body = new FormData();
body.append('recipient', '1');
body.append('device', '1');
body.append('meeting_type', 'online_meeting');
body.append('meeting_date', '2026-11-03 17:25:43');
body.append('contact_email', 'neal73@hotmail.com');
body.append('message[content]', 'Dolores veritatis porro et eveniet aperiam voluptatem iste aut.');
body.append('message[title]', 'Magnam nulla odio.');
body.append('message[attachments][]', document.querySelector('input[name="message[attachments][]"]').files[0]);

fetch(url, {
    method: "POST",
    headers,
    body,
}).then(response => response.json());

Example response (201):


{
    "id": 53,
    "sender_id": 196,
    "recipient_id": 197,
    "device_id": 138,
    "meeting_date": "2026-11-03 17:25:44",
    "meeting_type": "online_meeting",
    "contact_email": "quitzon.eleonore@hotmail.com",
    "status": "new",
    "created_at": "2026-11-03T17:25:45.000000Z",
    "updated_at": "2026-11-03T17:25:45.000000Z",
    "sender": {
        "id": 196,
        "mrn": "FSW5QL4B1793726744",
        "name": "Ms. Karolann Fritsch",
        "email": "1793726744mertz.bradley@example.org",
        "language": "en",
        "phone": "425.674.0186",
        "phone_country": "BJ",
        "phone_verified_at": null,
        "address1": "142 Naomi Corners Apt. 802",
        "address2": "South Kipside, HI 62866",
        "postal_code": "42276",
        "city": "Corkery PLC",
        "country": "SI",
        "clinic_name": "Grimesland",
        "clinic_location": "2543 Casimir Field\nNew Svenside, CO 65380",
        "image": null,
        "public_image": null,
        "mfa_enabled": 0,
        "mfa_method": null,
        "mfa_verified_to": null,
        "location_id": null,
        "created_by": null,
        "active": 1,
        "is_ambassador": 0,
        "test_account": false,
        "notifications_timezone": null,
        "notifications_at": null,
        "created_at": "2026-11-03T17:25:44.000000Z",
        "updated_at": "2026-11-03T17:25:44.000000Z",
        "invitation_status": null,
        "acadle_invitation_status": null,
        "roles": []
    },
    "recipient": {
        "id": 197,
        "mrn": "FX7MXM9W1793726744",
        "name": "Flavie Cartwright",
        "email": "1793726744blake18@example.com",
        "language": "en",
        "phone": "(854) 349-5423",
        "phone_country": "CR",
        "phone_verified_at": null,
        "address1": "758 Luettgen Coves",
        "address2": "Friedatown, WI 97108",
        "postal_code": "25970-9213",
        "city": "Waters, Schinner and Hettinger",
        "country": "MT",
        "clinic_name": "South Gardner",
        "clinic_location": "518 Minnie Street\nConcepcionton, NV 32749",
        "image": null,
        "public_image": null,
        "mfa_enabled": 0,
        "mfa_method": null,
        "mfa_verified_to": null,
        "location_id": null,
        "created_by": null,
        "active": 1,
        "is_ambassador": 0,
        "test_account": false,
        "notifications_timezone": null,
        "notifications_at": null,
        "created_at": "2026-11-03T17:25:44.000000Z",
        "updated_at": "2026-11-03T17:25:44.000000Z",
        "invitation_status": null,
        "acadle_invitation_status": null,
        "roles": []
    },
    "device": {
        "id": 138,
        "serial": "222a1d31-78f9-3af8-af33-1231a82e0330",
        "bluetooth_id": "e509c72b-2250-3a17-ae19-a78ffdaf2fba",
        "company_id": null,
        "model_id": null,
        "amputee_id": null,
        "clinician_id": null,
        "firmware_version_id": null,
        "pcb_version_id": null,
        "reverse_magnets": 0,
        "is_electrode": 0,
        "active": 1,
        "last_activity_at": "0000-00-00 00:00:00",
        "first_connected_at": null,
        "measurements": null,
        "created_at": "2026-11-03T17:25:45.000000Z",
        "updated_at": "2026-11-03T17:25:45.000000Z",
        "first_config_change_at": null
    },
    "messages": [
        {
            "id": 27,
            "ticket_id": 53,
            "sender_id": 198,
            "title": "Ms.",
            "content": "Voluptatum sit consequatur et.",
            "is_read": false,
            "created_at": "2026-11-03T17:25:46.000000Z",
            "updated_at": "2026-11-03T17:25:46.000000Z"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to create support ticket",
    "code": "TICKETS:CREATE:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/tickets

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: multipart/form-data

Accept      

Example: application/json

Body Parameters

recipient   integer   

User the support ticket is assigned to. For Amputee role main clinician will be automatically assigned instead. The id of an existing record in the App\Models\User table. Example: 1

device   integer  optional  

Device the support ticket is assigned to. The id of an existing record in the App\Models\Device table. Example: 1

meeting_type   string   

Type of support meeting. Example: online_meeting

Must be one of:
  • online_meeting
  • phone_call
  • personally
  • none
meeting_date   string   

Date of support meeting. MUST_BE_DATE. Example: 2026-11-03 17:25:43

contact_email   string  optional  

Email address for later contact. MUST_BE_EMAIL. Example: neal73@hotmail.com

message   object  optional  
content   string  optional  

Content of message. Example: Dolores veritatis porro et eveniet aperiam voluptatem iste aut.

title   string  optional  

Message title. Example: Magnam nulla odio.

attachments   file[]  optional  

Must be a file.

Response

Response Fields

id   integer   

Support ticket ID.

sender_id   integer   

ID of the user who created the ticket.

recipient_id   integer   

ID of the recipient user.

device_id   integer   

Associated device ID.

meeting_date   string   

Scheduled meeting date.

meeting_type   string   

Meeting type.

Must be one of:
  • online
  • in-person
contact_email   string   

Contact email address.

status   string   

Ticket status.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

sender   object   

User who created the ticket.

recipient   object   

Recipient user.

device   object   

Associated device.

messages   object[]   

Ticket messages.

Close support ticket

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/ticket/1/close" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/ticket/1/close"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (202):


{
    "id": 61,
    "sender_id": 210,
    "recipient_id": 211,
    "device_id": 139,
    "meeting_date": "2026-11-03 17:25:51",
    "meeting_type": "online_meeting",
    "contact_email": "dedrick.heller@gmail.com",
    "status": "new",
    "created_at": "2026-11-03T17:25:52.000000Z",
    "updated_at": "2026-11-03T17:25:52.000000Z",
    "sender": {
        "id": 210,
        "mrn": "C4DU5PH71793726751",
        "name": "Zaria Ward",
        "email": "1793726751marks.keanu@example.org",
        "language": "en",
        "phone": "339.459.6509",
        "phone_country": "LB",
        "phone_verified_at": null,
        "address1": "86421 Gerhold Street",
        "address2": "Dickibury, SD 36456",
        "postal_code": "77504-3074",
        "city": "Denesik, Wolff and Upton",
        "country": "DK",
        "clinic_name": "Crooksburgh",
        "clinic_location": "45655 Kuphal Inlet\nMarcellastad, NM 38207",
        "image": null,
        "public_image": null,
        "mfa_enabled": 0,
        "mfa_method": null,
        "mfa_verified_to": null,
        "location_id": null,
        "created_by": null,
        "active": 1,
        "is_ambassador": 0,
        "test_account": false,
        "notifications_timezone": null,
        "notifications_at": null,
        "created_at": "2026-11-03T17:25:51.000000Z",
        "updated_at": "2026-11-03T17:25:51.000000Z",
        "invitation_status": null,
        "acadle_invitation_status": null,
        "roles": []
    },
    "recipient": {
        "id": 211,
        "mrn": "UYEC8ZF31793726751",
        "name": "Prof. Corbin Sanford",
        "email": "1793726751karina19@example.net",
        "language": "en",
        "phone": "(574) 830-1957",
        "phone_country": "AS",
        "phone_verified_at": null,
        "address1": "76883 Jast Gardens",
        "address2": "Port Elfrieda, MS 18950-9307",
        "postal_code": "45121",
        "city": "Dibbert-Ritchie",
        "country": "LV",
        "clinic_name": "East Diana",
        "clinic_location": "514 Gilbert Run Suite 643\nSouth Raphaelton, CO 21912",
        "image": null,
        "public_image": null,
        "mfa_enabled": 0,
        "mfa_method": null,
        "mfa_verified_to": null,
        "location_id": null,
        "created_by": null,
        "active": 1,
        "is_ambassador": 0,
        "test_account": false,
        "notifications_timezone": null,
        "notifications_at": null,
        "created_at": "2026-11-03T17:25:51.000000Z",
        "updated_at": "2026-11-03T17:25:51.000000Z",
        "invitation_status": null,
        "acadle_invitation_status": null,
        "roles": []
    },
    "device": {
        "id": 139,
        "serial": "bfd50626-155f-3391-ac84-c78bdc8410b5",
        "bluetooth_id": "05576ec7-e202-3a0c-8745-8fe40b7a9303",
        "company_id": null,
        "model_id": null,
        "amputee_id": null,
        "clinician_id": null,
        "firmware_version_id": null,
        "pcb_version_id": null,
        "reverse_magnets": 0,
        "is_electrode": 0,
        "active": 1,
        "last_activity_at": "0000-00-00 00:00:00",
        "first_connected_at": null,
        "measurements": null,
        "created_at": "2026-11-03T17:25:52.000000Z",
        "updated_at": "2026-11-03T17:25:52.000000Z",
        "first_config_change_at": null
    },
    "messages": []
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to close support ticket",
    "code": "TICKETS:CLOSE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Support ticket not found):


{
    "message": "Support ticket not found",
    "code": "TICKETS:CLOSE:TICKET_NOT_FOUND"
}
 

Request   

POST api/ticket/{id}/close

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Support ticket ID. Example: 1

Response

Response Fields

id   integer   

Support ticket ID.

sender_id   integer   

ID of the user who created the ticket.

recipient_id   integer   

ID of the recipient user.

device_id   integer   

Associated device ID.

meeting_date   string   

Scheduled meeting date.

meeting_type   string   

Meeting type.

Must be one of:
  • online
  • in-person
contact_email   string   

Contact email address.

status   string   

Ticket status.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

sender   object   

User who created the ticket.

recipient   object   

Recipient user.

device   object   

Associated device.

messages   object[]   

Ticket messages.

Reopen support ticket

requires authentication

Patient (Amputee) role can reopen only non-config tickets. For config tickets patients will get "Insufficient permission" response. For patients role reason field is required.

Example request:
curl --request POST \
    "http://localhost:8000/api/ticket/1/reopen" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"reason\": \"Something is still not working\"
}"
const url = new URL(
    "http://localhost:8000/api/ticket/1/reopen"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "reason": "Something is still not working"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (202):


{
    "id": 3,
    "ticket_id": 62,
    "author_id": 213,
    "action": "esse",
    "reason": "Impedit illum aut velit minima est cum consectetur.",
    "created_at": "2026-11-03T17:25:53.000000Z",
    "updated_at": "2026-11-03T17:25:53.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to reopen support ticket",
    "code": "TICKETS:REOPEN:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Support ticket not found):


{
    "message": "Support ticket not found",
    "code": "TICKETS:REOPEN:TICKET_NOT_FOUND"
}
 

Request   

POST api/ticket/{id}/reopen

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Support ticket ID. Example: 1

Body Parameters

reason   string  optional  

Reason for reopen action. Example: Something is still not working

Response

Response Fields

id   integer   

History entry ID.

ticket_id   integer   

Associated support ticket ID.

author_id   integer   

ID of the user who made this change.

action   string   

Action performed.

reason   string   

Reason for the action.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

author   object   

User who made this change.

Create new support ticket message

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/ticket/1/messages" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: multipart/form-data" \
    --header "Accept: application/json" \
    --form "title=In consectetur molestias vitae."\
    --form "content=Eligendi ut aliquid quia aut aspernatur distinctio velit."\
    --form "attachments[]=@/tmp/phpKiW2Ce" 
const url = new URL(
    "http://localhost:8000/api/ticket/1/messages"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "multipart/form-data",
    "Accept": "application/json",
};

const body = new FormData();
body.append('title', 'In consectetur molestias vitae.');
body.append('content', 'Eligendi ut aliquid quia aut aspernatur distinctio velit.');
body.append('attachments[]', document.querySelector('input[name="attachments[]"]').files[0]);

fetch(url, {
    method: "POST",
    headers,
    body,
}).then(response => response.json());

Example response (201):


{
    "id": 63,
    "sender_id": 214,
    "recipient_id": 215,
    "device_id": 140,
    "meeting_date": "2026-11-03 17:25:53",
    "meeting_type": "online_meeting",
    "contact_email": "harber.reilly@gmail.com",
    "status": "new",
    "created_at": "2026-11-03T17:25:54.000000Z",
    "updated_at": "2026-11-03T17:25:54.000000Z",
    "sender": {
        "id": 214,
        "mrn": "UYVLVE3H1793726753",
        "name": "Emerald Littel",
        "email": "1793726753mcclure.dee@example.net",
        "language": "en",
        "phone": "386.440.2226",
        "phone_country": "MF",
        "phone_verified_at": null,
        "address1": "240 Kulas Green Suite 118",
        "address2": "Alishaville, IA 48646-9079",
        "postal_code": "94176",
        "city": "Marvin Group",
        "country": "IT",
        "clinic_name": "Spencerhaven",
        "clinic_location": "22942 Ashtyn Grove\nLake Ava, NV 40421-6462",
        "image": null,
        "public_image": null,
        "mfa_enabled": 0,
        "mfa_method": null,
        "mfa_verified_to": null,
        "location_id": null,
        "created_by": null,
        "active": 1,
        "is_ambassador": 0,
        "test_account": false,
        "notifications_timezone": null,
        "notifications_at": null,
        "created_at": "2026-11-03T17:25:53.000000Z",
        "updated_at": "2026-11-03T17:25:53.000000Z",
        "invitation_status": null,
        "acadle_invitation_status": null,
        "roles": []
    },
    "recipient": {
        "id": 215,
        "mrn": "XSJE87C81793726753",
        "name": "Prof. Julia Wunsch",
        "email": "1793726753wleannon@example.com",
        "language": "en",
        "phone": "+1-769-333-5036",
        "phone_country": "TW",
        "phone_verified_at": null,
        "address1": "99877 Reichel Ports Apt. 770",
        "address2": "Otiliafurt, WA 53121",
        "postal_code": "66729",
        "city": "Morissette, Smith and Morar",
        "country": "IT",
        "clinic_name": "Port Tessshire",
        "clinic_location": "4724 Kaitlin River Suite 659\nGracemouth, GA 70398",
        "image": null,
        "public_image": null,
        "mfa_enabled": 0,
        "mfa_method": null,
        "mfa_verified_to": null,
        "location_id": null,
        "created_by": null,
        "active": 1,
        "is_ambassador": 0,
        "test_account": false,
        "notifications_timezone": null,
        "notifications_at": null,
        "created_at": "2026-11-03T17:25:53.000000Z",
        "updated_at": "2026-11-03T17:25:53.000000Z",
        "invitation_status": null,
        "acadle_invitation_status": null,
        "roles": []
    },
    "device": {
        "id": 140,
        "serial": "057e4c75-610c-3e81-a9fa-5d4f88f88412",
        "bluetooth_id": "ad28c779-96a6-3c2c-a404-370b0cdd069e",
        "company_id": null,
        "model_id": null,
        "amputee_id": null,
        "clinician_id": null,
        "firmware_version_id": null,
        "pcb_version_id": null,
        "reverse_magnets": 0,
        "is_electrode": 0,
        "active": 1,
        "last_activity_at": "0000-00-00 00:00:00",
        "first_connected_at": null,
        "measurements": null,
        "created_at": "2026-11-03T17:25:54.000000Z",
        "updated_at": "2026-11-03T17:25:54.000000Z",
        "first_config_change_at": null
    },
    "messages": [
        {
            "id": 31,
            "ticket_id": 63,
            "sender_id": 216,
            "title": "Mr.",
            "content": "Dolorum quia aut ipsum sed veniam aut occaecati.",
            "is_read": false,
            "created_at": "2026-11-03T17:25:55.000000Z",
            "updated_at": "2026-11-03T17:25:55.000000Z"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to create support ticket",
    "code": "TICKETS:CREATE_MESSAGE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Support ticket not found):


{
    "message": "Support ticket not found",
    "code": "TICKETS:CREATE_MESSAGE:TICKET_NOT_FOUND"
}
 

Request   

POST api/ticket/{id}/messages

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: multipart/form-data

Accept      

Example: application/json

URL Parameters

id   integer   

Support ticket ID. Example: 1

Body Parameters

title   string  optional  

Message title. Example: In consectetur molestias vitae.

content   string  optional  

Content of message. Example: Eligendi ut aliquid quia aut aspernatur distinctio velit.

attachments   file[]  optional  

Must be a file.

Response

Response Fields

id   integer   

Support ticket ID.

sender_id   integer   

ID of the user who created the ticket.

recipient_id   integer   

ID of the recipient user.

device_id   integer   

Associated device ID.

meeting_date   string   

Scheduled meeting date.

meeting_type   string   

Meeting type.

Must be one of:
  • online
  • in-person
contact_email   string   

Contact email address.

status   string   

Ticket status.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

sender   object   

User who created the ticket.

recipient   object   

Recipient user.

device   object   

Associated device.

messages   object[]   

Ticket messages.

Mark all messages as read

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/ticket/1/read" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/ticket/1/read"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (202):


{
    "id": 71,
    "sender_id": 228,
    "recipient_id": 229,
    "device_id": 141,
    "meeting_date": "2026-11-03 17:26:00",
    "meeting_type": "online_meeting",
    "contact_email": "dayana08@hotmail.com",
    "status": "new",
    "created_at": "2026-11-03T17:26:01.000000Z",
    "updated_at": "2026-11-03T17:26:01.000000Z",
    "sender": {
        "id": 228,
        "mrn": "SCNE5FAA1793726760",
        "name": "Dejuan Kerluke",
        "email": "1793726760jennifer.kub@example.net",
        "language": "en",
        "phone": "(562) 395-1659",
        "phone_country": "FR",
        "phone_verified_at": null,
        "address1": "83303 Lebsack Islands Apt. 375",
        "address2": "Davismouth, OR 84277-5611",
        "postal_code": "74136-5941",
        "city": "Murphy, Witting and Walter",
        "country": "PL",
        "clinic_name": "Ferryfurt",
        "clinic_location": "3876 Welch Via Suite 275\nEast Bethanyfort, WV 49307-4651",
        "image": null,
        "public_image": null,
        "mfa_enabled": 0,
        "mfa_method": null,
        "mfa_verified_to": null,
        "location_id": null,
        "created_by": null,
        "active": 1,
        "is_ambassador": 0,
        "test_account": false,
        "notifications_timezone": null,
        "notifications_at": null,
        "created_at": "2026-11-03T17:26:00.000000Z",
        "updated_at": "2026-11-03T17:26:00.000000Z",
        "invitation_status": null,
        "acadle_invitation_status": null,
        "roles": []
    },
    "recipient": {
        "id": 229,
        "mrn": "72W6LTVB1793726760",
        "name": "August Heller",
        "email": "1793726760zmonahan@example.org",
        "language": "en",
        "phone": "(409) 945-7131",
        "phone_country": "SB",
        "phone_verified_at": null,
        "address1": "87182 Haag Ramp Suite 095",
        "address2": "Beahanshire, NH 99635-1581",
        "postal_code": "64058",
        "city": "Runte Inc",
        "country": "IN",
        "clinic_name": "Lake Meaghan",
        "clinic_location": "82657 Rutherford Station\nSouth Sylvester, IN 86159-5055",
        "image": null,
        "public_image": null,
        "mfa_enabled": 0,
        "mfa_method": null,
        "mfa_verified_to": null,
        "location_id": null,
        "created_by": null,
        "active": 1,
        "is_ambassador": 0,
        "test_account": false,
        "notifications_timezone": null,
        "notifications_at": null,
        "created_at": "2026-11-03T17:26:00.000000Z",
        "updated_at": "2026-11-03T17:26:00.000000Z",
        "invitation_status": null,
        "acadle_invitation_status": null,
        "roles": []
    },
    "device": {
        "id": 141,
        "serial": "d753afa2-e564-33d3-a4d7-ee23418d20ee",
        "bluetooth_id": "965a09a2-083a-3455-a813-03fc926313be",
        "company_id": null,
        "model_id": null,
        "amputee_id": null,
        "clinician_id": null,
        "firmware_version_id": null,
        "pcb_version_id": null,
        "reverse_magnets": 0,
        "is_electrode": 0,
        "active": 1,
        "last_activity_at": "0000-00-00 00:00:00",
        "first_connected_at": null,
        "measurements": null,
        "created_at": "2026-11-03T17:26:01.000000Z",
        "updated_at": "2026-11-03T17:26:01.000000Z",
        "first_config_change_at": null
    },
    "messages": [
        {
            "id": 35,
            "ticket_id": 71,
            "sender_id": 230,
            "title": "Mrs.",
            "content": "Corporis explicabo possimus nisi autem rem in praesentium.",
            "is_read": false,
            "created_at": "2026-11-03T17:26:02.000000Z",
            "updated_at": "2026-11-03T17:26:02.000000Z"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to read message",
    "code": "TICKETS:READ_ALL:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Support ticket not found):


{
    "message": "Support ticket not found",
    "code": "TICKETS:READ_ALL:TICKET_NOT_FOUND"
}
 

Request   

POST api/ticket/{id}/read

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Support ticket ID (id from ticket list; do not confuse with messages.id). Example: 1

Response

Response Fields

id   integer   

Support ticket ID.

sender_id   integer   

ID of the user who created the ticket.

recipient_id   integer   

ID of the recipient user.

device_id   integer   

Associated device ID.

meeting_date   string   

Scheduled meeting date.

meeting_type   string   

Meeting type.

Must be one of:
  • online
  • in-person
contact_email   string   

Contact email address.

status   string   

Ticket status.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

sender   object   

User who created the ticket.

recipient   object   

Recipient user.

device   object   

Associated device.

messages   object[]   

Ticket messages.

Mark single message as read

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/ticket/1/read/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/ticket/1/read/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (202):


{
    "id": 79,
    "sender_id": 242,
    "recipient_id": 243,
    "device_id": 142,
    "meeting_date": "2026-11-03 17:26:07",
    "meeting_type": "online_meeting",
    "contact_email": "alexis.boehm@yahoo.com",
    "status": "new",
    "created_at": "2026-11-03T17:26:08.000000Z",
    "updated_at": "2026-11-03T17:26:08.000000Z",
    "sender": {
        "id": 242,
        "mrn": "SJMQTT2M1793726767",
        "name": "Armand Franecki V",
        "email": "1793726767heller.sadye@example.com",
        "language": "en",
        "phone": "+12838025986",
        "phone_country": "BF",
        "phone_verified_at": null,
        "address1": "44687 Reynold Locks",
        "address2": "Hagenesmouth, FL 88600",
        "postal_code": "76351-3212",
        "city": "Kerluke LLC",
        "country": "NO",
        "clinic_name": "Cummingsview",
        "clinic_location": "37436 Lubowitz Garden\nWest Medafort, VA 09581-7742",
        "image": null,
        "public_image": null,
        "mfa_enabled": 0,
        "mfa_method": null,
        "mfa_verified_to": null,
        "location_id": null,
        "created_by": null,
        "active": 1,
        "is_ambassador": 0,
        "test_account": false,
        "notifications_timezone": null,
        "notifications_at": null,
        "created_at": "2026-11-03T17:26:07.000000Z",
        "updated_at": "2026-11-03T17:26:07.000000Z",
        "invitation_status": null,
        "acadle_invitation_status": null,
        "roles": []
    },
    "recipient": {
        "id": 243,
        "mrn": "28XJCBHL1793726767",
        "name": "Dr. Ken Paucek",
        "email": "1793726767johnson.braun@example.org",
        "language": "en",
        "phone": "1-334-279-2314",
        "phone_country": "RS",
        "phone_verified_at": null,
        "address1": "7197 Morissette Field",
        "address2": "Yundtberg, HI 32196",
        "postal_code": "56048",
        "city": "Volkman LLC",
        "country": "MT",
        "clinic_name": "East Keenan",
        "clinic_location": "1323 Treva Roads\nLake Kristoferfort, NV 49009-2628",
        "image": null,
        "public_image": null,
        "mfa_enabled": 0,
        "mfa_method": null,
        "mfa_verified_to": null,
        "location_id": null,
        "created_by": null,
        "active": 1,
        "is_ambassador": 0,
        "test_account": false,
        "notifications_timezone": null,
        "notifications_at": null,
        "created_at": "2026-11-03T17:26:07.000000Z",
        "updated_at": "2026-11-03T17:26:07.000000Z",
        "invitation_status": null,
        "acadle_invitation_status": null,
        "roles": []
    },
    "device": {
        "id": 142,
        "serial": "17d7d787-abfb-3caa-96bc-514abaf1bdd0",
        "bluetooth_id": "38e659dd-1c8e-3e99-8c06-64fb8e599ef2",
        "company_id": null,
        "model_id": null,
        "amputee_id": null,
        "clinician_id": null,
        "firmware_version_id": null,
        "pcb_version_id": null,
        "reverse_magnets": 0,
        "is_electrode": 0,
        "active": 1,
        "last_activity_at": "0000-00-00 00:00:00",
        "first_connected_at": null,
        "measurements": null,
        "created_at": "2026-11-03T17:26:08.000000Z",
        "updated_at": "2026-11-03T17:26:08.000000Z",
        "first_config_change_at": null
    },
    "messages": [
        {
            "id": 39,
            "ticket_id": 79,
            "sender_id": 244,
            "title": "Mrs.",
            "content": "Distinctio sunt et nobis aut magnam consequuntur.",
            "is_read": false,
            "created_at": "2026-11-03T17:26:09.000000Z",
            "updated_at": "2026-11-03T17:26:09.000000Z"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to read message",
    "code": "TICKETS:READ_MESSAGE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Message not found):


{
    "message": "Message not found",
    "code": "TICKETS:READ_MESSAGE:MESSAGE_NOT_FOUND"
}
 

Request   

POST api/ticket/{id}/read/{messageId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Support ticket ID (id from ticket list; do not confuse with messages.id). Example: 1

messageId   integer   

Message ID. Example: 1

Response

Response Fields

id   integer   

Support ticket ID.

sender_id   integer   

ID of the user who created the ticket.

recipient_id   integer   

ID of the recipient user.

device_id   integer   

Associated device ID.

meeting_date   string   

Scheduled meeting date.

meeting_type   string   

Meeting type.

Must be one of:
  • online
  • in-person
contact_email   string   

Contact email address.

status   string   

Ticket status.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

sender   object   

User who created the ticket.

recipient   object   

Recipient user.

device   object   

Associated device.

messages   object[]   

Ticket messages.

Tooltips

API endpoints for managing tooltips

List tooltips

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/tooltips" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/tooltips"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 1,
            "name": "Mitchell Erdman",
            "type": "video",
            "language": "de",
            "file": "/tmp/d62e764f22848819506cc1c1ce4fbf62.png",
            "created_by": 306,
            "created_at": "2026-11-03T17:26:48.000000Z",
            "updated_at": "2026-11-03T17:26:48.000000Z",
            "deleted_at": null
        },
        {
            "id": 2,
            "name": "Mrs. Cordia Kuphal V",
            "type": "image",
            "language": "ha",
            "file": "/tmp/e948421bb8021fa840510186a16ea8a8.png",
            "created_by": 307,
            "created_at": "2026-11-03T17:26:49.000000Z",
            "updated_at": "2026-11-03T17:26:49.000000Z",
            "deleted_at": null
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to list tooltips",
    "code": "TOOLTIPS:LIST:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/tooltips

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

Response

Response Fields

items   object   
id   integer   

Tooltip ID.

name   string   

Tooltip identifier name.

type   string   

Media type.

Must be one of:
  • image
  • video
language   string   

Language code (ISO 639-1).

file   string   

File path or URL.

created_by   integer   

ID of the user who created the tooltip.

deleted_at   string   

Soft delete timestamp.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

List archived tooltips

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/tooltips/archive" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/tooltips/archive"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 3,
            "name": "Prof. Ellen Walker III",
            "type": "video",
            "language": "id",
            "file": "/tmp/7ea8bdd330e4af71d5eec9c7f9697e6a.png",
            "created_by": 308,
            "created_at": "2026-11-03T17:26:49.000000Z",
            "updated_at": "2026-11-03T17:26:49.000000Z",
            "deleted_at": null
        },
        {
            "id": 4,
            "name": "Vernie Windler",
            "type": "video",
            "language": "dv",
            "file": "/tmp/486eaa7db03dcba5e5e156d497506889.png",
            "created_by": 309,
            "created_at": "2026-11-03T17:26:50.000000Z",
            "updated_at": "2026-11-03T17:26:50.000000Z",
            "deleted_at": null
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage tooltips",
    "code": "TOOLTIPS:LIST_ARCHIVE:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/tooltips/archive

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

Response

Response Fields

items   object   
id   integer   

Tooltip ID.

name   string   

Tooltip identifier name.

type   string   

Media type.

Must be one of:
  • image
  • video
language   string   

Language code (ISO 639-1).

file   string   

File path or URL.

created_by   integer   

ID of the user who created the tooltip.

deleted_at   string   

Soft delete timestamp.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

Create new tooltip

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/tooltips" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: multipart/form-data" \
    --header "Accept: application/json" \
    --form "name=Wilbert Street"\
    --form "type=image"\
    --form "language=pl"\
    --form "file=@/tmp/phpkOUtXa" 
const url = new URL(
    "http://localhost:8000/api/tooltips"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "multipart/form-data",
    "Accept": "application/json",
};

const body = new FormData();
body.append('name', 'Wilbert Street');
body.append('type', 'image');
body.append('language', 'pl');
body.append('file', document.querySelector('input[name="file"]').files[0]);

fetch(url, {
    method: "POST",
    headers,
    body,
}).then(response => response.json());

Example response (201):


{
    "id": 5,
    "name": "Dr. Van Heller",
    "type": "video",
    "language": "ak",
    "file": "/tmp/3402f3f1726b1d679511a787c961b4ce.png",
    "created_by": 310,
    "created_at": "2026-11-03T17:26:51.000000Z",
    "updated_at": "2026-11-03T17:26:51.000000Z",
    "deleted_at": null
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage tooltips",
    "code": "TOOLTIPS:CREATE:INSUFFICIENT_PERMISSION"
}
 

Example response (500, Server error):


{
    "message": "Server error: tooltip not created",
    "code": "TOOLTIPS:CREATE:SERVER_ERROR"
}
 

Request   

POST api/tooltips

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: multipart/form-data

Accept      

Example: application/json

Body Parameters

name   string   

Tooltip name. Example: Wilbert Street

type   string   

Tooltip content type. Example: image

Must be one of:
  • image
  • video
language   string   

Tooltip content language. Example: pl

file   file   

Tooltip content file. Must have one of the following MIME types: webp, png, jpg, jpeg, mp4. Must be a file. MAXIMUM:FILE_KB:102400. Example: /tmp/phpkOUtXa

Response

Response Fields

id   integer   

Tooltip ID.

name   string   

Tooltip identifier name.

type   string   

Media type.

Must be one of:
  • image
  • video
language   string   

Language code (ISO 639-1).

file   string   

File path or URL.

created_by   integer   

ID of the user who created the tooltip.

deleted_at   string   

Soft delete timestamp.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Archive tooltip

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/tooltips/1/archive" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/tooltips/1/archive"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (202, OK):


{
    "message": "Tooltip archived",
    "code": "TOOLTIPS:ARCHIVE:ARCHIVED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage tooltips",
    "code": "TOOLTIPS:ARCHIVE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Tooltip not found):


{
    "message": "Tooltip not found",
    "code": "TOOLTIPS:ARCHIVE:TOOLTIP_NOT_FOUND"
}
 

Request   

POST api/tooltips/{id}/archive

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Tooltip ID. Example: 1

Restore archived tooltip

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/tooltips/1/restore" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/tooltips/1/restore"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (202, OK):


{
    "message": "Tooltip restored",
    "code": "TOOLTIPS:RESTORE:RESTORED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage tooltips",
    "code": "TOOLTIPS:RESTORE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Tooltip not found):


{
    "message": "Tooltip not found",
    "code": "TOOLTIPS:RESTORE:TOOLTIP_NOT_FOUND"
}
 

Example response (404, Tooltip is not archived):


{
    "message": "Tooltip is not archived",
    "code": "TOOLTIPS:RESTORE:TOOLTIP_NOT_ARCHIVED"
}
 

Request   

POST api/tooltips/{id}/restore

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Tooltip ID. Example: 1

Trainings

API endpoints for trainings

Get user trainings

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/trainings" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/trainings"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


[
    {
        "id": 1,
        "user_id": 314,
        "training_id": 2,
        "notifications_enabled": 1,
        "created_at": "2026-11-03T17:26:53.000000Z",
        "updated_at": "2026-11-03T17:26:53.000000Z",
        "training": {
            "id": 2,
            "name": "assumenda cupiditate",
            "created_at": "2026-11-03T17:26:53.000000Z",
            "updated_at": "2026-11-03T17:26:53.000000Z"
        }
    },
    {
        "id": 2,
        "user_id": 315,
        "training_id": 4,
        "notifications_enabled": 1,
        "created_at": "2026-11-03T17:26:54.000000Z",
        "updated_at": "2026-11-03T17:26:54.000000Z",
        "training": {
            "id": 4,
            "name": "vitae eum",
            "created_at": "2026-11-03T17:26:54.000000Z",
            "updated_at": "2026-11-03T17:26:54.000000Z"
        }
    }
]
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage user trainings",
    "code": "TRAININGS:LIST:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/trainings

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Response

Response Fields

id   integer   

User training ID.

user_id   integer   

Associated user ID.

training_id   integer   

Associated training ID.

notifications_enabled   boolean   

Whether notifications are enabled for this training.

streak   integer   

Current training streak (days).

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

training   object   

Training details.

Get trainings for specific user (clinician access)

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/trainings/user/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/trainings/user/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


[
    {
        "id": 3,
        "user_id": 316,
        "training_id": 6,
        "notifications_enabled": 1,
        "created_at": "2026-11-03T17:26:54.000000Z",
        "updated_at": "2026-11-03T17:26:54.000000Z",
        "training": {
            "id": 6,
            "name": "iure quasi",
            "created_at": "2026-11-03T17:26:54.000000Z",
            "updated_at": "2026-11-03T17:26:54.000000Z"
        }
    },
    {
        "id": 4,
        "user_id": 317,
        "training_id": 8,
        "notifications_enabled": 1,
        "created_at": "2026-11-03T17:26:55.000000Z",
        "updated_at": "2026-11-03T17:26:55.000000Z",
        "training": {
            "id": 8,
            "name": "exercitationem esse",
            "created_at": "2026-11-03T17:26:55.000000Z",
            "updated_at": "2026-11-03T17:26:55.000000Z"
        }
    }
]
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to access patient training",
    "code": "TRAININGS:LIST:INSUFFICIENT_PERMISSION"
}
 

Example response (404, User not found):


{
    "message": "User not found",
    "code": "TRAININGS:LIST:USER_NOT_FOUND"
}
 

Request   

GET api/trainings/user/{userId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

userId   integer   

User ID Example: 1

Response

Response Fields

id   integer   

User training ID.

user_id   integer   

Associated user ID.

training_id   integer   

Associated training ID.

notifications_enabled   boolean   

Whether notifications are enabled for this training.

streak   integer   

Current training streak (days).

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

training   object   

Training details.

Get user badges

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/trainings/1/user-badges" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/trainings/1/user-badges"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


[
    {
        "id": 1,
        "user_id": 318,
        "badge_id": 1,
        "created_at": "2026-11-03T17:26:55.000000Z",
        "updated_at": "2026-11-03T17:26:55.000000Z"
    },
    {
        "id": 2,
        "user_id": 319,
        "badge_id": 2,
        "created_at": "2026-11-03T17:26:56.000000Z",
        "updated_at": "2026-11-03T17:26:56.000000Z"
    }
]
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage user trainings",
    "code": "TRAININGS:BADGES:INSUFFICIENT_PERMISSION"
}
 

Example response (403, User has no training started):


{
    "message": "User has no training started",
    "code": "TRAININGS:BADGES:NO_USER_TRAINING"
}
 

Example response (404, Training not found):


{
    "message": "Training not found",
    "code": "TRAININGS:BADGES:TRAINING_NOT_FOUND"
}
 

Request   

GET api/trainings/{trainingId}/user-badges

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

trainingId   integer   

Training ID. Example: 1

Response

Response Fields

id   integer   

User badge record ID.

user_id   integer   

Associated user ID.

badge_id   integer   

Associated badge ID.

created_at   string   

Awarded timestamp.

updated_at   string   

Last update timestamp.

Start user training

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/trainings/start/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/trainings/start/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (201):


{
    "id": 5,
    "user_id": 320,
    "training_id": 9,
    "notifications_enabled": 1,
    "created_at": "2026-11-03T17:26:56.000000Z",
    "updated_at": "2026-11-03T17:26:56.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage user trainings",
    "code": "TRAININGS:START:INSUFFICIENT_PERMISSION"
}
 

Example response (403, User training already started):


{
    "message": "Cannot start: training already started",
    "code": "TRAININGS:START:ALREADY_STARTED"
}
 

Example response (404, Training not found):


{
    "message": "Training not found",
    "code": "TRAININGS:START:TRAINING_NOT_FOUND"
}
 

Request   

POST api/trainings/start/{trainingId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

trainingId   integer   

Training ID. Example: 1

Response

Response Fields

id   integer   

User training ID.

user_id   integer   

Associated user ID.

training_id   integer   

Associated training ID.

notifications_enabled   boolean   

Whether notifications are enabled for this training.

streak   integer   

Current training streak (days).

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

training   object   

Training details.

Update training

requires authentication

Example request:
curl --request PUT \
    "http://localhost:8000/api/trainings/update/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"notifications_enabled\": false
}"
const url = new URL(
    "http://localhost:8000/api/trainings/update/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "notifications_enabled": false
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "id": 6,
    "user_id": 321,
    "training_id": 10,
    "notifications_enabled": 1,
    "created_at": "2026-11-03T17:26:57.000000Z",
    "updated_at": "2026-11-03T17:26:57.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage user trainings",
    "code": "TRAININGS:UPDATE:INSUFFICIENT_PERMISSION"
}
 

Example response (403, User has no training started):


{
    "message": "User has no training started",
    "code": "TRAININGS:UPDATE:NO_USER_TRAINING"
}
 

Example response (404, Training not found):


{
    "message": "Training not found",
    "code": "TRAININGS:UPDATE:TRAINING_NOT_FOUND"
}
 

Request   

PUT api/trainings/update/{trainingId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

trainingId   integer   

Training ID. Example: 1

Body Parameters

notifications_enabled   boolean  optional  

Notifications (reminders) status. Example: false

Response

Response Fields

id   integer   

User training ID.

user_id   integer   

Associated user ID.

training_id   integer   

Associated training ID.

notifications_enabled   boolean   

Whether notifications are enabled for this training.

streak   integer   

Current training streak (days).

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

training   object   

Training details.

Get training progress

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/trainings/progress/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/trainings/progress/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "id": 7,
    "user_id": 322,
    "training_id": 11,
    "notifications_enabled": 1,
    "created_at": "2026-11-03T17:26:57.000000Z",
    "updated_at": "2026-11-03T17:26:57.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage user trainings",
    "code": "TRAININGS:PROGRESS:INSUFFICIENT_PERMISSION"
}
 

Example response (403, User has no training started):


{
    "message": "User has no training started",
    "code": "TRAININGS:PROGRESS:NO_USER_TRAINING"
}
 

Example response (404, Training not found):


{
    "message": "Training not found",
    "code": "TRAININGS:PROGRESS:TRAINING_NOT_FOUND"
}
 

Request   

GET api/trainings/progress/{trainingId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

trainingId   integer   

Training ID. Example: 1

Response

Response Fields

id   integer   

User training ID.

user_id   integer   

Associated user ID.

training_id   integer   

Associated training ID.

notifications_enabled   boolean   

Whether notifications are enabled for this training.

streak   integer   

Current training streak (days).

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

training   object   

Training details.

Mark training task done

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/trainings/1/day/2/task/3" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/trainings/1/day/2/task/3"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200):


{
    "id": 8,
    "user_id": 323,
    "training_id": 12,
    "notifications_enabled": 1,
    "created_at": "2026-11-03T17:26:58.000000Z",
    "updated_at": "2026-11-03T17:26:58.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage user trainings",
    "code": "TRAININGS:MARK_DONE:INSUFFICIENT_PERMISSION"
}
 

Example response (403, User has no training started):


{
    "message": "User has no training started",
    "code": "TRAININGS:MARK_DONE:NO_USER_TRAINING"
}
 

Example response (404, Training not found):


{
    "message": "Training not found",
    "code": "TRAININGS:MARK_DONE:TRAINING_NOT_FOUND"
}
 

Example response (404, Training day not found):


{
    "message": "Training day not found",
    "code": "TRAININGS:MARK_DONE:TRAINING_DAY_NOT_FOUND"
}
 

Example response (404, Training task not found):


{
    "message": "Training task not found",
    "code": "TRAININGS:MARK_DONE:TRAINING_TASK_NOT_FOUND"
}
 

Request   

POST api/trainings/{trainingId}/day/{trainingDayId}/task/{trainingTaskId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

trainingId   integer   

Training ID. Example: 1

trainingDayId   integer   

Training Day ID. Example: 2

trainingTaskId   integer   

Training Task ID. Example: 3

Response

Response Fields

id   integer   

User training ID.

user_id   integer   

Associated user ID.

training_id   integer   

Associated training ID.

notifications_enabled   boolean   

Whether notifications are enabled for this training.

streak   integer   

Current training streak (days).

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

training   object   

Training details.

Save training exercises attempts

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/trainings/1/day/2/task/3/attempts" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: multipart/form-data" \
    --header "Accept: application/json" \
    --form "exercises[][date_start]=1976-04-21 12:41:47"\
    --form "exercises[][date_end]=2026-10-22 22:18:23"\
    --form "exercises[][end_reason]=fail"\
    --form "exercises[][config][common][fingerStrength][]=1"\
    --form "exercises[][config][common][gripPositions][_]=0"\
    --form "exercises[][config][common][gripPositions][0][initial][]=37"\
    --form "exercises[][config][common][gripPositions][0][limit][]=87"\
    --form "exercises[][config][common][gripPositions][1][initial][]=33"\
    --form "exercises[][config][common][gripPositions][1][limit][]=78"\
    --form "exercises[][config][common][gripPositions][2][initial][]=25"\
    --form "exercises[][config][common][gripPositions][2][limit][]=29"\
    --form "exercises[][config][common][gripPositions][3][initial][]=37"\
    --form "exercises[][config][common][gripPositions][3][limit][]=69"\
    --form "exercises[][config][common][gripPositions][4][initial][]=30"\
    --form "exercises[][config][common][gripPositions][4][limit][]=48"\
    --form "exercises[][config][common][gripPositions][5][initial][]=45"\
    --form "exercises[][config][common][gripPositions][5][limit][]=55"\
    --form "exercises[][config][common][gripPositions][6][initial][]=43"\
    --form "exercises[][config][common][gripPositions][6][limit][]=56"\
    --form "exercises[][config][common][gripPositions][7][initial][]=1"\
    --form "exercises[][config][common][gripPositions][7][limit][]=61"\
    --form "exercises[][config][common][gripPositions][8][initial][]=30"\
    --form "exercises[][config][common][gripPositions][8][limit][]=57"\
    --form "exercises[][config][common][gripPositions][9][initial][]=39"\
    --form "exercises[][config][common][gripPositions][9][limit][]=89"\
    --form "exercises[][config][common][gripPositions][10][initial][]=2"\
    --form "exercises[][config][common][gripPositions][10][limit][]=31"\
    --form "exercises[][config][common][gripPositions][11][initial][]=9"\
    --form "exercises[][config][common][gripPositions][11][limit][]=56"\
    --form "exercises[][config][common][gripPositions][12][initial][]=56"\
    --form "exercises[][config][common][gripPositions][12][limit][]=89"\
    --form "exercises[][config][common][gripPositions][13][initial][]=31"\
    --form "exercises[][config][common][gripPositions][13][limit][]=78"\
    --form "exercises[][config][common][inputSite][]=1"\
    --form "exercises[][config][modes][][id]=83"\
    --form "exercises[][config][modes][][name]=Delectus qui deleniti voluptatem nesciunt quasi laboriosam eum."\
    --form "exercises[][config][modes][][slot]=0"\
    --form "exercises[][config][modes][][config][autoGrasp][]=1"\
    --form "exercises[][config][modes][][config][coContractionTimings][]=500"\
    --form "exercises[][config][modes][][config][controlMode][]=1"\
    --form "exercises[][config][modes][][config][emgGains][]=100"\
    --form "exercises[][config][modes][][config][emgSpike][]=1"\
    --form "exercises[][config][modes][][config][emgThresholds][]=30"\
    --form "exercises[][config][modes][][config][gripPairsConfig][]=9"\
    --form "exercises[][config][modes][][config][gripSequentialConfig][]=2"\
    --form "exercises[][config][modes][][config][gripSwitchingMode][]=2"\
    --form "exercises[][config][modes][][config][holdOpen][]=2000"\
    --form "exercises[][config][modes][][config][pulseTimings][]=840"\
    --form "exercises[][config][modes][][config][softGrip][]=1"\
    --form "exercises[][config][modes][][config][speedControlStrategy][]=1"\
    --form "exercises[][firmware_id]=8999761"\
    --form "exercises[][app_version]=0.25.16"\
    --form "exercises[][attempts][][date_start]=1976-04-05 21:48:32"\
    --form "exercises[][attempts][][date_end]=1974-01-21 00:34:39"\
    --form "exercises[][attempts][][result]=success"\
    --form "exercises[][emg_file]=@/tmp/phpQKOQey" 
const url = new URL(
    "http://localhost:8000/api/trainings/1/day/2/task/3/attempts"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "multipart/form-data",
    "Accept": "application/json",
};

const body = new FormData();
body.append('exercises[][date_start]', '1976-04-21 12:41:47');
body.append('exercises[][date_end]', '2026-10-22 22:18:23');
body.append('exercises[][end_reason]', 'fail');
body.append('exercises[][config][common][fingerStrength][]', '1');
body.append('exercises[][config][common][gripPositions][_]', '0');
body.append('exercises[][config][common][gripPositions][0][initial][]', '37');
body.append('exercises[][config][common][gripPositions][0][limit][]', '87');
body.append('exercises[][config][common][gripPositions][1][initial][]', '33');
body.append('exercises[][config][common][gripPositions][1][limit][]', '78');
body.append('exercises[][config][common][gripPositions][2][initial][]', '25');
body.append('exercises[][config][common][gripPositions][2][limit][]', '29');
body.append('exercises[][config][common][gripPositions][3][initial][]', '37');
body.append('exercises[][config][common][gripPositions][3][limit][]', '69');
body.append('exercises[][config][common][gripPositions][4][initial][]', '30');
body.append('exercises[][config][common][gripPositions][4][limit][]', '48');
body.append('exercises[][config][common][gripPositions][5][initial][]', '45');
body.append('exercises[][config][common][gripPositions][5][limit][]', '55');
body.append('exercises[][config][common][gripPositions][6][initial][]', '43');
body.append('exercises[][config][common][gripPositions][6][limit][]', '56');
body.append('exercises[][config][common][gripPositions][7][initial][]', '1');
body.append('exercises[][config][common][gripPositions][7][limit][]', '61');
body.append('exercises[][config][common][gripPositions][8][initial][]', '30');
body.append('exercises[][config][common][gripPositions][8][limit][]', '57');
body.append('exercises[][config][common][gripPositions][9][initial][]', '39');
body.append('exercises[][config][common][gripPositions][9][limit][]', '89');
body.append('exercises[][config][common][gripPositions][10][initial][]', '2');
body.append('exercises[][config][common][gripPositions][10][limit][]', '31');
body.append('exercises[][config][common][gripPositions][11][initial][]', '9');
body.append('exercises[][config][common][gripPositions][11][limit][]', '56');
body.append('exercises[][config][common][gripPositions][12][initial][]', '56');
body.append('exercises[][config][common][gripPositions][12][limit][]', '89');
body.append('exercises[][config][common][gripPositions][13][initial][]', '31');
body.append('exercises[][config][common][gripPositions][13][limit][]', '78');
body.append('exercises[][config][common][inputSite][]', '1');
body.append('exercises[][config][modes][][id]', '83');
body.append('exercises[][config][modes][][name]', 'Delectus qui deleniti voluptatem nesciunt quasi laboriosam eum.');
body.append('exercises[][config][modes][][slot]', '0');
body.append('exercises[][config][modes][][config][autoGrasp][]', '1');
body.append('exercises[][config][modes][][config][coContractionTimings][]', '500');
body.append('exercises[][config][modes][][config][controlMode][]', '1');
body.append('exercises[][config][modes][][config][emgGains][]', '100');
body.append('exercises[][config][modes][][config][emgSpike][]', '1');
body.append('exercises[][config][modes][][config][emgThresholds][]', '30');
body.append('exercises[][config][modes][][config][gripPairsConfig][]', '9');
body.append('exercises[][config][modes][][config][gripSequentialConfig][]', '2');
body.append('exercises[][config][modes][][config][gripSwitchingMode][]', '2');
body.append('exercises[][config][modes][][config][holdOpen][]', '2000');
body.append('exercises[][config][modes][][config][pulseTimings][]', '840');
body.append('exercises[][config][modes][][config][softGrip][]', '1');
body.append('exercises[][config][modes][][config][speedControlStrategy][]', '1');
body.append('exercises[][firmware_id]', '8999761');
body.append('exercises[][app_version]', '0.25.16');
body.append('exercises[][attempts][][date_start]', '1976-04-05 21:48:32');
body.append('exercises[][attempts][][date_end]', '1974-01-21 00:34:39');
body.append('exercises[][attempts][][result]', 'success');
body.append('exercises[][emg_file]', document.querySelector('input[name="exercises[][emg_file]"]').files[0]);

fetch(url, {
    method: "POST",
    headers,
    body,
}).then(response => response.json());

Example response (200):


[
    {
        "id": 1,
        "user_id": 324,
        "training_task_id": 1,
        "date_start": "2005-07-12 08:34:15",
        "date_end": "2012-02-01 02:24:52",
        "end_reason": "ticketCreated",
        "config": "{\"common\":{\"fingerStrength\":[1,100],\"gripPositions\":{\"_\":0,\"0\":{\"initial\":[42,4,80,36,17],\"limit\":[85,61,83,92,27]},\"1\":{\"initial\":[28,90,7,18,4],\"limit\":[66,94,8,65,65]},\"2\":{\"initial\":[62,30,58,30,65],\"limit\":[86,90,64,61,68]},\"3\":{\"initial\":[7,23,54,15,16],\"limit\":[87,41,92,68,73]},\"4\":{\"initial\":[14,55,13,79,6],\"limit\":[44,60,30,94,74]},\"5\":{\"initial\":[59,45,65,47,5],\"limit\":[88,69,92,83,51]},\"6\":{\"initial\":[23,22,71,22,15],\"limit\":[24,26,77,67,53]},\"7\":{\"initial\":[27,7,73,10,53],\"limit\":[88,19,78,18,63]},\"8\":{\"initial\":[28,14,16,51,45],\"limit\":[80,80,49,74,74]},\"9\":{\"initial\":[23,49,27,6,21],\"limit\":[54,55,41,71,91]},\"10\":{\"initial\":[19,43,28,34,51],\"limit\":[48,93,57,86,88]},\"11\":{\"initial\":[12,39,20,40,34],\"limit\":[95,89,54,50,39]},\"12\":{\"initial\":[9,20,78,59,7],\"limit\":[87,81,79,77,54]},\"13\":{\"initial\":[25,65,56,62,9],\"limit\":[58,81,73,71,74]}},\"inputSite\":[1]},\"modes\":[{\"id\":86,\"name\":\"In quia exercitationem architecto quia inventore.\",\"slot\":0,\"config\":{\"autoGrasp\":[0,100],\"coContractionTimings\":[500,400],\"controlMode\":[1],\"emgGains\":[100,100],\"emgSpike\":[1,300],\"emgThresholds\":[0,50,70,90,100,70,50,100,50,10],\"gripPairsConfig\":[5,2,8,13,12,3,4,9],\"gripSequentialConfig\":[11,255,10,5,1,13,255,255,255,4,7,6],\"gripSwitchingMode\":[3],\"holdOpen\":[1500,2000],\"pulseTimings\":[920,990,970,350],\"softGrip\":[1],\"speedControlStrategy\":[0]}},{\"id\":87,\"name\":\"Accusantium adipisci suscipit quidem et reiciendis repellendus.\",\"slot\":1,\"config\":{\"autoGrasp\":[0,0],\"coContractionTimings\":[500,200],\"controlMode\":[1],\"emgGains\":[100,100],\"emgSpike\":[1,300],\"emgThresholds\":[30,90,0,60,50,80,30,40,70,60],\"gripPairsConfig\":[1,10,2,12,6,5,4,3],\"gripSequentialConfig\":[9,7,2,1,4,255,6,5,3,12,10,255],\"gripSwitchingMode\":[2],\"holdOpen\":[2000,2000],\"pulseTimings\":[640,170,270,290],\"softGrip\":[0],\"speedControlStrategy\":[1]}},{\"id\":88,\"name\":\"Placeat quia corporis veniam reprehenderit asperiores.\",\"slot\":2,\"config\":{\"autoGrasp\":[1,100],\"coContractionTimings\":[400,200],\"controlMode\":[0],\"emgGains\":[100,100],\"emgSpike\":[0,300],\"emgThresholds\":[70,100,20,60,0,50,100,100,10,40],\"gripPairsConfig\":[10,2,8,7,1,11,5,6],\"gripSequentialConfig\":[11,1,10,9,255,13,3,5,7,6,4,8],\"gripSwitchingMode\":[1],\"holdOpen\":[2000,2500],\"pulseTimings\":[280,250,90,550],\"softGrip\":[0],\"speedControlStrategy\":[0]}}]}",
        "firmware_id": 24,
        "app_version": "4.90.49",
        "emg_file": "/tmp/faker0HirAb",
        "created_at": "2026-11-03T17:26:59.000000Z",
        "updated_at": "2026-11-03T17:26:59.000000Z",
        "attempts": [
            {
                "id": 1,
                "training_log_id": 1,
                "date_start": "2005-06-29 01:03:05",
                "date_end": "1971-06-12 10:41:30",
                "result": "failure",
                "created_at": "2026-11-03T17:26:59.000000Z",
                "updated_at": "2026-11-03T17:26:59.000000Z"
            }
        ]
    },
    {
        "id": 3,
        "user_id": 326,
        "training_task_id": 3,
        "date_start": "2015-08-01 10:50:10",
        "date_end": "1997-10-17 01:58:50",
        "end_reason": "success",
        "config": "{\"common\":{\"fingerStrength\":[1,100],\"gripPositions\":{\"_\":0,\"0\":{\"initial\":[55,11,2,89,78],\"limit\":[93,95,6,90,89]},\"1\":{\"initial\":[25,47,37,34,22],\"limit\":[78,54,40,68,51]},\"2\":{\"initial\":[50,26,57,84,45],\"limit\":[77,38,82,95,58]},\"3\":{\"initial\":[11,29,13,1,31],\"limit\":[45,60,87,64,77]},\"4\":{\"initial\":[45,39,56,14,79],\"limit\":[70,68,85,22,81]},\"5\":{\"initial\":[37,31,61,51,42],\"limit\":[91,33,72,90,67]},\"6\":{\"initial\":[1,54,17,22,34],\"limit\":[71,61,95,53,51]},\"7\":{\"initial\":[1,50,49,14,13],\"limit\":[16,58,58,94,57]},\"8\":{\"initial\":[40,39,16,51,48],\"limit\":[67,70,41,69,73]},\"9\":{\"initial\":[2,4,15,6,46],\"limit\":[70,30,48,17,90]},\"10\":{\"initial\":[88,7,48,14,43],\"limit\":[89,8,71,95,86]},\"11\":{\"initial\":[42,30,16,74,30],\"limit\":[63,88,64,83,68]},\"12\":{\"initial\":[16,69,5,32,3],\"limit\":[53,83,34,51,40]},\"13\":{\"initial\":[43,31,26,11,20],\"limit\":[82,87,40,12,70]}},\"inputSite\":[1]},\"modes\":[{\"id\":92,\"name\":\"Laudantium dolor fuga deserunt voluptas rerum neque et.\",\"slot\":0,\"config\":{\"autoGrasp\":[0,100],\"coContractionTimings\":[500,100],\"controlMode\":[0],\"emgGains\":[100,100],\"emgSpike\":[1,300],\"emgThresholds\":[10,100,30,10,30,20,100,50,80,60],\"gripPairsConfig\":[4,6,5,10,8,2,12,13],\"gripSequentialConfig\":[255,12,255,255,6,2,1,255,255,4,7,8],\"gripSwitchingMode\":[3],\"holdOpen\":[1500,2500],\"pulseTimings\":[780,560,920,270],\"softGrip\":[0],\"speedControlStrategy\":[0]}},{\"id\":93,\"name\":\"Eveniet quis vero rerum voluptatem numquam rerum.\",\"slot\":1,\"config\":{\"autoGrasp\":[0,0],\"coContractionTimings\":[400,200],\"controlMode\":[1],\"emgGains\":[100,100],\"emgSpike\":[1,300],\"emgThresholds\":[70,0,80,40,50,60,40,60,10,90],\"gripPairsConfig\":[12,4,2,8,1,10,6,9],\"gripSequentialConfig\":[255,12,8,255,9,11,7,255,6,10,255,2],\"gripSwitchingMode\":[2],\"holdOpen\":[1500,2000],\"pulseTimings\":[810,940,230,800],\"softGrip\":[1],\"speedControlStrategy\":[0]}},{\"id\":94,\"name\":\"Magni corrupti velit dignissimos voluptas perferendis.\",\"slot\":2,\"config\":{\"autoGrasp\":[0,0],\"coContractionTimings\":[200,100],\"controlMode\":[1],\"emgGains\":[100,100],\"emgSpike\":[1,300],\"emgThresholds\":[70,60,90,50,80,80,50,40,20,50],\"gripPairsConfig\":[8,6,11,12,5,7,4,1],\"gripSequentialConfig\":[7,255,2,13,5,4,6,3,1,8,11,12],\"gripSwitchingMode\":[1],\"holdOpen\":[1500,2500],\"pulseTimings\":[700,950,250,280],\"softGrip\":[1],\"speedControlStrategy\":[0]}}]}",
        "firmware_id": 26,
        "app_version": "7.49.90",
        "emg_file": "/tmp/fakeryBz0Ho",
        "created_at": "2026-11-03T17:27:00.000000Z",
        "updated_at": "2026-11-03T17:27:00.000000Z",
        "attempts": [
            {
                "id": 2,
                "training_log_id": 3,
                "date_start": "1983-06-29 22:40:31",
                "date_end": "1990-07-28 10:53:06",
                "result": "failure",
                "created_at": "2026-11-03T17:27:00.000000Z",
                "updated_at": "2026-11-03T17:27:00.000000Z"
            }
        ]
    }
]
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage user trainings",
    "code": "TRAININGS:SAVE_ATTEMPTS:INSUFFICIENT_PERMISSION"
}
 

Example response (403, User has no training started):


{
    "message": "User has no training started",
    "code": "TRAININGS:SAVE_ATTEMPTS:NO_USER_TRAINING"
}
 

Example response (404, Training not found):


{
    "message": "Training not found",
    "code": "TRAININGS:SAVE_ATTEMPTS:TRAINING_NOT_FOUND"
}
 

Example response (404, Training day not found):


{
    "message": "Training day not found",
    "code": "TRAININGS:SAVE_ATTEMPTS:TRAINING_DAY_NOT_FOUND"
}
 

Example response (404, Training task not found):


{
    "message": "Training task not found",
    "code": "TRAININGS:SAVE_ATTEMPTS:TRAINING_TASK_NOT_FOUND"
}
 

Request   

POST api/trainings/{trainingId}/day/{trainingDayId}/task/{trainingTaskId}/attempts

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: multipart/form-data

Accept      

Example: application/json

URL Parameters

trainingId   integer   

Training ID. Example: 1

trainingDayId   integer   

Training Day ID. Example: 2

trainingTaskId   integer   

Training Task ID. Example: 3

Body Parameters

exercises   object[]  optional  
date_start   string   

Exercise start date. MUST_BE_DATE. Example: 1976-04-21 12:41:47

date_end   string  optional  

Exercise end date. MUST_BE_DATE. Example: 2026-10-22 22:18:23

end_reason   string  optional  

Exercise end reason. Example: fail

Must be one of:
  • success
  • back
  • ticketCreated
  • fail
  • null
config   string   

Device config during exercise.

firmware_id   integer   

Device Firmware Version ID during exercise. The id of an existing record in the App\Models\FirmwareVersion table. Example: 8999761

app_version   string   

Mobile app version during exercise. Example: 0.25.16

emg_file   file  optional  

EMG file created during exercise. Must be a file. MAXIMUM:FILE_KB:102400. Example: /tmp/phpQKOQey

attempts   object[]  optional  
date_start   string   

Exercise attempt start date. MUST_BE_DATE. Example: 1976-04-05 21:48:32

date_end   string   

Exercise attempt end date. MUST_BE_DATE. Example: 1974-01-21 00:34:39

result   string   

Exercise attempt result. Example: success

Must be one of:
  • success
  • failure

Response

Response Fields

id   integer   

Training log entry ID.

user_id   integer   

Associated user ID.

training_task_id   integer   

Associated training task ID.

date_start   string   

Session start datetime.

date_end   string   

Session end datetime.

end_reason   string   

Reason the session ended.

config   string   

Device config snapshot at time of session.

firmware_id   integer   

Firmware version ID used during session.

app_version   string   

App version used during session.

emg_file   string   

EMG data file URL.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

attempts   object[]   

Training log attempts.

Get reward status

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/trainings/1/reward" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/trainings/1/reward"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, Reward status):


{
    "status": true
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage user trainings",
    "code": "TRAININGS:REWARD_STATUS:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Training not found):


{
    "message": "Training not found",
    "code": "TRAININGS:REWARD_STATUS:TRAINING_NOT_FOUND"
}
 

Request   

GET api/trainings/{trainingId}/reward

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

trainingId   integer   

Training ID. Example: 1

Save reward details

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/trainings/1/reward" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"type\": \"physical\",
    \"country\": \"CY\",
    \"delivery\": \"home_delivery\",
    \"email\": \"santina.stark@kunde.com\",
    \"phone\": \"937.709.9271\",
    \"full_name\": \"Nannie Sipes\",
    \"address1\": \"2524 Will Passage\",
    \"address2\": \"Apt. 786\",
    \"city\": \"Eddieview\",
    \"postal_code\": \"09127\",
    \"state\": \"NON\",
    \"parcel_locker_code\": \"T67ZAEWY\",
    \"pin_code\": \"312132\"
}"
const url = new URL(
    "http://localhost:8000/api/trainings/1/reward"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "type": "physical",
    "country": "CY",
    "delivery": "home_delivery",
    "email": "santina.stark@kunde.com",
    "phone": "937.709.9271",
    "full_name": "Nannie Sipes",
    "address1": "2524 Will Passage",
    "address2": "Apt. 786",
    "city": "Eddieview",
    "postal_code": "09127",
    "state": "NON",
    "parcel_locker_code": "T67ZAEWY",
    "pin_code": "312132"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "id": 1,
    "user_id": 328,
    "training_id": 21,
    "type": "digital",
    "country": "LU",
    "delivery": "ua_nova_poshta_parcel_locker",
    "email": "ucassin@hotmail.com",
    "phone": "1-928-209-6142",
    "full_name": "Corrine O'Kon",
    "address1": "717 Klocko Cliffs",
    "address2": "37989 Beahan Lodge\nPort Briannefurt, NC 97522",
    "city": "Padbergton",
    "postal_code": "50846",
    "state": "SINT",
    "parcel_locker_code": "7URGDZFM",
    "pin_code": "446627",
    "created_at": "2026-11-03T17:27:01.000000Z",
    "updated_at": "2026-11-03T17:27:01.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage user trainings",
    "code": "TRAININGS:REWARD_SAVE:INSUFFICIENT_PERMISSION"
}
 

Example response (403, Reward details already exists):


{
    "message": "Reward details already exists",
    "code": "TRAININGS:REWARD_SAVE:ALREADY_EXISTS"
}
 

Example response (404, Training not found):


{
    "message": "Training not found",
    "code": "TRAININGS:REWARD_SAVE:TRAINING_NOT_FOUND"
}
 

Request   

POST api/trainings/{trainingId}/reward

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

trainingId   integer   

Training ID. Example: 1

Body Parameters

type   string   

Reward type. Example: physical

Must be one of:
  • digital
  • physical
country   string   

Country code. SIZE:STRING_LENGTH:2. Example: CY

Must be one of:
  • AF
  • AX
  • AL
  • DZ
  • AS
  • AD
  • AO
  • AI
  • AQ
  • AG
  • AR
  • AM
  • AW
  • AU
  • AT
  • AZ
  • BS
  • BH
  • BD
  • BB
  • BY
  • BE
  • BZ
  • BJ
  • BM
  • BT
  • BO
  • BQ
  • BA
  • BW
  • BV
  • BR
  • IO
  • BN
  • BG
  • BF
  • BI
  • CV
  • KH
  • CM
  • CA
  • KY
  • CF
  • TD
  • CL
  • CN
  • CX
  • CC
  • CO
  • KM
  • CD
  • CG
  • CK
  • CR
  • CI
  • HR
  • CU
  • CW
  • CY
  • CZ
  • DK
  • DJ
  • DM
  • DO
  • EC
  • EG
  • SV
  • GQ
  • ER
  • EE
  • SZ
  • ET
  • FK
  • FO
  • FJ
  • FI
  • FR
  • GF
  • PF
  • TF
  • GA
  • GM
  • GE
  • DE
  • GH
  • GI
  • GR
  • GL
  • GD
  • GP
  • GU
  • GT
  • GG
  • GN
  • GW
  • GY
  • HT
  • HM
  • VA
  • HN
  • HK
  • HU
  • IS
  • IN
  • ID
  • IR
  • IQ
  • IE
  • IM
  • IL
  • IT
  • JM
  • JP
  • JE
  • JO
  • KZ
  • KE
  • KI
  • KP
  • KR
  • KW
  • KG
  • LA
  • LV
  • LB
  • LS
  • LR
  • LY
  • LI
  • LT
  • LU
  • MO
  • MG
  • MW
  • MY
  • MV
  • ML
  • MT
  • MH
  • MQ
  • MR
  • MU
  • YT
  • MX
  • FM
  • MD
  • MC
  • MN
  • ME
  • MS
  • MA
  • MZ
  • MM
  • NA
  • NR
  • NP
  • NL
  • NC
  • NZ
  • NI
  • NE
  • NG
  • NU
  • NF
  • MK
  • MP
  • NO
  • OM
  • PK
  • PW
  • PS
  • PA
  • PG
  • PY
  • PE
  • PH
  • PN
  • PL
  • PT
  • PR
  • QA
  • RE
  • RO
  • RU
  • RW
  • BL
  • SH
  • KN
  • LC
  • MF
  • PM
  • VC
  • WS
  • SM
  • ST
  • SA
  • SN
  • RS
  • SC
  • SL
  • SG
  • SX
  • SK
  • SI
  • SB
  • SO
  • ZA
  • GS
  • SS
  • ES
  • LK
  • SD
  • SR
  • SJ
  • SE
  • CH
  • SY
  • TW
  • TJ
  • TZ
  • TH
  • TL
  • TG
  • TK
  • TO
  • TT
  • TN
  • TR
  • TM
  • TC
  • TV
  • UG
  • UA
  • AE
  • GB
  • UM
  • US
  • UY
  • UZ
  • VU
  • VE
  • VN
  • VG
  • VI
  • WF
  • EH
  • YE
  • ZM
  • ZW
delivery   string  optional  

Delivery method. Default: home_delivery. Example: home_delivery

Must be one of:
  • home_delivery
  • pl_inpost_parcel_locker
  • ua_nova_poshta_branch_pickup
  • ua_nova_poshta_parcel_locker
email   string   

Contact email. MUST_BE_EMAIL. Example: santina.stark@kunde.com

phone   string   

Contact phone. Example: 937.709.9271

full_name   string   

Full name. Example: Nannie Sipes

address1   string  optional  

Address line 1. Example: 2524 Will Passage

address2   string  optional  

Address line 2. Example: Apt. 786

city   string  optional  

City. Example: Eddieview

postal_code   string  optional  

Postal code/ZIP code/Postcode. Example: 09127

state   string  optional  

State/Province. Example: NON

parcel_locker_code   string  optional  

Parcel locker code/Branch pickup number (for: InPost parcel locker, Nova Poshta branch pickup, Nova Poshta parcel locker). Example: T67ZAEWY

pin_code   string  optional  

PIN code (India delivery). Example: 312132

Response

Response Fields

id   integer   

User reward ID.

user_id   integer   

Associated user ID.

training_id   integer   

Associated training ID.

type   string   

Reward type.

Must be one of:
  • digital
  • physical
country   string   

Delivery country code.

delivery   string   

Delivery method.

Must be one of:
  • home_delivery
  • parcel_locker
email   string   

Recipient email.

phone   string   

Recipient phone.

full_name   string   

Recipient full name.

address1   string   

Delivery address line 1.

address2   string   

Delivery address line 2.

city   string   

Delivery city.

postal_code   string   

Delivery postal code.

state   string   

Delivery state.

parcel_locker_code   string   

Parcel locker code.

pin_code   string   

PIN code for delivery.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Get user progress (clinician view)

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/trainings/progress/minus/user/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/trainings/progress/minus/user/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, OK):


[
    {
        "number": 1,
        "key": "openClose",
        "status": "completed",
        "status_updated_at": "2026-02-04 11:00:00"
    },
    {
        "number": 2,
        "key": "holdOpen",
        "status": "in_progress",
        "status_updated_at": "2026-02-05 09:00:00"
    },
    {
        "number": 3,
        "key": "changeSignal",
        "status": "not_started",
        "status_updated_at": null
    },
    {
        "number": 4,
        "key": "thumbPositioning",
        "status": "not_started",
        "status_updated_at": null
    },
    {
        "number": 5,
        "key": "sequentialAndPairingMode",
        "status": "not_started",
        "status_updated_at": null
    },
    {
        "number": 6,
        "key": "fingerSpeedTraining",
        "status": "not_started",
        "status_updated_at": null
    },
    {
        "number": 7,
        "key": "freezeMode",
        "status": "not_started",
        "status_updated_at": null
    },
    {
        "number": 8,
        "key": "coreSkillsTest",
        "status": "not_started",
        "status_updated_at": null
    },
    {
        "number": 9,
        "key": "powerSoftGrip",
        "status": "not_started",
        "status_updated_at": null
    },
    {
        "number": 10,
        "key": "precisionGrip",
        "status": "not_started",
        "status_updated_at": null
    },
    {
        "number": 11,
        "key": "tripodGrip",
        "status": "not_started",
        "status_updated_at": null
    },
    {
        "number": 12,
        "key": "keyGrip",
        "status": "not_started",
        "status_updated_at": null
    },
    {
        "number": 13,
        "key": "gripSelection",
        "status": "not_started",
        "status_updated_at": null
    },
    {
        "number": 14,
        "key": "finalGripAndControlTest",
        "status": "not_started",
        "status_updated_at": null
    }
]
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to access patient training",
    "code": "TRAININGS:PROGRESS_CLINICIAN:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Training not found):


{
    "message": "Training not found",
    "code": "TRAININGS:PROGRESS_CLINICIAN:TRAINING_NOT_FOUND"
}
 

Example response (404, User not found):


{
    "message": "User not found",
    "code": "TRAININGS:PROGRESS_CLINICIAN:USER_NOT_FOUND"
}
 

Request   

GET api/trainings/progress/{trainingId}/user/{userId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

trainingId   string   

Example: minus

userId   integer   

User ID. Example: 1

User preferences

API endpoints for user preferences

List user preferences

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/preferences" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/preferences"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, OK):


{
    "preference1": "value1",
    "preference2": null,
    "preference3": 1
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage user preferences",
    "code": "USERS_PREFERENCES:LIST:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/preferences

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Get user preference

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/preferences/sidebar_enabled" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/preferences/sidebar_enabled"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "id": 1,
    "user_id": 329,
    "name": "LightSteelBlue",
    "value": "1",
    "created_at": "2026-11-03T17:27:02.000000Z",
    "updated_at": "2026-11-03T17:27:02.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage user preferences",
    "code": "USERS_PREFERENCES:GET:INSUFFICIENT_PERMISSION"
}
 

Example response (404, User preference not found):


{
    "message": "User preference not found",
    "code": "USERS_PREFERENCES:GET:NOT_FOUND"
}
 

Request   

GET api/preferences/{name}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

name   string   

Preference name. Example: sidebar_enabled

Set user preference

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/preferences" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"sidebar_enabled\",
    \"value\": \"1\"
}"
const url = new URL(
    "http://localhost:8000/api/preferences"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "sidebar_enabled",
    "value": "1"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (202):


{
    "id": 2,
    "user_id": 330,
    "name": "MediumSeaGreen",
    "value": "0",
    "created_at": "2026-11-03T17:27:02.000000Z",
    "updated_at": "2026-11-03T17:27:02.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to manage user preferences",
    "code": "USERS_PREFERENCES:SET:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/preferences

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

name   string   

Preference name. Example: sidebar_enabled

value   string   

Preference value. Example: 1

Users

API endpoints for user management

Get users list

requires authentication

Possible extend options:

Example request:
curl --request GET \
    --get "http://localhost:8000/api/users?search=Tom&active=-1&clinician[]=13&roles=Clinician%2CAmputee&has_devices=1&user_toggles=goals%2Ctrainings" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/users"
);

const params = {
    "search": "Tom",
    "active": "-1",
    "clinician[0]": "13",
    "roles": "Clinician,Amputee",
    "has_devices": "1",
    "user_toggles": "goals,trainings",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 7,
            "mrn": "C4WW8ZTV1793726639",
            "name": "Simone Daugherty",
            "email": "1793726639darrel05@example.net",
            "language": "en",
            "phone": "+1-912-909-1678",
            "phone_country": "GG",
            "phone_verified_at": null,
            "address1": "63765 Goldner Island",
            "address2": "East Mabelle, MS 40719-2407",
            "postal_code": "62397-8123",
            "city": "Bradtke, Spencer and Cruickshank",
            "country": "HU",
            "clinic_name": "Nickview",
            "clinic_location": "9018 Bernhard Knoll Apt. 142\nSanfordfurt, HI 22393-0595",
            "image": null,
            "public_image": null,
            "mfa_enabled": 0,
            "mfa_method": null,
            "mfa_verified_to": null,
            "location_id": null,
            "created_by": null,
            "active": 1,
            "is_ambassador": 0,
            "test_account": false,
            "notifications_timezone": null,
            "notifications_at": null,
            "created_at": "2026-11-03T17:23:59.000000Z",
            "updated_at": "2026-11-03T17:23:59.000000Z",
            "invitation_status": null,
            "acadle_invitation_status": null,
            "clinicians": [
                {
                    "id": 8,
                    "mrn": "NBAWF3XW1793726640",
                    "name": "Hillard D'Amore",
                    "email": "1793726640alfonzo.heller@example.net",
                    "language": "en",
                    "phone": "1-231-278-2347",
                    "phone_country": "BM",
                    "phone_verified_at": null,
                    "address1": "61141 Jerad Trace",
                    "address2": "Langworthmouth, ID 40611-6252",
                    "postal_code": "69061-7072",
                    "city": "Lindgren-Harber",
                    "country": "GR",
                    "clinic_name": "North Valliebury",
                    "clinic_location": "9704 Gutkowski Pine\nDiannaland, NV 90596-4461",
                    "image": null,
                    "public_image": null,
                    "mfa_enabled": 0,
                    "mfa_method": null,
                    "mfa_verified_to": null,
                    "location_id": null,
                    "created_by": null,
                    "active": 1,
                    "is_ambassador": 0,
                    "test_account": false,
                    "notifications_timezone": null,
                    "notifications_at": null,
                    "created_at": "2026-11-03T17:24:00.000000Z",
                    "updated_at": "2026-11-03T17:24:00.000000Z",
                    "invitation_status": null,
                    "acadle_invitation_status": null,
                    "pivot": {
                        "user_id": 7,
                        "assigned_user_id": 8
                    },
                    "roles": []
                }
            ],
            "devices": [
                {
                    "id": 1,
                    "serial": "a11596f6-e4e8-3b9c-8786-47cfaf595554",
                    "bluetooth_id": "bbff0026-7734-3165-9edc-d2b247bbc638",
                    "company_id": null,
                    "model_id": null,
                    "amputee_id": 7,
                    "clinician_id": null,
                    "firmware_version_id": null,
                    "pcb_version_id": null,
                    "reverse_magnets": 0,
                    "is_electrode": 0,
                    "active": 1,
                    "last_activity_at": "0000-00-00 00:00:00",
                    "first_connected_at": null,
                    "measurements": null,
                    "created_at": "2026-11-03T17:24:00.000000Z",
                    "updated_at": "2026-11-03T17:24:00.000000Z",
                    "first_config_change_at": null
                }
            ],
            "roles": [
                {
                    "id": 1,
                    "name": "SuperAdmin"
                }
            ]
        },
        {
            "id": 9,
            "mrn": "S8ZPR8TC1793726640",
            "name": "Prof. Rickey Schuster",
            "email": "1793726640adrain78@example.com",
            "language": "en",
            "phone": "425-485-1349",
            "phone_country": "BE",
            "phone_verified_at": null,
            "address1": "1598 Feest Inlet",
            "address2": "New Jessikaside, CO 17137-2646",
            "postal_code": "43180",
            "city": "Marquardt, Braun and Bartoletti",
            "country": "LU",
            "clinic_name": "West Jannie",
            "clinic_location": "698 Ettie Row Apt. 547\nPort Elverastad, CT 66482",
            "image": null,
            "public_image": null,
            "mfa_enabled": 0,
            "mfa_method": null,
            "mfa_verified_to": null,
            "location_id": null,
            "created_by": null,
            "active": 1,
            "is_ambassador": 0,
            "test_account": false,
            "notifications_timezone": null,
            "notifications_at": null,
            "created_at": "2026-11-03T17:24:00.000000Z",
            "updated_at": "2026-11-03T17:24:00.000000Z",
            "invitation_status": null,
            "acadle_invitation_status": null,
            "clinicians": [
                {
                    "id": 10,
                    "mrn": "CAT9JVMZ1793726641",
                    "name": "Caroline Littel MD",
                    "email": "1793726641bayer.effie@example.net",
                    "language": "en",
                    "phone": "+1.660.366.8307",
                    "phone_country": "TT",
                    "phone_verified_at": null,
                    "address1": "1351 Elisabeth Way Suite 642",
                    "address2": "New Oscarbury, LA 57546-3706",
                    "postal_code": "23991",
                    "city": "Russel Group",
                    "country": "CZ",
                    "clinic_name": "Koreybury",
                    "clinic_location": "260 Warren Flat Apt. 557\nPort Wilburnchester, HI 06278",
                    "image": null,
                    "public_image": null,
                    "mfa_enabled": 0,
                    "mfa_method": null,
                    "mfa_verified_to": null,
                    "location_id": null,
                    "created_by": null,
                    "active": 1,
                    "is_ambassador": 0,
                    "test_account": false,
                    "notifications_timezone": null,
                    "notifications_at": null,
                    "created_at": "2026-11-03T17:24:01.000000Z",
                    "updated_at": "2026-11-03T17:24:01.000000Z",
                    "invitation_status": null,
                    "acadle_invitation_status": null,
                    "pivot": {
                        "user_id": 9,
                        "assigned_user_id": 10
                    },
                    "roles": []
                }
            ],
            "devices": [
                {
                    "id": 2,
                    "serial": "dd61c10f-5551-39b9-ab4a-c2eb9d095dd4",
                    "bluetooth_id": "f003b895-d80e-3541-89d6-0dccf5995975",
                    "company_id": null,
                    "model_id": null,
                    "amputee_id": 9,
                    "clinician_id": null,
                    "firmware_version_id": null,
                    "pcb_version_id": null,
                    "reverse_magnets": 0,
                    "is_electrode": 0,
                    "active": 1,
                    "last_activity_at": "0000-00-00 00:00:00",
                    "first_connected_at": null,
                    "measurements": null,
                    "created_at": "2026-11-03T17:24:01.000000Z",
                    "updated_at": "2026-11-03T17:24:01.000000Z",
                    "first_config_change_at": null
                }
            ],
            "roles": [
                {
                    "id": 6,
                    "name": "Amputee"
                }
            ]
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view user list",
    "code": "USERS:LIST:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/users

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

search   string  optional  

Filter users by name. For SuperAdmin role it filters by UUID. Example: Tom

active   integer  optional  

Filter users by active status (available: 0 - only inactive, 1 - only active, -1 - all users). Default: 1. Example: -1

clinician   integer[]  optional  

Filter users by clinician. Provide single ID (clinician=1), array of IDs (clinician[]=1&clinician[]=2) or comma-separated list of IDs (clinician=1,2).

roles   string  optional  

Filter users by roles (comma-separated). Example: Clinician,Amputee

has_devices   integer  optional  

Filter users who has at least one device assigned. Example: 1

user_toggles   string  optional  

Filter users by their user-specific product toggles (comma-separated). Example: goals,trainings

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

extend   string  optional  

Comma-separated list of relation extensions (available: clinicians, patients, devices, devices.model, devicesAsClinician, devicesAsClinician.model, roles, permissions, userToggles).

sortby   string  optional  

Sort by field (available: user_mrn, user_name, role_name, date). Default: date, desc.

sortdir   string  optional  

Sort direction (available: asc, desc).

Response

Response Fields

items   object   
id   integer   

User ID.

mrn   string   

Medical Record Number.

name   string   

User full name.

email   string   

User email address.

language   string   

User preferred language.

phone   string   

User phone number.

phone_country   string   

Phone country code.

phone_verified_at   string   

Phone verification date.

address1   string   

Address line 1.

address2   string   

Address line 2.

postal_code   string   

Postal code.

city   string   

City.

country   string   

Country code (ISO 3166 Alpha-2).

clinic_name   string   

Clinic name.

clinic_location   string   

Clinic location.

image   string   

User profile image URL.

mfa_enabled   boolean   

Whether MFA is enabled.

mfa_method   string   

Preferred MFA method.

mfa_verified_to   string   

MFA session verified until.

location_id   integer   

Location ID.

created_by   integer   

ID of the user who created this account.

active   boolean   

Whether the user account is active.

is_internal   boolean   

Super Admin only: Whether the user is an internal user (QA/ADP team).

notifications_timezone   string   

Timezone for notifications.

notifications_at   string   

Preferred notification time.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

invitation_status   string   

User invitation status.

acadle_invitation_status   string   

Acadle invitation status.

roles   object[]   

User roles.

id   integer   

Role ID.

name   string   

Role name.

permissions   object[]   

User permissions.

clinicians   object[]   

Assigned clinicians.

devices   object[]   

Assigned devices.

patients   object[]   

Assigned patients.

invitations   object[]   

User invitations.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

Get current user data

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/me" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/me"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "id": 11,
    "mrn": "GESNYWNS1793726642",
    "name": "Kailey Adams V",
    "email": "1793726642americo42@example.net",
    "language": "en",
    "phone": "+1-650-272-8103",
    "phone_country": "RS",
    "phone_verified_at": null,
    "address1": "278 Elfrieda Path",
    "address2": "Lake Cleta, IL 88671-3392",
    "postal_code": "49524",
    "city": "Hahn Ltd",
    "country": "GB",
    "clinic_name": "Runtetown",
    "clinic_location": "294 Hillard Mills Apt. 641\nLeratown, AL 92386-6724",
    "image": null,
    "public_image": null,
    "mfa_enabled": 0,
    "mfa_method": null,
    "mfa_verified_to": null,
    "location_id": null,
    "created_by": null,
    "active": 1,
    "is_ambassador": 0,
    "test_account": false,
    "notifications_timezone": null,
    "notifications_at": null,
    "created_at": "2026-11-03T17:24:02.000000Z",
    "updated_at": "2026-11-03T17:24:02.000000Z",
    "invitation_status": "accepted",
    "acadle_invitation_status": null,
    "roles": [
        {
            "id": 4,
            "name": "Clinician"
        }
    ]
}
 

Request   

GET api/me

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

extend   string  optional  

Comma-separated list of relation extensions (available: clinicians, patients, devices, devicesAsClinician, roles, permissions).

Response

Response Fields

id   integer   

User ID.

mrn   string   

Medical Record Number.

name   string   

User full name.

email   string   

User email address.

language   string   

User preferred language.

phone   string   

User phone number.

phone_country   string   

Phone country code.

phone_verified_at   string   

Phone verification date.

address1   string   

Address line 1.

address2   string   

Address line 2.

postal_code   string   

Postal code.

city   string   

City.

country   string   

Country code (ISO 3166 Alpha-2).

clinic_name   string   

Clinic name.

clinic_location   string   

Clinic location.

image   string   

User profile image URL.

mfa_enabled   boolean   

Whether MFA is enabled.

mfa_method   string   

Preferred MFA method.

mfa_verified_to   string   

MFA session verified until.

location_id   integer   

Location ID.

created_by   integer   

ID of the user who created this account.

active   boolean   

Whether the user account is active.

is_internal   boolean   

Super Admin only: Whether the user is an internal user (QA/ADP team).

notifications_timezone   string   

Timezone for notifications.

notifications_at   string   

Preferred notification time.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

invitation_status   string   

User invitation status.

acadle_invitation_status   string   

Acadle invitation status.

roles   object[]   

User roles.

id   integer   

Role ID.

name   string   

Role name.

permissions   object[]   

User permissions.

clinicians   object[]   

Assigned clinicians.

devices   object[]   

Assigned devices.

patients   object[]   

Assigned patients.

invitations   object[]   

User invitations.

Get other user data

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/user/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/user/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "id": 12,
    "mrn": "ZUMN5N5Z1793726642",
    "name": "Miss Kayla Collier",
    "email": "1793726642timothy.ratke@example.org",
    "language": "en",
    "phone": "+1-954-283-3554",
    "phone_country": "EC",
    "phone_verified_at": null,
    "address1": "4902 Savion Forges Suite 238",
    "address2": "South Amelie, LA 17972",
    "postal_code": "95121",
    "city": "Feeney PLC",
    "country": "SI",
    "clinic_name": "Evelinestad",
    "clinic_location": "45544 Lauriane Wells\nLake Kaelaview, SC 25463",
    "image": null,
    "public_image": null,
    "mfa_enabled": 0,
    "mfa_method": null,
    "mfa_verified_to": null,
    "location_id": null,
    "created_by": null,
    "active": 1,
    "is_ambassador": 0,
    "test_account": false,
    "notifications_timezone": null,
    "notifications_at": null,
    "created_at": "2026-11-03T17:24:02.000000Z",
    "updated_at": "2026-11-03T17:24:02.000000Z",
    "invitation_status": null,
    "acadle_invitation_status": null,
    "roles": [
        {
            "id": 1,
            "name": "SuperAdmin"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view user data",
    "code": "USERS:GET:INSUFFICIENT_PERMISSION"
}
 

Example response (404, User not found):


{
    "message": "User not found",
    "code": "USERS:GET:USER_NOT_FOUND"
}
 

Request   

GET api/user/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

User ID. Example: 1

Query Parameters

extend   string  optional  

Comma-separated list of relation extensions (available: clinicians, patients, devices, devicesAsClinician, roles, permissions).

Response

Response Fields

id   integer   

User ID.

mrn   string   

Medical Record Number.

name   string   

User full name.

email   string   

User email address.

language   string   

User preferred language.

phone   string   

User phone number.

phone_country   string   

Phone country code.

phone_verified_at   string   

Phone verification date.

address1   string   

Address line 1.

address2   string   

Address line 2.

postal_code   string   

Postal code.

city   string   

City.

country   string   

Country code (ISO 3166 Alpha-2).

clinic_name   string   

Clinic name.

clinic_location   string   

Clinic location.

image   string   

User profile image URL.

mfa_enabled   boolean   

Whether MFA is enabled.

mfa_method   string   

Preferred MFA method.

mfa_verified_to   string   

MFA session verified until.

location_id   integer   

Location ID.

created_by   integer   

ID of the user who created this account.

active   boolean   

Whether the user account is active.

is_internal   boolean   

Super Admin only: Whether the user is an internal user (QA/ADP team).

notifications_timezone   string   

Timezone for notifications.

notifications_at   string   

Preferred notification time.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

invitation_status   string   

User invitation status.

acadle_invitation_status   string   

Acadle invitation status.

roles   object[]   

User roles.

id   integer   

Role ID.

name   string   

Role name.

permissions   object[]   

User permissions.

clinicians   object[]   

Assigned clinicians.

devices   object[]   

Assigned devices.

patients   object[]   

Assigned patients.

invitations   object[]   

User invitations.

Create new user account

requires authentication

Predefined permissions:

Example request:
curl --request POST \
    "http://localhost:8000/api/user" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: multipart/form-data" \
    --header "Accept: application/json" \
    --form "mrn=MRN12345678"\
    --form "name=Tom Smith"\
    --form "email=test@example.com"\
    --form "language=en"\
    --form "address1=4606 Rodriguez Mill"\
    --form "address2=Lakinport, IA 03164"\
    --form "postal_code=47028-2224"\
    --form "city=Nathentown"\
    --form "country=LU"\
    --form "clinic_name=Aether"\
    --form "clinic_location=4606 Rodriguez Mill"\
    --form "mfa_enabled=1"\
    --form "mfa_method=email"\
    --form "is_internal="\
    --form "is_ambassador="\
    --form "test_account="\
    --form "clinicians[]=2"\
    --form "notifications_timezone=Europe/Warsaw"\
    --form "notifications_at=8:00"\
    --form "role=Amputee"\
    --form "image=@/tmp/phphEENkD" \
    --form "public_image=@/tmp/phprT79Bm" 
const url = new URL(
    "http://localhost:8000/api/user"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "multipart/form-data",
    "Accept": "application/json",
};

const body = new FormData();
body.append('mrn', 'MRN12345678');
body.append('name', 'Tom Smith');
body.append('email', 'test@example.com');
body.append('language', 'en');
body.append('address1', '4606 Rodriguez Mill');
body.append('address2', 'Lakinport, IA 03164');
body.append('postal_code', '47028-2224');
body.append('city', 'Nathentown');
body.append('country', 'LU');
body.append('clinic_name', 'Aether');
body.append('clinic_location', '4606 Rodriguez Mill');
body.append('mfa_enabled', '1');
body.append('mfa_method', 'email');
body.append('is_internal', '');
body.append('is_ambassador', '');
body.append('test_account', '');
body.append('clinicians[]', '2');
body.append('notifications_timezone', 'Europe/Warsaw');
body.append('notifications_at', '8:00');
body.append('role', 'Amputee');
body.append('image', document.querySelector('input[name="image"]').files[0]);
body.append('public_image', document.querySelector('input[name="public_image"]').files[0]);

fetch(url, {
    method: "POST",
    headers,
    body,
}).then(response => response.json());

Example response (201):


{
    "id": 13,
    "mrn": "PVYJR59J1793726643",
    "name": "Clarabelle Sauer",
    "email": "1793726643kacie48@example.org",
    "language": "en",
    "phone": "+1-319-835-3852",
    "phone_country": "HU",
    "phone_verified_at": null,
    "address1": "649 Emmerich Village Suite 948",
    "address2": "New Augustusmouth, WA 39985",
    "postal_code": "71468",
    "city": "Goyette PLC",
    "country": "GB",
    "clinic_name": "Jeanmouth",
    "clinic_location": "6014 Ellis Stream Apt. 161\nHeidenreichville, KY 75062-4882",
    "image": null,
    "public_image": null,
    "mfa_enabled": 0,
    "mfa_method": null,
    "mfa_verified_to": null,
    "location_id": null,
    "created_by": null,
    "active": 1,
    "is_ambassador": 0,
    "test_account": false,
    "notifications_timezone": null,
    "notifications_at": null,
    "created_at": "2026-11-03T17:24:03.000000Z",
    "updated_at": "2026-11-03T17:24:03.000000Z",
    "invitation_status": null,
    "acadle_invitation_status": "accepted",
    "roles": [
        {
            "id": 7,
            "name": "AcadleUser"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to create user with given role",
    "code": "USERS:CREATE:INSUFFICIENT_PERMISSION"
}
 

Example response (403, E-mail in use (in another region)):


{
    "message": "E-mail address already in use (in another region)",
    "code": "USERS:CREATE:EMAIL_IN_USE"
}
 

Request   

POST api/user

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: multipart/form-data

Accept      

Example: application/json

Body Parameters

mrn   string  optional  

Medical Record Number. Example: MRN12345678

name   string  optional  

User full name. Example: Tom Smith

email   string   

User email. MUST_BE_EMAIL. Example: test@example.com

language   string  optional  

User language. Example: en

address1   string  optional  

Address line 1. MAXIMUM:STRING_LENGTH:100. Example: 4606 Rodriguez Mill

address2   string  optional  

Address line 2. MAXIMUM:STRING_LENGTH:100. Example: Lakinport, IA 03164

postal_code   string  optional  

Postal code. MAXIMUM:STRING_LENGTH:100. Example: 47028-2224

city   string  optional  

City. MAXIMUM:STRING_LENGTH:100. Example: Nathentown

country   string  optional  

Country. MAXIMUM:STRING_LENGTH:2. Example: LU

clinic_name   string  optional  

Clinic name. MAXIMUM:STRING_LENGTH:100. Example: Aether

clinic_location   string  optional  

Clinic location. MAXIMUM:STRING_LENGTH:100. Example: 4606 Rodriguez Mill

image   file  optional  

Attached user image. MUST_BE_IMAGE MAXIMUM:FILE_KB:5120. Example: /tmp/phphEENkD

public_image   file  optional  

MUST_BE_IMAGE MAXIMUM:FILE_KB:5120. Example: /tmp/phprT79Bm

mfa_enabled   boolean  optional  

Super Admin only: MFA enabled. Example: true

mfa_method   string  optional  

Super Admin only: MFA method. Example: email

Must be one of:
  • email
  • sms
is_internal   boolean  optional  

Super Admin only: Mark user as internal (QA/ADP team). Example: false

is_ambassador   boolean  optional  

Mark user as a Community ambassador. Example: false

test_account   boolean  optional  

Example: false

clinicians   string[]  optional  

Clinician ID. The id of an existing record in the App\Models\User table.

notifications_timezone   string  optional  

User notifications timezone. Example: Europe/Warsaw

notifications_at   string  optional  

Time when notifications and reminders should be sent. Format: HH:MM. Set null to notify at default time. Must be a valid date in the format H:i. Example: 8:00

role   string   

Role name. One of: SuperAdmin, CommunityAdmin, ClinicAdmin, Clinician, ClinicianSupport, Amputee. Example: Amputee

Must be one of:
  • Amputee
  • ClinicianSupport
  • Clinician
  • ClinicAdmin
  • CommunityAdmin
  • SuperAdmin
  • AcadleUser
permissions   object  optional  

List of permissions given to this user. For now, it is used for ClinicianSupport role to give access to specific actions (for example user.update means that user can update users). You can assign both predefined permissions (already checked by some endpoints) and custom permissions (check them on your own). You can also use wildcards like user.* to give access to all actions for given scope.

Response

Response Fields

id   integer   

User ID.

mrn   string   

Medical Record Number.

name   string   

User full name.

email   string   

User email address.

language   string   

User preferred language.

phone   string   

User phone number.

phone_country   string   

Phone country code.

phone_verified_at   string   

Phone verification date.

address1   string   

Address line 1.

address2   string   

Address line 2.

postal_code   string   

Postal code.

city   string   

City.

country   string   

Country code (ISO 3166 Alpha-2).

clinic_name   string   

Clinic name.

clinic_location   string   

Clinic location.

image   string   

User profile image URL.

mfa_enabled   boolean   

Whether MFA is enabled.

mfa_method   string   

Preferred MFA method.

mfa_verified_to   string   

MFA session verified until.

location_id   integer   

Location ID.

created_by   integer   

ID of the user who created this account.

active   boolean   

Whether the user account is active.

is_internal   boolean   

Super Admin only: Whether the user is an internal user (QA/ADP team).

notifications_timezone   string   

Timezone for notifications.

notifications_at   string   

Preferred notification time.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

invitation_status   string   

User invitation status.

acadle_invitation_status   string   

Acadle invitation status.

roles   object[]   

User roles.

id   integer   

Role ID.

name   string   

Role name.

permissions   object[]   

User permissions.

clinicians   object[]   

Assigned clinicians.

devices   object[]   

Assigned devices.

patients   object[]   

Assigned patients.

invitations   object[]   

User invitations.

Update user account

requires authentication

Example request:
curl --request PUT \
    "http://localhost:8000/api/user/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: multipart/form-data" \
    --header "Accept: application/json" \
    --form "mrn=MRN12345678"\
    --form "name=Tom Smith"\
    --form "email=test@example.com"\
    --form "language=en"\
    --form "address1=7797 Yundt Row Apt. 185"\
    --form "address2=Cindychester, ME 66232"\
    --form "postal_code=89902-2932"\
    --form "city=West Rahsaanview"\
    --form "country=HR"\
    --form "clinic_name=Aether"\
    --form "clinic_location=7797 Yundt Row Apt. 185"\
    --form "image_delete=1"\
    --form "mfa_enabled=1"\
    --form "mfa_method=email"\
    --form "active=1"\
    --form "is_internal="\
    --form "is_ambassador="\
    --form "test_account="\
    --form "clinicians[]=2"\
    --form "notifications_timezone=Europe/Warsaw"\
    --form "notifications_at=8:00"\
    --form "role=Amputee"\
    --form "image=@/tmp/phpV4TGNs" \
    --form "public_image=@/tmp/phpOeZTnU" 
const url = new URL(
    "http://localhost:8000/api/user/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "multipart/form-data",
    "Accept": "application/json",
};

const body = new FormData();
body.append('mrn', 'MRN12345678');
body.append('name', 'Tom Smith');
body.append('email', 'test@example.com');
body.append('language', 'en');
body.append('address1', '7797 Yundt Row Apt. 185');
body.append('address2', 'Cindychester, ME 66232');
body.append('postal_code', '89902-2932');
body.append('city', 'West Rahsaanview');
body.append('country', 'HR');
body.append('clinic_name', 'Aether');
body.append('clinic_location', '7797 Yundt Row Apt. 185');
body.append('image_delete', '1');
body.append('mfa_enabled', '1');
body.append('mfa_method', 'email');
body.append('active', '1');
body.append('is_internal', '');
body.append('is_ambassador', '');
body.append('test_account', '');
body.append('clinicians[]', '2');
body.append('notifications_timezone', 'Europe/Warsaw');
body.append('notifications_at', '8:00');
body.append('role', 'Amputee');
body.append('image', document.querySelector('input[name="image"]').files[0]);
body.append('public_image', document.querySelector('input[name="public_image"]').files[0]);

fetch(url, {
    method: "PUT",
    headers,
    body,
}).then(response => response.json());

Example response (202):


{
    "id": 14,
    "mrn": "BLVXZB4E1793726644",
    "name": "Lucile Brakus PhD",
    "email": "1793726644rico19@example.com",
    "language": "en",
    "phone": "+13237548376",
    "phone_country": "TM",
    "phone_verified_at": null,
    "address1": "3224 Ismael Road Apt. 533",
    "address2": "Port Lawrencefurt, IN 36547",
    "postal_code": "94052-4830",
    "city": "Robel and Sons",
    "country": "FR",
    "clinic_name": "South Lempimouth",
    "clinic_location": "23371 Tre Tunnel Apt. 107\nNorth Stellaland, OH 08102-9981",
    "image": null,
    "public_image": null,
    "mfa_enabled": 0,
    "mfa_method": null,
    "mfa_verified_to": null,
    "location_id": null,
    "created_by": null,
    "active": 1,
    "is_ambassador": 0,
    "test_account": false,
    "notifications_timezone": null,
    "notifications_at": null,
    "created_at": "2026-11-03T17:24:04.000000Z",
    "updated_at": "2026-11-03T17:24:04.000000Z",
    "invitation_status": null,
    "acadle_invitation_status": null,
    "roles": [
        {
            "id": 6,
            "name": "Amputee"
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to update user data",
    "code": "USERS:UPDATE:INSUFFICIENT_PERMISSION"
}
 

Example response (403, Insufficient permission to assign role):


{
    "message": "Insufficient permission to assign this role as ClinicAdmin",
    "code": "USERS:UPDATE:INSUFFICIENT_PERMISSION_ASSIGN_ROLE"
}
 

Example response (404, User not found):


{
    "message": "User not found",
    "code": "USERS:UPDATE:USER_NOT_FOUND"
}
 

Request   

PUT api/user/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: multipart/form-data

Accept      

Example: application/json

URL Parameters

id   integer   

User ID. Example: 1

Body Parameters

mrn   string  optional  

Medical Record Number. Example: MRN12345678

name   string  optional  

User full name. Example: Tom Smith

email   string  optional  

User email. MUST_BE_EMAIL. Example: test@example.com

language   string  optional  

User language. Example: en

address1   string  optional  

Address line 1. MAXIMUM:STRING_LENGTH:100. Example: 7797 Yundt Row Apt. 185

address2   string  optional  

Address line 2. MAXIMUM:STRING_LENGTH:100. Example: Cindychester, ME 66232

postal_code   string  optional  

Postal code. MAXIMUM:STRING_LENGTH:100. Example: 89902-2932

city   string  optional  

City. MAXIMUM:STRING_LENGTH:100. Example: West Rahsaanview

country   string  optional  

Country. MAXIMUM:STRING_LENGTH:2. Example: HR

clinic_name   string  optional  

Clinic name. MAXIMUM:STRING_LENGTH:100. Example: Aether

clinic_location   string  optional  

Clinic location. MAXIMUM:STRING_LENGTH:100. Example: 7797 Yundt Row Apt. 185

image   file  optional  

Attached user image. MUST_BE_IMAGE MAXIMUM:FILE_KB:5120. Example: /tmp/phpV4TGNs

image_delete   boolean  optional  

Send this parameter instead of image to remove previously added image. Example: true

public_image   file  optional  

MUST_BE_IMAGE MAXIMUM:FILE_KB:5120. Example: /tmp/phpOeZTnU

mfa_enabled   boolean  optional  

Super Admin only: MFA enabled. Example: true

mfa_method   string  optional  

Super Admin only: MFA method. Example: email

Must be one of:
  • email
  • sms
active   boolean  optional  

User active status (0 - inactive, 1 - active). Example: true

is_internal   boolean  optional  

Super Admin only: Mark user as internal (QA/ADP team). Example: false

is_ambassador   boolean  optional  

Mark user as a Community ambassador. Example: false

test_account   boolean  optional  

Example: false

clinicians   string[]  optional  

Clinician ID. The id of an existing record in the App\Models\User table.

notifications_timezone   string  optional  

User notifications timezone. Example: Europe/Warsaw

notifications_at   string  optional  

Time when notifications and reminders should be sent. Format: HH:MM. Set null to notify at default time. Must be a valid date in the format H:i. Example: 8:00

role   string  optional  

Role name. One of: SuperAdmin, CommunityAdmin, ClinicAdmin, Clinician, ClinicianSupport, Amputee. Example: Amputee

Must be one of:
  • Amputee
  • ClinicianSupport
  • Clinician
  • ClinicAdmin
  • CommunityAdmin
  • SuperAdmin
  • AcadleUser
permissions   object  optional  

List of permissions given to this user. For now, it is used for ClinicianSupport role to give access to specific actions (for example user.update means that user can update users). You can assign both predefined permissions (already checked by some endpoints) and custom permissions (check them on your own). You can also use wildcards like user.* to give access to all actions for given scope.

Response

Response Fields

id   integer   

User ID.

mrn   string   

Medical Record Number.

name   string   

User full name.

email   string   

User email address.

language   string   

User preferred language.

phone   string   

User phone number.

phone_country   string   

Phone country code.

phone_verified_at   string   

Phone verification date.

address1   string   

Address line 1.

address2   string   

Address line 2.

postal_code   string   

Postal code.

city   string   

City.

country   string   

Country code (ISO 3166 Alpha-2).

clinic_name   string   

Clinic name.

clinic_location   string   

Clinic location.

image   string   

User profile image URL.

mfa_enabled   boolean   

Whether MFA is enabled.

mfa_method   string   

Preferred MFA method.

mfa_verified_to   string   

MFA session verified until.

location_id   integer   

Location ID.

created_by   integer   

ID of the user who created this account.

active   boolean   

Whether the user account is active.

is_internal   boolean   

Super Admin only: Whether the user is an internal user (QA/ADP team).

notifications_timezone   string   

Timezone for notifications.

notifications_at   string   

Preferred notification time.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

invitation_status   string   

User invitation status.

acadle_invitation_status   string   

Acadle invitation status.

roles   object[]   

User roles.

id   integer   

Role ID.

name   string   

Role name.

permissions   object[]   

User permissions.

clinicians   object[]   

Assigned clinicians.

devices   object[]   

Assigned devices.

patients   object[]   

Assigned patients.

invitations   object[]   

User invitations.

Update user phone number

requires authentication

Phone number has to be verified after update. Call /api/mfa/phone/verify with user-filled code after performing this operation. If value is "0" phone number will be removed without any verification.

Example request:
curl --request POST \
    "http://localhost:8000/api/user/1/phone" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"phone\": \"+1 (208) 892-0242\",
    \"phone_country\": \"US\"
}"
const url = new URL(
    "http://localhost:8000/api/user/1/phone"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "phone": "+1 (208) 892-0242",
    "phone_country": "US"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, Phone number removed):


{
    "message": "Phone number removed",
    "code": "USERS:SET_PHONE:REMOVED"
}
 

Example response (200, Phone number updated):


{
    "message": "Verification code sent. Call /api/mfa/phone/verify to verify phone number.",
    "code": "USERS:SET_PHONE:UPDATED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to update user data",
    "code": "USERS:SET_PHONE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, User not found):


{
    "message": "User not found",
    "code": "USERS:SET_PHONE:USER_NOT_FOUND"
}
 

Example response (500, Code send failed):


{
    "message": "Verification code sending failed",
    "code": "USERS:SET_PHONE:SEND_FAILED"
}
 

Request   

POST api/user/{id}/phone

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

User ID. Example: 1

Body Parameters

phone   string  optional  

User phone number. Pass "0" to remove current one. Example: +1 (208) 892-0242

phone_country   string  optional  

Phone number's country (2 characters). SIZE:STRING_LENGTH:2. Example: US

Delete user account

requires authentication

Example request:
curl --request DELETE \
    "http://localhost:8000/api/user/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/user/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (202, OK):


{
    "message": "User deleted",
    "code": "USERS:DELETE:DELETED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to delete user",
    "code": "USERS:DELETE:INSUFFICIENT_PERMISSION"
}
 

Example response (403, User has existing patients or chat rooms):


{
    "message": "Cannot delete: user has existing patients or chat rooms (patients: 1, chat rooms: 0)",
    "code": "USERS:DELETE:HAS_PATIENTS"
}
 

Example response (403, User has existing devices):


{
    "message": "Cannot delete: user has existing devices (as patient: 1, as clinician: 0)",
    "code": "USERS:DELETE:HAS_DEVICES"
}
 

Example response (403, User has open P2P sessions):


{
    "message": "Cannot delete: user has open P2P sessions (as patient: 0, as clinician: 1)",
    "code": "USERS:DELETE:HAS_P2P_SESSIONS"
}
 

Example response (404, User not found):


{
    "message": "User not found",
    "code": "USERS:DELETE:USER_NOT_FOUND"
}
 

Example response (500, Server error):


{
    "message": "Server error: user not deleted",
    "code": "USERS:DELETE:SERVER_ERROR"
}
 

Request   

DELETE api/user/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

User ID. Example: 1

Delete own user account

requires authentication

Example request:
curl --request DELETE \
    "http://localhost:8000/api/user" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"password\": \"password\"
}"
const url = new URL(
    "http://localhost:8000/api/user"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "password": "password"
};

fetch(url, {
    method: "DELETE",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (202, OK):


{
    "message": "User deleted",
    "code": "USERS:SELF_DELETE:DELETED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to delete user",
    "code": "USERS:SELF_DELETE:INSUFFICIENT_PERMISSION"
}
 

Example response (422, Invalid password):


{
    "message": "Invalid password",
    "code": "USERS:SELF_DELETE:INVALID_PASSWORD"
}
 

Example response (500, Server error):


{
    "message": "Server error: user not deleted",
    "code": "USERS:SELF_DELETE:SERVER_ERROR"
}
 

Request   

DELETE api/user

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

password   string   

User account password. Example: password

Change other user password

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/user/1/password" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"password\": \"nihil\"
}"
const url = new URL(
    "http://localhost:8000/api/user/1/password"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "password": "nihil"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (202, OK):


{
    "message": "User password changed",
    "code": "USERS:PASSWORD_CHANGE:CHANGED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to change user password",
    "code": "USERS:PASSWORD_CHANGE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, User not found):


{
    "message": "User not found",
    "code": "USERS:PASSWORD_CHANGE:USER_NOT_FOUND"
}
 

Request   

POST api/user/{id}/password

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

User ID. Example: 1

Body Parameters

password   string   

Example: nihil

Get user devices list

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/user/1/devices" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/user/1/devices"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 3,
            "serial": "8dd2b8c0-c4af-3fee-a294-b66597176ec8",
            "bluetooth_id": "c5a1c016-78d7-3f5d-aa7e-a1aa12f867dc",
            "company_id": null,
            "model_id": 1,
            "amputee_id": null,
            "clinician_id": null,
            "firmware_version_id": null,
            "pcb_version_id": null,
            "reverse_magnets": 0,
            "is_electrode": 0,
            "active": 1,
            "last_activity_at": "0000-00-00 00:00:00",
            "first_connected_at": null,
            "measurements": null,
            "created_at": "2026-11-03T17:24:04.000000Z",
            "updated_at": "2026-11-03T17:24:04.000000Z",
            "first_config_change_at": null,
            "model": {
                "id": 1,
                "name": "Zeus hand v1",
                "type": "arm",
                "orientation": "left",
                "active": 1,
                "created_at": "2026-11-03T17:24:04.000000Z",
                "updated_at": "2026-11-03T17:24:04.000000Z"
            }
        },
        {
            "id": 4,
            "serial": "008e15e6-e991-37f6-a020-21382462d86c",
            "bluetooth_id": "1f13a04a-2b31-310e-9a1f-e310f9612ecc",
            "company_id": null,
            "model_id": 2,
            "amputee_id": null,
            "clinician_id": null,
            "firmware_version_id": null,
            "pcb_version_id": null,
            "reverse_magnets": 0,
            "is_electrode": 0,
            "active": 1,
            "last_activity_at": "0000-00-00 00:00:00",
            "first_connected_at": null,
            "measurements": null,
            "created_at": "2026-11-03T17:24:04.000000Z",
            "updated_at": "2026-11-03T17:24:04.000000Z",
            "first_config_change_at": null,
            "model": {
                "id": 2,
                "name": "Zeus hand v1",
                "type": "arm",
                "orientation": "left",
                "active": 1,
                "created_at": "2026-11-03T17:24:04.000000Z",
                "updated_at": "2026-11-03T17:24:04.000000Z"
            }
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to view user devices",
    "code": "USERS:DEVICES:INSUFFICIENT_PERMISSION"
}
 

Example response (404, User not found):


{
    "message": "User not found",
    "code": "USERS:DEVICES:USER_NOT_FOUND"
}
 

Request   

GET api/user/{id}/devices

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

User ID. Example: 1

Query Parameters

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

extend   string  optional  

Comma-separated list of relation extensions (available: model, firmwareVersion, pcbVersion, joinedDevices, joinedElectrodes).

Response

Response Fields

items   object   
id   integer   

Device ID.

serial   string   

Device serial number.

bluetooth_id   string   

Bluetooth identifier.

model_id   integer   

Device model ID.

amputee_id   integer   

Assigned patient (amputee) user ID.

firmware_version_id   integer   

Firmware version ID.

pcb_version_id   integer   

PCB version ID.

company_id   integer   

Company ID.

reverse_magnets   boolean   

Whether magnets are reversed.

is_electrode   boolean   

Whether this device is an electrode.

active   boolean   

Whether the device is active.

last_activity_at   string   

Last activity timestamp.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

model   object   

Device model details.

id   integer   

Device model ID.

name   string   

Model name.

type   string   

Model type.

orientation   string   

Model orientation.

active   boolean   

Whether the model is active.

amputee   object   

Assigned patient (amputee) user.

id   integer   

User ID.

name   string   

User full name.

email   string   

User email address.

clinicians   object[]   

Clinicians assigned to this device.

firmwareVersion   object   

Firmware version details.

id   integer   

Firmware version ID.

name   string   

Version name.

file_firmware   string   

Firmware file URL.

file_firmware_v2   string   

Firmware v2 file URL.

file_firmware_v3   string   

Firmware v3 file URL.

file_firmware_v4   string   

Firmware v4 file URL.

file_firmware_v5   string   

Firmware v5 file URL.

file_firmware_new_pcb   string   

New PCB firmware file URL.

file_bootloader   string   

Bootloader file URL.

file_bootloader_v2   string   

Bootloader v2 file URL.

file_bootloader_v3   string   

Bootloader v3 file URL.

file_bootloader_v4   string   

Bootloader v4 file URL.

changelog   string   

Changelog file URL.

pcbVersion   object   

PCB version details.

id   integer   

PCB version ID.

name   string   

Version name.

hardware_id   string   

Hardware identifier.

joinedDevices   object[]   

Joined hand devices.

joinedElectrodes   object[]   

Joined electrode devices.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

Attach patient to clinician

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/user/attach" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"email\": \"name@domain.com\"
}"
const url = new URL(
    "http://localhost:8000/api/user/attach"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "email": "name@domain.com"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (202, OK):


{
    "message": "Patient attached",
    "code": "USERS:ATTACH:ATTACHED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to attach patients",
    "code": "USERS:ATTACH:INSUFFICIENT_PERMISSION"
}
 

Example response (403, Patient is already assigned to another clinician):


{
    "message": "Patient is already assigned to another clinician",
    "code": "USERS:ATTACH:ALREADY_ASSIGNED"
}
 

Example response (403, User reached the temporary limit of attached devices):


{
    "message": "Reached the limit of assigned devices",
    "code": "USERS:ATTACH:LIMIT_REACHED"
}
 

Example response (404, User not found):


{
    "message": "User not found",
    "code": "USERS:ATTACH:USER_NOT_FOUND"
}
 

Request   

POST api/user/attach

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

email   string   

User e-mail address. MUST_BE_EMAIL. Example: name@domain.com

Detach patient from clinician

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/user/1/detach" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/user/1/detach"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (202, OK):


{
    "message": "Patient detached",
    "code": "USERS:DETACH:DETACHED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to detach patient",
    "code": "USERS:DETACH:INSUFFICIENT_PERMISSION"
}
 

Example response (403, Cannot detach last clinician):


{
    "message": "Cannot detach patient's last clinician",
    "code": "USERS:DETACH:CANNOT_DETACH_LAST_CLINICIAN"
}
 

Example response (404, User not found):


{
    "message": "User not found",
    "code": "USERS:DETACH:USER_NOT_FOUND"
}
 

Request   

POST api/user/{id}/detach

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

User ID. Example: 1

Admin access to MFA codes

requires authentication

Requires admin.mfa_access permission.

Example request:
curl --request GET \
    --get "http://localhost:8000/api/user/1/mfa-codes" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/user/1/mfa-codes"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, OK):


[
    {
        "id": 1,
        "user_id": 1,
        "code": "123456",
        "channel": "email",
        "expires": "2025-05-09 12:00:00"
    }
]
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to access MFA codes",
    "code": "USERS:ADMIN_MFA_ACCESS:INSUFFICIENT_PERMISSION"
}
 

Example response (404, User not found):


{
    "message": "User not found",
    "code": "USERS:ADMIN_MFA_ACCESS:USER_NOT_FOUND"
}
 

Request   

GET api/user/{id}/mfa-codes

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

User ID. Example: 1

Export clinician devices

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/clinician-devices/csv" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/clinician-devices/csv"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, CSV file):


{
    "file": "https://staging-us-east-2-aether-biomedical-s3-us-bucket.s3.us-east-2.amazonaws.com/clinicians/clinicians-devices-1759228216.csv",
    "expires": "2026-07-02 10:40:00"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to export clinician devices",
    "code": "USERS:EXPORT_CLINICIAN_DEVICES:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/clinician-devices/csv

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Get user mobile consents

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/consents" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/consents"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


[
    {
        "id": 1,
        "user_id": 34,
        "name": "explicabo",
        "value": "hic",
        "created_at": "1983-07-12T09:34:31.000000Z",
        "updated_at": "2016-11-16T14:35:47.000000Z"
    },
    {
        "id": 2,
        "user_id": 35,
        "name": "facilis",
        "value": "rem",
        "created_at": "2013-07-10T04:29:13.000000Z",
        "updated_at": "1974-01-17T17:44:25.000000Z"
    }
]
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to use mobile consents",
    "code": "USERS:GET_MOBILE_CONSENTS:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/consents

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Response

Response Fields

id   integer   

Consent record ID.

user_id   integer   

Associated user ID.

name   string   

Consent name/key.

value   string   

Consent value.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Set user mobile consent

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/consents" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Push notifications\",
    \"value\": 1
}"
const url = new URL(
    "http://localhost:8000/api/consents"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Push notifications",
    "value": 1
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


[
    {
        "id": 3,
        "user_id": 36,
        "name": "officiis",
        "value": "dicta",
        "created_at": "1980-04-07T02:35:14.000000Z",
        "updated_at": "1978-12-07T15:10:37.000000Z"
    },
    {
        "id": 4,
        "user_id": 37,
        "name": "consequuntur",
        "value": "culpa",
        "created_at": "1975-09-02T03:01:06.000000Z",
        "updated_at": "2006-03-23T10:15:48.000000Z"
    }
]
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to use mobile consents",
    "code": "USERS:SET_MOBILE_CONSENTS:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/consents

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

name   string   

Consent name. Example: Push notifications

value   string   

Consent value. Example: 1

Response

Response Fields

id   integer   

Consent record ID.

user_id   integer   

Associated user ID.

name   string   

Consent name/key.

value   string   

Consent value.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Clear user notifications

requires authentication

Clearing notifications allow to receive another notification on same day. This endpoint deletes notifications of type:

    This endpoint is intended for testing use only.
Example request:
curl --request DELETE \
    "http://localhost:8000/api/notifications/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/notifications/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (202, OK):


{
    "messages": "1 notifications deleted",
    "code": "USERS:CLEAR_NOTIFICATIONS:CLEARED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to update user data",
    "code": "USERS:CLEAR_NOTIFICATIONS:INSUFFICIENT_PERMISSION"
}
 

Example response (404, User not found):


{
    "message": "User not found",
    "code": "USERS:CLEAR_NOTIFICATIONS:USER_NOT_FOUND"
}
 

Request   

DELETE api/notifications/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

User ID. Example: 1

Versions

API endpoints for versions management

Get software versions

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/versions/software" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/versions/software"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


[
    {
        "id": 1,
        "name": "8.8.10",
        "created_at": "2026-11-03T17:26:16.000000Z",
        "updated_at": "2026-11-03T17:26:16.000000Z"
    },
    {
        "id": 2,
        "name": "8.24.22",
        "created_at": "2026-11-03T17:26:16.000000Z",
        "updated_at": "2026-11-03T17:26:16.000000Z"
    }
]
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to list versions",
    "code": "SOFTWARE_VERSION:LIST:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/versions/software

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Response

Response Fields

id   integer   

Software version ID.

name   string   

Version name.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Create software version

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/versions/software" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"1.0\"
}"
const url = new URL(
    "http://localhost:8000/api/versions/software"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "1.0"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 3,
    "name": "8.23.86",
    "created_at": "2026-11-03T17:26:16.000000Z",
    "updated_at": "2026-11-03T17:26:16.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to create versions",
    "code": "SOFTWARE_VERSION:CREATE:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/versions/software

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

name   string   

Name of version. Example: 1.0

Response

Response Fields

id   integer   

Software version ID.

name   string   

Version name.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Delete software version

requires authentication

Example request:
curl --request DELETE \
    "http://localhost:8000/api/versions/software/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/versions/software/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (202, OK):


{
    "message": "Version deleted",
    "code": "SOFTWARE_VERSION:DELETE:DELETED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to delete versions",
    "code": "SOFTWARE_VERSION:DELETE:INSUFFICIENT_PERMISSION"
}
 

Example response (403, Version in use (compatibility)):


{
    "message": "Cannot delete: version is used in compatibility entries (1)",
    "code": "SOFTWARE_VERSION:DELETE:VERSION_IN_USE_COMPATIBILITY"
}
 

Example response (404, Version not found):


{
    "message": "Version not found",
    "code": "SOFTWARE_VERSION:DELETE:VERSION_NOT_FOUND"
}
 

Request   

DELETE api/versions/software/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Software Version ID. Example: 1

Get firmware versions

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/versions/firmware" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/versions/firmware"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


[
    {
        "id": 2,
        "name": "0.2.10",
        "file_firmware": "/tmp/faker3FrU23",
        "file_firmware_v2": "/tmp/fakerchrUwt",
        "file_firmware_v3": "/tmp/faker69MOO3",
        "file_firmware_v4": "/tmp/fakerAP0DK0",
        "file_firmware_v5": "/tmp/fakerL3WEB2",
        "file_firmware_new_pcb": "/tmp/fakerBM6lut",
        "file_bootloader": "/tmp/fakerr47RVZ",
        "file_bootloader_v2": "/tmp/faker5QFEiH",
        "file_bootloader_v3": "/tmp/fakerzg5KMj",
        "file_bootloader_v4": "/tmp/fakercVLfge",
        "changelog": "Sunt voluptatibus officia aliquid cum. Perspiciatis quod vero et aut et consequatur nulla. Odit quos omnis occaecati. Omnis dolore et ipsam pariatur veritatis. Ut quas ut maxime id non et saepe eos.",
        "created_at": "2026-11-03T17:26:16.000000Z",
        "updated_at": "2026-11-03T17:26:16.000000Z"
    },
    {
        "id": 3,
        "name": "7.21.84",
        "file_firmware": "/tmp/fakerzPTiwL",
        "file_firmware_v2": "/tmp/faker9Nyizr",
        "file_firmware_v3": "/tmp/faker19PWhk",
        "file_firmware_v4": "/tmp/fakerlzQ61R",
        "file_firmware_v5": "/tmp/fakerupqhz9",
        "file_firmware_new_pcb": "/tmp/fakerM6BMVs",
        "file_bootloader": "/tmp/fakeruj6ggV",
        "file_bootloader_v2": "/tmp/fakerrTa3HS",
        "file_bootloader_v3": "/tmp/fakerd6Wmfr",
        "file_bootloader_v4": "/tmp/fakerueznnY",
        "changelog": "Molestiae atque dolor sed suscipit. Quibusdam et nihil voluptatem ut. Ipsa voluptatem saepe quas.",
        "created_at": "2026-11-03T17:26:16.000000Z",
        "updated_at": "2026-11-03T17:26:16.000000Z"
    }
]
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to list versions",
    "code": "FIRMWARE_VERSION:LIST:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/versions/firmware

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

extend   string  optional  

Comma-separated list of relation extensions (available: models).

Response

Response Fields

id   integer   

Firmware version ID.

name   string   

Version name.

file_firmware   string   

Firmware file URL.

file_firmware_v2   string   

Firmware v2 file URL.

file_firmware_v3   string   

Firmware v3 file URL.

file_firmware_v4   string   

Firmware v4 file URL.

file_firmware_v5   string   

Firmware v5 file URL.

file_firmware_new_pcb   string   

New PCB firmware file URL.

file_bootloader   string   

Bootloader file URL.

file_bootloader_v2   string   

Bootloader v2 file URL.

file_bootloader_v3   string   

Bootloader v3 file URL.

file_bootloader_v4   string   

Bootloader v4 file URL.

changelog   string   

Changelog file URL.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Create firmware version

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/versions/firmware" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: multipart/form-data" \
    --header "Accept: application/json" \
    --form "name=1.0"\
    --form "models[]=1"\
    --form "changelog[][language]=en"\
    --form "changelog[][changelog]=Fixed bug with the connection"\
    --form "file_firmware=@/tmp/phplnXgr0" \
    --form "file_firmware_v2=@/tmp/phpEAUlrV" \
    --form "file_firmware_v3=@/tmp/phpy7d6SN" \
    --form "file_firmware_v4=@/tmp/phpWwQ7Vl" \
    --form "file_firmware_v5=@/tmp/phptsqgVA" \
    --form "file_firmware_new_pcb=@/tmp/php8THeXd" \
    --form "file_bootloader=@/tmp/phpLrJ3np" \
    --form "file_bootloader_v2=@/tmp/phpo1pqo4" \
    --form "file_bootloader_v3=@/tmp/php0BvCBQ" \
    --form "file_bootloader_v4=@/tmp/phpjxDVEJ" 
const url = new URL(
    "http://localhost:8000/api/versions/firmware"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "multipart/form-data",
    "Accept": "application/json",
};

const body = new FormData();
body.append('name', '1.0');
body.append('models[]', '1');
body.append('changelog[][language]', 'en');
body.append('changelog[][changelog]', 'Fixed bug with the connection');
body.append('file_firmware', document.querySelector('input[name="file_firmware"]').files[0]);
body.append('file_firmware_v2', document.querySelector('input[name="file_firmware_v2"]').files[0]);
body.append('file_firmware_v3', document.querySelector('input[name="file_firmware_v3"]').files[0]);
body.append('file_firmware_v4', document.querySelector('input[name="file_firmware_v4"]').files[0]);
body.append('file_firmware_v5', document.querySelector('input[name="file_firmware_v5"]').files[0]);
body.append('file_firmware_new_pcb', document.querySelector('input[name="file_firmware_new_pcb"]').files[0]);
body.append('file_bootloader', document.querySelector('input[name="file_bootloader"]').files[0]);
body.append('file_bootloader_v2', document.querySelector('input[name="file_bootloader_v2"]').files[0]);
body.append('file_bootloader_v3', document.querySelector('input[name="file_bootloader_v3"]').files[0]);
body.append('file_bootloader_v4', document.querySelector('input[name="file_bootloader_v4"]').files[0]);

fetch(url, {
    method: "POST",
    headers,
    body,
}).then(response => response.json());

Example response (201):


{
    "id": 4,
    "name": "1.76.84",
    "file_firmware": "/tmp/fakerwgIzGj",
    "file_firmware_v2": "/tmp/fakerzrDFsc",
    "file_firmware_v3": "/tmp/fakerf38Cwd",
    "file_firmware_v4": "/tmp/fakerL9o2zV",
    "file_firmware_v5": "/tmp/fakernwjCF5",
    "file_firmware_new_pcb": "/tmp/fakerQR9bnl",
    "file_bootloader": "/tmp/faker56FsVN",
    "file_bootloader_v2": "/tmp/fakerHZb8hJ",
    "file_bootloader_v3": "/tmp/fakerPsqF5x",
    "file_bootloader_v4": "/tmp/fakersBFR9S",
    "changelog": "Magnam et nam et omnis impedit dolore corrupti est. Enim eum excepturi et modi sit et totam. Repellendus dolores sint nisi a dolorem voluptatem voluptatem sunt.",
    "created_at": "2026-11-03T17:26:17.000000Z",
    "updated_at": "2026-11-03T17:26:17.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to create versions",
    "code": "FIRMWARE_VERSION:CREATE:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/versions/firmware

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: multipart/form-data

Accept      

Example: application/json

Body Parameters

name   string   

Name of version. Example: 1.0

models   integer[]  optional  

Device Model ID. The id of an existing record in the App\Models\DeviceModel table.

file_firmware   file  optional  

Attached firmware file. Must be a file. MAXIMUM:FILE_KB:102400. Example: /tmp/phplnXgr0

file_firmware_v2   file  optional  

Attached firmware file. Must be a file. MAXIMUM:FILE_KB:102400. Example: /tmp/phpEAUlrV

file_firmware_v3   file  optional  

Attached firmware file. Must be a file. MAXIMUM:FILE_KB:102400. Example: /tmp/phpy7d6SN

file_firmware_v4   file  optional  

Attached firmware file. Must be a file. MAXIMUM:FILE_KB:102400. Example: /tmp/phpWwQ7Vl

file_firmware_v5   file  optional  

Attached firmware file. Must be a file. MAXIMUM:FILE_KB:102400. Example: /tmp/phptsqgVA

file_firmware_new_pcb   file  optional  

Attached firmware for new PCB file. Must be a file. MAXIMUM:FILE_KB:102400. Example: /tmp/php8THeXd

file_bootloader   file  optional  

Attached bootloader file. Must be a file. MAXIMUM:FILE_KB:102400. Example: /tmp/phpLrJ3np

file_bootloader_v2   file  optional  

Attached bootloader file. Must be a file. MAXIMUM:FILE_KB:102400. Example: /tmp/phpo1pqo4

file_bootloader_v3   file  optional  

Attached bootloader file. Must be a file. MAXIMUM:FILE_KB:102400. Example: /tmp/php0BvCBQ

file_bootloader_v4   file  optional  

Attached bootloader file. Must be a file. MAXIMUM:FILE_KB:102400. Example: /tmp/phpjxDVEJ

changelog   object[]  optional  

List of changelog translations.

language   string  optional  

Changelog language (2 characters code). Example: en

changelog   string  optional  

Changelog for the version. Example: Fixed bug with the connection

Response

Response Fields

id   integer   

Firmware version ID.

name   string   

Version name.

file_firmware   string   

Firmware file URL.

file_firmware_v2   string   

Firmware v2 file URL.

file_firmware_v3   string   

Firmware v3 file URL.

file_firmware_v4   string   

Firmware v4 file URL.

file_firmware_v5   string   

Firmware v5 file URL.

file_firmware_new_pcb   string   

New PCB firmware file URL.

file_bootloader   string   

Bootloader file URL.

file_bootloader_v2   string   

Bootloader v2 file URL.

file_bootloader_v3   string   

Bootloader v3 file URL.

file_bootloader_v4   string   

Bootloader v4 file URL.

changelog   string   

Changelog file URL.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Update firmware version

requires authentication

Example request:
curl --request PUT \
    "http://localhost:8000/api/versions/firmware/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: multipart/form-data" \
    --header "Accept: application/json" \
    --form "name=1.0"\
    --form "models[]=1"\
    --form "file_firmware_delete=1"\
    --form "file_firmware_v2_delete=1"\
    --form "file_firmware_v3_delete=1"\
    --form "file_firmware_v4_delete=1"\
    --form "file_firmware_v5_delete=1"\
    --form "file_firmware_new_pcb_delete=1"\
    --form "file_bootloader_delete=1"\
    --form "file_bootloader_v2_delete=1"\
    --form "file_bootloader_v3_delete=1"\
    --form "file_bootloader_v4_delete=1"\
    --form "changelog[][language]=en"\
    --form "changelog[][changelog]=Fixed bug with the connection"\
    --form "file_firmware=@/tmp/phpci42pU" \
    --form "file_firmware_v2=@/tmp/phpmb2jdT" \
    --form "file_firmware_v3=@/tmp/phpV5bFga" \
    --form "file_firmware_v4=@/tmp/phpwZ2JoE" \
    --form "file_firmware_v5=@/tmp/phpkYEgrg" \
    --form "file_firmware_new_pcb=@/tmp/phpdK3bfk" \
    --form "file_bootloader=@/tmp/phpW6gFpy" \
    --form "file_bootloader_v2=@/tmp/php9VthMg" \
    --form "file_bootloader_v3=@/tmp/phpWuhkfL" \
    --form "file_bootloader_v4=@/tmp/phpzLPKDc" 
const url = new URL(
    "http://localhost:8000/api/versions/firmware/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "multipart/form-data",
    "Accept": "application/json",
};

const body = new FormData();
body.append('name', '1.0');
body.append('models[]', '1');
body.append('file_firmware_delete', '1');
body.append('file_firmware_v2_delete', '1');
body.append('file_firmware_v3_delete', '1');
body.append('file_firmware_v4_delete', '1');
body.append('file_firmware_v5_delete', '1');
body.append('file_firmware_new_pcb_delete', '1');
body.append('file_bootloader_delete', '1');
body.append('file_bootloader_v2_delete', '1');
body.append('file_bootloader_v3_delete', '1');
body.append('file_bootloader_v4_delete', '1');
body.append('changelog[][language]', 'en');
body.append('changelog[][changelog]', 'Fixed bug with the connection');
body.append('file_firmware', document.querySelector('input[name="file_firmware"]').files[0]);
body.append('file_firmware_v2', document.querySelector('input[name="file_firmware_v2"]').files[0]);
body.append('file_firmware_v3', document.querySelector('input[name="file_firmware_v3"]').files[0]);
body.append('file_firmware_v4', document.querySelector('input[name="file_firmware_v4"]').files[0]);
body.append('file_firmware_v5', document.querySelector('input[name="file_firmware_v5"]').files[0]);
body.append('file_firmware_new_pcb', document.querySelector('input[name="file_firmware_new_pcb"]').files[0]);
body.append('file_bootloader', document.querySelector('input[name="file_bootloader"]').files[0]);
body.append('file_bootloader_v2', document.querySelector('input[name="file_bootloader_v2"]').files[0]);
body.append('file_bootloader_v3', document.querySelector('input[name="file_bootloader_v3"]').files[0]);
body.append('file_bootloader_v4', document.querySelector('input[name="file_bootloader_v4"]').files[0]);

fetch(url, {
    method: "PUT",
    headers,
    body,
}).then(response => response.json());

Example response (201):


{
    "id": 5,
    "name": "7.52.47",
    "file_firmware": "/tmp/fakerMoxpfs",
    "file_firmware_v2": "/tmp/fakerxwCIj3",
    "file_firmware_v3": "/tmp/fakerGVjPdd",
    "file_firmware_v4": "/tmp/fakersfUCtX",
    "file_firmware_v5": "/tmp/fakerFMwlt8",
    "file_firmware_new_pcb": "/tmp/fakertY9iP3",
    "file_bootloader": "/tmp/fakerzlZuTj",
    "file_bootloader_v2": "/tmp/faker78wGqu",
    "file_bootloader_v3": "/tmp/fakerrEhEzQ",
    "file_bootloader_v4": "/tmp/fakerxdGWHI",
    "changelog": "Porro suscipit corrupti quia sint ea incidunt omnis. Cum et eum optio quia aut qui. Nemo hic eaque aut sed et. Vero cum nostrum est et velit. Consequatur sequi aut accusantium quaerat ipsum.",
    "created_at": "2026-11-03T17:26:17.000000Z",
    "updated_at": "2026-11-03T17:26:17.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to update versions",
    "code": "FIRMWARE_VERSION:UPDATE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Version not found):


{
    "message": "Version not found",
    "code": "FIRMWARE_VERSION:UPDATE:VERSION_NOT_FOUND"
}
 

Request   

PUT api/versions/firmware/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: multipart/form-data

Accept      

Example: application/json

URL Parameters

id   integer   

Firmware Version ID. Example: 1

Body Parameters

name   string  optional  

Name of version. Example: 1.0

models   integer[]  optional  

Device Model ID. The id of an existing record in the App\Models\DeviceModel table.

file_firmware   file  optional  

Attached firmware file. Must be a file. MAXIMUM:FILE_KB:102400. Example: /tmp/phpci42pU

file_firmware_v2   file  optional  

Attached firmware file. Must be a file. MAXIMUM:FILE_KB:102400. Example: /tmp/phpmb2jdT

file_firmware_v3   file  optional  

Attached firmware file. Must be a file. MAXIMUM:FILE_KB:102400. Example: /tmp/phpV5bFga

file_firmware_v4   file  optional  

Attached firmware file. Must be a file. MAXIMUM:FILE_KB:102400. Example: /tmp/phpwZ2JoE

file_firmware_v5   file  optional  

Attached firmware file. Must be a file. MAXIMUM:FILE_KB:102400. Example: /tmp/phpkYEgrg

file_firmware_new_pcb   file  optional  

Attached firmware for new PCB file. Must be a file. MAXIMUM:FILE_KB:102400. Example: /tmp/phpdK3bfk

file_bootloader   file  optional  

Attached bootloader file. Must be a file. MAXIMUM:FILE_KB:102400. Example: /tmp/phpW6gFpy

file_bootloader_v2   file  optional  

Attached bootloader file. Must be a file. MAXIMUM:FILE_KB:102400. Example: /tmp/php9VthMg

file_bootloader_v3   file  optional  

Attached bootloader file. Must be a file. MAXIMUM:FILE_KB:102400. Example: /tmp/phpWuhkfL

file_bootloader_v4   file  optional  

Attached bootloader file. Must be a file. MAXIMUM:FILE_KB:102400. Example: /tmp/phpzLPKDc

changelog   object[]  optional  

List of changelog translations.

language   string  optional  

Changelog language (2 characters code). Example: en

changelog   string  optional  

Changelog for the version. Pass "NULL" string to remove the entry for given language. Example: Fixed bug with the connection

file_firmware_delete   string  optional  

Send this parameter with value 1 to remove existing file for file_firmware. Example: 1

file_firmware_v2_delete   string  optional  

Send this parameter with value 1 to remove existing file for file_firmware_v2. Example: 1

file_firmware_v3_delete   string  optional  

Send this parameter with value 1 to remove existing file for file_firmware_v3. Example: 1

file_firmware_v4_delete   string  optional  

Send this parameter with value 1 to remove existing file for file_firmware_v4. Example: 1

file_firmware_v5_delete   string  optional  

Send this parameter with value 1 to remove existing file for file_firmware_v5. Example: 1

file_firmware_new_pcb_delete   string  optional  

Send this parameter with value 1 to remove existing file for file_firmware_new_pcb. Example: 1

file_bootloader_delete   string  optional  

Send this parameter with value 1 to remove existing file for file_bootloader. Example: 1

file_bootloader_v2_delete   string  optional  

Send this parameter with value 1 to remove existing file for file_bootloader_v2. Example: 1

file_bootloader_v3_delete   string  optional  

Send this parameter with value 1 to remove existing file for file_bootloader_v3. Example: 1

file_bootloader_v4_delete   string  optional  

Send this parameter with value 1 to remove existing file for file_bootloader_v4. Example: 1

Response

Response Fields

id   integer   

Firmware version ID.

name   string   

Version name.

file_firmware   string   

Firmware file URL.

file_firmware_v2   string   

Firmware v2 file URL.

file_firmware_v3   string   

Firmware v3 file URL.

file_firmware_v4   string   

Firmware v4 file URL.

file_firmware_v5   string   

Firmware v5 file URL.

file_firmware_new_pcb   string   

New PCB firmware file URL.

file_bootloader   string   

Bootloader file URL.

file_bootloader_v2   string   

Bootloader v2 file URL.

file_bootloader_v3   string   

Bootloader v3 file URL.

file_bootloader_v4   string   

Bootloader v4 file URL.

changelog   string   

Changelog file URL.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Delete firmware version

requires authentication

Example request:
curl --request DELETE \
    "http://localhost:8000/api/versions/firmware/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/versions/firmware/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (202, OK):


{
    "message": "Version deleted",
    "code": "FIRMWARE_VERSION:DELETE:DELETED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to delete versions",
    "code": "FIRMWARE_VERSION:DELETE:INSUFFICIENT_PERMISSION"
}
 

Example response (403, Version in use (compatibility)):


{
    "message": "Cannot delete: version is used in compatibility entries (1)",
    "code": "FIRMWARE_VERSION:DELETE:VERSION_IN_USE_COMPATIBILITY"
}
 

Example response (403, Version in use (device)):


{
    "message": "Cannot delete: version is assigned to devices (1)",
    "code": "FIRMWARE_VERSION:DELETE:VERSION_IN_USE_DEVICE"
}
 

Example response (404, Version not found):


{
    "message": "Version not found",
    "code": "FIRMWARE_VERSION:DELETE:VERSION_NOT_FOUND"
}
 

Request   

DELETE api/versions/firmware/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Firmware Version ID. Example: 1

Get PCB versions

requires authentication

Example request:
curl --request GET \
    --get "http://localhost:8000/api/versions/pcb" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/versions/pcb"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


[
    {
        "id": 2,
        "name": "4.35.26",
        "hardware_id": "",
        "created_at": "2026-11-03T17:26:17.000000Z",
        "updated_at": "2026-11-03T17:26:17.000000Z"
    },
    {
        "id": 3,
        "name": "7.93.7",
        "hardware_id": "",
        "created_at": "2026-11-03T17:26:17.000000Z",
        "updated_at": "2026-11-03T17:26:17.000000Z"
    }
]
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to list versions",
    "code": "PCB_VERSION:LIST:INSUFFICIENT_PERMISSION"
}
 

Request   

GET api/versions/pcb

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

extend   string  optional  

Comma-separated list of relation extensions (available: models).

Response

Response Fields

id   integer   

PCB version ID.

name   string   

Version name.

hardware_id   string   

Hardware identifier.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Create PCB version

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/versions/pcb" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"1.0\",
    \"models\": [
        1
    ],
    \"hardware_id\": \"1\"
}"
const url = new URL(
    "http://localhost:8000/api/versions/pcb"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "1.0",
    "models": [
        1
    ],
    "hardware_id": "1"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 4,
    "name": "9.79.56",
    "hardware_id": "",
    "created_at": "2026-11-03T17:26:17.000000Z",
    "updated_at": "2026-11-03T17:26:17.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to create versions",
    "code": "PCB_VERSION:CREATE:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/versions/pcb

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

name   string   

Name of version. Example: 1.0

models   integer[]  optional  

Device Model ID. The id of an existing record in the App\Models\DeviceModel table.

hardware_id   string   

Hardware ID name. Example: 1

Response

Response Fields

id   integer   

PCB version ID.

name   string   

Version name.

hardware_id   string   

Hardware identifier.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Update PCB version

requires authentication

Example request:
curl --request PUT \
    "http://localhost:8000/api/versions/pcb/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"1.0\",
    \"models\": [
        1
    ],
    \"hardware_id\": \"1\"
}"
const url = new URL(
    "http://localhost:8000/api/versions/pcb/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "1.0",
    "models": [
        1
    ],
    "hardware_id": "1"
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 5,
    "name": "3.15.25",
    "hardware_id": "",
    "created_at": "2026-11-03T17:26:17.000000Z",
    "updated_at": "2026-11-03T17:26:17.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to update versions",
    "code": "PCB_VERSION:UPDATE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Version not found):


{
    "message": "Version not found",
    "code": "PCB_VERSION:UPDATE:VERSION_NOT_FOUND"
}
 

Request   

PUT api/versions/pcb/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

PCB Version ID. Example: 1

Body Parameters

name   string  optional  

Name of version. Example: 1.0

models   integer[]  optional  

Device Model ID. The id of an existing record in the App\Models\DeviceModel table.

hardware_id   string  optional  

Hardware ID name. Example: 1

Response

Response Fields

id   integer   

PCB version ID.

name   string   

Version name.

hardware_id   string   

Hardware identifier.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

Delete PCB version

requires authentication

Example request:
curl --request DELETE \
    "http://localhost:8000/api/versions/pcb/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/versions/pcb/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (202, OK):


{
    "message": "Version deleted",
    "code": "PCB_VERSION:DELETE:DELETED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to delete versions",
    "code": "PCB_VERSION:DELETE:INSUFFICIENT_PERMISSION"
}
 

Example response (403, Version in use (compatibility)):


{
    "message": "Cannot delete: version is used in compatibility entries (1)",
    "code": "PCB_VERSION:DELETE:VERSION_IN_USE_COMPATIBILITY"
}
 

Example response (403, Version in use (device)):


{
    "message": "Cannot delete: version is assigned to devices (1)",
    "code": "PCB_VERSION:DELETE:VERSION_IN_USE_DEVICE"
}
 

Example response (404, Version not found):


{
    "message": "Version not found",
    "code": "PCB_VERSION:DELETE:VERSION_NOT_FOUND"
}
 

Request   

DELETE api/versions/pcb/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

PCB Version ID. Example: 1

List compatibilities

requires authentication

Most typical scenarios to use compatibility matrix:

Example request:
curl --request GET \
    --get "http://localhost:8000/api/versions/compatibility?model=1&software=1.2&firmware=1.5&pcb=1.0&summary=" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/versions/compatibility"
);

const params = {
    "model": "1",
    "software": "1.2",
    "firmware": "1.5",
    "pcb": "1.0",
    "summary": "0",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "paginator": {
        "total": 2,
        "count": 2,
        "perpage": 20,
        "current_page": 1,
        "last_page": 1
    },
    "items": [
        {
            "id": 1,
            "device_model_id": 10,
            "software_version_id": 4,
            "firmware_version_id": 14,
            "pcb_version_id": 6,
            "is_fully_compatible": 1,
            "created_at": "2026-11-03T17:26:17.000000Z",
            "updated_at": "2026-11-03T17:26:17.000000Z",
            "features": [
                {
                    "id": 1,
                    "compatibility_id": 1,
                    "feature_id": 5,
                    "is_compatible": 1,
                    "reason": "Repellendus iste vero et quam animi. Pariatur voluptatum tenetur vero error assumenda. Quam enim molestias officiis illum itaque.",
                    "created_at": "2026-11-03T17:26:17.000000Z",
                    "updated_at": "2026-11-03T17:26:17.000000Z",
                    "feature": {
                        "id": 5,
                        "name": "yellow",
                        "slug": "dolores-magni-impedit-recusandae-amet-ab",
                        "created_at": "2026-11-03T17:26:17.000000Z",
                        "updated_at": "2026-11-03T17:26:17.000000Z"
                    }
                },
                {
                    "id": 2,
                    "compatibility_id": 1,
                    "feature_id": 7,
                    "is_compatible": 0,
                    "reason": "Et incidunt voluptatem nostrum asperiores magnam hic. Ut ex voluptatibus ea ea. Corporis accusamus enim modi repudiandae facere sit.",
                    "created_at": "2026-11-03T17:26:17.000000Z",
                    "updated_at": "2026-11-03T17:26:17.000000Z",
                    "feature": {
                        "id": 7,
                        "name": "green",
                        "slug": "et-neque-rem-necessitatibus-ea-sit-et",
                        "created_at": "2026-11-03T17:26:17.000000Z",
                        "updated_at": "2026-11-03T17:26:17.000000Z"
                    }
                },
                {
                    "id": 3,
                    "compatibility_id": 1,
                    "feature_id": 9,
                    "is_compatible": 0,
                    "reason": "Vero aut provident eligendi. Quibusdam non ipsa vero. Perspiciatis nobis porro voluptas consequatur sit quae ea. Animi illum sint ducimus laboriosam. Officia voluptatem aliquid tenetur dolores earum.",
                    "created_at": "2026-11-03T17:26:17.000000Z",
                    "updated_at": "2026-11-03T17:26:17.000000Z",
                    "feature": {
                        "id": 9,
                        "name": "white",
                        "slug": "distinctio-veritatis-eius-sapiente-quis-enim-nulla-et",
                        "created_at": "2026-11-03T17:26:17.000000Z",
                        "updated_at": "2026-11-03T17:26:17.000000Z"
                    }
                }
            ]
        },
        {
            "id": 5,
            "device_model_id": 14,
            "software_version_id": 8,
            "firmware_version_id": 18,
            "pcb_version_id": 10,
            "is_fully_compatible": 1,
            "created_at": "2026-11-03T17:26:17.000000Z",
            "updated_at": "2026-11-03T17:26:17.000000Z",
            "features": [
                {
                    "id": 4,
                    "compatibility_id": 5,
                    "feature_id": 10,
                    "is_compatible": 1,
                    "reason": "Temporibus vitae sint at pariatur asperiores amet rerum. Laudantium error nostrum vel velit ipsam.",
                    "created_at": "2026-11-03T17:26:17.000000Z",
                    "updated_at": "2026-11-03T17:26:17.000000Z",
                    "feature": {
                        "id": 10,
                        "name": "maroon",
                        "slug": "est-reprehenderit-aut-cumque-ex-nemo-ipsam-delectus",
                        "created_at": "2026-11-03T17:26:17.000000Z",
                        "updated_at": "2026-11-03T17:26:17.000000Z"
                    }
                },
                {
                    "id": 5,
                    "compatibility_id": 5,
                    "feature_id": 12,
                    "is_compatible": 0,
                    "reason": "Quae quo dicta soluta dignissimos debitis et distinctio. Laboriosam ipsa doloremque ab repellat molestiae. Enim et quia nesciunt consequatur porro in ipsa labore.",
                    "created_at": "2026-11-03T17:26:17.000000Z",
                    "updated_at": "2026-11-03T17:26:17.000000Z",
                    "feature": {
                        "id": 12,
                        "name": "olive",
                        "slug": "ut-doloribus-aut-omnis-sed-saepe-voluptas",
                        "created_at": "2026-11-03T17:26:17.000000Z",
                        "updated_at": "2026-11-03T17:26:17.000000Z"
                    }
                },
                {
                    "id": 6,
                    "compatibility_id": 5,
                    "feature_id": 14,
                    "is_compatible": 0,
                    "reason": "Quis eveniet ut nihil doloribus corporis molestias nobis. Id eum necessitatibus numquam. Provident rerum praesentium ad ratione dicta id.",
                    "created_at": "2026-11-03T17:26:17.000000Z",
                    "updated_at": "2026-11-03T17:26:17.000000Z",
                    "feature": {
                        "id": 14,
                        "name": "gray",
                        "slug": "rerum-suscipit-veritatis-eos-voluptatem",
                        "created_at": "2026-11-03T17:26:17.000000Z",
                        "updated_at": "2026-11-03T17:26:17.000000Z"
                    }
                }
            ]
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to list versions compatibilities",
    "code": "COMPATIBILITY:LIST:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device model not found):


{
    "message": "Device model not found",
    "code": "COMPATIBILITY:LIST:DEVICE_MODEL_NOT_FOUND"
}
 

Example response (404, Software version not found):


{
    "message": "Software version not found",
    "code": "COMPATIBILITY:LIST:SOFTWARE_VERSION_NOT_FOUND"
}
 

Example response (404, Firmware version not found):


{
    "message": "Firmware version not found",
    "code": "COMPATIBILITY:LIST:FIRMWARE_VERSION_NOT_FOUND"
}
 

Example response (404, PCB version not found):


{
    "message": "PCB version not found",
    "code": "COMPATIBILITY:LIST:PCB_VERSION_NOT_FOUND"
}
 

Request   

GET api/versions/compatibility

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

model   integer  optional  

Filter compatibility matrix by device model ID. Example: 1

software   string  optional  

Filter compatibility matrix by software version name (not ID!). Example: 1.2

firmware   string  optional  

Filter compatibility matrix by firmware version name (not ID!). Example: 1.5

pcb   string  optional  

Filter compatibility matrix by PCB version name (not ID!). Example: 1.0

summary   boolean  optional  

Add to response the device model, software, firmware and PCB entries used to filter the list. Example: false

perpage   integer  optional  

Elements per page (Default: 20).

page   integer  optional  

Page number (Default: 1).

extend   string  optional  

Comma-separated list of relation extensions (available: features, features.feature, deviceModel, softwareVersion, firmwareVersion, pcbVersion).

Response

Response Fields

items   object   
id   integer   

Compatibility entry ID.

device_model_id   integer   

Associated device model ID.

software_version_id   integer   

Associated software version ID.

firmware_version_id   integer   

Associated firmware version ID.

pcb_version_id   integer   

Associated PCB version ID.

is_fully_compatible   boolean   

Whether all versions are fully compatible.

note   string   

Compatibility note.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

features   object[]   

Associated compatibility features.

paginator   object   
total   integer   

Total number of items.

count   integer   

Number of items on current page.

perpage   integer   

Items per page.

current_page   integer   

Current page number.

last_page   integer   

Last page number.

Add compatibility

requires authentication

Adds or updates compatibility matrix with assigned features. You can specify many versions for each list.

Example request:
curl --request POST \
    "http://localhost:8000/api/versions/compatibility" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"device_models\": [
        1
    ],
    \"software_versions\": [
        1
    ],
    \"firmware_versions\": [
        1
    ],
    \"pcb_versions\": [
        1
    ],
    \"is_fully_compatible\": true,
    \"features\": [
        {
            \"feature_id\": 1,
            \"compatible\": true,
            \"reason\": \"Firmware does not support...\"
        }
    ]
}"
const url = new URL(
    "http://localhost:8000/api/versions/compatibility"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "device_models": [
        1
    ],
    "software_versions": [
        1
    ],
    "firmware_versions": [
        1
    ],
    "pcb_versions": [
        1
    ],
    "is_fully_compatible": true,
    "features": [
        {
            "feature_id": 1,
            "compatible": true,
            "reason": "Firmware does not support..."
        }
    ]
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (202, OK):


{
    "added": 1,
    "updated": 0,
    "not_changed": 0
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to add versions compatibility",
    "code": "COMPATIBILITY:ADD:INSUFFICIENT_PERMISSION"
}
 

Request   

POST api/versions/compatibility

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

device_models   integer[]  optional  

Array of Device Model IDs. The id of an existing record in the App\Models\DeviceModel table.

software_versions   integer[]  optional  

Array of Software Version IDs. The id of an existing record in the App\Models\SoftwareVersion table.

firmware_versions   integer[]  optional  

Array of Firmware Version IDs. The id of an existing record in the App\Models\FirmwareVersion table.

pcb_versions   integer[]  optional  

Array of PCB Version IDs. The id of an existing record in the App\Models\PCBVersion table.

is_fully_compatible   boolean  optional  

Is this set of versions fully compatible?. Example: true

features   object[]  optional  

Array of features to be assigned to this set of versions.

feature_id   string  optional  

Product feature ID. The id of an existing record in the App\Models\ProductFeature table. Example: 1

compatible   boolean  optional  

Is this feature compatible?. Example: true

reason   string  optional  

Reason of incompatibility. Example: Firmware does not support...

Update compatibility

requires authentication

Example request:
curl --request PUT \
    "http://localhost:8000/api/versions/compatibility/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"device_model_id\": 19,
    \"software_version_id\": 6,
    \"firmware_version_id\": 10,
    \"pcb_version_id\": 12,
    \"is_fully_compatible\": true,
    \"features\": [
        {
            \"id\": 1,
            \"feature_id\": 1,
            \"compatible\": true,
            \"reason\": \"Firmware does not support...\",
            \"delete\": true
        }
    ]
}"
const url = new URL(
    "http://localhost:8000/api/versions/compatibility/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "device_model_id": 19,
    "software_version_id": 6,
    "firmware_version_id": 10,
    "pcb_version_id": 12,
    "is_fully_compatible": true,
    "features": [
        {
            "id": 1,
            "feature_id": 1,
            "compatible": true,
            "reason": "Firmware does not support...",
            "delete": true
        }
    ]
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (202):


{
    "id": 9,
    "device_model_id": 18,
    "software_version_id": 12,
    "firmware_version_id": 22,
    "pcb_version_id": 14,
    "is_fully_compatible": 1,
    "created_at": "2026-11-03T17:26:18.000000Z",
    "updated_at": "2026-11-03T17:26:18.000000Z",
    "features": [
        {
            "id": 7,
            "compatibility_id": 9,
            "feature_id": 15,
            "is_compatible": 1,
            "reason": "Eligendi dolores autem est. Et autem quis amet sed adipisci. Nihil ea corporis quas et. Repellat sint alias officia quidem ea.",
            "created_at": "2026-11-03T17:26:18.000000Z",
            "updated_at": "2026-11-03T17:26:18.000000Z",
            "feature": {
                "id": 15,
                "name": "maroon",
                "slug": "consectetur-at-sit-dolores-velit-doloremque-blanditiis-eveniet",
                "created_at": "2026-11-03T17:26:18.000000Z",
                "updated_at": "2026-11-03T17:26:18.000000Z"
            }
        }
    ]
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to update versions compatibility",
    "code": "COMPATIBILITY:UPDATE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Versions Compatibility not found):


{
    "message": "Versions compatibility not found",
    "code": "COMPATIBILITY:UPDATE:COMPATIBILITY_NOT_FOUND"
}
 

Request   

PUT api/versions/compatibility/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Versions Compatibility ID. Example: 1

Body Parameters

device_model_id   integer  optional  

Device Model ID. The id of an existing record in the App\Models\DeviceModel table. Example: 19

software_version_id   integer  optional  

Software Version ID. The id of an existing record in the App\Models\SoftwareVersion table. Example: 6

firmware_version_id   integer  optional  

Firmware Version ID. The id of an existing record in the App\Models\FirmwareVersion table. Example: 10

pcb_version_id   integer  optional  

PCB Version ID. The id of an existing record in the App\Models\PCBVersion table. Example: 12

is_fully_compatible   boolean  optional  

Is this set of versions fully compatible?. Example: true

features   object[]  optional  

Array of features to be assigned to this set of versions.

id   string  optional  

Versions compatibility feature ID. Use with delete parameter to delete specific entry. Do not confuse with feature_id, this field refers to ID of relation between versions compatibility and product feature. The id of an existing record in the App\Models\VersionsCompatibilityFeature table. Example: 1

feature_id   string  optional  

Product feature ID. The id of an existing record in the App\Models\ProductFeature table. Example: 1

compatible   boolean  optional  

Is this feature compatible?. Example: true

reason   string  optional  

Reason of incompatibility. Example: Firmware does not support...

delete   boolean  optional  

Pass 1 to detach feature from this set of versions, skip otherwise. Example: true

Response

Response Fields

id   integer   

Compatibility entry ID.

device_model_id   integer   

Associated device model ID.

software_version_id   integer   

Associated software version ID.

firmware_version_id   integer   

Associated firmware version ID.

pcb_version_id   integer   

Associated PCB version ID.

is_fully_compatible   boolean   

Whether all versions are fully compatible.

note   string   

Compatibility note.

created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

features   object[]   

Associated compatibility features.

Delete compatibility

requires authentication

Example request:
curl --request DELETE \
    "http://localhost:8000/api/versions/compatibility/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/versions/compatibility/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (202, OK):


{
    "message": "Versions compatibility deleted",
    "code": "COMPATIBILITY:DELETE:DELETED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to delete versions compatibility",
    "code": "COMPATIBILITY:DELETE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Versions compatibility not found):


{
    "message": "Versions compatibility not found",
    "code": "COMPATIBILITY:DELETE:COMPATIBILITY_NOT_FOUND"
}
 

Request   

DELETE api/versions/compatibility/{id}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Versions Compatibility ID. Example: 1

Video sessions

API endpoints for managing video sessions

List video sessions

requires authentication

Returns list of open video sessions. For most cases there should be exactly one entry (open/active session) or exactly zero entries (empty array, no open sessions).

Example request:
curl --request GET \
    --get "http://localhost:8000/api/sessions/video/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/sessions/video/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


[
    {
        "id": 1,
        "moderator_id": 127,
        "guest_id": 128,
        "jwt_moderator": "edaa35934d519354269ce55676ef2325e614105bbbf95c50a3e83e01a6143caa",
        "jwt_guest": "70ba33708cbfb103f1a8e34afef333ba7dc021022b2d9aaa583aabb8058d8d67",
        "room_name": "room_ff4985bcecf5f44d70abb1ad1708969d",
        "expires": 1793727607,
        "status": "open",
        "created_at": "2026-11-03T17:25:08.000000Z",
        "updated_at": "2026-11-03T17:25:08.000000Z"
    },
    {
        "id": 2,
        "moderator_id": 131,
        "guest_id": 132,
        "jwt_moderator": "469cc643a19fb75901e5d331b8dc14befa8b98810b246c2fb22fff0c382dc39e",
        "jwt_guest": "d51925d2d0dd4a170666f5387f0b5e43081bf2e3998ea5916d5076d89ab29495",
        "room_name": "room_1d7515dd279373ec649c8cb39e4c4e06",
        "expires": 1793727609,
        "status": "open",
        "created_at": "2026-11-03T17:25:10.000000Z",
        "updated_at": "2026-11-03T17:25:10.000000Z"
    }
]
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to access video session",
    "code": "VIDEO_SESSIONS:LIST:INSUFFICIENT_PERMISSION"
}
 

Example response (404, User not found):


{
    "message": "User not found",
    "code": "VIDEO_SESSIONS:LIST:USER_NOT_FOUND"
}
 

Request   

GET api/sessions/video/{guestId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

guestId   integer   

User ID (guest of the meeting). Example: 1

Response

Response Fields

id   integer   

Video session ID.

moderator_id   integer   

Moderator user ID.

guest_id   integer   

Guest user ID.

jwt_moderator   string   

JWT token for the moderator.

jwt_guest   string   

JWT token for the guest.

room_name   string   

Video room name.

expires   integer   

Session expiry as Unix timestamp.

status   string   

Session status.

Must be one of:
  • open
  • expired
  • closed
created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

moderator   object   

Moderator user.

guest   object   

Guest user.

Initialize video session

requires authentication

Creates JWT (JSON Web Token) and room name for Jitsi session. Logged-in user is a moderator of the session. JWT lifetime is 15 minutes.

Example request:
curl --request POST \
    "http://localhost:8000/api/sessions/video/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/sessions/video/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (201):


{
    "id": 3,
    "moderator_id": 135,
    "guest_id": 136,
    "jwt_moderator": "4504d14cc2f8ef2afb0ed7f26cc56121ed753bfbccb0ac0359177cebacaac7d0",
    "jwt_guest": "5ad1ebea0aafeb88c3114324ceb91db18605e498e476a787b5c2d2aec09d31e0",
    "room_name": "room_5e933da22c26cc5f803854b6336332ec",
    "expires": 1793727611,
    "status": "open",
    "created_at": "2026-11-03T17:25:12.000000Z",
    "updated_at": "2026-11-03T17:25:12.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to initialize video session",
    "code": "VIDEO_SESSIONS:INIT:INSUFFICIENT_PERMISSION"
}
 

Example response (404, User not found):


{
    "message": "User not found",
    "code": "VIDEO_SESSIONS:INIT:USER_NOT_FOUND"
}
 

Example response (500, Server error):


{
    "message": "Server error",
    "code": "VIDEO_SESSIONS:INIT:SERVER_ERROR"
}
 

Request   

POST api/sessions/video/{guestId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

guestId   integer   

User ID (guest of the meeting). Example: 1

Response

Response Fields

id   integer   

Video session ID.

moderator_id   integer   

Moderator user ID.

guest_id   integer   

Guest user ID.

jwt_moderator   string   

JWT token for the moderator.

jwt_guest   string   

JWT token for the guest.

room_name   string   

Video room name.

expires   integer   

Session expiry as Unix timestamp.

status   string   

Session status.

Must be one of:
  • open
  • expired
  • closed
created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

moderator   object   

Moderator user.

guest   object   

Guest user.

Refresh video session

requires authentication

Refreshes video session JWT tokens to extend session lifetime.

Example request:
curl --request POST \
    "http://localhost:8000/api/sessions/video/1/refresh" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/sessions/video/1/refresh"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (202):


{
    "id": 4,
    "moderator_id": 139,
    "guest_id": 140,
    "jwt_moderator": "863a6b632034d24c2d404f944dde121158683787b89cd065fd00d5fa76f68853",
    "jwt_guest": "b6cf348bc9b1a71d9db0ce76c3b2645a3d57e2a6b2119b58a236adbef5e05c43",
    "room_name": "room_dbe51c991f1e29d822311a4bb612aa18",
    "expires": 1793727614,
    "status": "open",
    "created_at": "2026-11-03T17:25:15.000000Z",
    "updated_at": "2026-11-03T17:25:15.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to refresh video session",
    "code": "VIDEO_SESSIONS:REFRESH:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Session not found):


{
    "message": "Video session not found",
    "code": "VIDEO_SESSIONS:REFRESH:SESSION_NOT_FOUND"
}
 

Request   

POST api/sessions/video/{sessionId}/refresh

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

sessionId   integer   

Video Session ID. Example: 1

Response

Response Fields

id   integer   

Video session ID.

moderator_id   integer   

Moderator user ID.

guest_id   integer   

Guest user ID.

jwt_moderator   string   

JWT token for the moderator.

jwt_guest   string   

JWT token for the guest.

room_name   string   

Video room name.

expires   integer   

Session expiry as Unix timestamp.

status   string   

Session status.

Must be one of:
  • open
  • expired
  • closed
created_at   string   

Creation timestamp.

updated_at   string   

Last update timestamp.

moderator   object   

Moderator user.

guest   object   

Guest user.

Close video session

requires authentication

Marks video session (database entry) as closed. Does not close session on Jitsi side.

Example request:
curl --request DELETE \
    "http://localhost:8000/api/sessions/video/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost:8000/api/sessions/video/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (202, OK):


{
    "message": "Video session closed",
    "code": "VIDEO_SESSIONS:CLOSE:CLOSED"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to close video session",
    "code": "VIDEO_SESSIONS:CLOSE:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Session not found):


{
    "message": "Video session not found",
    "code": "VIDEO_SESSIONS:CLOSE:SESSION_NOT_FOUND"
}
 

Request   

DELETE api/sessions/video/{sessionId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

sessionId   integer   

Video Session ID. Example: 1

Invite technical support

requires authentication

Sends notification to Slack channel. Link to meeting is valid for 10 minutes.

Example request:
curl --request POST \
    "http://localhost:8000/api/sessions/video/invite-support/1" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"room\": \"room_1081c785cf1464e4d6ebc3976561d490\",
    \"description\": \"Grip switching is not working, please join.\"
}"
const url = new URL(
    "http://localhost:8000/api/sessions/video/invite-support/1"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "room": "room_1081c785cf1464e4d6ebc3976561d490",
    "description": "Grip switching is not working, please join."
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, OK):


{
    "response": "success",
    "code": "VIDEO_SESSIONS:INVITE_TECH_SUPPORT:SUCCESS"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to invite tech support",
    "code": "VIDEO_SESSIONS:INVITE_TECH_SUPPORT:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "VIDEO_SESSIONS:INVITE_TECH_SUPPORT:DEVICE_NOT_FOUND"
}
 

Example response (500, Request failed):


{
    "response": "invalid_payload",
    "code": "VIDEO_SESSIONS:INVITE_TECH_SUPPORT:FAILED"
}
 

Request   

POST api/sessions/video/invite-support/{deviceId}

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

deviceId   integer   

Device ID (current device of the session). Example: 1

Body Parameters

room   string   

Name of Jitsi room to connect. Example: room_1081c785cf1464e4d6ebc3976561d490

description   string  optional  

Description of problem provided by clinician. Example: Grip switching is not working, please join.

Wizard

Endpoints related to the device setup wizard prompt (Guide vs Configurator choice)

Save wizard prompt choice

requires authentication

Example request:
curl --request POST \
    "http://localhost:8000/api/device/1/wizard-prompt" \
    --header "Authorization: Bearer {ACCESS_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"choice\": \"guide\",
    \"dont_ask_again\": false
}"
const url = new URL(
    "http://localhost:8000/api/device/1/wizard-prompt"
);

const headers = {
    "Authorization": "Bearer {ACCESS_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "choice": "guide",
    "dont_ask_again": false
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "id": 1,
    "user_id": 79,
    "device_id": 68,
    "choice": "configurator",
    "dont_ask_again": 0,
    "created_at": "2026-11-03T17:24:41.000000Z",
    "updated_at": "2026-11-03T17:24:41.000000Z"
}
 

Example response (403, Insufficient permission):


{
    "message": "Insufficient permission to update device config",
    "code": "WIZARD:SAVE_PROMPT:INSUFFICIENT_PERMISSION"
}
 

Example response (404, Device not found):


{
    "message": "Device not found",
    "code": "WIZARD:SAVE_PROMPT:DEVICE_NOT_FOUND"
}
 

Request   

POST api/device/{deviceId}/wizard-prompt

Headers

Authorization      

Example: Bearer {ACCESS_TOKEN}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

deviceId   integer   

Device ID. Example: 1

Body Parameters

choice   string   

What the clinician chose in the wizard prompt. Example: guide

Must be one of:
  • guide
  • configurator
  • dismissed
dont_ask_again   boolean  optional  

Whether user checked the "don't ask again" checkbox. Example: false