Integration API
Integration reference for your software. There are two groups of endpoints using two different credentials: the shipped client uses X-Api-Key, your own team uses X-Dev-Token. Both come from Admin → Tools.
https://cyanpro.app
Endpoint overview
| Method | Path | Auth | What it does |
|---|---|---|---|
| GET | /api/v1/ping | API key | Check the API key still works |
| POST | /api/v1/auth/login | API key | Sign a user in from your software; returns tokens and licence |
| POST | /api/v1/license/verify | API key + Bearer | Check the licence is still valid; call periodically while running |
| POST | /api/v1/auth/refresh | API key | Trade a refresh token for a new access token |
| POST | /api/v1/auth/logout | API key + Bearer | Give back one device slot |
| GET | /api/v1/account/profile | API key + Bearer | Details of the signed-in account |
| GET | /api/v1/version | API key | Ask whether a newer build exists |
| GET | /api/v1/dev/product | Dev key | Read the product |
| PATCH | /api/v1/dev/product | Dev key | Update description, platforms, download link |
| POST | /api/v1/dev/releases | Dev key | Publish a new version |
| GET | /api/v1/dev/releases | Dev key | Release history |
How it works
- Your software collects a stable fingerprint of the machine (disk serial, MAC, machine GUID…) and hashes it.
- It calls
POST /api/v1/auth/loginwith the email, password and fingerprint. - It receives an
access_token(1 hour) and arefresh_token(90 days) — store the refresh token in your app’s private data area. - On every start, and every 6 hours after that, it calls
POST /api/v1/license/verifyto confirm the licence is still valid. - When the access token expires, it trades the refresh token for a new one at
POST /api/v1/auth/refresh.
1. Signing in from your software
Returns the user’s currently valid licence for the product tied to the API key.
POST https://cyanpro.app/api/v1/auth/login
Content-Type: application/json
X-Api-Key: cyk_live_xxxxxxxx
{
"email": "user@example.com",
"password": "••••••••",
"fingerprint": "a3f1c8...", // SHA-256 hashed on the client
"device_name": "Office PC",
"platform": "Windows 11",
"app_version": "2.4.1"
}
200 OK
{
"ok": true,
"access_token": "eyJhbGciOi...",
"refresh_token": "9f2b...",
"expires_in": 3600,
"license": {
"key": "CYAN-4K2P-9XQW-7MTB-R3VD",
"status": "active",
"plan": "Pro",
"is_trial": false, // true while the free trial is running
"expires_at": 1792454400,
"days_left": 27,
"max_devices": 3,
"devices_in_use": 2,
"entitlements": { "export_pdf": true, "max_projects": 50 }
},
"account": { "email": "user@example.com", "full_name": "Nguyen Van A" }
}
Common errors: invalid_credentials, email_not_verified, no_license, license_expired, device_limit_reached. When is_trial is true, your software should show the days remaining and an upgrade prompt rather than staying silent.
2. Checking the licence
Call this periodically while the software runs. It is the single source of truth about expiry.
POST https://cyanpro.app/api/v1/license/verify
X-Api-Key: cyk_live_xxxxxxxx
Authorization: Bearer <access_token>
{ "fingerprint": "a3f1c8...", "app_version": "2.4.1" }
200 OK
{ "ok": true, "valid": true, "license": { ... }, "server_time": 1792108800 }
200 OK — licence expired (still 200 — "valid" is the field to read)
{ "ok": true, "valid": false, "reason": "license_expired", "license": { ... } }
3. Refreshing the access token
POST https://cyanpro.app/api/v1/auth/refresh
X-Api-Key: cyk_live_xxxxxxxx
{ "refresh_token": "9f2b..." }
4. Releasing a device
Frees one device slot so another machine can use it.
POST https://cyanpro.app/api/v1/auth/logout X-Api-Key: cyk_live_xxxxxxxx Authorization: Bearer <access_token>
5. Checking for updates
The installed software calls this at startup to find out whether a newer build exists. Read-only, and the only release-related thing the shipped API key may touch.
GET https://cyanpro.app/api/v1/version?current=1.0.0 X-Api-Key: cyk_live_xxxxxxxx
200 OK
{
"ok": true,
"latest_version": "1.1.0",
"update_available": true,
"is_mandatory": false,
"download_url": "https://example.com/setup-1.1.0.exe",
"notes": "- Sửa lỗi đăng nhập\n- Tăng tốc khởi động",
"released_at": 1792470000,
"server_time": 1792470100
}
Send current as the version you are running. Without it, update_available comes back null — the system does not guess. Comparison is numeric, so 1.10.0 is understood to be newer than 1.9.0.
Developer API
For your own team to edit the product description and publish releases without opening the admin area. It uses a different credential from the API key above.
X-Api-Key (cyk_live_…) ships inside the installer you give customers; anyone can pull it out of the executable, which is why it can only sign in and ask about licence expiry. X-Dev-Token (cyd_live_…) can change data, so it stays on developer machines and in your build system — never inside a build you ship. Create one under Admin → Tools → Developer keys. Each key reaches exactly one product.
Read the product
GET https://cyanpro.app/api/v1/dev/product X-Dev-Token: cyd_live_xxxxxxxx
Update the product
Send only the fields you want changed. slug cannot be changed here (it would break every public link pointing at the product) and neither can visibility — both belong in the admin area.
PATCH https://cyanpro.app/api/v1/dev/product
X-Dev-Token: cyd_live_xxxxxxxx
Content-Type: application/json
{
"tagline": "Bộ công cụ desktop cho công việc hằng ngày",
"tagline_en": "A desktop toolkit for everyday work",
"description": "Mô tả dài hơn, hiện ở trang chi tiết.",
"platforms": ["Windows", "macOS"],
"download_url": "https://example.com/setup.exe"
}
Publish a release
Call this from your build system once the package is ready. Each version number can only be published once; republishing over an existing one is refused.
POST https://cyanpro.app/api/v1/dev/releases
X-Dev-Token: cyd_live_xxxxxxxx
Content-Type: application/json
{
"version": "1.1.0",
"notes": "- Sửa lỗi đăng nhập\n- Tăng tốc khởi động",
"notes_en": "- Fixed a sign-in bug\n- Faster startup",
"download_url": "https://example.com/setup-1.1.0.exe",
"is_mandatory": false
}
201 Created
{
"ok": true,
"release": {
"version": "1.1.0",
"is_latest": true,
"is_mandatory": false,
"download_url": "https://example.com/setup-1.1.0.exe",
"released_at": 1792470000
}
}
A patch for an older branch is still recorded but does not drag the current version backwards: publish 1.9.5 after 2.0.0 and is_latest comes back false, so customers on 2.0.0 are never prompted to downgrade.
Release history
GET https://cyanpro.app/api/v1/dev/releases X-Dev-Token: cyd_live_xxxxxxxx
Error codes
Every error response has the same shape: { "ok": false, "error": "error_code", "message": "description" }. Branch on error, never on message — the wording may change, the code will not.
| Code | HTTP | Meaning and what to do |
|---|---|---|
| missing_api_key | 401 | The X-Api-Key header is missing. Check how the build is configured. |
| invalid_api_key | 401 | The API key is wrong or has been revoked. Report a configuration error; do not retry in a loop. |
| missing_dev_token | 401 | X-Dev-Token is missing. The shipped API key does not work on the /dev branch. |
| invalid_dev_token | 401 | The developer key is wrong or has been revoked. |
| invalid_credentials | 401 | Wrong email or password. Show one generic message; do not say which was wrong. |
| email_not_verified | 403 | The account has not verified its email yet. Point the user at their inbox. |
| no_license | 403 | The account has no licence for this product. Send them to the pricing page. |
| license_expired | 403 | The licence has expired. Send them to the renewal page. |
| device_limit_reached | 403 | All device slots on this plan are in use. Tell them to release an old machine from their dashboard. |
| device_revoked | 403 | This device was released. Clear the stored tokens and ask the user to sign in again. |
| missing_token | 401 | The Authorization header is missing. Send the access token. |
| invalid_token | 401 | The access token is invalid or expired. Trade the refresh token for a new one. |
| version_exists | 409 | That version number has already been published. Use a new one. |
| invalid_version | 400 | The version string is not valid. Letters, digits and . - + only. |
| rate_limited | 429 | Too many calls. Wait the number of seconds given in Retry-After, then try again. |
Rate limits
Hitting a limit returns HTTP 429 with a Retry-After header in seconds. Do not retry immediately — wait that long.
| Operation | Limit | Counted per |
|---|---|---|
| Sign-in from software | 15 / 15 min | account |
| Sign-in from software | 100 / 15 min | IP address |
| Licence check | 120 / 1 min | device |
| Developer API | 120 / 1 hour | dev key |
Implementation rules
- Never ship the API secret inside the client. The API key is enough for a client; the secret belongs on your own backend, if you have one.
- Always trust the server’s clock (
server_time), never the client’s — users can change theirs. - Allow limited offline use: remember the last successful check and keep running for 3–7 days without a network, then require a reconnect.
- The fingerprint must survive software updates but change when the machine does. Machine GUID plus motherboard serial is a good combination.
- Every error response has the shape
{ "ok": false, "error": "error_code", "message": "description" }.
Full sample code for Node.js/Electron and .NET lives in the project’s client-sdk/ folder.