Authorization
To use the API, you need to authenticate your requests using an API token.
Getting Your API Token
- Open the store whose workspace you want to call, then go to https://cart.easy.tools/creator/store-settings/developer
- Create a new API key and select Easytools
- Copy and securely store your token
The token is shown only once. It does not expire. Delete it on the same page to revoke it.
Selecting Easytools requires the Manage Easytools API keys permission on that store; a team member also needs View settings to open the Developer page. Removing the permission does not revoke a key that already exists — the key keeps working until it is deleted.
A token on its own calls one workspace: the easytools projects of that store's owner. There is no header to point a request at a different workspace. Create a separate token in the other store.
Using the API
Base URL
All API requests should be made to:
https://app.easy.tools/api/v1
Authentication
Include your API token in the Authorization header using Bearer authentication:
Authorization: Bearer YOUR_API_TOKEN
It's important to also include these headers in all requests:
Content-Type: application/json
Accept: application/json
Example Request
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
https://app.easy.tools/api/v1/players
Workspace
When the token was created for your own store, you are the workspace owner and these endpoints run as you.
When a team member created the token, each request checks that they hold either of the two permissions for the tool being called:
| Tool | Permissions (either one) |
|---|---|
| Courses | Manage courses, Publish courses |
| Testimonial Walls | Manage testimonials, Publish testimonials |
| Legal Pages | Manage legal pages, Publish legal pages |
Without either permission, every call to that tool returns 403 FORBIDDEN, except the comment reads. A team member can call those without Manage courses or Publish courses. A caller who is not the workspace owner and not on that owner's team still receives 403 FORBIDDEN on those routes.
{
"error": {
"code": "FORBIDDEN",
"message": "You are not allowed to perform this action"
}
}
This is not an authentication failure — a missing or invalid token returns
401 instead — and it is not a rate limit.
Rate Limiting
The API enforces rate limiting per user — the person the token was issued to. Tokens for the same person share one limit.
- 100 requests per 60 seconds
Rate Limit Headers
Responses to an authenticated request include rate limit information:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed in the window |
X-RateLimit-Remaining | Requests remaining in the current window |
X-RateLimit-Reset | Unix timestamp, in seconds, when the window resets |
When rate limited, an additional header is included:
| Header | Description |
|---|---|
Retry-After | Seconds until the rate limit resets |
Rate Limit Error (429 Too Many Requests)
{
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests. Please retry after 60 seconds."
}
}
The number of seconds in message matches the Retry-After header.
Response Formats
Success Response
All successful responses wrap the payload in a data key:
{
"data": { ... }
}
A list also includes pagination. See List Courses.
Error Response
Error responses, including 401, follow this format:
{
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error message"
}
}
Validation errors include an additional details field with field-specific messages:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "The given data was invalid",
"details": {
"name": ["name should not be empty"]
}
}
}
Date/Time Format
Timestamps are ISO 8601 in UTC, with milliseconds: YYYY-MM-DDTHH:mm:ss.sssZ
Example: 2026-01-15T10:30:00.000Z
A timestamp field is always present. When there is no time, the value is null.
Common Error Codes
| Status | Code | Description |
|---|---|---|
| 401 | UNAUTHENTICATED | No bearer token |
| 401 | INVALID_TOKEN | Malformed, unknown, wrong, or expired token |
| 402 | PUBLISH_NOT_ALLOWED | The account needs to start the Easytools trial or choose a plan before anything can go live. Publish Course returns this and changes nothing. |
| 402 | PUBLISH_LIMIT_REACHED | The account has no free publish slot left. Publish Course returns this and changes nothing. |
| 403 | FORBIDDEN | Authenticated, but not allowed to use this tool in the workspace |
| 404 | RESOURCE_NOT_FOUND | No route matches the path and method, or the id does not match a resource |
| 409 | CONFLICT | draft_version is not the current counter. Read the course and retry. See Draft version. |
| 409 | DUPLICATE_RESOURCE | The resource already exists, such as a product that is already linked to the course |
| 422 | VALIDATION_ERROR | Invalid input data |
| 429 | RATE_LIMITED | Too many requests |
| 500 | SERVER_ERROR | Unexpected server error |
| 502 | UPSTREAM_ERROR | Easycart answered, but not with a result the API can use. Paywall, include=products, and analytics routes only. |
| 503 | SERVICE_UNAVAILABLE | A service the call depends on, such as Easycart, is unavailable. Nothing was changed. Retry later. |
401 uses the same error envelope as the other status codes. A missing token returns UNAUTHENTICATED with message "Missing bearer token". A malformed, unknown, wrong, or expired token returns INVALID_TOKEN with message "Invalid or expired token" — the response does not say which of those it was.