# VideoTestimonials REST API reference

> Read and manage the testimonials and spaces in a VideoTestimonials account: pull approved testimonials into your own site or app, push testimonials in from another system, moderate in bulk, and manage spaces.

## Overview

Base URL: `https://app.videotestimonials.video/api/v1`. Requests and responses are JSON. The same reference is available as an OpenAPI 3.1 document at [/openapi.json](https://videotestimonials.video/openapi.json) and through the API catalog at [/.well-known/api-catalog](https://videotestimonials.video/.well-known/api-catalog) (RFC 9727). Call the API from a server: a key can read and change your account's data.

## Authentication

Create a key in the dashboard under [Settings → API Keys](https://app.videotestimonials.video/dashboard/settings/connection-keys). The full key is shown once, when you create it; only a hash is stored. Send it as a bearer token on every request:

```
Authorization: Bearer sk_live_…
```

A key acts for the account that created it and sees every space and testimonial that account owns. Each key has one or more scopes, and each endpoint needs one of them:

- `read` — every `GET`
- `write` — creating and updating, including batch status updates
- `delete` — deleting, including batch deletes

A key can be switched off or given an expiry date in the dashboard; requests with it then fail with `403`.

## Rate limits

Each account can make 1,000 requests per hour, shared by all of its keys. The hour starts with the first request and resets an hour later. Successful responses carry:

- `X-RateLimit-Limit` — Requests allowed per hour for this account.
- `X-RateLimit-Remaining` — Requests left in the current window.
- `X-RateLimit-Reset` — When the current window ends, as a Unix timestamp in milliseconds.

Past the limit, requests return `429` with `RATE_LIMIT_EXCEEDED` and the reset time in `error.details.reset`. The count is kept per server instance, so treat the limit as a ceiling to stay under rather than an exact budget.

## Errors

Every error has the same shape:

```json
{
  "error": {
    "code": "TESTIMONIAL_NOT_FOUND",
    "message": "Testimonial not found or you do not have access to it"
  }
}
```

- `UNAUTHORIZED` — No `Authorization` header was sent (401).
- `INVALID_API_KEY` — The key is not `sk_live_…`/`sk_test_…` shaped, or no key with that value exists (401).
- `API_KEY_DISABLED` — The key was switched off in the dashboard (403).
- `API_KEY_EXPIRED` — The key is past its expiry date (403).
- `INSUFFICIENT_SCOPE` — The key lacks the scope this operation needs: `read`, `write` or `delete` (403).
- `RATE_LIMIT_EXCEEDED` — More than 1,000 requests in the current hour (429).
- `VALIDATION_ERROR` — A query parameter or body field failed validation (400).
- `TESTIMONIAL_NOT_FOUND` — No testimonial with that id belongs to this account (404).
- `SPACE_NOT_FOUND` — No space with that id belongs to this account (404).
- `INVALID_ENDPOINT` — The batch operation is neither `update` nor `delete` (404).
- `INTERNAL_ERROR` — Anything else, including a request body that is not valid JSON (500).

`VALIDATION_ERROR` does not say which field failed; check the field rules under each endpoint below.

## Pagination

List endpoints take `limit` (1–100, default 50) and `offset` (default 0), and return a `pagination` object next to `data`: `{"total": 120, "limit": 50, "offset": 50, "has_more": true}`.

## Browser access (CORS)

`/testimonials`, `/testimonials/{id}` and `/spaces` answer browser preflight requests and send `Access-Control-Allow-Origin: *`. `/spaces/{id}` and the batch endpoints do not, so call those from a server. Either way, never put a key in front-end code — anyone who can load the page can read it. To show testimonials on a website, use the [embed widget](https://videotestimonials.video/docs) instead.

## Webhooks

Writes through the API fire the same webhooks as the dashboard: `testimonial.created` on create, `testimonial.approved` and `testimonial.rejected` when a status changes to one of those, and `testimonial.deleted` on delete. Set up endpoints in the dashboard — see [Webhooks & automations](https://videotestimonials.video/help/share/integrations-webhooks).

## Testimonials

Read, create, moderate and delete testimonials.

### List testimonials

`GET /testimonials` · scope: `read`

Testimonials across every space the account owns, newest first unless `sort_by`/`sort_order` say otherwise.

**Query parameters**

- `space_id` (string (uuid)) — Only testimonials in this space.
- `status` (string) — Only testimonials with this moderation status. One of: `pending`, `approved`, `rejected`
- `type` (string) — Only text or only video testimonials. One of: `text`, `video`
- `rating` (integer) — Only testimonials with exactly this star rating. 1–5
- `search` (string) — Case-insensitive substring match on `text_content`.
- `date_from` (string (date-time)) — Only testimonials created at or after this time. ISO 8601 in UTC with a `Z` suffix, e.g. `2026-01-01T00:00:00Z`.
- `date_to` (string (date-time)) — Only testimonials created at or before this time. ISO 8601 in UTC with a `Z` suffix.
- `limit` (integer) — Number of items to return. 1–100; Default `50`
- `offset` (integer) — Number of items to skip, for paging. Min 0; Default `0`
- `sort_by` (string) — Column to sort by. Defaults to `created_at`. One of: `created_at`, `updated_at`, `rating`, `shares`
- `sort_order` (string) — Sort direction. One of: `asc`, `desc`; Default `desc`

**Responses**

- `200` A page of testimonials.
- `400` A query parameter or body field failed validation. Codes: `VALIDATION_ERROR`.
- `401` Missing, malformed or unknown API key. Codes: `UNAUTHORIZED`, `INVALID_API_KEY`.
- `403` The key is disabled, expired, or lacks the scope this operation needs. Codes: `INSUFFICIENT_SCOPE`, `API_KEY_DISABLED`, `API_KEY_EXPIRED`.
- `429` More than 1,000 requests in the current hour. Codes: `RATE_LIMIT_EXCEEDED`.
- `500` Unexpected failure, or a request body that is not valid JSON. Codes: `INTERNAL_ERROR`.

```bash
curl "https://app.videotestimonials.video/api/v1/testimonials?status=approved&limit=10" \
  -H "Authorization: Bearer $VT_API_KEY"
```

Example response:

```json
{
  "data": [
    {
      "id": "c7d1a2b3-5e6f-4a8b-9c0d-1e2f3a4b5c6d",
      "space_id": "8b3e2f9a-4c1d-4e5f-9a7b-2c3d4e5f6a7b",
      "user_id": "1f2e3d4c-5b6a-4978-8a9b-0c1d2e3f4a5b",
      "type": "text",
      "status": "approved",
      "submitter_name": "Jane Doe",
      "submitter_email": "jane@example.com",
      "submitter_company": "Example Co",
      "submitter_role": "Head of Marketing",
      "submitter_avatar_url": null,
      "text_content": "We collected twelve video testimonials in our first week.",
      "video_url": null,
      "video_thumbnail_url": null,
      "rating": 5,
      "created_at": "2026-10-01T09:30:00.123456+00:00"
    }
  ],
  "pagination": {
    "total": 1,
    "limit": 50,
    "offset": 0,
    "has_more": false
  }
}
```

### Create a testimonial

`POST /testimonials` · scope: `write`

Adds a testimonial to one of the account's spaces and fires the `testimonial.created` webhook. For a `video` testimonial with a `video_url`, the request also transcribes the video before it responds; on success the transcript is saved to `ai_transcript` and `text_content`. The response is the row as inserted, before transcription.

**Request body (JSON)**

- `space_id` (string (uuid), required) — Space to add the testimonial to. Must belong to the account that owns the API key.
- `type` (string, required) — `text` or `video`. One of: `text`, `video`
- `submitter_name` (string, required) — Name of the person giving the testimonial. 1–100 characters
- `submitter_email` (string (email), required) — Email of the person giving the testimonial.
- `submitter_company` (string) — Their company. Max 100 characters
- `submitter_role` (string) — Their job title or role. Max 100 characters
- `submitter_avatar_url` (string (uri)) — URL of their photo.
- `text_content` (string) — The testimonial text. For a video testimonial this is replaced by the transcript once transcription succeeds.
- `video_url` (string (uri)) — Publicly reachable URL of the video file. When `type` is `video`, the API downloads it and transcribes it before responding.
- `video_thumbnail_url` (string (uri)) — URL of a thumbnail image for the video.
- `rating` (integer) — Star rating. 1–5
- `status` (string) — Moderation status. Embed widgets only show `approved` testimonials. One of: `pending`, `approved`, `rejected`; Default `pending`

**Responses**

- `201` The created testimonial.
- `400` A query parameter or body field failed validation. Codes: `VALIDATION_ERROR`.
- `401` Missing, malformed or unknown API key. Codes: `UNAUTHORIZED`, `INVALID_API_KEY`.
- `403` The key is disabled, expired, or lacks the scope this operation needs. Codes: `INSUFFICIENT_SCOPE`, `API_KEY_DISABLED`, `API_KEY_EXPIRED`.
- `404` `space_id` is not a space this account owns. Codes: `SPACE_NOT_FOUND`.
- `429` More than 1,000 requests in the current hour. Codes: `RATE_LIMIT_EXCEEDED`.
- `500` Unexpected failure, or a request body that is not valid JSON. Codes: `INTERNAL_ERROR`.

```bash
curl -X POST "https://app.videotestimonials.video/api/v1/testimonials" \
  -H "Authorization: Bearer $VT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"space_id":"8b3e2f9a-4c1d-4e5f-9a7b-2c3d4e5f6a7b","type":"text","submitter_name":"Jane Doe","submitter_email":"jane@example.com","submitter_company":"Example Co","text_content":"We collected twelve video testimonials in our first week.","rating":5,"status":"approved"}'
```

Example response:

```json
{
  "data": {
    "id": "c7d1a2b3-5e6f-4a8b-9c0d-1e2f3a4b5c6d",
    "space_id": "8b3e2f9a-4c1d-4e5f-9a7b-2c3d4e5f6a7b",
    "user_id": "1f2e3d4c-5b6a-4978-8a9b-0c1d2e3f4a5b",
    "type": "text",
    "status": "approved",
    "submitter_name": "Jane Doe",
    "submitter_email": "jane@example.com",
    "submitter_company": "Example Co",
    "submitter_role": "Head of Marketing",
    "submitter_avatar_url": null,
    "text_content": "We collected twelve video testimonials in our first week.",
    "video_url": null,
    "video_thumbnail_url": null,
    "rating": 5,
    "created_at": "2026-10-01T09:30:00.123456+00:00"
  }
}
```

### Get a testimonial

`GET /testimonials/{id}` · scope: `read`

**Path parameters**

- `id` (string (uuid), required) — Resource id (UUID).

**Responses**

- `200` The testimonial.
- `401` Missing, malformed or unknown API key. Codes: `UNAUTHORIZED`, `INVALID_API_KEY`.
- `403` The key is disabled, expired, or lacks the scope this operation needs. Codes: `INSUFFICIENT_SCOPE`, `API_KEY_DISABLED`, `API_KEY_EXPIRED`.
- `404` No testimonial with that id belongs to this account. Codes: `TESTIMONIAL_NOT_FOUND`.
- `429` More than 1,000 requests in the current hour. Codes: `RATE_LIMIT_EXCEEDED`.
- `500` Unexpected failure, or a request body that is not valid JSON. Codes: `INTERNAL_ERROR`.

```bash
curl "https://app.videotestimonials.video/api/v1/testimonials/c7d1a2b3-5e6f-4a8b-9c0d-1e2f3a4b5c6d" \
  -H "Authorization: Bearer $VT_API_KEY"
```

Example response:

```json
{
  "data": {
    "id": "c7d1a2b3-5e6f-4a8b-9c0d-1e2f3a4b5c6d",
    "space_id": "8b3e2f9a-4c1d-4e5f-9a7b-2c3d4e5f6a7b",
    "user_id": "1f2e3d4c-5b6a-4978-8a9b-0c1d2e3f4a5b",
    "type": "text",
    "status": "approved",
    "submitter_name": "Jane Doe",
    "submitter_email": "jane@example.com",
    "submitter_company": "Example Co",
    "submitter_role": "Head of Marketing",
    "submitter_avatar_url": null,
    "text_content": "We collected twelve video testimonials in our first week.",
    "video_url": null,
    "video_thumbnail_url": null,
    "rating": 5,
    "created_at": "2026-10-01T09:30:00.123456+00:00"
  }
}
```

### Update a testimonial

`PATCH /testimonials/{id}` · scope: `write`

Changes status, text, rating, company or role. A status change to `approved` or `rejected` fires `testimonial.approved` or `testimonial.rejected`.

**Path parameters**

- `id` (string (uuid), required) — Resource id (UUID).

**Request body (JSON)**

- `status` (string) — New moderation status. Changing it to `approved` or `rejected` fires the matching webhook. One of: `pending`, `approved`, `rejected`
- `text_content` (string) — Replacement testimonial text.
- `rating` (integer) — Star rating. 1–5
- `submitter_company` (string) — Their company. Max 100 characters
- `submitter_role` (string) — Their job title or role. Max 100 characters

Send at least one of these fields. Fields not listed are ignored.

**Responses**

- `200` The updated testimonial.
- `400` A query parameter or body field failed validation. Codes: `VALIDATION_ERROR`.
- `401` Missing, malformed or unknown API key. Codes: `UNAUTHORIZED`, `INVALID_API_KEY`.
- `403` The key is disabled, expired, or lacks the scope this operation needs. Codes: `INSUFFICIENT_SCOPE`, `API_KEY_DISABLED`, `API_KEY_EXPIRED`.
- `404` No testimonial with that id belongs to this account. Codes: `TESTIMONIAL_NOT_FOUND`.
- `429` More than 1,000 requests in the current hour. Codes: `RATE_LIMIT_EXCEEDED`.
- `500` Unexpected failure, or a request body that is not valid JSON. Codes: `INTERNAL_ERROR`.

```bash
curl -X PATCH "https://app.videotestimonials.video/api/v1/testimonials/c7d1a2b3-5e6f-4a8b-9c0d-1e2f3a4b5c6d" \
  -H "Authorization: Bearer $VT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status":"approved"}'
```

Example response:

```json
{
  "data": {
    "id": "c7d1a2b3-5e6f-4a8b-9c0d-1e2f3a4b5c6d",
    "space_id": "8b3e2f9a-4c1d-4e5f-9a7b-2c3d4e5f6a7b",
    "user_id": "1f2e3d4c-5b6a-4978-8a9b-0c1d2e3f4a5b",
    "type": "text",
    "status": "approved",
    "submitter_name": "Jane Doe",
    "submitter_email": "jane@example.com",
    "submitter_company": "Example Co",
    "submitter_role": "Head of Marketing",
    "submitter_avatar_url": null,
    "text_content": "We collected twelve video testimonials in our first week.",
    "video_url": null,
    "video_thumbnail_url": null,
    "rating": 5,
    "created_at": "2026-10-01T09:30:00.123456+00:00"
  }
}
```

### Delete a testimonial

`DELETE /testimonials/{id}` · scope: `delete`

Deletes the testimonial, fires `testimonial.deleted`, and removes its uploaded avatar, video and thumbnail files from storage.

**Path parameters**

- `id` (string (uuid), required) — Resource id (UUID).

**Responses**

- `204` Deleted. No body.
- `401` Missing, malformed or unknown API key. Codes: `UNAUTHORIZED`, `INVALID_API_KEY`.
- `403` The key is disabled, expired, or lacks the scope this operation needs. Codes: `INSUFFICIENT_SCOPE`, `API_KEY_DISABLED`, `API_KEY_EXPIRED`.
- `404` No testimonial with that id belongs to this account. Codes: `TESTIMONIAL_NOT_FOUND`.
- `429` More than 1,000 requests in the current hour. Codes: `RATE_LIMIT_EXCEEDED`.
- `500` Unexpected failure, or a request body that is not valid JSON. Codes: `INTERNAL_ERROR`.

```bash
curl -X DELETE "https://app.videotestimonials.video/api/v1/testimonials/c7d1a2b3-5e6f-4a8b-9c0d-1e2f3a4b5c6d" \
  -H "Authorization: Bearer $VT_API_KEY"
```

## Batch

Moderate or delete up to 100 testimonials in one request.

### Set the status of up to 100 testimonials

`POST /testimonials/batch/update` · scope: `write`

Sets one status on every listed testimonial the account owns. Setting `approved` or `rejected` fires the matching webhook once per testimonial.

**Request body (JSON)**

- `testimonial_ids` (string (uuid)[], required) — Testimonials to update. Ids that do not exist or belong to another account are skipped. 1–100 items
- `status` (string, required) — Status to set on every listed testimonial. One of: `pending`, `approved`, `rejected`

**Responses**

- `200` The updated testimonials and how many changed.
- `400` A query parameter or body field failed validation. Codes: `VALIDATION_ERROR`.
- `401` Missing, malformed or unknown API key. Codes: `UNAUTHORIZED`, `INVALID_API_KEY`.
- `403` The key is disabled, expired, or lacks the scope this operation needs. Codes: `INSUFFICIENT_SCOPE`, `API_KEY_DISABLED`, `API_KEY_EXPIRED`.
- `429` More than 1,000 requests in the current hour. Codes: `RATE_LIMIT_EXCEEDED`.
- `500` Unexpected failure, or a request body that is not valid JSON. Codes: `INTERNAL_ERROR`.

```bash
curl -X POST "https://app.videotestimonials.video/api/v1/testimonials/batch/update" \
  -H "Authorization: Bearer $VT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"testimonial_ids":["c7d1a2b3-5e6f-4a8b-9c0d-1e2f3a4b5c6d","d2e3f4a5-6b7c-4d8e-8f9a-0b1c2d3e4f5a"],"status":"approved"}'
```

Example response:

```json
{
  "data": [
    {
      "id": "c7d1a2b3-5e6f-4a8b-9c0d-1e2f3a4b5c6d",
      "space_id": "8b3e2f9a-4c1d-4e5f-9a7b-2c3d4e5f6a7b",
      "user_id": "1f2e3d4c-5b6a-4978-8a9b-0c1d2e3f4a5b",
      "type": "text",
      "status": "approved",
      "submitter_name": "Jane Doe",
      "submitter_email": "jane@example.com",
      "submitter_company": "Example Co",
      "submitter_role": "Head of Marketing",
      "submitter_avatar_url": null,
      "text_content": "We collected twelve video testimonials in our first week.",
      "video_url": null,
      "video_thumbnail_url": null,
      "rating": 5,
      "created_at": "2026-10-01T09:30:00.123456+00:00"
    }
  ],
  "updated": 1
}
```

### Delete up to 100 testimonials

`POST /testimonials/batch/delete` · scope: `delete`

Deletes every listed testimonial the account owns and fires `testimonial.deleted` once per testimonial. Unlike the single delete, uploaded files are not removed from storage.

**Request body (JSON)**

- `testimonial_ids` (string (uuid)[], required) — Testimonials to delete. Ids that do not exist or belong to another account are skipped. 1–100 items

**Responses**

- `200` How many testimonials were deleted.
- `400` A query parameter or body field failed validation. Codes: `VALIDATION_ERROR`.
- `401` Missing, malformed or unknown API key. Codes: `UNAUTHORIZED`, `INVALID_API_KEY`.
- `403` The key is disabled, expired, or lacks the scope this operation needs. Codes: `INSUFFICIENT_SCOPE`, `API_KEY_DISABLED`, `API_KEY_EXPIRED`.
- `429` More than 1,000 requests in the current hour. Codes: `RATE_LIMIT_EXCEEDED`.
- `500` Unexpected failure, or a request body that is not valid JSON. Codes: `INTERNAL_ERROR`.

```bash
curl -X POST "https://app.videotestimonials.video/api/v1/testimonials/batch/delete" \
  -H "Authorization: Bearer $VT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"testimonial_ids":["c7d1a2b3-5e6f-4a8b-9c0d-1e2f3a4b5c6d","d2e3f4a5-6b7c-4d8e-8f9a-0b1c2d3e4f5a"]}'
```

Example response:

```json
{
  "deleted": 2
}
```

## Spaces

A space groups one brand's collection forms, testimonials and Wall of Love.

### List spaces

`GET /spaces` · scope: `read`

Spaces the account owns, newest first unless `sort_by`/`sort_order` say otherwise.

**Query parameters**

- `limit` (integer) — Number of items to return. 1–100; Default `50`
- `offset` (integer) — Number of items to skip, for paging. Min 0; Default `0`
- `sort_by` (string) — Column to sort by. Defaults to `created_at`. One of: `created_at`, `updated_at`, `rating`, `shares`
- `sort_order` (string) — Sort direction. One of: `asc`, `desc`; Default `desc`

**Responses**

- `200` A page of spaces.
- `400` A query parameter or body field failed validation. Codes: `VALIDATION_ERROR`.
- `401` Missing, malformed or unknown API key. Codes: `UNAUTHORIZED`, `INVALID_API_KEY`.
- `403` The key is disabled, expired, or lacks the scope this operation needs. Codes: `INSUFFICIENT_SCOPE`, `API_KEY_DISABLED`, `API_KEY_EXPIRED`.
- `429` More than 1,000 requests in the current hour. Codes: `RATE_LIMIT_EXCEEDED`.
- `500` Unexpected failure, or a request body that is not valid JSON. Codes: `INTERNAL_ERROR`.

```bash
curl "https://app.videotestimonials.video/api/v1/spaces" \
  -H "Authorization: Bearer $VT_API_KEY"
```

Example response:

```json
{
  "data": [
    {
      "id": "8b3e2f9a-4c1d-4e5f-9a7b-2c3d4e5f6a7b",
      "user_id": "1f2e3d4c-5b6a-4978-8a9b-0c1d2e3f4a5b",
      "name": "Example Co",
      "slug": "example-co",
      "description": "Tell us how Example Co helped you.",
      "logo_url": null,
      "primary_color": "#7C3AED",
      "custom_questions": [
        "What problem did we solve for you?",
        "What result did you get?"
      ],
      "created_at": "2026-09-15T12:00:00.000000+00:00"
    }
  ],
  "pagination": {
    "total": 1,
    "limit": 50,
    "offset": 0,
    "has_more": false
  }
}
```

### Create a space

`POST /spaces` · scope: `write`

**Request body (JSON)**

- `name` (string, required) — Space name, shown on its collection page. 1–100 characters
- `description` (string) — Short description of the space. Max 500 characters
- `logo_url` (string (uri)) — URL of the space logo.
- `primary_color` (string) — Brand colour as a 6-digit hex value, e.g. `#7C3AED`. Pattern `^#[0-9A-Fa-f]{6}$`
- `custom_questions` (string[]) — Question strings stored on the space. Collection forms take their questions from the form builder, not from this field.

**Responses**

- `201` The created space.
- `400` A query parameter or body field failed validation. Codes: `VALIDATION_ERROR`.
- `401` Missing, malformed or unknown API key. Codes: `UNAUTHORIZED`, `INVALID_API_KEY`.
- `403` The key is disabled, expired, or lacks the scope this operation needs. Codes: `INSUFFICIENT_SCOPE`, `API_KEY_DISABLED`, `API_KEY_EXPIRED`.
- `429` More than 1,000 requests in the current hour. Codes: `RATE_LIMIT_EXCEEDED`.
- `500` Unexpected failure, or a request body that is not valid JSON. Codes: `INTERNAL_ERROR`.

```bash
curl -X POST "https://app.videotestimonials.video/api/v1/spaces" \
  -H "Authorization: Bearer $VT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Example Co","primary_color":"#7C3AED"}'
```

Example response:

```json
{
  "data": {
    "id": "8b3e2f9a-4c1d-4e5f-9a7b-2c3d4e5f6a7b",
    "user_id": "1f2e3d4c-5b6a-4978-8a9b-0c1d2e3f4a5b",
    "name": "Example Co",
    "slug": "example-co",
    "description": "Tell us how Example Co helped you.",
    "logo_url": null,
    "primary_color": "#7C3AED",
    "custom_questions": [
      "What problem did we solve for you?",
      "What result did you get?"
    ],
    "created_at": "2026-09-15T12:00:00.000000+00:00"
  }
}
```

### Get a space

`GET /spaces/{id}` · scope: `read`

**Path parameters**

- `id` (string (uuid), required) — Resource id (UUID).

**Responses**

- `200` The space.
- `401` Missing, malformed or unknown API key. Codes: `UNAUTHORIZED`, `INVALID_API_KEY`.
- `403` The key is disabled, expired, or lacks the scope this operation needs. Codes: `INSUFFICIENT_SCOPE`, `API_KEY_DISABLED`, `API_KEY_EXPIRED`.
- `404` No space with that id belongs to this account. Codes: `SPACE_NOT_FOUND`.
- `429` More than 1,000 requests in the current hour. Codes: `RATE_LIMIT_EXCEEDED`.
- `500` Unexpected failure, or a request body that is not valid JSON. Codes: `INTERNAL_ERROR`.

```bash
curl "https://app.videotestimonials.video/api/v1/spaces/8b3e2f9a-4c1d-4e5f-9a7b-2c3d4e5f6a7b" \
  -H "Authorization: Bearer $VT_API_KEY"
```

Example response:

```json
{
  "data": {
    "id": "8b3e2f9a-4c1d-4e5f-9a7b-2c3d4e5f6a7b",
    "user_id": "1f2e3d4c-5b6a-4978-8a9b-0c1d2e3f4a5b",
    "name": "Example Co",
    "slug": "example-co",
    "description": "Tell us how Example Co helped you.",
    "logo_url": null,
    "primary_color": "#7C3AED",
    "custom_questions": [
      "What problem did we solve for you?",
      "What result did you get?"
    ],
    "created_at": "2026-09-15T12:00:00.000000+00:00"
  }
}
```

### Update a space

`PATCH /spaces/{id}` · scope: `write`

**Path parameters**

- `id` (string (uuid), required) — Resource id (UUID).

**Request body (JSON)**

- `name` (string) — Space name, shown on its collection page. 1–100 characters
- `description` (string) — Short description of the space. Max 500 characters
- `logo_url` (string (uri)) — URL of the space logo.
- `primary_color` (string) — Brand colour as a 6-digit hex value, e.g. `#7C3AED`. Pattern `^#[0-9A-Fa-f]{6}$`
- `custom_questions` (string[]) — Question strings stored on the space. Collection forms take their questions from the form builder, not from this field.

Send at least one of these fields. Fields not listed are ignored.

**Responses**

- `200` The updated space.
- `400` A query parameter or body field failed validation. Codes: `VALIDATION_ERROR`.
- `401` Missing, malformed or unknown API key. Codes: `UNAUTHORIZED`, `INVALID_API_KEY`.
- `403` The key is disabled, expired, or lacks the scope this operation needs. Codes: `INSUFFICIENT_SCOPE`, `API_KEY_DISABLED`, `API_KEY_EXPIRED`.
- `404` No space with that id belongs to this account. Codes: `SPACE_NOT_FOUND`.
- `429` More than 1,000 requests in the current hour. Codes: `RATE_LIMIT_EXCEEDED`.
- `500` Unexpected failure, or a request body that is not valid JSON. Codes: `INTERNAL_ERROR`.

```bash
curl -X PATCH "https://app.videotestimonials.video/api/v1/spaces/8b3e2f9a-4c1d-4e5f-9a7b-2c3d4e5f6a7b" \
  -H "Authorization: Bearer $VT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Example Co — Customers"}'
```

Example response:

```json
{
  "data": {
    "id": "8b3e2f9a-4c1d-4e5f-9a7b-2c3d4e5f6a7b",
    "user_id": "1f2e3d4c-5b6a-4978-8a9b-0c1d2e3f4a5b",
    "name": "Example Co",
    "slug": "example-co",
    "description": "Tell us how Example Co helped you.",
    "logo_url": null,
    "primary_color": "#7C3AED",
    "custom_questions": [
      "What problem did we solve for you?",
      "What result did you get?"
    ],
    "created_at": "2026-09-15T12:00:00.000000+00:00"
  }
}
```

### Delete a space

`DELETE /spaces/{id}` · scope: `delete`

Deletes the space if the account owns it. An id that matches no space of this account also returns 204.

**Path parameters**

- `id` (string (uuid), required) — Resource id (UUID).

**Responses**

- `204` Deleted, or nothing matched. No body.
- `401` Missing, malformed or unknown API key. Codes: `UNAUTHORIZED`, `INVALID_API_KEY`.
- `403` The key is disabled, expired, or lacks the scope this operation needs. Codes: `INSUFFICIENT_SCOPE`, `API_KEY_DISABLED`, `API_KEY_EXPIRED`.
- `429` More than 1,000 requests in the current hour. Codes: `RATE_LIMIT_EXCEEDED`.
- `500` Unexpected failure, or a request body that is not valid JSON. Codes: `INTERNAL_ERROR`.

```bash
curl -X DELETE "https://app.videotestimonials.video/api/v1/spaces/8b3e2f9a-4c1d-4e5f-9a7b-2c3d4e5f6a7b" \
  -H "Authorization: Bearer $VT_API_KEY"
```

## Objects

### Testimonial

A testimonial as stored. Responses return the whole row, so fields beyond those listed here (for example AI analysis fields) can appear; treat unknown fields as optional.

**Fields (required = always present)**

- `id` (string (uuid), required)
- `space_id` (string (uuid), required)
- `user_id` (string (uuid)) — Account that owns the testimonial.
- `type` (string, required) — One of: `text`, `video`
- `status` (string, required) — Moderation status. The API filters and writes `pending`, `approved` and `rejected`.
- `submitter_name` (string, required)
- `submitter_email` (string)
- `submitter_company` (string)
- `submitter_role` (string)
- `submitter_avatar_url` (string)
- `text_content` (string)
- `video_url` (string)
- `video_thumbnail_url` (string)
- `rating` (integer) — 1–5
- `ai_transcript` (string) — Transcript of a video testimonial, once transcribed.
- `created_at` (string (date-time), required)

### Space

A space groups one brand's collection forms, testimonials and Wall of Love. Responses return the whole row, so fields beyond those listed here (branding, domain and wall settings) can appear.

**Fields (required = always present)**

- `id` (string (uuid), required)
- `user_id` (string (uuid)) — Account that owns the space.
- `name` (string, required)
- `slug` (string) — Used in the public collection and wall URLs.
- `description` (string)
- `logo_url` (string)
- `primary_color` (string)
- `custom_questions` (array)
- `created_at` (string (date-time), required)

### Pagination

**Fields (required = always present)**

- `total` (integer, required) — Items matching the filters, across all pages.
- `limit` (integer, required)
- `offset` (integer, required)
- `has_more` (boolean, required) — True when `offset + limit < total`.

Questions about the API, or need an endpoint that is not here? Email support@videotestimonials.video.

---

Source: https://videotestimonials.video/docs/api
