# 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.

Breadcrumb: API v1 › Overview · Status: Legacy · Updated: 2026-10-18
Docs index: https://docs.cevoid.com/developers/llms.txt

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](https://docs.cevoid.com/developers/api/v1#reference) lists the endpoints. For the Analytics SDK, MCP and other implementation guides, see the [Developers overview](https://docs.cevoid.com/developers/index.md), and for using Cevoid in the platform, see the [Help Center](https://docs.cevoid.com/index.md).

> [!NOTE]
> API v1 (legacy) means only `api.cevoid.com/v1`. New integrations use [API v2](https://docs.cevoid.com/developers/api.md). The [Storefront API](https://docs.cevoid.com/developers/storefront-api.md) 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

1. Log in to your Cevoid account
2. Go to [Settings → Integrations → Cevoid API](https://app.cevoid.com/settings/integrations/api)
3. 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 needs `posts:read` for the post and inbox endpoints and `members:read` for `GET /v1/members`. The presets `*` and `*:read` include both, and the market endpoints need no scope. See [Secret key scopes](https://docs.cevoid.com/developers/api/authentication.md#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:

```bash
curl https://api.cevoid.com/v1/posts \
  -H "x-api-key: YOUR_API_KEY"
```

> [!TIP]
> If your request is properly authenticated, you receive a successful response with the requested data.

### Keep keys secure

> [!WARNING]
> 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.

> [!WARNING]
> 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.

> [!NOTE]
> Need a higher rate limit? [Contact support](mailto:support@cevoid.com) 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 title="429 Too Many Requests"
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 exceeded
```

The `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:

```javascript
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:

```javascript
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:

```javascript
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

```bash
curl -G https://api.cevoid.com/v1/posts \
  -H "x-api-key: YOUR_API_KEY" \
  -d skip=0 \
  -d limit=10
```

Change `skip` and `limit` to request another part of the result set:

```bash
curl -G https://api.cevoid.com/v1/posts \
  -H "x-api-key: YOUR_API_KEY" \
  -d skip=5 \
  -d limit=15
```

### Paginated response format

Paginated responses include metadata to navigate through the dataset:

```json
{
  "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 available
- `next`: URL for the next page of results, or `null` on the last page
- `nodes`: array containing the requested items

### Pagination best practices

> [!TIP]
> Use the `next` field from the response to get the URL for the next page of results. This ensures you use the correct parameters.

1. **Start with the default limit**: 25 items, which is also the maximum
2. **Use the `next` URL**: don't construct pagination URLs by hand
3. **Handle empty results**: check whether `nodes` is empty to detect the end of results
4. **Respect rate limits**: don't make requests too quickly when you page through large datasets

### Iterate through all results

```javascript
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;
}
```

> [!WARNING]
> 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. Only `label` is supported: it filters posts by their associated labels.
- `operator`: how to combine multiple values. `OR` returns posts matching any of the values, and `AND` returns posts matching all of them.
- `values`: array of IDs to filter by

### Filter by one label

```bash
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:

```bash
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:

```bash
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:

1. Log in to your Cevoid account
2. Go to [Settings → Labels](https://app.cevoid.com/settings/labels)
3. Find the label you want to filter by
4. 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](https://docs.cevoid.com/developers/api/v1#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. |

> [!WARNING]
> 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](https://docs.cevoid.com/developers/api/v1#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](https://docs.cevoid.com/developers/api/v1#rate-limits). |
| `500` | `Something went wrong` | An error occurred on Cevoid's servers. Retry with backoff. |

### Check the status code

```javascript
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

> [!TIP]
> For `500` responses, retry with exponential backoff. These are typically temporary server issues.

```javascript
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

```javascript
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()
  });
}
```

> [!WARNING]
> Never expose API keys or sensitive information in error logs or user-facing error messages.

## Reference

The reference is generated from the [OpenAPI specification](https://docs.cevoid.com/developers/openapi/v1.json). Download it to generate a client or import the API into your HTTP tool.

- Posts: [list posts](https://docs.cevoid.com/developers/api/v1/posts/list-posts.md), [get a post](https://docs.cevoid.com/developers/api/v1/posts/get-post-by-id.md), and list the posts of a [gallery](https://docs.cevoid.com/developers/api/v1/posts/get-posts-from-a-specific-gallery.md), [product](https://docs.cevoid.com/developers/api/v1/posts/get-posts-tagged-with-a-specific-product.md), [collection](https://docs.cevoid.com/developers/api/v1/posts/get-posts-in-a-collection-by-id.md), [member](https://docs.cevoid.com/developers/api/v1/posts/get-posts-from-a-specific-member.md) or [label](https://docs.cevoid.com/developers/api/v1/posts/get-posts-with-a-specific-label.md)
- Inbox: [list pending submissions](https://docs.cevoid.com/developers/api/v1/inbox/get-inbox-posts.md)
- Markets: [list markets](https://docs.cevoid.com/developers/api/v1/markets/get-markets.md) and [get a market](https://docs.cevoid.com/developers/api/v1/markets/get-market-by-id.md)
- Members: [list members](https://docs.cevoid.com/developers/api/v1/members/get-members.md)

---

Canonical: https://docs.cevoid.com/developers/api/v1
