Snooplytics API DocsHome

API Structure

Request headers, response envelope, pagination, and the query conventions shared by every endpoint

2 min read

Every endpoint follows the same request and response contract. Once you know the envelope, the pagination scheme, and the auth headers, you can call any route without re-reading per-endpoint docs.

Base URL

https://api.example.com

Replace the host with your deployment.

Request Headers

HeaderWhen to sendValue
Content-TypeAny request with a bodyapplication/json
CookieBrowser or server acting as a signed-in userSession cookie (sent automatically)
X-Api-KeyPersonal Access Token (scripts, CI, integrations)ba_AbCdEf...
AuthorizationThird-party OAuth app on behalf of a userBearer eyJhbGciOi...

The organization id for tenant-scoped routes goes in the URL path (/api/user/organizations/{organizationId}/...), not in a header.

Sending a typical authenticated request

ORG_ID="01HZ3K5R4X9Y2V6QF8TJ7W0CDN"
curl "https://api.example.com/api/user/organizations/${ORG_ID}/projects" \
-H "X-Api-Key: ba_AbCdEf0123456789XyZ..."

Request Bodies

Bodies are JSON. Write endpoints reject Content-Type values other than application/json. Request bodies are capped at 10 MiB — anything larger returns 400 Bad Request.

ORG_ID="01HZ3K5R4X9Y2V6QF8TJ7W0CDN"
curl -X POST "https://api.example.com/api/user/organizations/${ORG_ID}/projects" \
-H "Content-Type: application/json" \
-H "X-Api-Key: ba_AbCdEf..." \
-d '{
  "name": "Acme Studio",
  "slug": "acme-studio",
  "website": "https://acme.example"
}'

Response Envelope

Every JSON response — success or error — uses the same envelope.

Success

{
"success": true,
"status": 201,
"code": "OK",
"message": "Project created successfully",
"data": {
  "id": "01HZ3KA7V0J9Z8Q2G5B1T7XWDN",
  "name": "Acme Studio",
  "slug": "acme-studio",
  "website": "https://acme.example",
  "status": "active",
  "createdAt": "2026-04-05T10:00:00.000Z",
  "updatedAt": "2026-04-05T10:00:00.000Z"
}
}

Error

{
"success": false,
"status": 400,
"code": "VALIDATION_ERROR",
"message": "Validation failed for fields: website, slug",
"meta": {
  "errors": [
    { "field": "website", "error": "Invalid url",            "location": "body" },
    { "field": "slug",    "error": "The slug must be unique", "location": "body" }
  ]
}
}

Each entry in meta.errors has field (the offending field path), error (the message), and location (body, query, or params).

Fields

FieldTypeDescription
successbooleantrue when status < 400, false otherwise
statusnumberHTTP status code, mirrored in the body so clients only parse once
codestringMachine-readable code — OK on success, see Error Handling
messagestringHuman-readable summary
dataobject | arrayResource, list, or operation result on success
metaobjectPagination, validation error details, or other structured extras

Pagination

List endpoints use offset-based pagination. The query parameters are offset and limit; the response meta returns total, offset, limit, and hasMore.

ParameterTypeDefaultMaxDescription
offsetnumber0Number of items to skip
limitnumber20100Number of items to return

Some endpoints add their own filters on top — a q search query, a status filter, etc. See the API Reference for endpoint-specific parameters.

ORG_ID="01HZ3K5R4X9Y2V6QF8TJ7W0CDN"
curl "https://api.example.com/api/user/organizations/${ORG_ID}/projects?offset=40&limit=20" \
-H "X-Api-Key: ba_AbCdEf..."

Paginated response

{
"success": true,
"status": 200,
"code": "OK",
"message": "Projects fetched successfully",
"data": [
  {
    "id": "01HZ3KA7V0J9Z8Q2G5B1T7XWDN",
    "name": "Acme Studio",
    "slug": "acme-studio",
    "status": "active"
  }
],
"meta": {
  "total": 156,
  "offset": 40,
  "limit": 20,
  "hasMore": true
}
}

HTTP Status Codes

Dates and IDs

  • Dates — ISO 8601 with UTC (2026-04-05T10:00:00.000Z). Parse with new Date(...) in JS or datetime.fromisoformat(...) in Python.
  • IDs — UUID v7 strings. They are time-sortable, so ordering by id descending roughly matches createdAt descending.

Field Naming

  • camelCase for every field.
  • Booleans use is, has, or needs prefixes (isActive, isVerified, hasMore).
  • Date fields end in At (createdAt, updatedAt, expiresAt, lastLoginAt).
  • List endpoints return arrays in data and pagination in meta. Single-resource endpoints return an object in data and omit meta unless there is something to report.

A minimal API client

class ApiClient {
constructor({ apiKey, organizationId, baseUrl = 'https://api.example.com' }) {
  this.apiKey = apiKey;
  this.organizationId = organizationId;
  this.baseUrl = baseUrl;
}

async request(path, { method = 'GET', body, searchParams } = {}) {
const url = new URL(this.baseUrl + path);
if (searchParams) {
for (const [k, v] of Object.entries(searchParams)) url.searchParams.set(k, v);
}

  const res = await fetch(url, {
    method,
    headers: {
      'Content-Type': 'application/json',
      'X-Api-Key': this.apiKey,
    },
    body: body ? JSON.stringify(body) : undefined,
  });

  const envelope = await res.json();
  if (!envelope.success) {
    const err = new Error(envelope.message);
    err.code = envelope.code;
    err.status = envelope.status;
    err.meta = envelope.meta;
    throw err;
  }
  return envelope;

}

listProjects({ offset = 0, limit = 20 } = {}) {
return this.request(
`/api/user/organizations/${this.organizationId}/projects`,
{ searchParams: { offset, limit } },
);
}
}

Best Practices

  • Check envelope.success (or code === 'OK') before reading data: The HTTP status is also correct, but checking the body is cheaper than re-parsing the status.
  • Use hasMore, not arithmetic: Trust the server's hasMore flag instead of comparing offset + limit against total; total can shift between paginated requests.
  • Build URLs from the active organization id: Keep the organization id in one place (e.g., a client wrapper that prepends /api/user/organizations/{organizationId}/...) so callers cannot forget to scope their requests.
  • Parse dates as timezone-aware: ISO 8601 with Z is UTC — do not truncate the trailing Z.
  • Surface the code field in logs: It is stable across versions; the human message is not.
  • Retry only idempotent methods: Safe to retry on GET, PUT, DELETE; be careful with POST unless the endpoint documents idempotency.

Next Steps

  1. Handle failures: Read Error Handling for the full code list.
  2. Plan for limits: See Rate Limits before batching calls.
  3. Lock down scopes: Pick the right scopes in Scopes & Permissions.
  4. Configure SSO (optional): Set up Enterprise SSO for domain-based authentication.
  5. Browse endpoints: Jump to the API Reference and start integrating.