Conventions
Response envelope
Every response is JSON. Two shapes:
Success
{
"data": <object or array>,
"meta": <optional, e.g. pagination>
}
Error
{
"error": {
"code": "validation_failed",
"message": "Request validation failed",
"details": [
{ "field": "end_seconds", "message": "must be greater than start_seconds" }
]
}
}
Error codes
| HTTP | code | When |
|---|---|---|
| 401 | unauthorized | Missing or invalid API key |
| 402 | plan_required | Free plan or no plan — upgrade to use the API |
| 402 | quota_exceeded | Plan-level credit (project, transcription, etc.) used up |
| 403 | forbidden | Caller doesn't own the resource, or is a participant |
| 404 | not_found | Resource doesn't exist (or is in another workspace) |
| 422 | validation_failed | Body or query params didn't validate |
| 429 | rate_limited | Per-minute request cap exceeded — see Retry-After |
| 500 | internal | Something broke our side |
Always branch on error.code, not on the message — messages are human-readable and may change.
Pagination
List endpoints take ?limit (default 25, max 100) and ?cursor. The response carries meta.next_cursor; pass it back to fetch the next page. null means you've reached the end.
curl "https://api.userevaluation.com/v1/projects?limit=25"
# response: { "data": [...], "meta": { "next_cursor": "eyJsYXN0SWQi..." } }
curl "https://api.userevaluation.com/v1/projects?limit=25&cursor=eyJsYXN0SWQi..."
Cursors are opaque — don't try to decode them. They embed an internal id and orderings; if we change the sort, we may invalidate cursors mid-pagination.
Idempotency
Send Idempotency-Key: <unique-string> on any POST to make the request safe to retry. We cache the first 2xx response and replay it for any subsequent request with the same key (per-user, 24h TTL).
curl -X POST https://api.userevaluation.com/v1/projects \
-H "Authorization: Bearer ue_live_..." \
-H "Idempotency-Key: my-create-project-2026-05-12-a" \
-H "Content-Type: application/json" \
-d '{"name": "New study"}'
A retry with the same key returns the same project — not a duplicate.
Use a UUID, a hash of your business object, or any string unique-per-write that you can regenerate if needed. The same key from a different user is a different cache entry.
Dates and times
All dates are ISO-8601 UTC strings: 2026-05-12T14:32:01.234Z.
The one exception is engage-test deadlines (postDetails.deadline), which are wall-clock dates in the format YYYY-MM-DD. This is intentional — researchers pick a calendar date, and we treat it as inclusive end-of-day on that date in the participant's timezone.
IDs
IDs are opaque strings. Don't depend on the format — it happens to be a Mongo ObjectId today, may not be tomorrow. Use them as-is in URLs and bodies.
Versioning
This is /v1. We promise not to:
- rename or remove fields
- change a field's type
- change a status code
- change an error code
We reserve the right to:
- add new fields to responses
- add new endpoints
- relax validation (accept previously-rejected inputs)
- accept new optional request fields
A breaking change means /v2. We'll announce it with at least 12 months of overlap.
Your client should ignore unknown response fields gracefully.