API v1
Authenticate, paginate, filter and include related data, and handle errors and rate limits, in the legacy Cevoid REST API at api.cevoid.com/v1.
Use this section when you call Cevoid's legacy REST API at https://api.cevoid.com/v1 directly. It covers API-specific behavior: authentication, rate limits, pagination, filtering, includes and errors. The reference lists the endpoints. For the Analytics SDK, MCP and other implementation guides, see the Developers overview, and for using Cevoid in the platform, see the Help Center.
API v1 (legacy) means only api.cevoid.com/v1. New integrations use API v2. The Storefront API also has /v1 paths, but it is a separate, current API.
Authentication
Every API v1 endpoint requires an API key. Include it in the x-api-key header of each request.
Get an API key
- Log in to your Cevoid account
- Go to Settings → Integrations → Cevoid API
- Create an API key with the scopes your integration needs, and copy it when it is shown
A new key is shown only once, when you create it.
Legacy and scoped keys
API v1 accepts two kinds of key:
- Scoped secret keys start with
cev_sk_. They are the keys API v2 uses. On API v1, a scoped key needsposts:readfor the post and inbox endpoints andmembers:readforGET /v1/members. The presets*and*:readinclude both, and the market endpoints need no scope. See Secret key scopes. - Legacy v1 keys keep full v1 access until you revoke them. You can no longer create them: existing legacy keys are listed under Legacy keys on the same settings page, where you can revoke them. Legacy keys do not work with API v2.
Send the API key
Include your API key in the request headers using the x-api-key field:
curl https://api.cevoid.com/v1/posts \
-H "x-api-key: YOUR_API_KEY"If your request is properly authenticated, you receive a successful response with the requested data.
Keep keys secure
Never commit your Cevoid API key to version control systems like GitHub. Store your API keys securely and treat them like passwords.
- Store API keys as environment variables
- Use different API keys for development and production environments
- Rotate your API keys regularly
- Restrict API key access to only necessary team members
Rate limits
To ensure fair usage and keep the API stable for all users, API v1 enforces rate limits. When you exceed a limit, the API returns a 429 Too Many Requests response.
Limits per key and per company
| Budget | Applies to | Rate | Burst |
|---|---|---|---|
| Per API key | Every endpoint | 200 requests per second | 400 requests |
| Per company | GET /v1/inbox, GET /v1/posts/{id} and GET /v1/collections/{id}/posts, each with its own budget | 20 requests per second | 40 requests |
A request must pass every budget that applies to it. Requests served from a cache still count.
Rate limits are applied per API key, so applications that share a key share its limit. The per-company budgets are shared by all of your API keys: creating more keys does not add capacity.
Need a higher rate limit? Contact support to discuss your requirements.
Rate limit headers
Responses include headers that help you monitor your usage. They follow the IETF RateLimit header fields:
| Header | Description |
|---|---|
RateLimit-Limit | Burst capacity of the budget: 400 for the API key, or 40 for a per-company budget |
RateLimit-Remaining | Whole requests left in that budget |
RateLimit-Reset | Seconds to wait before retrying a 429; 0 on successful requests |
retry-after | Seconds to wait before making another request (only on 429 responses) |
Handle rate limit errors
When you exceed a limit, you receive a 429 status code with a plain-text body:
HTTP/1.1 429 Too Many Requests
content-type: text/plain; charset=utf-8
RateLimit-Limit: 400
RateLimit-Remaining: 0
RateLimit-Reset: 1
retry-after: 1
Rate limit exceededThe retry-after header tells you how long to wait before retrying.
Monitor rate limit headers
Check the RateLimit-Remaining header to slow down before you reach the limit:
const response = await fetch('https://api.cevoid.com/v1/posts', {
headers: { 'x-api-key': 'YOUR_API_KEY' }
});
const remaining = response.headers.get('RateLimit-Remaining');
if (parseInt(remaining) < 2) {
console.log('Low on requests. Slow down before the next request.');
}Retry after the delay
Wait for the retry-after delay before you retry, and cap the number of attempts:
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('retry-after') || '1');
console.log(`Rate limited. Retrying in ${retryAfter} seconds...`);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
continue;
}
return response;
}
throw new Error('Max retries exceeded');
}Cache responses
Reduce API calls by caching responses when possible:
const cache = new Map();
const CACHE_TTL = 60000; // 1 minute
async function getCachedPosts(galleryId) {
const cacheKey = `posts-${galleryId}`;
const cached = cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
return cached.data;
}
const response = await fetch(`https://api.cevoid.com/v1/galleries/${galleryId}/posts`, {
headers: { 'x-api-key': 'YOUR_API_KEY' }
});
const data = await response.json();
cache.set(cacheKey, { data, timestamp: Date.now() });
return data;
}Pagination
API v1 uses pagination to limit the data returned in a single request, which makes large datasets easier to work with. All endpoints that return multiple items support pagination.
Pagination parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | integer | 25 | Number of items returned per request, from 1 to 25 |
skip | integer | 0 | Number of items to skip from the beginning of the result set |
A limit above 25 returns 400 with the body limit: less than or equal to 25.
Make paginated requests
curl -G https://api.cevoid.com/v1/posts \
-H "x-api-key: YOUR_API_KEY" \
-d skip=0 \
-d limit=10Change skip and limit to request another part of the result set:
curl -G https://api.cevoid.com/v1/posts \
-H "x-api-key: YOUR_API_KEY" \
-d skip=5 \
-d limit=15Paginated response format
Paginated responses include metadata to navigate through the dataset:
{
"count": 30,
"next": "https://api.cevoid.com/v1/posts?skip=15&limit=10",
"nodes": [
{
"id": "507f1f77bcf86cd799439011",
// ... post data
},
{
"id": "507f191e810c19729de860ea",
// ... post data
}
// ... more items
]
}count: total number of items availablenext: URL for the next page of results, ornullon the last pagenodes: array containing the requested items
Pagination best practices
Use the next field from the response to get the URL for the next page of results. This ensures you use the correct parameters.
- Start with the default limit: 25 items, which is also the maximum
- Use the
nextURL: don't construct pagination URLs by hand - Handle empty results: check whether
nodesis empty to detect the end of results - Respect rate limits: don't make requests too quickly when you page through large datasets
Iterate through all results
let skip = 0;
const limit = 25; // Use maximum for efficiency
let hasMoreData = true;
while (hasMoreData) {
const response = await fetch(`https://api.cevoid.com/v1/posts?skip=${skip}&limit=${limit}`, {
headers: {
'x-api-key': 'YOUR_API_KEY'
}
});
const data = await response.json();
// Process the current batch
data.nodes.forEach(post => {
// Handle each post
});
// Check if there are more results
hasMoreData = data.nodes.length === limit;
skip += limit;
}The maximum limit is 25 items per request. A request with a higher limit returns 400.
Filtering
Filtering helps you retrieve specific subsets of data. Filters narrow down results based on criteria such as label associations. GET /v1/posts and GET /v1/products/{id}/posts accept a filter parameter.
Filter structure
Pass filters as a URL-encoded JSON string containing an array of filter objects. Each filter object must include three fields:
type: the type of filter to apply. Onlylabelis supported: it filters posts by their associated labels.operator: how to combine multiple values.ORreturns posts matching any of the values, andANDreturns posts matching all of them.values: array of IDs to filter by
Filter by one label
curl -G https://api.cevoid.com/v1/posts \
-H "x-api-key: YOUR_API_KEY" \
--data-urlencode 'filter=[{"type":"label","operator":"OR","values":["507f1f77bcf86cd799439011"]}]'Filter by any of several labels
Returns posts that have at least one of the specified labels:
curl -G https://api.cevoid.com/v1/posts \
-H "x-api-key: YOUR_API_KEY" \
--data-urlencode 'filter=[{"type":"label","operator":"OR","values":["507f1f77bcf86cd799439011","507f191e810c19729de860ea"]}]'Filter by all of several labels
Returns posts that have all of the specified labels:
curl -G https://api.cevoid.com/v1/posts \
-H "x-api-key: YOUR_API_KEY" \
--data-urlencode 'filter=[{"type":"label","operator":"AND","values":["507f1f77bcf86cd799439011","507f191e810c19729de860ea"]}]'Get label IDs
To filter by label, you need each label's ID:
- Log in to your Cevoid account
- Go to Settings → Labels
- Find the label you want to filter by
- Open the label's menu and choose Copy ID
Label IDs are 24-character hexadecimal strings. GET /v1/posts also returns the ID of each post's labels when you request include=labels. See Includes.
Filter errors
An invalid filter returns 400 with a plain-text body:
| Response body | Solution |
|---|---|
A JSON parse error, such as Unexpected token 'o', "not-json" is not valid JSON | Make sure the filter is valid JSON and properly URL-encoded. |
Filter param needs to be an array | The filter parameter must be a JSON array, even if you filter by one label. |
Type is required, Operator is required or Values is required | Each filter object must include type, operator and values. |
Filter type category is not supported | Only label is supported as a filter type. |
Filter operator > is not supported | Only the OR and AND operators are supported. |
Filter values must be an array | values must be an array of IDs. |
Filter value "invalid-id" is not a valid ID | Every value must be a valid ID: a 24-character hexadecimal string. |
Always URL-encode the filter parameter when making requests. Invalid JSON or incorrectly formatted IDs return 400 errors.
Includes
Use the include query parameter when you need related data alongside the resources you request. You get that data in the same response, and responses stay smaller when you do not need it.
Each endpoint's reference lists the relationships it supports and the fields they add. Includes are only available on endpoints that document the include parameter.
Supported routes
| Endpoint | Supported includes |
|---|---|
GET /v1/posts | labels |
Include query format
Pass include once, using a comma-separated list of supported relationship names. Whitespace around names is ignored; repeating a name does not duplicate the included data.
Unsupported names, empty values and repeated include query parameters return 400. Use only the names listed in the endpoint's reference.
Include response behavior
Includes add related data to each returned resource. They do not change which resources match your request. When you omit include, you get the endpoint's default response.
The endpoint's response schema describes the included fields and what to expect when related data is unavailable.
Paginate with includes
Follow the response's next URL to keep includes on subsequent pages. See Pagination for the response format.
Errors
API v1 uses standard HTTP status codes to indicate the success or failure of your requests. A successful request returns 200 with a JSON body. An error returns a plain-text body that describes the problem, not JSON.
Status codes
| Status | Example body | Meaning and solution |
|---|---|---|
400 | limit: less than or equal to 25 | A parameter is invalid. The body names each invalid parameter and the rule it broke, separated by commas. |
400 | Could not find gallery | A gallery, product, member, label, collection or market in the request does not exist. Check the ID. |
401 | No valid API key provided | The API key is missing or invalid. Check the key and that you send it in the x-api-key header. |
403 | You are not allowed to access this resource | The key lacks the scope this endpoint needs, or your plan does not include API access. |
404 | Could not find post | GET /v1/posts/{id} or GET /v1/markets/{id} found no post or market with that ID. |
429 | Rate limit exceeded | Wait for the retry-after delay. See Rate limits. |
500 | Something went wrong | An error occurred on Cevoid's servers. Retry with backoff. |
Check the status code
const response = await fetch('https://api.cevoid.com/v1/posts', {
headers: {
'x-api-key': 'YOUR_API_KEY'
}
});
if (!response.ok) {
const message = await response.text();
console.error(`API Error (${response.status}):`, message);
return;
}
const data = await response.json();Retry server errors
For 500 responses, retry with exponential backoff. These are typically temporary server issues.
async function retryRequest(url, options, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(url, options);
if (response.ok || response.status < 500) {
return response; // Success or client error (don't retry)
}
if (attempt === maxRetries) {
throw new Error(`Request failed after ${maxRetries} attempts`);
}
// Wait before retrying (exponential backoff)
await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, attempt - 1)));
} catch (error) {
if (attempt === maxRetries) throw error;
}
}
}Log errors for debugging
if (!response.ok) {
const message = await response.text();
// Log the error with context
console.error('API Request Failed:', {
status: response.status,
message,
requestUrl: response.url,
timestamp: new Date().toISOString()
});
}Never expose API keys or sensitive information in error logs or user-facing error messages.
Reference
The reference is generated from the OpenAPI specification. Download it to generate a client or import the API into your HTTP tool.
- Posts: list posts, get a post, and list the posts of a gallery, product, collection, member or label
- Inbox: list pending submissions
- Markets: list markets and get a market
- Members: list members