# Products

A product is a piece of software the workspace builds and tests, such as a website, an API or a CLI. It runs in environments through deployments, and its test runs and secrets belong to it.

## The Product object

Software the workspace builds and tests.

### Attributes

- `id` (string, required): Unique ID of the product.
- `slug` (string, required): Lowercase letters, digits and hyphens, unique in the workspace, such as storefront.
- `name` (string, required): The product's display name.
- `type` (string, one of: `website`, `api`, `cli`, `mobile_app`, `desktop_app`, `agent`, required): What kind of software the product is.
- `description` (nullable string, required): A short description, or null.
- `repository` (nullable string, required): The source repository, such as acme/storefront, that agent sessions report.
- `archivedAt` (nullable string, required): When it was archived, as an ISO 8601 timestamp; null while active.
- `createdAt` (string, required): When it was created, as an ISO 8601 timestamp.
- `updatedAt` (string, required): When it last changed, as an ISO 8601 timestamp.
- `deployments` (array of objects, required): Deployments whose environment is active. See [the Deployment object](https://docs.qa.clankership.net/api/deployments/object.md).
  - `id` (string, required): Unique ID of the deployment.
  - `product` (object, required): The product that is deployed.
    - `id` (string, required): Unique ID of the product.
    - `slug` (string, required): Lowercase letters, digits and hyphens, unique in the workspace, such as storefront.
    - `name` (string, required): The product's display name.
    - `type` (string, one of: `website`, `api`, `cli`, `mobile_app`, `desktop_app`, `agent`, required): What kind of software the product is.
    - `archived` (boolean, required): Whether the product is archived.
  - `environment` (object, required): The environment it is deployed to.
    - `id` (string, required): Unique ID of the environment.
    - `slug` (string, required): Lowercase letters, digits and hyphens, unique in the workspace, such as staging.
    - `name` (string, required): The environment's display name.
    - `class` (string, one of: `production`, `non-production`, required): Production holds real customer data; everything else is non-production.
    - `emoji` (nullable string, required): One emoji shown beside the environment's name, or null.
    - `archived` (boolean, required): Whether the environment is archived.
  - `baseUrl` (nullable string, required): The http(s) URL where the product answers in this environment. Required for website and API products.
  - `fixtureUrl` (nullable string, required): The deployment's Fixture API, which test runs call to create and delete test data, or null when it has none.
  - `version` (nullable string, required): The build that is running, such as a git SHA, or null when unknown.
  - `versionUpdatedAt` (nullable string, required): When `version` last changed, as an ISO 8601 timestamp.
  - `active` (boolean, required): False while the product or the environment is archived.
  - `createdAt` (string, required): When it was created, as an ISO 8601 timestamp.
  - `updatedAt` (string, required): When it last changed, as an ISO 8601 timestamp.

### Example

```json
{
  "id": "Pr4GtY9sLm3B",
  "slug": "storefront",
  "name": "Storefront",
  "type": "website",
  "description": "The customer-facing shop.",
  "repository": "acme/storefront",
  "archivedAt": null,
  "createdAt": "2026-10-01T16:20:00.000Z",
  "updatedAt": "2026-10-14T09:05:12.000Z",
  "deployments": [
    {
      "id": "Dp3FkR8wNv2T",
      "product": {
        "id": "Pr4GtY9sLm3B",
        "slug": "storefront",
        "name": "Storefront",
        "type": "website",
        "archived": false
      },
      "environment": {
        "id": "En8RqZ2vKp5D",
        "slug": "staging",
        "name": "Staging",
        "class": "non-production",
        "emoji": "🧪",
        "archived": false
      },
      "baseUrl": "https://staging.acme.dev",
      "fixtureUrl": "https://staging.acme.dev/fixtures",
      "version": "9f2c41e",
      "versionUpdatedAt": "2026-10-14T09:05:12.000Z",
      "active": true,
      "createdAt": "2026-10-01T16:20:00.000Z",
      "updatedAt": "2026-10-14T09:05:12.000Z"
    }
  ]
}
```

## List products

`GET /v1/workspaces/{slug}/products`

Returns the workspace's products with their active deployments. Archived products are left out unless `archived` asks for them.

### Parameters

- `slug` (string, in path, required): The workspace's slug or ID, such as acme.
- `archived` (string, one of: `exclude`, `include`, `only`, in query): Which archived objects to return: exclude (the default), include or only archived ones.

### Returns

Returns an object with these fields. The status is 200.

- `products` (array of objects, required): The products, sorted by name. See [the Product object](https://docs.qa.clankership.net/api/products/object.md).
  - `id` (string, required): Unique ID of the product.
  - `slug` (string, required): Lowercase letters, digits and hyphens, unique in the workspace, such as storefront.
  - `name` (string, required): The product's display name.
  - `type` (string, one of: `website`, `api`, `cli`, `mobile_app`, `desktop_app`, `agent`, required): What kind of software the product is.
  - `description` (nullable string, required): A short description, or null.
  - `repository` (nullable string, required): The source repository, such as acme/storefront, that agent sessions report.
  - `archivedAt` (nullable string, required): When it was archived, as an ISO 8601 timestamp; null while active.
  - `createdAt` (string, required): When it was created, as an ISO 8601 timestamp.
  - `updatedAt` (string, required): When it last changed, as an ISO 8601 timestamp.
  - `deployments` (array of objects, required): Deployments whose environment is active. See [the Deployment object](https://docs.qa.clankership.net/api/deployments/object.md).
    - `id` (string, required): Unique ID of the deployment.
    - `product` (object, required): The product that is deployed.
      - `id` (string, required): Unique ID of the product.
      - `slug` (string, required): Lowercase letters, digits and hyphens, unique in the workspace, such as storefront.
      - `name` (string, required): The product's display name.
      - `type` (string, one of: `website`, `api`, `cli`, `mobile_app`, `desktop_app`, `agent`, required): What kind of software the product is.
      - `archived` (boolean, required): Whether the product is archived.
    - `environment` (object, required): The environment it is deployed to.
      - `id` (string, required): Unique ID of the environment.
      - `slug` (string, required): Lowercase letters, digits and hyphens, unique in the workspace, such as staging.
      - `name` (string, required): The environment's display name.
      - `class` (string, one of: `production`, `non-production`, required): Production holds real customer data; everything else is non-production.
      - `emoji` (nullable string, required): One emoji shown beside the environment's name, or null.
      - `archived` (boolean, required): Whether the environment is archived.
    - `baseUrl` (nullable string, required): The http(s) URL where the product answers in this environment. Required for website and API products.
    - `fixtureUrl` (nullable string, required): The deployment's Fixture API, which test runs call to create and delete test data, or null when it has none.
    - `version` (nullable string, required): The build that is running, such as a git SHA, or null when unknown.
    - `versionUpdatedAt` (nullable string, required): When `version` last changed, as an ISO 8601 timestamp.
    - `active` (boolean, required): False while the product or the environment is archived.
    - `createdAt` (string, required): When it was created, as an ISO 8601 timestamp.
    - `updatedAt` (string, required): When it last changed, as an ISO 8601 timestamp.

### Request

```bash
curl https://api.qa.clankership.net/v1/workspaces/{slug}/products \
  -H "Authorization: Bearer $CLANKER_API_KEY"
```

### Response

```json
{
  "products": [
    {
      "id": "Pr4GtY9sLm3B",
      "slug": "storefront",
      "name": "Storefront",
      "type": "website",
      "description": "The customer-facing shop.",
      "repository": "acme/storefront",
      "archivedAt": null,
      "createdAt": "2026-10-01T16:20:00.000Z",
      "updatedAt": "2026-10-14T09:05:12.000Z",
      "deployments": [
        {
          "id": "Dp3FkR8wNv2T",
          "product": {
            "id": "Pr4GtY9sLm3B",
            "slug": "storefront",
            "name": "Storefront",
            "type": "website",
            "archived": false
          },
          "environment": {
            "id": "En8RqZ2vKp5D",
            "slug": "staging",
            "name": "Staging",
            "class": "non-production",
            "emoji": "🧪",
            "archived": false
          },
          "baseUrl": "https://staging.acme.dev",
          "fixtureUrl": "https://staging.acme.dev/fixtures",
          "version": "9f2c41e",
          "versionUpdatedAt": "2026-10-14T09:05:12.000Z",
          "active": true,
          "createdAt": "2026-10-01T16:20:00.000Z",
          "updatedAt": "2026-10-14T09:05:12.000Z"
        }
      ]
    }
  ]
}
```

### Errors

- `400` `InvalidInput`: The request is malformed, or a value is not allowed.
- `401` `Unauthorized`: The request has no valid API key.
- `403` `Forbidden`: The key's user or role may not do this.
- `404` `NotFound`: The workspace or object does not exist, or the caller cannot see it.
- `409` `Conflict`: The change conflicts with current data, such as a slug that is already in use.
- `500` `Internal`: Clanker Ship failed to handle the request. Retry, or report the response's x-request-id.

## Create a product

`POST /v1/workspaces/{slug}/products`

Adds a product to the workspace. Viewers cannot create products, and the slug must be unused.

### Parameters

- `slug` (string, in path, required): The workspace's slug or ID, such as acme.
- `slug` (string, required): Lowercase letters, digits and hyphens, unique in the workspace, such as storefront.
- `name` (string, required): The product's display name.
- `type` (string, one of: `website`, `api`, `cli`, `mobile_app`, `desktop_app`, `agent`, required): What kind of software this is.
- `description` (nullable string): A short description.
- `repository` (nullable string): The source repository as owner/name, such as acme/storefront.

### Returns

Returns the [Product](https://docs.qa.clankership.net/api/products/object.md) object. The status is 201.

### Request

```bash
curl -X POST https://api.qa.clankership.net/v1/workspaces/{slug}/products \
  -H "Authorization: Bearer $CLANKER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "slug": "storefront",
    "name": "Storefront",
    "type": "website",
    "repository": "acme/storefront"
  }'
```

### Response

```json
{
  "id": "Pr4GtY9sLm3B",
  "slug": "storefront",
  "name": "Storefront",
  "type": "website",
  "description": "The customer-facing shop.",
  "repository": "acme/storefront",
  "archivedAt": null,
  "createdAt": "2026-10-01T16:20:00.000Z",
  "updatedAt": "2026-10-14T09:05:12.000Z",
  "deployments": [
    {
      "id": "Dp3FkR8wNv2T",
      "product": {
        "id": "Pr4GtY9sLm3B",
        "slug": "storefront",
        "name": "Storefront",
        "type": "website",
        "archived": false
      },
      "environment": {
        "id": "En8RqZ2vKp5D",
        "slug": "staging",
        "name": "Staging",
        "class": "non-production",
        "emoji": "🧪",
        "archived": false
      },
      "baseUrl": "https://staging.acme.dev",
      "fixtureUrl": "https://staging.acme.dev/fixtures",
      "version": "9f2c41e",
      "versionUpdatedAt": "2026-10-14T09:05:12.000Z",
      "active": true,
      "createdAt": "2026-10-01T16:20:00.000Z",
      "updatedAt": "2026-10-14T09:05:12.000Z"
    }
  ]
}
```

### Errors

- `400` `InvalidInput`: The request is malformed, or a value is not allowed.
- `401` `Unauthorized`: The request has no valid API key.
- `403` `Forbidden`: The key's user or role may not do this.
- `404` `NotFound`: The workspace or object does not exist, or the caller cannot see it.
- `409` `Conflict`: The change conflicts with current data, such as a slug that is already in use.
- `500` `Internal`: Clanker Ship failed to handle the request. Retry, or report the response's x-request-id.

## Get a product

`GET /v1/workspaces/{slug}/products/{product}`

Returns one product, archived or not, with its active deployments.

### Parameters

- `slug` (string, in path, required): The workspace's slug or ID, such as acme.
- `product` (string, in path, required): The product's slug or ID, such as storefront.

### Returns

Returns the [Product](https://docs.qa.clankership.net/api/products/object.md) object. The status is 200.

### Request

```bash
curl https://api.qa.clankership.net/v1/workspaces/{slug}/products/{product} \
  -H "Authorization: Bearer $CLANKER_API_KEY"
```

### Response

```json
{
  "id": "Pr4GtY9sLm3B",
  "slug": "storefront",
  "name": "Storefront",
  "type": "website",
  "description": "The customer-facing shop.",
  "repository": "acme/storefront",
  "archivedAt": null,
  "createdAt": "2026-10-01T16:20:00.000Z",
  "updatedAt": "2026-10-14T09:05:12.000Z",
  "deployments": [
    {
      "id": "Dp3FkR8wNv2T",
      "product": {
        "id": "Pr4GtY9sLm3B",
        "slug": "storefront",
        "name": "Storefront",
        "type": "website",
        "archived": false
      },
      "environment": {
        "id": "En8RqZ2vKp5D",
        "slug": "staging",
        "name": "Staging",
        "class": "non-production",
        "emoji": "🧪",
        "archived": false
      },
      "baseUrl": "https://staging.acme.dev",
      "fixtureUrl": "https://staging.acme.dev/fixtures",
      "version": "9f2c41e",
      "versionUpdatedAt": "2026-10-14T09:05:12.000Z",
      "active": true,
      "createdAt": "2026-10-01T16:20:00.000Z",
      "updatedAt": "2026-10-14T09:05:12.000Z"
    }
  ]
}
```

### Errors

- `400` `InvalidInput`: The request is malformed, or a value is not allowed.
- `401` `Unauthorized`: The request has no valid API key.
- `403` `Forbidden`: The key's user or role may not do this.
- `404` `NotFound`: The workspace or object does not exist, or the caller cannot see it.
- `409` `Conflict`: The change conflicts with current data, such as a slug that is already in use.
- `500` `Internal`: Clanker Ship failed to handle the request. Retry, or report the response's x-request-id.

## Update a product

`PATCH /v1/workspaces/{slug}/products/{product}`

Changes only the fields sent. A new slug must be unused in the workspace.

### Parameters

- `slug` (string, in path, required): The workspace's slug or ID, such as acme.
- `product` (string, in path, required): The product's slug or ID, such as storefront.
- `slug` (string): Lowercase letters, digits and hyphens, unique in the workspace, such as storefront.
- `name` (string): The product's display name.
- `type` (string, one of: `website`, `api`, `cli`, `mobile_app`, `desktop_app`, `agent`): What kind of software this is.
- `description` (nullable string): A short description. `null` clears it.
- `repository` (nullable string): The source repository as owner/name, such as acme/storefront. `null` clears it.

### Returns

Returns the [Product](https://docs.qa.clankership.net/api/products/object.md) object. The status is 200.

### Request

```bash
curl -X PATCH https://api.qa.clankership.net/v1/workspaces/{slug}/products/{product} \
  -H "Authorization: Bearer $CLANKER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Storefront web",
    "repository": "acme/storefront"
  }'
```

### Response

```json
{
  "id": "Pr4GtY9sLm3B",
  "slug": "storefront",
  "name": "Storefront",
  "type": "website",
  "description": "The customer-facing shop.",
  "repository": "acme/storefront",
  "archivedAt": null,
  "createdAt": "2026-10-01T16:20:00.000Z",
  "updatedAt": "2026-10-14T09:05:12.000Z",
  "deployments": [
    {
      "id": "Dp3FkR8wNv2T",
      "product": {
        "id": "Pr4GtY9sLm3B",
        "slug": "storefront",
        "name": "Storefront",
        "type": "website",
        "archived": false
      },
      "environment": {
        "id": "En8RqZ2vKp5D",
        "slug": "staging",
        "name": "Staging",
        "class": "non-production",
        "emoji": "🧪",
        "archived": false
      },
      "baseUrl": "https://staging.acme.dev",
      "fixtureUrl": "https://staging.acme.dev/fixtures",
      "version": "9f2c41e",
      "versionUpdatedAt": "2026-10-14T09:05:12.000Z",
      "active": true,
      "createdAt": "2026-10-01T16:20:00.000Z",
      "updatedAt": "2026-10-14T09:05:12.000Z"
    }
  ]
}
```

### Errors

- `400` `InvalidInput`: The request is malformed, or a value is not allowed.
- `401` `Unauthorized`: The request has no valid API key.
- `403` `Forbidden`: The key's user or role may not do this.
- `404` `NotFound`: The workspace or object does not exist, or the caller cannot see it.
- `409` `Conflict`: The change conflicts with current data, such as a slug that is already in use.
- `500` `Internal`: Clanker Ship failed to handle the request. Retry, or report the response's x-request-id.

## Archive a product

`POST /v1/workspaces/{slug}/products/{product}/archive`

Hides the product and refuses vault pulls for its deployments until it is restored. Archiving an archived product changes nothing.

### Parameters

- `slug` (string, in path, required): The workspace's slug or ID, such as acme.
- `product` (string, in path, required): The product's slug or ID, such as storefront.

### Returns

Returns the [Product](https://docs.qa.clankership.net/api/products/object.md) object. The status is 200.

### Request

```bash
curl -X POST https://api.qa.clankership.net/v1/workspaces/{slug}/products/{product}/archive \
  -H "Authorization: Bearer $CLANKER_API_KEY"
```

### Response

```json
{
  "id": "Pr4GtY9sLm3B",
  "slug": "storefront",
  "name": "Storefront",
  "type": "website",
  "description": "The customer-facing shop.",
  "repository": "acme/storefront",
  "archivedAt": null,
  "createdAt": "2026-10-01T16:20:00.000Z",
  "updatedAt": "2026-10-14T09:05:12.000Z",
  "deployments": [
    {
      "id": "Dp3FkR8wNv2T",
      "product": {
        "id": "Pr4GtY9sLm3B",
        "slug": "storefront",
        "name": "Storefront",
        "type": "website",
        "archived": false
      },
      "environment": {
        "id": "En8RqZ2vKp5D",
        "slug": "staging",
        "name": "Staging",
        "class": "non-production",
        "emoji": "🧪",
        "archived": false
      },
      "baseUrl": "https://staging.acme.dev",
      "fixtureUrl": "https://staging.acme.dev/fixtures",
      "version": "9f2c41e",
      "versionUpdatedAt": "2026-10-14T09:05:12.000Z",
      "active": true,
      "createdAt": "2026-10-01T16:20:00.000Z",
      "updatedAt": "2026-10-14T09:05:12.000Z"
    }
  ]
}
```

### Errors

- `400` `InvalidInput`: The request is malformed, or a value is not allowed.
- `401` `Unauthorized`: The request has no valid API key.
- `403` `Forbidden`: The key's user or role may not do this.
- `404` `NotFound`: The workspace or object does not exist, or the caller cannot see it.
- `409` `Conflict`: The change conflicts with current data, such as a slug that is already in use.
- `500` `Internal`: Clanker Ship failed to handle the request. Retry, or report the response's x-request-id.

## Restore a product

`POST /v1/workspaces/{slug}/products/{product}/restore`

Brings an archived product back with its deployments and secrets. Restoring an active product changes nothing.

### Parameters

- `slug` (string, in path, required): The workspace's slug or ID, such as acme.
- `product` (string, in path, required): The product's slug or ID, such as storefront.

### Returns

Returns the [Product](https://docs.qa.clankership.net/api/products/object.md) object. The status is 200.

### Request

```bash
curl -X POST https://api.qa.clankership.net/v1/workspaces/{slug}/products/{product}/restore \
  -H "Authorization: Bearer $CLANKER_API_KEY"
```

### Response

```json
{
  "id": "Pr4GtY9sLm3B",
  "slug": "storefront",
  "name": "Storefront",
  "type": "website",
  "description": "The customer-facing shop.",
  "repository": "acme/storefront",
  "archivedAt": null,
  "createdAt": "2026-10-01T16:20:00.000Z",
  "updatedAt": "2026-10-14T09:05:12.000Z",
  "deployments": [
    {
      "id": "Dp3FkR8wNv2T",
      "product": {
        "id": "Pr4GtY9sLm3B",
        "slug": "storefront",
        "name": "Storefront",
        "type": "website",
        "archived": false
      },
      "environment": {
        "id": "En8RqZ2vKp5D",
        "slug": "staging",
        "name": "Staging",
        "class": "non-production",
        "emoji": "🧪",
        "archived": false
      },
      "baseUrl": "https://staging.acme.dev",
      "fixtureUrl": "https://staging.acme.dev/fixtures",
      "version": "9f2c41e",
      "versionUpdatedAt": "2026-10-14T09:05:12.000Z",
      "active": true,
      "createdAt": "2026-10-01T16:20:00.000Z",
      "updatedAt": "2026-10-14T09:05:12.000Z"
    }
  ]
}
```

### Errors

- `400` `InvalidInput`: The request is malformed, or a value is not allowed.
- `401` `Unauthorized`: The request has no valid API key.
- `403` `Forbidden`: The key's user or role may not do this.
- `404` `NotFound`: The workspace or object does not exist, or the caller cannot see it.
- `409` `Conflict`: The change conflicts with current data, such as a slug that is already in use.
- `500` `Internal`: Clanker Ship failed to handle the request. Retry, or report the response's x-request-id.
