Documentation updated: 2026-09-16
This is official API for integration external applications with AI Helps products Beauty Pro and Fitness Pro.
API tends to follow REST principles.
API root URL for HTTP requests is: https://api.aihelps.com/v1/
API status page: https://api.aihelps.com/status
Questions about API: https://t.me/joinchat/J1-qj0gTp_i6EmE2rtvGvw (Telegram channel)
AI Helps API consultant: https://chatgpt.com/g/g-69a06e7b02908191818b18d2b77f1f40-ai-helps-api-consultant
About API
Getting started
To obtain credentials for a test sandbox:
- Contact AI Helps or your account manager to register an application via CRM (or ask support at api@aihelps.com).
- You receive
application_id and application_secret (shown once - keep the secret safe).
- The
databases field on the application limits which database codes the app may access (use test database codes for sandbox).
- Call
GET /auth/application, then GET /auth/database with a test database_code to obtain a database access token.
- Each new application is reviewed before production access is granted.
- Choose appropriate Scopes when registering - they determine which endpoints your application can call.
Data history limit
By default, historical data available through the API is limited to the last 2 months. This applies to appointments and appointment items, sales, client history (GET /clients/{id}/history), client change logs (GET /clients/changeLogs), appointment change logs (GET /appointments/changeLogs), and dashboard reports. If you need older data, contact AI Helps support individually.
API versioning
Current API version is "Version 1". Version information is mandatory in API URL and described as v{number}, where {number} is integer, starting from 1 (current version is 1).
Versioning rules are following:
- New API methods can be added without incrementing version number.
- New parameters can be added to existing API methods if they don"t change API behavior with these new parameters missing.
- Changing API URLs, parameters names or their behaviour will increment version number.
- Removing some API URLs or parameters names will increment version number.
These rules guarantee that code, working with specific API version won"t broke on minor changes made. All changes that potentially can break code will be presented in new version.
Last API version is considered "public". All developers will be notified about API version incremented, if they provided contact_email (see Application account section).
Old API version will be available at least 2 months (it can happens that several old versions will be available) and this period can be extended by request.
All ideas, errors and propositions can be sent to Telegram channel https://t.me/joinchat/J1-qj0gTp_i6EmE2rtvGvw.
HTTPS protocol
All API available only through HTTPS (HTTP not available for both public/private API).
Current server supports TLS 1.0, 1.1 and 1.2 (SSL 1-3 commonly recognized vulnerable)
REST API with extensions
Current API implementation uses REST API guidelines. According to idea, that in most cases API is used for both get/update data, in many cases with local store, resources representation seems to be better choice than GraphQL "mostly read with complex views" idea and gRPC "all is call" idea (more useful in some-transaction-like APIs). According to that, REST API seems to be the most natural way for our API, taking into account our specifics, GraphQL has some promising features can be useful and gRPC usage is far from our case, so we didn't took it into account.
So, AI Helps API fulfills REST principles: client-server, stateless, cacheable (not important in our case), uniform interface and layered system.
Resources are identified in /resources/{id} way (subresources /resources/{id}/subresources/{id2} and so on), methods to work with resources are GET, POST, PUT and DELETE.
API URI usually represents either list of resources (e.g. \clients) or specific resource from list, given by id (e.g. \clients\{id}).
Next table describes what methods can be used to operate with data:
| API url |
GET |
POST |
PUT |
DELETE |
List of resources (e.g. \clients) |
get all resources |
create new resource |
nothing |
nothing |
Specific resource (e.g. \clients\{id}) |
get resource by id |
nothing |
update resource |
delete resource |
Not all methods will be available for all lists or resources, see detailed documentation below.
If method not available for given API URL, response with HTTP status code 404 (Not Found) will be returned.
We also took best ideas from GraphQL and implemented in our API:
- While retrieving data you forced to pass fields you want to get. This idea gives more efficient data loading and easier API usage - even if we add extra fields, you will not receive them until explicitly requested.
- To add or update resource you can pass only part of fields - you do not need to pass all fields. While update, non-passed fields will leave unchanged (so, no more need for PATCH method). This greatly simplifies API usage and decrease API complexity. Along with previous idea this makes API much more flexible and easy to use (non-fixed data structure).
- In some situations API can be used mostly to read complex data without close to zero changes. So we added complex read-only or read-write fields to our resources. Such "fields" can be retrieved and updated in same request with main resource, greatly decreasing complexity of client code, number of requests and speed. These "fields" are specific for each resource and fine-tuned for speed (contrary to GraphQL where complex structure is hard to tune), still reach enough to satisfy client needs.
- We also implemented subscription for changes for all resources in API with WebHooks.
May be in future we also will add GraphQL wrapper around our REST API, so you will be able to use GraphQL client libraries.
Data format
All API methods use JSON for both input and output. For some requests (all GET requests) body not needed, for DELETE server returns nothing.
For retrieving images, where response will be image itself, for updating image request body is image itself.
ContentType (application/json) is not mandatory for requests and can be skipped, but server always return content type
(application/json for 200/201 responces (do not return content type for 204 (No content)) and image/jpeg or image/png for images).
XML not supported now and no plans to support it in future.
All API requests should use UTF-8 encoding, all responses also in UTF-8.
We recommend you use Postman to test our API.
//https://www.getpostman.com/
Data types
All items in response/request are JSON objects, each field can be either simple type shown below or array/set of subitems.
In requests and responses following simple data types can be used:
- string - formatted as simple JSON string (
"abc"). Empty string can be represented either as null or empty string. API always returns empty string as null. String parameters can be marked as required, that means null or empty string are not allowed for such strings and all required fields should be provided on creating new item.
- number - formatted as simple JSON number (
12.3)
- boolean - formatted as simple JSON true or false (
true)
- identifier - all identifiers are represented as GUIDs. Should be formatted as string
with 36 characters: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX". Empty identifier can be
represented either as null, empty string or "00000000-0000-0000-0000-000000000000"
API always returns empty identifier as null.
- datetime - is represented in ISO 8601 Extended format
"YYYY-MM-DDTHH:mm:ss.sssZ".
T - delimiter char; milliseconds shown, but in this API wlll always be zero; timezone is always UTC ("Z" char). E.g. "2017-01-01T13:51:55.000Z".
This format is native for Javascript and used in Date.toJSON(), Date.parse(). API also can receive dates in several other formats, full list of available incoming formats (but output is still always the same):
"YYYY-MM-DD"
"YYYY-MM-DDTHH:mm"
"YYYY-MM-DDTHH:mm:ss"
"YYYY-MM-DDTHH:mm:ss.sss"
"YYYY-MM-DDTHH:mm:ss.sssZ"
- image - in JSON formatted as base64, usually can be retrieved by direct link
- html_text - text with additional html tags, used to format that text. There are some differences with a valid html document
- There is no
<!DOCTYPE html> comment
- Content should not be wrapped with
<html> or <head> or <body> tags
- Content should contains only one of these kind of nodes:
#text - contains plain text
<span> - can contain style attribute, which can contain these CSS declarations:
font-weight: bold - for bold text
font-style: italic - for italic text
text-decoration: underline - for underlined text
color: #FF0000 - text color. There can be any HEX-value color
<h1>, <h2>, <h3> - describes font size, can contains the same CSS declarations, as <span> node. Note, that headlines will be considered as NOT bold
<div> - describes paragraph with alignment. <div> should not contain another <div> inside it. Left-aligned text should NOT be wrapped with <div> tags. Alignment is set using style attribute, which contains one of two CSS declarations:
text-align: center - centered text
text-align: right - right-aligned text
<br/> - line break
Get list of items format
In current and next sections someitems in URL will describe clients, products, sales or any other item type.
Request: GET /someitems?fields=field_list[&filter1=value1&filter2=value2&..]
Get someitems with fields specified in fields parameter (required parameter).
Example of fields parameter:
fields=field1,field2,field_set3(subfield1,subfield2,..),..
If some field is array/set, use parentheses to describe subfields. If some subfield is also array/set, use parentheses again: e.g.
fields=id,name,history(date,name),relatives(name,history(date,name))
Each item has unique id (identifier). id can be used in API URL for GET/PUT/DELETE operations. id field is always returned,
no need to specify it in fields parameter.
If you specify fields parameter empty, only ids will be returned.
If filter values are set, filters applied. More details about filters will be provided in specific filter description.
For all items list there is one universal filter: ids - provide list of ids, separated by comma, only items with provided ids will be returned.
Response example:
[
{
"id": "246db366-061f-45ac-984d-de8050c045a1",
"name": "Item 1",
"type": "Simple",
"color": "Red"
},
{
"id": "905e5ff6-9d41-465b-aced-7ea81a7e2ee3",
"name": "Item 2",
"type": "Complex",
"color": "Green"
},
{
"id": "cfbb963e-3ceb-4764-b3f4-805f3a3e056b",
"name": "Item 3",
"type": "Simple",
"color": "Yellow"
}
]
Items in response are sorted either by field "name" or field "date" (if "name" not exists, any item has either "name" or "date" fields).
Get item by id format
Request: GET /someitems/{id}?fields=field_list
Get item with given id and fields specified in fields parameter (required parameter).
Example of fields parameter:
fields=field1,field2,field_set3(subfield1,subfield2,..),..
If some field is array/set, use parentheses to describe subfields. If some subfield is also array/set, use parentheses again: e.g.
fields=id,name,history(date,name),relatives(name,history(date,name))
id field is always returned, no need to specify it in fields parameter.
Response example:
{
"id": "ecb3521a-3b50-4281-b947-34e7ca526b28",
"name": "Item 1",
"type": "Simple",
"color": "Red"
}
Create new item format
Request: POST /someitems
{
"name": "Item 4",
"type": "Simple",
"color": "Blue",
"category": "8c98a00b-1526-4dc0-9968-008439a78019"
}
Not all fields should be specified - missing fields will be initialized with default values.
Response will return status 201 (not 200) and id of newly created item:
{
"id": "1bfcf663-3f5d-4a86-8e0c-12c5ea34c137"
}
In some cases after item was created, some items fields except id needed. Making another GET request
(GET /someitems or GET /someitems/{id}) seems redundant. While making original request you can just describe
fields you want to receive in fields parameter (optional):
Request: POST /someitems?fields=name,type,color,category
{
"name": "Item 4"
}
Response will look like:
{
"id": "1bfcf663-3f5d-4a86-8e0c-12c5ea34c137",
"name": "Item 4",
"type": "Simple",
"color": "",
"category": null
}
Update item format
Request: PUT /someitems/{id}
{
"type": "Complex"
}
Not all fields should be specified - missing fields will left field values unchanged.
In some cases after item was updated, some fields are needed. Making another GET request
(GET /someitems or GET /someitems/{id}) seems redundant. While making original request you can just describe
fields you want to receive in fields parameter (optional). Server always returns id of updated item
and fields of fields parameter if specified:
Request: PUT /someitems/{id}?fields=name,type,color,category
{
"type": "Complex"
}
Response will look like:
{
"id": "1bfcf663-3f5d-4a86-8e0c-12c5ea34c137",
"name": "Item 1",
"type": "Complex",
"color": "Red",
"category": null
}
Delete item format
Request: DELETE /someitems/{0}
(no body needed)
For DELETE requests JSON body is not needed and no body can be sent at all.
Response: status 204 (No content)
Operations with categories
Categories are present for several items types (products, services, groups, etc.), they are special
object types thus can be edited using special methods.
Each category 2 fields (except id):
- name - category name,
- parent - id of parent category.
Categories helps to make multi level hierarchies, like:
+ Top category (virtual)
+ Item 1
+ Category 1
+ Item 2
+ Subcategory 1
+ Item 3
Get list of categories format
Request: GET /someitems/categories?fields=field_list[&filter1=value1&filter2=value2&..]
Get categories with fields specified in fields parameter (required parameter). If filter values are set, filters applied.
Response example:
[
{
"id": "246db366-061f-45ac-984d-de8050c045a1",
"name": "Subcategory 1",
"parent": "8c98a00b-1526-4dc0-9968-008439a78019"
},
{
"id": "5c8316ef-68fb-47d4-b99a-ef715a60bd16",
"name": "Subcategory 2",
"parent": "8c98a00b-1526-4dc0-9968-008439a78019"
}
]
All categories in response are sorted by field "name".
Get category by id format
Request: GET /someitems/categories/{id}?fields=field_list
Get category with given id with fields specified in fields parameter (required parameter).
Response example:
{
"id": "246db366-061f-45ac-984d-de8050c045a1",
"name": "Subcategory 1",
"parent": "8c98a00b-1526-4dc0-9968-008439a78019"
}
Create new category format
Same to creating new items:
Request: POST /someitems/categories
{
"name": "Category 2",
"parent": "8c98a00b-1526-4dc0-9968-008439a78019"
}
Not all fields should be specified - missing fields will be initialized with default values.
Response will return status 201 (not 200) and id of newly created category:
{
"id": "1bfcf663-3f5d-4a86-8e0c-12c5ea34c137"
}
If some category fields except id is needed, you can just describe fields you want to receive in fields parameter (optional):
Request: POST /someitems/categories?fields=name,parent
{
"name": "Category 2"
}
Response will look like:
{
"id": "1bfcf663-3f5d-4a86-8e0c-12c5ea34c137",
"name": "Category 2",
"parent": null
}
Update category format
Request: PUT /someitems/categories/{id}
{
"parent": null
}
Not all fields should be specified - missing fields will left field values unchanged.
Response: status 204 (No content)
If some category fields except id is needed, you can just describe fields you want to receive in fields parameter (optional):
Request: PUT /someitems/categories/{id}?fields=name,parent
{
"parent": null
}
Response will look like:
{
"id": "1bfcf663-3f5d-4a86-8e0c-12c5ea34c137",
"name": "Category 2",
"parent": null
}
Delete category format
Request: DELETE /someitems/categories/{0}
(no body needed)
For DELETE requests JSON body is not needed and no body can be sent at all.
Response: status 204 (No content)
Please mention, that removing category will remove all subcategories and all items in category and
all subcategories.
Get items with categories
Request: GET /someitems/tree[?fields=fields_list,categories_fields=categories_fields_list,empty_categories&filter1=value1&filter2=value2&..]
If some items are grouped in categories, in most cases most convenient will be to receive such items from API in
some sort of tree view. This request combine both items and categories and represent them in tree view:
- Result root is "virtual" top category with empty id, name and parent. You can't update or delete this category.
- Each category, including root, has two arrays:
categories and items.
- Items can be filtered using filters (same as for get items).
- By default, empty categories (that has no subcategories or items) are not returned. You can force to return them,
setting empty_categories in parameters.
Response will look like:
{
"id": null,
"name": "",
"parent": null,
"categories": [
{
"id": "8c98a00b-1526-4dc0-9968-008439a78019",
"name": "Category 1",
"parent": null,
"categories": [
{
"id": "5c8316ef-68fb-47d4-b99a-ef715a60bd16",
"name": "Subcategory 1",
"parent": "8c98a00b-1526-4dc0-9968-008439a78019",
"categories": [
],
"items": [
{
"id": "cfbb963e-3ceb-4764-b3f4-805f3a3e056b",
"name": "Item 3",
"type": "Simple",
"color": "Yellow",
"category": "5c8316ef-68fb-47d4-b99a-ef715a60bd16"
}
]
}
],
"items": [
{
"id": "905e5ff6-9d41-465b-aced-7ea81a7e2ee3",
"name": "Item 2",
"type": "Complex",
"color": "Green",
"category": "8c98a00b-1526-4dc0-9968-008439a78019"
]
}
}
]
"items": [
{
"id": "246db366-061f-45ac-984d-de8050c045a1",
"name": "Item 1",
"type": "Simple",
"color": "Red",
"category": null
}
]
}
Categories in "categories" arrays are sorted by field "name", items in "items" array in response are sorted either by field "name" or "date" (if "name" not exists).
One moment to keep in mind with empty categories. Assume we have next structure:
+ Top category (virtual)
+ Item 1
+ Category 1
+ Item 2
+ Subcategory 1
+ Item 3
+ Empty subcategory 1
+ Category 2
+ Empty subcategory 2
Empty categories, as in example above can happen in two cases:
- Someone add category in program and forgot to add items inside (or adding items in progress).
- You use filters to filter some items and originally non-empty categories become empty with your filters.
In most cases you don't need to receive empty categories, so API automatically filter empty categories and don't return them.
So, tree structure above will look like:
+ Top category (virtual)
+ Item 1
+ Category 1
+ Item 2
+ Subcategory 1
+ Item 3
Empty subcategories 1 and 2 was not shown, Category 2 become empty and was not shown also.
If you need to receive empty categories, set parameter empty_categories and all categories will be preserved.
Getting/updating images
Difference between images and other data types are that images are binary data type. Thus it is non-optimal to
get/update them via JSON (using base64 for example). Also, receiving images inside JSON makes not so comfortable
to set IMG/PICTURE tags.
That's why getting/receiving images (client photos, employee photos, products pictures, etc.) are always separate methods.
But they all have same format.
Get image
To get image, use GET request one level deeper than object which image you want to get: for example, /items/{id}/photo.
Result will always be of content mime type image/jpeg, image/png or image/gif (if no image found, 404 error will be returned).
The most common problem with images is resizing them to specific size. Usually browser is capable of doing this,
but it is better to do that on server to decrease size of data transferred and increase speed. So, on getting images,
these 3 optional parameters can be provided (please note both width and height should be provided for resizing):
| Parameter |
Possible values |
Description |
| width |
integer |
Width of resized image in pixels |
| height |
integer |
Height of resized image in pixels |
| resize |
fit (default)/fit_center_transparent/stretch/crop |
Type of operation in case proportions of original image will not match proportins of needed image. |
List of possible resize values:
| Value |
Description |
| fit (default) |
Image will be resized constraining proportions to fit into provided width & height. One of dimensions can be less than needed. |
| fit_center_transparent |
Same as previous but if one of dimensions is less than needed, additional transparent rows/colums added to image to make width and height equal to needed |
| stretch |
Image will be resized to needed width & height without constraining proportions (width and height will be exact as needed but image will be distorted) |
| crop |
Image will be resized constraining proportions to such scale that one of dimensions is equal to needed while another is equal or bigger than needed. If second dimension is bigger, image will be cropped to match needed width and height. In this case proportions are constrained, image size equals to needed but part of image can be cropped (for example, original size 2000x1000, needed 100x100: first scale to 200x100 than crop left 50 and right 50 pixels). |
In all cases scaling is done only for bigger images. Scaling is skipped if needed size is bigger than original (otherwise artefacts can be seen on image).
In some cases it is preferable to use API URL inside IMG/PICTURE tag, without using JavaScript to fetch image. But all image requests need access token in
Authorization header. So for getting image requests (and only for them) access token can be specified as additional parameter access_token in URL, thus
Authorization header not needed for such requests. Resulting URL can be easily inserted as src parameter of IMG tag.
A little about caching images: all images should be cached for 10 minutes, after that ETag can be used to check if image was changed or not.
If you use images inside IMG/PICTURE tags, browser do all this magic for you, but in case you get images from JavaScript or some other language,
please use Cache-Control and ETag headers to decrease traffic and increase speed of your application.
Update image
Updating image is very straightforward process.
Use same URL as for getting image but with PUT method.
Content should be image itself (mime types image/jpeg, image/png or image/gif are supported).
Mime type of an image will be defined by it's content, so Content-Type header will be ignored.
No parameters supported:
width, height and resize has no meaning: for each image type (client photo, product picture) there are custom max allowed size. If image is bigger,
it will be fitted in max allowed size.
access_token not allowed: access token should be passed as Authorization header.
There are three request types supported:
1) Sending binary data (recommended)
Images are binary files. So passing them 'as is' is recommended. This method requires least memory and time resources in comparison with the other methods
Request header
Authorization: Bearer 9c4068e2-c81f-4d70-ad31-8f627ed9bced
Request body
FF D8 FF E0 00 10 4A 46 49 46 00 01 01 00 00 01
00 01 00 00 FF DB 00 43 00 05 03 04 04 04 03 05
...
00 51 45 14 00 51 45 14 00 51 45 14 00 51 45 14
01 FF D9
2) Sending base64 encoded image
Alternative way: use base64 encoding instead of pure binary data.
To send image as base64, Content-Transfer-Encoding header must be set to base64
Request header
Authorization: Bearer 9c4068e2-c81f-4d70-ad31-8f627ed9bced
Content-Transfer-Encoding: base64
Request body
/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7...AUUUUAFFFFABRRRQB/9k=
3) Sending resource url
You can also pass url, server will download image by itself.
To provide url, set Content-Type header to text/plain, then the request body will be considered as url.
Url should be well-formed in accordance with RFC 2396 and RFC 2732
To delete image, just send request without content (content length 0 bytes) or use DELETE method with same URL.
Result is 204 (No content) on success.
Scopes
Scopes describe which API is available for specific application (more about applications will be described later).
You can see needed scope in method description (only if Database or Client tokens used, otherwise scope is not appliable).
| Scope |
Description |
| full |
Full read and write access to all salon/fitness database data. |
| clients_module |
Online booking module: read services, employees, locations, free time; create and update client appointments. |
| reports |
Read-only access for reports and analytics: appointments, sales, clients, services, products. |
| services_aggregator |
Appointment aggregator: search and add clients by phone (no edit/delete); read services, employees and free time; create and update appointments without history access. |
| client_login |
Login with client phone and SMS code (or create a new client). Available only for AI Helps products. |
Scopes are chosen once when the application is registered and cannot be changed afterwards without contacting AI Helps support. An application may be granted one or more scopes. Token types client_access_token and application_access_token are assigned automatically by the token type (see Authorization) and are not selected at registration.
Request limits
The following limits apply to all API consumers:
- Up to 100 requests per second per IP address (overall).
- Up to 10 requests per second per IP address on
/auth/* endpoints.
- Up to 5 concurrent requests per access token.
- Maximum request body size: 10 MB.
- Prefer batching work and avoiding polling list endpoints too frequently.
- Abusive traffic patterns may result in the source IP being blocked for up to 7 days.
If you exceed these limits, the API returns HTTP 429 (see 429 Too Many Requests below).
Errors
Depending on the request method, server return different HTTP status codes:
GET:
- 200 - OK: on success
- 404 - Not Found: item not found by given id
POST:
- 201 - Created: on success
- 202 - Accepted: for long-running requests
PUT:
- 200 - OK: on success if item returned
- 202 - Accepted: for long-running requests
- 204 - No content: on success if item not returned
- 404 - Not Found: item not found by given id
DELETE: 200 (OK)
- 204 - No content: on success
- 404 - Not Found: item not found by given id
Common errors:
- 400 - Bad Request: error in request parameters (bad parameters, required parameter not given)
- 401 - Unauthorized: authorization required (if not specified) or failed (bad token, token expires etc.)
- 403 - Forbidden: your scope does not allow running current API (check needed scope in documentation)
- 409 - Conflict: your changes conflicts with data in database
- 429 - Too Many Requests: server limits number of requests by ip for some methods (like authorization)
- 500 - Internal Server Error: error happen on server while API method running (please write us at ag@aihelps.com)
- 503 - Service Unavailable: server unavailable due to maintainance or some server problem (please try again later)
Success codes are 200 for GET, 201 for POST, 200/204 for PUT and 204 for DELETE requests.
In case of error (error codes >= 400) server response has next format (for all types of requests):
{
"status": 401,
"type": "INVALID",
"request": "a7dfb9a8-0383-4087-a1e6-606fc2bc714c",
"message": "Unknown employee login or password",
"details": "https://aihelpssoft.github.io/documentations/#error-INVALID"
}
Where:
- status: HTTP status code (described earlier)
- type: error type, if provided
- request: request id (you can use this id to get details from API developers via dev@aihelps.com)
- message: message for developer, describing problem
- details: link to documentation with more details
There are two types of errors:
- errors like Bad Request which notifies that you use API in wrong way. These errors do not have
type field, but provides detailed message. Such errors usually happens during development phase, thus developer see them and fix inplace. Such errors are: 400 Bad Request, 401 Unathorized, 403 Forbidden, 404 Not Found, 429 Too Many Requests, 500 Internal Server Error and 503 Service Unavailable. All of them can happen at any request.
- errors that happens in production like invalid password while logging in or token expired. Such errors always have specific
type and should be handled in code that uses API. Usually they have additional information in JSON parameters that helps to handle this error. You need to write code to manage them in production. They are: 400 Bad Request - APPOINTMENT_VALIDATION, 401 Unathorized - UNKNOWN, EXPIRED, INVALID and BLOCKED; 403 Forbidden - LICENSE; 409 Conflict - ALREADY_EXISTS and TIME_CONFLICT; 503 Service Unavailable - REMOTE_DATABASE, SMS, SMS_AUTH and SMS_BALANCE. From them 401 Unathorized - UNKNOWN, EXPIRED; 403 Forbidden - LICENSE; 503 Service Unavailable - REMOTE_DATABASE can happen at any request, other - only at specific requests (all such requests will be marked).
400 Bad Request
These errors means that your request is incorrect - not enough parameters, bad parameter type, failed validation and so on.
{
"status": 400,
"request": "a7dfb9a8-0383-4087-a1e6-606fc2bc714c",
"message": "Parameter `name` value should be equals or greater than zero",
"details": "https://aihelpssoft.github.io/documentations/#introduction/errors/400-bad-request"
}
Can happen at any request.
400 Bad Request - APPOINTMENT_VALIDATION
These errors means some validation errors while changing appointment. List of these errors is long enough, details can be found in Appointments section. You can pass force=true parameter to ignore such validations.
{
"status": 400,
"type": "APPOINTMENT_VALIDATION",
"request": "a7dfb9a8-0383-4087-a1e6-606fc2bc714c",
"message": "Validation error(s) while updating appointment",
"details": "https://aihelpssoft.github.io/documentations/#introduction/errors/400-bad-request-appointment-validation",
"validations":
[
{
"id": "da77cb7c-5725-4afa-abf4-c097a9e9081a",
"type": "SERVICE_PROFESSIONAL_CANT_PROVIDE_SERVICE",
"service": "ced508d8-b4cf-498b-9893-a976a912ba21",
"professional": "6b98745c-d72d-4831-b7cc-104449d17617"
}
]
}
Can happen only at appointment insert/update endpoints.
401 Unathorized
These errors means some problems with authorization. If no type specified, it's usually common problem, that happens during development.
{
"status": 401,
"request": "a7dfb9a8-0383-4087-a1e6-606fc2bc714c",
"message": "Unknown authorization scheme: Basic",
"details": "https://aihelpssoft.github.io/documentations/#introduction/errors/401-unathorized"
}
Can happen at any request.
401 Unathorized - UNKNOWN
These errors means some problems with authorization. type=UNKNOWN means that provided access token, refresh token, application id & password ot other requisites are unknown. There is no way to refresh token or continue session.
{
"status": 401,
"type": "UNKNOWN",
"request": "a7dfb9a8-0383-4087-a1e6-606fc2bc714c",
"message": "Unknown access token: 3227f712-eb6c-4003-8260-ca70fcad50d1",
"details": "https://aihelpssoft.github.io/documentations/#introduction/errors/401-unathorized-unknown"
}
Correct handling this error in production - treat session as logged out and redirect user to login page.
Can happen at any request.
401 Unathorized - EXPIRED
These errors means some problems with authorization. type=EXPIRED means that provided access token, auth id or other temporary access object is expired,
{
"status": 401,
"type": "EXPIRED",
"request": "a7dfb9a8-0383-4087-a1e6-606fc2bc714c",
"message": "Access token expired",
"details": "https://aihelpssoft.github.io/documentations/#introduction/errors/401-unathorized-expired"
}
Correct handling this error in production - silently refresh token, save new token and repeat original request.
Can happen at any request.
401 Unathorized - INVALID
These errors means some problems with authorization. type=INVALID means that during authorization process you provided wrong login/password, sms code or other authorization information.
{
"status": 401,
"type": "INVALID",
"request": "a7dfb9a8-0383-4087-a1e6-606fc2bc714c",
"message": "Wrong provided sms code",
"details": "https://aihelpssoft.github.io/documentations/#introduction/errors/401-unathorized-invalid"
}
Correct handling this error in production - notify user his credentials are invalid and provide possibility to enter credentials once more.
Can happen only at few authorization requests, such request will contain info about this error.
401 Unathorized - BLOCKED
These errors means some problems with authorization. type=BLOCKED means that during authorization process you overlimit attempts to enter password/sms code or other credentials and verification blocked for some time.
Additional parameter added unblockAfter, which contains number of seconds after which unblock occures and you can repeat attempts.
{
"status": 401,
"type": "BLOCKED",
"request": "a7dfb9a8-0383-4087-a1e6-606fc2bc714c",
"message": "Can't check password: blocked for 10 minutes because 5 wrong passwords provided, left 124 seconds.",
"details": "https://aihelpssoft.github.io/documentations/#introduction/errors/401-unathorized-blocked",
"unblockAfter": 124
}
Another similar case: sms can be send only once a minute:
{
"status": 401,
"type": "BLOCKED",
"request": "a7dfb9a8-0383-4087-a1e6-606fc2bc714c",
"message": "Sms verification code was sent recently, minimum interval is one minute between sending smses, 17 seconds left.",
"details": "https://aihelpssoft.github.io/documentations/#introduction/errors/401-unathorized-blocked",
"unblockAfter": 17
}
If you don't receive unblockAfter field, the only option for user is to start authentification process from start:
{
"status": 401,
"type": "BLOCKED",
"request": "a7dfb9a8-0383-4087-a1e6-606fc2bc714c",
"message": "auth_id becomes invalid after sending 20 wrong codes",
"details": "https://aihelpssoft.github.io/documentations/#introduction/errors/401-unathorized-blocked"
}
Correct handling this error in production - notify user his credentials are invalid, wait for specified amount of time (if provided) and provide possibility to enter credentials once more. If unblockAfter not specified, start authorization process from beginning.
Can happen only at few authorization requests, such request will contain info about this error.
403 Forbidden
These errors means your scope does not allow running current API (check needed scope in documentation). If no type specified, it's usually problem with scopes and easily found during development process.
{
"status": 403,
"request": "a7dfb9a8-0383-4087-a1e6-606fc2bc714c",
"message": "Your scope (reports) does not allow running 'POST /clients'",
"details": "https://aihelpssoft.github.io/documentations/#introduction/errors/403-forbidden"
}
Can happen at any request.
403 Forbidden - LICENSE
type=LICENSE means some problem with license, for example, "Licenses expired for all locations expired" or "This endpoint not allowed for your license type".
{
"status": 403,
"request": "a7dfb9a8-0383-4087-a1e6-606fc2bc714c",
"message": "Licenses expired for all locations expired",
"details": "https://aihelpssoft.github.io/documentations/#introduction/errors/403-forbidden-license"
}
Correct handling this error in production - notify user that his license is finished, he should prolonge it until continue using product.
Can happen at any request.
404 Not Found
These errors means that either URL was not found or some object was not found by id, phone, name etc.
Additional parameters added object, type, value, which describes what object was not found.
object can be either "url" or item from database: "client", "employee", "appointment", etc.
type describes type of requisites used for search: "url" (for "url" only), "id", "phone", "email", "name", "code" (for database)
value value of type type used for search.
If URL was not found:
{
"status": 404,
"request": "a7dfb9a8-0383-4087-a1e6-606fc2bc714c",
"message": "No API with URL POST /abc found",
"details": "https://aihelpssoft.github.io/documentations/#introduction/errors/404-not-found",
"object": "url",
"type": "url",
"value": "POST /abc"
}
If some object was not found:
{
"status": 404,
"request": "a7dfb9a8-0383-4087-a1e6-606fc2bc714c",
"message": "Client with id ccac7904-dbaa-4373-95c2-464031fdb533 not found",
"details": "https://aihelpssoft.github.io/documentations/#introduction/errors/404-not-found",
"object": "client",
"type": "id",
"value": "ccac7904-dbaa-4373-95c2-464031fdb533"
}
Can happen at any request.
409 Conflict - ALREADY_EXISTS
These errors means request could not be completed due to a conflict with the current state of the target resource. type=ALREADY_EXISTS means that object with provided parameters already exists.
Additional parameters added type, id, where:
itemType - type of item already exists
id of object already exists
where - list of conditions to be unique, used to find item
{
"status": 409,
"type": "ALREADY_EXISTS",
"request": "a7dfb9a8-0383-4087-a1e6-606fc2bc714c",
"message": "Only one planned or confirmed appointment should exists for specified date + location + client + employee",
"details": "https://aihelpssoft.github.io/documentations/#introduction/errors/409-conflict-already_exists",
"itemType": "appointment",
"id": "74e85faf-de73-4fe6-9530-3af1dd7c1b34",
"where":
{
"date": "2020-01-01T00:00:00Z000",
"location": "e0cd7be0-dcf1-4b49-b704-a5d2cbd59125",
"client": "012b6f96-dbea-4e47-8c27-c474021c03f5",
"employee": null,
"state": "planned/confirmed"
}
}
Correct handling this error in production - depending from endpoint, correct input data and make request once more if needed (in many cases item already exists and no need to repeat request).
Can happen only at few requests, such request will contain info about this error.
409 Conflict - TIME_CONFLICT
These errors means request could not be completed due to a conflict with the current state of the target resource. type=TIME_CONFLICT means that object can be saved on proposed time because it will conflict with already stored items.
Additional parameter added anotherItems, which is array of items, each containing:
id: id of object which changes raised time conflict (id of group lesson or aappointment service when changing itself, id of specific appointment service when changing appointment)
itemType: professional, hall, resource or appointment - type of conflicted resource
itemId: id of conflicted resource
objectType: another object, that use resource, possible values appointmentService, groupLesson, or nonWorkingTime (for professional only)
objectId: appointment service id or group lesson id
objectDescriptionId: object that describes conflicted object: appointment client or employee id for appointmentService, group id for groupLesson
objectStart: start of object (date and time)
objectDuration: object duration in minutes
{
"status": 409,
"type": "TIME_CONFLICT",
"request": "a7dfb9a8-0383-4087-a1e6-606fc2bc714c",
"message": "Only one planned or confirmed appointment should exists for specified date + location + client + employee",
"details": "https://aihelpssoft.github.io/documentations/#introduction/errors/409-conflict-time_conflict",
"anotherItems":
[
{
"id": "da77cb7c-5725-4afa-abf4-c097a9e9081a",
"itemType": "professional",
"itemId": "9d5437d2-b868-426c-a572-38d532d47987",
"objectType": "appointmentService",
"objectId": "e03ee732-2f5a-476f-afdb-0d53a16a072e",
"objectDescriptionId": "4ac7591c-8811-430b-a3fb-d2e53f42c177",
"objectStart": "2020-01-01T00:00:00Z000",
"objectDuration": 120
}
]
}
Correct handling this error in production - show received conflicts to user. If user really sure, most endpoints returning this error can be called once more with force=true parameter, ignoring this error.
Can happen only at few requests, such request will contain info about this error.
429 Too Many Requests
If you exceed the request limits, the API returns HTTP 429. Reduce request rate, avoid polling when webhooks are available, and retry after a short delay.
{
"status": 429,
"request": "a7dfb9a8-0383-4087-a1e6-606fc2bc714c",
"message": "Up to 100 requests per second allowed from one IP (try again soon)",
"details": "https://aihelpssoft.github.io/documentations/#introduction/errors/429-too-many-requests"
}
Can happen at any request.
500 Internal Server Error
These errors usually means some server bug. Provide request identifier to development team to solve the bug.
Details of error are not shown for security reasons.
{
"status": 500,
"request": "a7dfb9a8-0383-4087-a1e6-606fc2bc714c",
"message": "Internal Server Error: error id 'a7dfb9a8-0383-4087-a1e6-606fc2bc714c'. You can use this id to get error details using api@aihelps.com",
"details": "https://aihelpssoft.github.io/documentations/#introduction/errors/500-internal-server-error"
}
Can happen at any request.
503 Service Unavailable
These errors means some structural internal API problems, like problems with database. If no type specified, it's usually problems that can be solved only by AI Helps team in nearest future, you can do nothing with it.
{
"status": 500,
"request": "a7dfb9a8-0383-4087-a1e6-606fc2bc714c",
"message": "Service Unavailable (some of internal infrastructure is unavailable thus request can't be processed)",
"details": "https://aihelpssoft.github.io/documentations/#introduction/errors/503-service-unavailable"
}
Can happen at any request.
503 Service Unavailable - REMOTE_DATABASE
In rare cases clients database is located not on AI Helps servers, but hosted on clients server. AI Helps can't guarantee such server will be always accessible.
type=REMOTE_DATABASE means API can't connect to clients database and process request (either server/database unavailable or wrong credentials stored in AI Helps for remote server). You can push clients by yourself to guarantee stable connection to their database.
{
"status": 500,
"type": "REMOTE_DATABASE",
"request": "a7dfb9a8-0383-4087-a1e6-606fc2bc714c",
"message": "Remote database unavailable (client database located on other server is unavailable)",
"details": "https://aihelpssoft.github.io/documentations/#introduction/errors/503-service-unavailable-remote_database"
}
Can happen at any request.
503 Service Unavailable - SMS
Clients usually have accounts in text messaging services to send smses to their clients. Some endpoints requires sending sms to proceed (like login via sms code). If some error happens during sending sms, errors with type=SMS wil be received. These errors are usually either problems with connectivity or situations when service changed their API and integration become invalid.
{
"status": 503,
"type": "SMS",
"request": "a7dfb9a8-0383-4087-a1e6-606fc2bc714c",
"message": "Sending SMS: can't connect to sending service",
"details": "https://aihelpssoft.github.io/documentations/#introduction/errors/503-service-unavailable-sms"
}
Correct handling this error in production - notify user message can't be sent due to problens with sending service and he should try once more in some time.
Can happen only at few requests, where sending sms is crucial, such request will contain info about this error.
503 Service Unavailable - SMS_AUTH
Clients usually have accounts in text messaging services to send smses to their clients. Some endpoints requires sending sms to proceed (like login via sms code). If during sending sms we discovered, that login, password or alpha name for sms sending service is incorrect, error with type=SMS_AUTH will happen. Contrary to error type=SMS this one can be easily fixed changing login/password/alpha name in program settings.
{
"status": 503,
"type": "SMS_AUTH",
"request": "a7dfb9a8-0383-4087-a1e6-606fc2bc714c",
"message": "Sending SMS: wrong login/password",
"details": "https://aihelpssoft.github.io/documentations/#introduction/errors/503-service-unavailable-sms_auth"
}
Correct handling this error in production - notify user message can't be sent due to wrong credentials and he should try once more in some time.
Can happen only at few requests, where sending sms is crucial, such request will contain info about this error.
503 Service Unavailable - SMS_BALANCE
Clients usually have accounts in text messaging services to send smses to their clients. Some endpoints requires sending sms to proceed (like login via sms code). If during sending sms we discovered, that no more credits left, error with type=SMS_BALANCE will happen. Contrary to error type=SMS this one can be easily fixed by client, making payment to sms sending service.
{
"status": 503,
"type": "SMS_BALANCE",
"request": "a7dfb9a8-0383-4087-a1e6-606fc2bc714c",
"message": "Sending SMS: out of money",
"details": "https://aihelpssoft.github.io/documentations/#introduction/errors/503-service-unavailable-sms_balance"
}
Correct handling this error in production - notify user message can't be sent due to lack of money and he should try once more in some time.
Can happen only at few requests, where sending sms is crucial, such request will contain info about this error.
Change history
Here you can see history of changes in API and documentation.
| Date |
Description |
| 2026-09-16 |
Documentation cleanup: scopes table, authorization annotations, webhooks rewrite, request limits, getting started, removed deprecated /common/employees, added missing endpoints |
| 2026-08-19 |
clients: utm_campaign, utm_source, utm_medium fields added |
Deprecated endpoints and fields
This section contains information about deprecated endpoints or fields in endpoints. We usually deprecate endpoints or fields if we propose some better alternative for such fields or resources. After deprecation published we give at least 3 months to API users to make corresponding changes in their code.
According to IETF standard, we return 3 headers in response in case endpoint is deprecated:
Deprecation: true
Sunset: [date]
Link: <[link to subsection in this section]>; rel="deprecation"
Link leads to subsection of this section, where more details and alternatives are described.
In case of deprecated field, we add one more non-standard header to make it easier to distinguish problem:
X-Deprecated-Field: [field]
Sunset date is date when we plan to remove endpoint or field. We will not remove endpont or field before that date, but can do it in any moment after date happens (thus in some cases endpoint/field can be valid for months more, but no guarantee).